react自用总结

react学习

<!DOCTYPE html>
<html lang="zh-CN">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Hello React</title>

    <!-- 加载 React。-->
    <!-- 注意: 部署时,将 "development.js" 替换为 "production.min.js"。-->
    <script type="text/javascript" src="https://unpkg.com/react@16/umd/react.development.js"></script>
    <!-- 用于之覅react操作dom -->
    <script type="text/javascript" src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
    <!-- 引入babel,用于将jsx转换为js -->
    <script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
</head>

<body>
    <!-- 准备好一个“容器” -->
    <div id="app"></div>
    <div id="demo"></div>




    <!-- type="text/babel" 编写jsx -->
    <script type="text/babel">
        // 1.创建虚拟DOM
        const VDOM = (
            <h1 id="titile">
                <span>HELLO,REACT</span>
            </h1>
        )
  
        console.log(typeof VDOM);
        console.log(VDOM instanceof Object);

      

        //2.渲染虚拟DOM到页面
        // ReactDOM.render(虚拟Dom,容器)
        ReactDOM.render(VDOM, document.getElementById('app'))


        // 真实DOM
        const TDOM = document.getElementById('demo')
        console.log('虚拟DOM:', VDOM );
        console.log('真实DOM:', TDOM);

          /*
        关于虚拟DOM:
        本质是Object类型的对象(一般对象)
        虚拟DOM比较‘轻’,真实DOM比较‘重’
        虚拟DOM最终会被React转化为真实DOM,呈现在页面上
        */
        debugger
    </script>
</body>

</html>

开发者插件

React Developer Tools
//谷歌应用商店下载

组件(两种创建组件方式)

函数式组件

//函数式组件    
<!DOCTYPE html>
<html lang="zh-CN">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Hello React</title>
    <!-- 加载 React。-->
    <!-- 注意: 部署时,将 'development.js' 替换为 'production.min.js'。-->
    <script type='text/javascript' src='https://unpkg.com/react@16/umd/react.development.js'></script>
    <!-- 用于react操作dom -->
    <script type='text/javascript' src='https://unpkg.com/react-dom@16/umd/react-dom.development.js'></script>
    <!-- 引入babel,用于将jsx转换为js -->
    <script src='https://unpkg.com/babel-standalone@6/babel.min.js'></script>
</head>

<body>

    <div id='app'></div>


    <script type='text/babel'>
           console.log(this);
        // 1.创建函数式组件
        function MyComponent() {
            console.log(this);//此处的this是undefined 因为babel编译后开启了严格模式
               return <h2>我是函数定义的组件(适用于【简单组件】)</h2>
        }
        // 2.渲染组件到页面
        ReactDOM.render(<MyComponent/>,document.getElementById('app'))

        /*
         执行了ReactDOM,.render(<MyComponent/>......之后,发生了什么?
           1.React解析组件标签,找到了MyComponent组件。
           2.发现组件是使用函数定义的,随后调用该函数,将返回的虚拟DOM转为真实的DOM,随后呈现在页面中。
         
        */
    </script>
</body>

</html>

类式组件

<!DOCTYPE html>
<html lang="zh-CN">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Hello React</title>
    <!-- 加载 React。-->
    <!-- 注意: 部署时,将 'development.js' 替换为 'production.min.js'。-->
    <script type='text/javascript' src='https://unpkg.com/react@16/umd/react.development.js'></script>
    <!-- 用于react操作dom -->
    <script type='text/javascript' src='https://unpkg.com/react-dom@16/umd/react-dom.development.js'></script>
    <!-- 引入babel,用于将jsx转换为js -->
    <script src='https://unpkg.com/babel-standalone@6/babel.min.js'></script>
</head>

<body>

    <div id='app'></div>


    <script type='text/babel'>
        //1.创建类式组件      需要继承React中的一个类
        class MyComponent extends React.Component {
            // render是放在哪里的?--MyComponent(类)的原型对象上,供实例使用。
            //render中的this是谁?--MyComponent的实例对象 <=> MyComponent组件实例对象。
            render() {
                console.log('render中的this:',this);
                return (
                    <h1>我是用类定义的组件【适用于复杂组件的定义】</h1>
                )
            }
        }

        // 2.渲染组件到页面
        ReactDOM.render(<MyComponent />, document.getElementById('app'))
        /*
         执行了ReacatDOM.render(<MyComponent/)......之后,发生了什么?
            1.React解析组件标签,找到了MyComponent组件。
            2.发现组件是使用类定义得,随后new出来该类的实例,并通过实例调用到原型上的render方法。
            3.将render返回的虚拟DOM转化为真实DOM,随后呈现在页面中。
        */
    </script>
</body>

</html>

安装

//直接安装
npx create-react-app my-pro1

//全局安装
npm i -g create-react-app //安装脚手架

create-react-app my-pro1  //创建项目

使用

1. jsx语法规则

//JSX就是Javascript和XML结合的一种格式。React发明了JSX,可以方便的利用HTML语法来创建虚拟DOM,当遇到<,JSX就当作HTML解析,遇到{就当JavaScript解析.

                1.定义虚拟DOM,不要写引号
                2.标签中混入JS表达式时要用{}
                3.样式的类名指定不要用class,要用className
                4.内联样式,要用style={{key:value}}的形式去写,即style= {{color:'yellow'}}
                5.必须有且仅有一个根标签 没有根节点可以使用<></> (幽灵节点,页面不显示元素)替代
                6.标签必须闭合
                7.标签首字母
                (1).若小写字母开头,转为html中同名元素,若无则报错
                (2).若大写字母开头,react就去渲染对应的组件,若没有
//示例:
<!DOCTYPE html>
<html lang="zh-CN">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Hello React</title>

    <!-- 加载 React。-->
    <!-- 注意: 部署时,将 "development.js" 替换为 "production.min.js"。-->
    <script type="text/javascript" src="https://unpkg.com/react@16/umd/react.development.js"></script>
    <!-- 用于之覅react操作dom -->
    <script type="text/javascript" src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
    <!-- 引入babel,用于将jsx转换为js -->
    <script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
</head>

<style>
    .title {
        background-color: salmon;
    }
</style>


<body>
    <!-- 准备好一个“容器” -->
    <div id="app"></div>




    <!-- type="text/babel" 编写jsx -->
    <script type="text/babel">

        const myId = "title"
        const myData = "Hellow,React"

        // 1.创建虚拟dom
        const VDOM = (
            <div>
                <h1 className="title" id={myId.toLowerCase()}>
                    <span style={{ color: 'yellow', fontSize: '20px' }}>{myData.toLowerCase()}</span>
                </h1>
                <h1 className="title" id={myId.toUpperCase()}>
                    <span style={{ color: 'yellow', fontSize: '20px' }}>{myData.toLowerCase()}</span>
                </h1>
                <Good>123</Good>
            </div>
        )//虚拟DOM
        //2.渲染虚拟DOM到页面
        // ReactDOM.render(虚拟Dom,容器)
        ReactDOM.render(VDOM, document.getElementById('app'))


        /*
         jsx语法规则:
                1.定义虚拟DOM,不要写引号
                2.标签中混入JS表达式时要用{}
                3.样式的类名指定不要用class,要用className
                4.内联样式,要用style={{key:value}}的形式去写,即style= {{color:'yellow'}}
                5.虚拟DOM只能有一个根标签
                6.标签必须闭合
                7.标签首字母
                (1)..若小写字母开头,转为html中同名元素,若无则报错
                (2).若大写字母开头,react就去渲染对应的组件,若没有
        */
    </script>
</body>

</html>

2.状态(数据)修改

        // 1.创建类式组件
        class Weather extends React.Component {
            state = { isHot: false, wind: '微风' }

            render() {
                const { isHot, wind } = this.state
                return <h1 onClick={this.changeWeather}>今天天气很{isHot ? '炎热' : '凉爽'},{wind}</h1>
            }

            changeWeather = () => {
                const { isHot } = this.state
                //使用setState修改状态(数据)
                this.setState((state, props) => {
                    return {
                        isHot: !isHot
                    }
                })
            }

        }
  //2.渲染虚拟DOM到页面
  ReactDOM.render(<Weather />, document.getElementById('app'))

3. props

    <script src="https://unpkg.com/prop-types@15.6/prop-types.js"></script>  

// 1.创建虚拟dom
        class Person extends React.Component {
            //组件标签限制属性类型 
            static propTypes = {
                name: PropTypes.string.isRequired,//限制属性类型 isRequired代表必传
                sex: PropTypes.string,
                age: PropTypes.number,
            }
            // 指定默认标签属性的默认值
            static defaultProps = {
                sex: "阴阳人",
                age: 20
            }
            render() {
                // console.log(this);
                const { name, age, sex } = this.props    //获取props
                return (
                    <ul>
                        <li>姓名:{name}</li>
                        <li>性别:{sex}</li>
                        <li>年龄:{age + 1}</li>
                    </ul>
                )
            }

        }


        //2.渲染虚拟DOM到页面
        const p = { name: "老刘" }

        // console.log('@', ...p);
        //react中可以通过扩展语法,{}分割 注意:与js中的{}不是一个意思
        ReactDOM.render(<Person {...p} />, document.getElementById('app'))

使用方法

1. jsx

1.jsx列表渲染
//列表渲染
//注意:遍历列表需要一个number/string类型的key 提高性能
const songs = [
  {
    id: 1, name: "1~"
  },
  {
    id: 2, name: "2~"
  },
  {
    id: 3, name: "3~"
  },
  {
    id: 4, name: "4~"
  }
]
function App() {
  return (
    <div className="App">
      <ul>
        {songs.map(song => <li key={song.id}>{song.name}</li>)}
      </ul>
    </div>
  );
}

export default App;

2.jsx条件渲染
//条件渲染

const flag=true
function App() {
  return (
    <div className="App">
      <ul>
        {flag ? '1' : '2'}
      </ul>
    </div>
  );
}

export default App;

----------------------------------------------------------------------------------
// 有一个状态type 1 2 3
//1 h1
//2 h2
//3 h3
// 原则:模板中的逻辑尽量保持精简
// 复杂的多分枝的逻辑 收敛为一个函数  模板只负责调用函数

const getHtag = (type) => {
  if (type === 1) return <h1>h1标签</h1>
  if (type === 2) return <h2>h2标签</h2>
  if (type === 3) return <h3>h3标签</h3>
}
function App() {
  return (
    <div className="App">
      {getHtag(3)}
    </div>
  );
}

export default App;

3.jsx样式控制
//1.行内样式- 元素身上绑定一个style属性即可

//2.类名样式-在元素身上绑定一个className属性即可

import './App.css'
const getHtag = (type) => {
  if (type === 1) return <h1>h1标签</h1>
  if (type === 2) return <h2>h2标签</h2>
  if (type === 3) return <h3>h3标签</h3>
}
const style = {
  color: 'red',
  fontSize: '24px'
}
// 动态控制这个active类名 满足条件渲染
const activeFlag = true

function App() {
  return (
    <div className={activeFlag?'active':''} style={style}>
      {getHtag(3)}
    </div>
  );
}

export default App;

2. 组件

1.函数组件
// 定义函数组件
function Hello (){
    return <div>这是函数组件</div>
}

function App(){
    return (
       <div className="App">
          <Hello />  
       </div>
    )
}

export default App
2.class类组件
import React from 'react'
// 定义函数组件
class Hello extends React.Component{
   render(){
        return <div>这是类组件</div>
   }
}

function App(){
    return (
       <div className="App">
          <Hello />  
       </div>
    )
}

export default App

3. 事件绑定与参数传递

import React from 'react'

//1.传递一个额外参数  ()=>this.clickHandler('自定义参数')
//2.既需要e也需要额外参数 (e)=>this.clickHandler(e,'自定义参数')

// 定义函数组件
class Hello extends React.Component {

  clickHandler = (e) => {
    console.log(e);
    alert('函数事件被触发了',e)
  }

  render() {
    return <div onClick={this.clickHandler}>这是类组件</div>
  }
}

function App() {
  return (
    <div className="App">
      <Hello />
    </div>
  )
}

export default App

4. 表单组件

1.受控表单(类似vue中的v-model双向绑定)
import React from "react"
// 定义组件
class Counter extends React.Component {
    state = {
      message:"message",
    }


    inputChange = (e) => {

    console.log('change事件触发了',e,this.state.message);
    // 只能使用setState方法修改数据
    this.setState({
        message:e.target.value   //将input value值交给state中的message
    })
    }

    render() {
        return (
            <div>
                <input type='text'
                onChange={this.inputChange}
                value={this.state.message}></input>
             
            </div>
        )
    }
}

function App() {
    return (
        <div>
            {

                <Counter />
            }
        </div>
    )
}

export default App
2. 非受控表单(操作dom 类似vue中的ref)
import React, { createRef } from "react"
// 定义组件
class Counter extends React.Component {
//msgRef可以自定义名称  
    msgRef = createRef()  //creatRef 方法
    getValue = (e) => {
        console.log(this.msgRef.current.value);  //获取当前input元素的value值
    }
    render() {
        return (
            <div>
                <input type='text'
                    ref={this.msgRef}   //通过ref获取元素
                ></input>
                <button onClick={this.getValue}>获取input值</button>
            </div>
        )
    }
}

function App() {
    return (
        <div>
            {

                <Counter />
            }
        </div>
    )
}

export default App

5. 综合案例

import './index.css'
import avatar from './images/avatar.png'
import React from 'react'
import { v4 as uuid } from 'uuid'
// 时间格式化
function formatDate(time) {
  return `${time.getFullYear()}-${time.getMonth()}-${time.getDate()}`
}

class App extends React.Component {
  state = {
    // hot: 热度排序  time: 时间排序
    tabs: [
      {
        id: 1,
        name: '热度',
        type: 'hot'
      },
      {
        id: 2,
        name: '时间',
        type: 'time'
      }
    ],
    active: 'hot',
    list: [
      {
        id: 1,
        author: '刘德华',
        comment: '给我一杯忘情水',
        time: new Date('2021-10-10 09:09:00'),
        // 1: 点赞 0:无态度 -1:踩
        attitude: 1
      },
      {
        id: 2,
        author: '周杰伦',
        comment: '哎哟,不错哦',
        time: new Date('2021-10-11 09:09:00'),
        // 1: 点赞 0:无态度 -1:踩
        attitude: 0
      },
      {
        id: 3,
        author: '五月天',
        comment: '不打扰,是我的温柔',
        time: new Date('2021-10-11 10:09:00'),
        // 1: 点赞 0:无态度 -1:踩
        attitude: -1
      }
    ],
    comment: '请输入内容',  //评论框中的类容
  }

  tabChange = (type) => {
    console.log(type);
    this.setState({
      active: type
    })
  }
  // 受控组件回调
  changeComment = (e) => {
    this.setState({
      comment: e.target.value
    })
  }
  // 发表评价 
  submitComment = () => {
    // 在state.list后面添加一项新的
    this.setState({
      list: [...this.state.list,
      {
        id: uuid(),//使用uuid确保id的唯一性
        author: '张学友',
        comment: this.state.comment,
        time: new Date('2021-10-10 09:09:00'),
        // 1: 点赞 0:无态度 -1:踩
        attitude: 1
      },
      ]
    })
  }
  deleteComment = (id) => {
    console.log(id);
    this.setState({
      list: this.state.list.filter(item => item.id !== id)
    })
  }
  toogleLike = (currtItem) => {
    console.log(currtItem);
    const { id, attitude } = currtItem
    this.setState({
      list: this.state.list.map(item => {
        // 如果id相同 把item的attitude的属性修改一下
        //否则原样返回
        if (item.id === id) {
          return {
            ...item,
            attitude: attitude === 1 ? 0 : 1
          }
        }
        return item
      })
    })
  }
  toogleHate = (currtItem) => {
    console.log(currtItem);
    const { id, attitude } = currtItem
    this.setState({
      list: this.state.list.map(item => {
        if (item.id === id) {
          return {
            ...item,
            attitude: attitude === -1 ? 0 : -1
          }
        }
        return item
      })
    })
  }

  render() {
    return (
      <div className="App">
        <div className="comment-container">
          {/* 评论数 */}
          <div className="comment-head">
            <span>5 评论</span>
          </div>
          {/* 排序 */}
          <div className="tabs-order">
            <ul className="sort-container">
              {
                this.state.tabs.map(tab => (
                  <li
                    onClick={() => this.tabChange(tab.type)}
                    key={tab.id}
                    className={tab.type === this.state.active ? 'on' : ''}
                  >{tab.name}排序</li>
                ))
              }
            </ul>
          </div>

          {/* 添加评论 */}
          <div className="comment-send">
            <div className="user-face">
              <img className="user-head" src={avatar} alt="" />
            </div>
            <div className="textarea-container">
              {/* 输入框 受控组件方式 */}
              <textarea
                cols="80"
                rows="5"
                onChange={this.changeComment}
                value={this.state.comment}
                placeholder="发条友善的评论"
                className="ipt-txt"
              />
              <button onClick={this.submitComment} className="comment-submit">发表评论</button>
            </div>
            <div className="comment-emoji">
              <i className="face"></i>
              <span className="text">表情</span>
            </div>
          </div>

          {/* 评论列表 */}
          <div className="comment-list">
            {
              this.state.list.map(item => (
                <div className="list-item" key={item.id}>
                  <div className="user-face">
                    <img className="user-head" src={avatar} alt="" />
                  </div>
                  <div className="comment">
                    <div className="user">{item.author}</div>
                    <p className="text">{item.comment}</p>
                    <div className="info">
                      <span className="time">{formatDate(item.time)}</span>
                      <span onClick={() => this.toogleLike(item)} className={item.attitude === 1 ? 'like liked' : 'like'}>
                        <i className="icon" />
                      </span>
                      <span onClick={() => this.toogleHate(item)} className={item.attitude === -1 ? 'hate hated' : 'hate'}>
                        <i className="icon" />
                      </span>
                      <span onClick={() => this.deleteComment(item.id)} className="reply btn-hover">删除</span>
                    </div>
                  </div>
                </div>
              ))
            }
          </div>
        </div>
      </div>)
  }
}


export default App

6. 组件通信

1.父子通信
①.方法
import React from 'react'


//父传子 props   函数
// 子传父: 子组件调用父组件传递过来的函数,将参数当成函数的实参传递给函数

// 子组件 Son
//函数子组件 SonA
function SonA(props) {
    //通过传入的props获取父组件上所有的数据
  const { msg, userInfo, getMsg ,template} = props
  console.log(props);
  return (
    <div>我是函数子组件
      <div>
        {msg.map(item => <p key={item}>{item}</p>)}
      </div>
      <div>
        {userInfo.name}
        {userInfo.age}
      </div>
           {/* 子组件向父组件传递数据 */}
      <button onClick={() => getMsg('这里是来自子组件中的数据')}>触发父组件传入的函数,传递参数</button>
      <div>我是模板{template}</div>
    </div>
  )

}

//类子组件SonB
class SonB extends React.Component {
//通过this获取 props名称是固定的
  render() {
    const { msg, userInfo, getMsg, template } = this.props
    getMsg()
    return <div>我是类子组件
      <div>
        {msg.map(item => <p key={item}>{item}</p>)}
      </div>
      <div>
        {userInfo.name}
        {userInfo.age}
      </div>
            {/* 子组件向父组件传递数据 */}
      <button onClick={() => getMsg('这里是来自子组件中的数据')}>触发父组件传入的函数</button>
      <div>我是模板{template}</div>
    </div>
  }
}


// 父组件App
class App extends React.Component {
  state = {
    list: [
      1, 2, 3
    ],
    userInfo: {
      name: "张三",
      age: 18
    },
    template: <span> 父组件中的模板 jsx</span>
  }
    //接收子组件传递的参数
  getMsg = (sonMsg) => {
    console.log('父组件中的函数执行',sonMsg); 
  }
  render() {
    return (
      <div>
        {/* 子组件身上绑定属性 属性名可以自定义 保持语义化 */}
        <SonA msg={this.state.list} userInfo={this.state.userInfo} getMsg={this.getMsg} template={this.state.template}></SonA>
        <SonB msg={this.state.list} userInfo={this.state.userInfo} getMsg={this.getMsg} template={this.state.template}></SonB>
      </div>
    )
  }
}


/*
1.props 是只读对象(readonly) 
单项数据流 子组件只能读取,不能进行修改
2.props可以传递任意数据
数字 字符串 布尔值 数组 对象 函数 jsx
*/
export default App
②.案例

import React, { createContext } from "react"

// App->A->C
// App数据->C
/*
 注意事项:
 1.上层组件和下层组件关系是相对的 只要存在就可以使用 通常我们都会通过App作为数据提供方
 2.这里涉及到的语法都是固定的 有两处 提供的位置 vlaue提供数据 获取的位置{value=>{value}}
*/

// 使用步骤:
// 1.导入createContext方法并执行,解构提供者与消费者 

// 子组件 渲染列表
function ListItem(props) {
  const { item, delItem } = props
  return (
    <div>
      <div >
        <h3>{item.name}</h3>
        <p>{item.price}</p>
        <p>{item.info}</p>
        {/* 传递要删除子项的id */}
        <button onClick={() => delItem(item.id)}>删除</button>
      </div>
    </div>
  )
}

// 父组件  数据提供者
// 先不抽离组件 完成基础渲染后再去抽离
class App extends React.Component {
  state = {
    list: [
      { id: 1, name: '超级好吃的棒棒糖', price: 18.8, info: "开业大酬宾" },
      { id: 2, name: '超级好吃的大鸡腿', price: 28.8, info: "开业大酬宾" },
      { id: 3, name: '超级好吃的冰淇淋', price: 3.8, info: "开业大酬宾" },
    ]
  }

  // 给子组件传递的函数
  delItem = (id) => {
    console.log(id);
    this.setState({
      // 删除数据 filter
      list: this.state.list.filter(item => {
        return item.id !== id
      })
    })
  }
  render() {
    return (
      <div>
        {this.state.list.map(item => <ListItem key={item.id} item={item} delItem={this.delItem} />
        )}
      </div>
    )
  }
}

export default App
2.兄弟通信 自定义事件模式产生技术方法evenetBus/通过共同的父组件通信
// 目标: B组件中的数据传给A
// 技术方案:
// 1.先把B中的数据通过子传父 传给App
// 2.再把App接收的Son中的数据 通过父传子 传给A
import React from "react"

function SonA(props) {
  const { sendMsg, getMsg } = props
  return (
    <div>子组件A,
      {sendMsg}
    </div>
  )
}



function SonB(props) {
  const bMsg = "这是来自B组件中的数据"

  const { getMsg } = props

  function sendMsg() {
    getMsg(bMsg)
  }
  return (
    <div>子组件B

      <button onClick={sendMsg}>发送数据</button>
    </div>
  )
}



// 父组件
class App extends React.Component {
  state = {
    sendMsg: ''
  }

  // 声明一个传给B组件的方法
  getMsg = (msg) => {
    console.log(msg);
    // 把msg数据交给sendMsg
    this.setState({
      sendMsg: msg
    })
  }


  render() {
    return (
      <div>
        <SonA sendMsg={this.state.sendMsg} />
        <SonB getMsg={this.getMsg} />
      </div>
    )
  }
}

export default App
3.其他通信 mobx/redux/基于hook的方案

4.跨组件通信

import React, { createContext } from "react"

// App->A->C
// App数据->C
/*
 注意事项:
 1.上层组件和下层组件关系是相对的 只要存在就可以使用 通常我们都会通过App作为数据提供方
 2.这里涉及到的语法都是固定的 有两处 提供的位置 vlaue提供数据 获取的位置{value=>{value}}
*/

// 使用步骤:
// 1.导入createContext方法并执行,解构提供者与消费者 类似vue中的provide,inject
const { Provider, Consumer } = createContext()
// 子组件A
function ComA() {
  return (
    <div>
      ComA
      <ComC />
    </div>
  )
}

// 孙组件C
function ComC() {
  return (
    <div>
      ComC,获取爷组件数据:
      {/* 使用Consumer 获取value */}
      <Consumer>
        {value=><span>{value}</span>}
      </Consumer>
    </div>
  )
}


// 父组件
class App extends React.Component {
  state = {
    msg: "this is message"
  }

  render() {
    return (
      // 2.使用Provider包裹根组件 提供数据
      <Provider value={this.state.msg}>
        <div>
          <ComA />
        </div>
      </Provider>
    )
  }
}

export default App

//hooks使用方式:
import React, { useState, createContext, useContext } from "react"

// App->A->C
// App数据->C

//const { Provider, Consumer } = createContext()
const Context = createContext()  //Context身上有提供者Provider与消费者Consumer
// 子组件A
function ComA() {
  const count = useContext(Context)//使用useContext获取Context身上的数据
  return (
    <div>
      ComA
      <br />
      app传过来的数据为:{count}
      <ComC />
    </div>
  )
}

// 孙组件C
function ComC() {
  const count = useContext(Context)
  return (
    <div>
      ComC,获取爷组件数据:
      <br />
      {count}
    </div>
  )
}


// 父组件
function App() {
  const [count, setCount] = useState(0)


  return (
    // 2.使用Provider包裹根组件 提供数据
    <Context.Provider value={count}>
      <div>
        <ComA />
        <button onClick={()=>{setCount(count+1)}}>count+1</button>
      </div>
    </Context.Provider>
  )

}

export default App

7. children 类似vue中的插槽

//children 可以传递jsx 函数 文本 普通标签元素等
import React from "react"

// 渲染列表
function ListItem(props) {
  console.log(props);
  const { children } = props

  return (
    <div>
      {/* {children.map(child => child)} */}
      {children}
    </div>
  )
}

class App extends React.Component {

  render() {
    return (
      <div>
        <ListItem>
          <div>{'可以传jsx'}</div>
          <p>{'p标签'}</p>
        </ListItem>
      </div>
    )
  }
}

export default App

8. props校验

1.安装属性校验包 yarn add prop-types
2.导入prop-types包
3.使用 组件名.propTypes={}给组件添加校验规则


import React from "react"
import PropTypes from 'prop-types'

function Test({ list }) {
  // const { list } = props
  return (
    <div>
      {list.map(item => <p>{item}</p>)}
    </div>
  )
}

Test.propTypes = {
  // 定义各种规则
  list: PropTypes.array  //限定这里的list参数类型必须是数组类型
}
/*
1.常见类型: 数组 布尔值 字符串 函数 对象 symbol
2.React元素类型:element
3.必填项 isRequired  如:PropTypes.array.isRequired
4.特定的结构对象: shape({})
*/


class App extends React.Component {

  render() {
    return (
      <div>
        <Test list={[1,2,3]} />
      </div>
    )
  }
}

export default App

9. props默认值

//默认值

import React from "react"
import PropTypes from 'prop-types'

function Test(props) {

   const {
    list,
    pageSize = 10 //默认参数传递  组件内部才有这个prop
  } = props
   
  console.log(props)
  return (
    <div>
          {pageSize} 
    </div>
  )
}

Test.propTypes = {
  // 定义各种规则
  list: PropTypes.array.isRequired  //限定这里的list参数类型必须是数组类型 
}


class App extends React.Component {

  render() {
    return (
      <div>
        <Test list={[1, 2, 3]} />
      </div>
    )
  }
}

export default App

10.生命周期


挂载阶段(按顺序执行一次)
constructor
render 渲染UI
componetDidMount    1.发送网络请求 2.dom操作

更新阶段(组件更新就会执行) 
render 渲染UI
componentDidUpdate  注意:不要在里面使用setState

卸载阶段(组件被销毁时执行一次) 
componentWillUnmount 消除操作 如定时器的销毁

注意:不可以在render/componnetDidUpdate中执行setState(防止循环渲染)

import React from "react"

class Test extends React.Component {
  // 如果数据是组件的状态需要去影响视图 定义到state中 
  // 如果需要的数据状态 不需要影响视图 直接定义一个普通实例属性就可以了
  // state中尽量保持精简
  timer = null
  componentDidMount() {
    this.timer = setInterval(() => {
      console.log('定时器开启');
    }, 1000);
  }
  componentWillUnmount() {
    console.log('componentWillUnmount');
    // 清除定时器
    clearInterval(this.timer)
  }
  render() {
    return (
      <div>Test</div>
    )
  }
}


class App extends React.Component {
  constructor() {
    super()
    console.log('constructor');
  }
  state = {
    count: 0,
    flag: true
  }
  changeCount = () => {
    this.setState({
      count: this.state.count + 1
    })
  }
  delTest = () => {
    this.setState({
      flag: !this.state.flag
    })
  }
  // 组件挂载完毕时候  类似vue中的mounted 1.发送网络请求 2.dom操作
  componentDidMount() {
    console.log('componentDidMount');
  }
  // 组件更新时执行 注意:不要在里面使用setState
  componentDidUpdate() {
    console.log('componentDidUpdate');
  }
  // 渲染UI
  render() {
    console.log('render');
    return (
      <div>
        this is div
        {
          this.state.flag ? <Test /> : ''
        }
        <button onClick={this.changeCount}>{this.state.count}click</button>
        {/* 修改数据状态 达到销毁重建Test组件 */}
        <button onClick={this.delTest}>修改flag 销毁或重建Test组件</button>
      </div>
    )
  }
}

export default App

11. hooks(只能在函数组件中使用)

1.useState(数据与更新数据)
//使用步骤:
//1.导入useState函数
//2.执行这个函数并且传入初始值 必须在函数组件中
//3.[数据,修改数据的方法]
//4.使用数据 修改数据

//状态的读取和修改
//1.useState传过来的参数 作为count的初始值
//2.[count,setCount]这里的写法是一个解构赋值  useState返回值是一个数组 
//   名字可以自定义吗?-》可以自定义保持语义化
//   顺序可以换吗?-》不可以  第一个参数就是数据状态  第二个参数就是修改数据的方法
//3.setCount函数 作用:用来修改count 依旧保持不能直接修改原值 生成一个新值替换
//   setCount(基于原值计算得到的新值)
//4.count和setCount是一对的 绑在一起的  setCount只能修改对应的count的值

import {useDate} from 'react' //1.导入useState函数
function App(){
 const {count,setCount}=useState(0) //3.[数据,修改数据的方法] //2.执行这个函数并且传入初始值 必须在函数组件中
 return(
  <div>
    <button oncClick={()=>setCount(count+1)}>{count}</button> {/* 4.使用数据 修改数据 */}
   </div>
 )
}
//注意事项与渲染过程:
import { useState } from "react";

// 组件的更新
/*
当调用setCount时 更新过程

首次渲染
组件内部代码会被执行一次 useState也会跟着执行 注意:初始值0只在首次渲染时生效

更新渲染 setCount都会更新
1.app组件会再次渲染  这个函数会再次执行
2.useState再次执行得到的新的count值不是0 是修改后的值 以此类推 
*/

/*
useState注意事项:
1.useState可以执行多次 每次执行互相独立,每调用一次为函数组件提供一个状态
2.只能出现在函数组件中
3.不能嵌套在if/for/其他函数中(react按照hooks调用顺序识别每一个hook) 必须在函数组件最外层使用
*/

function App() {
  // count 数据状态
  // setCount 修改count的函数(专有函数)
  const [count, setCount] = useState(0) //只能在函数组件最外层使用
  const [flag, setFlag] = useState(true)
  const [list, setList] = useState([])
  console.log(count, flag, list);

  function test() {
    setCount(count + 1)
    setFlag(!flag)
    setList([1,2,3])
  }
  return (
    <div>
      count:{count}
      flag:{flag ? '1' : '0'}
      list:{list.join('-')}<button onClick={test}>修改</button>
    </div>
  )
}
export default App
//回调函数的参数场景
初始数据需要计算时候
const [name,setName]=useState(()=>{
    //编写计算逻辑
    return '计算后的值'
})
2.useEffect 副作用(操作外部)
函数副作用
指函数中的代码会对函数外部的内容进行更改,在react中副作用如:ajax请求 手动操作dom localstorage操作

useEffect(() => {
  // 副作用函数的内容
})  
组件初始化时先执行一次 等到每次数据修改组件更新再执行
--------------------
useEffect(() => {
  // 副作用函数的内容
}, []) 
组件初始化时先执行一次 只执行第一次
--------------------
useEffect(() => {
  // 副作用函数的内容
}, [依赖项]) //依赖项可以有多个
1 组件初始化时执行一次 2 每次依赖项的值变化时执行

/*
注意事项
1.useEffect只能是一个同步函数 不能使用async
//如果发送请求或者异步操作 需要额外加一个箭头函数包裹
useEffect(()=>{
  const getData = async () => {
    const res = await xxx()
   }
   getData()
}, [])

2. 副作用函数的返回值
useEffect 函数是可以return的格式是下图,他可以返回一个函数,这个函数称为清理函数
useEffect(() => {
  // 副作用函数的内容
 
  return 副作用函数的返回值
}, [])
*/

案例

import { useState, useEffect } from "react";

// 在修改数据后 把count值放到页面标题中
// 1.导入useEffect函数
// 2.在函数组件中执行 传入回调 
// 3.当我们修改状态更新组件时 副作用也会不断执行



// 依赖项控制副作用的执行时机
// 1.默认状态(无依赖项)
// 组件初始化时先执行一次 等到每次数据修改组件更新再执行
// 2.添加空数组依赖项 
// 组件初始化时候执行一次 后续不执行 
// 3.添加特定依赖项
// 组件渲染时执行一次 依赖项发生变化重新执行
// 4.只要在useEffect回调函数中用到的数据就应该出现在依赖项数组中声明,否则可能会出现bug
// 某种意义上 hook的出现 就是不想用生命周期概念也可以写业务代码
function App() {
  const [count, setCount] = useState(0)
  const [name, setName] = useState('李四')
  useEffect(() => {
    // 定义副作用
    document.title = count
    console.log(name);

  },[count])
  return (
    <div>
      <button onClick={() => setCount(count + 1)}>{count}</button>
      <button onClick={() => setName('张三')}>{name}</button>
    </div>
  )
}
export default App
//清理副作用
使用场景:组件被销毁时  如常见的定时器

useEffect(()=>{
    console.log('副作用执行了')
    //副作用函数的执行实际为:在下一次副作用函数执行之前执行
    return ()=>{
        //清理副作用
 }
})
-----------------------------------------------------------------------------
//示例:
import { useEffect, useState } from 'react';

//子组件
function Test() {
useEffect(()=>{
  var timer= setInterval(() => {
    console.log('定时器执行了');
  }, 1000);
  //return回调函数 清理副作用
  return ()=>{
    clearInterval(timer)//清除定时器
  }
})

  return (
    <div>
      Test
    </div>
  )

}

//父组件
function App() {
  const [flag, setFlag] = useState(true)
  return (
    <div>
      {flag ? <Test /> : null}
      <button onClick={() => setFlag(!flag)}>switch</button>
    </div>
  )
}

export default App
3. hooks使用

//需求描述:自定义一个hook函数 实现获取滚动距离Y
//const [y]=useWindowScroll() y就是滚动到顶部的距离

1.新建hooks文件夹下 useWindow.js //自定义一个hook 
//引入useState方法 
import { useState } from "react"
export function useWindowScroll (){
    const [y,setY] =useState(0) //数据与更新数据方法 
    window.addEventListener('scroll',()=>{
       const h= document.documentElement.scrollTop //获取滚动到顶部高度
       setY(h)  //更新数据
    })
    return [y]
}

2.引入获取高度的该hook方法
import { useWindowScroll } from './hooks/useWindowScroll'

function App() {
  const [y] = useWindowScroll()
  console.log(y);
  return (
    <div style={{ height: '12000px' }}>
      {y}
    </div>
  )
}

export default App
//需求 更新数据在本地存储一份
1.定义一个hook
//导入useEffect useState方法 修改数据与操作外部方法
import { useEffect, useState } from "react";

export function useLocalStorage(key,defaultValue) {
    const [message, setMessage] = useState(defaultValue)

    // 每次只要message变化 就会自动同步到本地localStorage (使用副作用useEffect函数操作本地缓存)
    useEffect(() => {
        window.localStorage.setItem(key, message)
    }, [message,key])

    return [message, setMessage]
}

2.引用hook
import { useLocalStorage } from './hooks/useLocalStorage';

function App() {
  const [message,setMessage] =useLocalStorage('hook-key','阿飞')
  setTimeout(() => {
    setMessage('张三')
  }, 5000);
  return (
    <div>
    {message}
    </div>
  )
}

export default App
4. useRef(获取dom)
步骤:
1.导入useRef函数
2.执行useRef函数传入null,返回值为一个对象,内部有current属性存放拿到的dom对象(组件实例
3.通过ref绑定要获取的元素或者组件 

//1.引用useRef
import React, { useEffect, useRef } from "react" 

class Test extends React.Component {
   state={
    name:"jack"
  }
  getName=()=>{
    console.log('this is child Test');
  }
  render() {
    return (
      <div>
        类组件
      </div>
    )
  }
}

function App() {
    //3.获取该元素
  const testRef = useRef(null)
  const h1 = useRef(null)
  useEffect(() => {
    console.log(testRef);
    testRef.current.getName()  //调用组件方法
    console.log(testRef.current.state.name); //获取组件上的数据/状态
    console.log(h1);
  }, [])
  return (
    <div style={{ height: '12000px' }}>
       {/* 2.绑定ref */} 
      <Test ref={testRef} />   
      <h1 ref={h1}>this is h1</h1>
    </div>
  )
}

export default App
5.useContext(跨组件通信)
import React, { useState, createContext, useContext } from "react"

// App->A->C
// App数据->C

实现步骤:
1.使用createContext创建Context对象
2.顶层组件通过Provider提供数据   (数据静态不变可以index.js包裹 、  数据需要变化可以app.js)
3.底层组件通过useContext(Context)函数获取数据

//const { Provider, Consumer } = createContext()
const Context = createContext() //Context中包含提供者Provider与消费者Consumer
// 子组件A
function ComA() {
  const count = useContext(Context)//使用useContext获取Context身上的数据
  return (
    <div>
      ComA
      <br />
      app传过来的数据为:{count}
      <ComC />
    </div>
  )
}

// 孙组件C
function ComC() {
  const count = useContext(Context)//使用useContext获取Context身上的数据
  return (
    <div>
      ComC,获取爷组件数据:
      <br />
      {count}
    </div>
  )
}


// 父组件
function App() {
  const [count, setCount] = useState(0)

  return (
    // 2.使用Provider包裹根组件 提供数据
    <Context.Provider value={count}>
      <div>
        <ComA />
        <button onClick={()=>{setCount(count+1)}}>count+1</button>
      </div>
    </Context.Provider>
  )

}

export default App

12. react-router

1.简单demo
// 引入两个组件
import Home from "./Home";
import About from "./About";


// 进行路由配置
import { BrowserRouter, Link, Route, Routes } from 'react-router-dom'


function App() {
  return (
    <div className="App">
      {/* 声明当前要用一个非Hash模式的路由 */}
      <BrowserRouter>
        {/* 指定跳转的组件 to用来配置路由地址 */}
        <Link to="/">首页</Link>
        <Link to="/about">关于</Link>
        {/* 路由出口 路由对应的组件会在这里进行渲染 */}
        <Routes>
          {/* 指定路径和组件的对应关系 path代表路径 element代表组件 */}
          <Route path="/" element={<Home />}></Route>
          <Route path="/about" element={<About />}></Route>
        </Routes>
      </BrowserRouter>
    </div>
  );
}

export default App;

2. 核心模块-BrowerRouter与HashRouter
作用:包裹整个应用 一个React应用只需要使用一次
两种常用Router : HashRouter和BrowerRouter(推荐)
HashRouter
使用url的哈希值实现(http://localhost:3000/#/first
BrowerRouter(推荐)
使用h5的history.pushState API实现(http://localhost:3000/first
3. 核心模块-Link
//to指定导航链接
<Link to="/about">关于</Link>
4. 核心组件-Routes
  {/* 作用:路由出口 路由对应的组件会在这里进行渲染 */}
 <Routes>
   {/* 指定路径和组件的对应关系 path代表路径 element代表组件 */}
     <Route path="/" element={<Home />}></Route>
     <Route path="/about" element={<About />}></Route>
 </Routes>
5. 核心组件-Route
//作用:用于指定导航链接 完成路由匹配
{/* 语法:指定路径和组件的对应关系 path代表路径 element代表组件 */}
<Route path="/about" element={<About />}></Route>
6.编程式导航
// 1.导入useNavigate
import { useNavigate } from "react-router-dom"

function Login() {
    // 2.执行useNaviget得到一个跳转函数
    const navigate = useNavigate()
    function goAbout() {
        //3.路由跳转  跳转时不添加到历史记录可以设置replace为true
         navigate('/about',{replace:true})
    }
    return (
        <div>
            login
            <button onClick={goAbout}>跳到关于</button>
        </div>
    )
}

export default Login
1.路由传参
//方式1:searchParams传参(推荐)
//路由传参
navigate('/about?id=1')

//取参
let [params] =useSearchParams()
let id=params.get('id')


//方式二:params传参
navigate('/about/1')

//取参
let params=useParams()
let id=params.id

//注意:需要配置路由参数
 <Route path="/about/:id" element={<About />}></Route>

2.嵌套路由
//App.js中
import { BrowserRouter, Routes, Route } from "react-router-dom";

import Layout from "./Layout";
import Login from "./Login";
import Board from "./Board";
import Article from "./Article";

function App() {
  return (
    <div className="App">
      <BrowserRouter>
        <Routes>
          <Route path="/" element={<Layout />}>
            {/* 定义二级路由嵌套 */}
            {/* 默认二级 添加index属性 把它自己的path去掉 */}
            <Route index  element={<Index />}></Route>
            <Route path="board" element={<Board />}></Route>
            <Route path="article" element={<Article />}></Route>
          </Route>
          <Route path="/login" element={<Login />}></Route>
          {/* 当所有路径都不匹配 跳转到此页面 用来配置404页面 */}
          <Route path="*" element={<NotFound />}></Route>
        </Routes>
      </BrowserRouter>
    </div>
  );
}

export default App;

//Layout.js中:(一级路由组件)
import { Outlet } from "react-router-dom"

function Layout() {
    return (
        <div>
            Layout
            {/* 二级路由出口 */}
            <Outlet />
        </div>
    )
}

export default Layout




//Board.js中:
function Board(){
    return (
        <div>
            this is board
        </div>
    )
}

export default Board
13. mobx(类似vue中的vuex)
//集中状态管理工具,相当于vue与vuex关系
//同类工具还有redux dva recoil

Mobx是一个独立的响应式的库,可以独立于任何UI框架而存在,但是通常与react框架绑定使用,使用Mobx,使用Mobx来做响应式的数据建模,reacat作为UI视图渲染内容。
三部分:
1.react项目环境
2.mobx本身
3.一个链接mobx与react的中间件 mobx-react-lite

1.初始化过程:
声明数据->响应式处理->定义actions函数->实例化导出
2.mobx如何配合react,需要依赖什么包
mobx-react-lite作为链接包,导出observer方法,包裹组件(只能和函数组件配合)
3.模块化解决了什么问题
维护性问题
4.如何实现mobx的模块化
按功能拆分store模块,根模块中组合子模块,利用context机制依赖注入
1. 简单用法
//根目录新建store文件夹下顶一个一个counter.js状态管理
import { makeAutoObservable } from "mobx"

class CounterStore {
    // 1.定义数据
    count = 0

    constructor() {
        // 2.把数据弄成响应式
        makeAutoObservable(this)
    }
    // 3.定义action函数(修改数据)
    addCount=()=>{
        this.count++
    }
}

// 4.实例化 然后导出给react使用
const counterStore=new CounterStore()
export {counterStore}

---------------------------------------------------------------------------------------

//App.js中:
    //1.导入counterStore
import { counterStore } from "./store/counter";
  //2.导入中间件链接mobx与react完成响应式变化
import { observer } from "mobx-react-lite"

function App() {
  return (
    <div className="App">
      {/*把store中的count渲染一下 */}
      {/* 点击触发actions函数修改count值 */}
      <button onClick={counterStore.addCount}>
        {counterStore.count}
      </button>
    </div>
  );
}

//3.包裹App
export default observer(App);

2. computed(计算属性)
//根目录新建store文件夹下顶一个counter.js状态管理
import { makeAutoObservable } from "mobx"

class CounterStore {
    // 1.定义数据
    count = 0
    // 定义一个原始数据 list
    list = [1, 2, 3, 4, 5, 6]
    constructor() {
        // 2.把数据弄成响应式
        makeAutoObservable(this)
    }
    // 定义计算属性(加一个get修饰符即可)
    get filterList() {
        return this.list.filter(item => item > 2)
    }

    // 3.定义action函数(修改数据)
        // 修改list
    addList = () => {
        this.list.push(7, 8, 9)
    }
    addCount = () => {
        this.count++
    }
}

// 4.实例化 然后导出给react使用
const counterStore = new CounterStore()
export { counterStore }

---------------------------------------------------------------------------------

//App.js中:

import { counterStore } from "./store/counter";
import { observer } from "mobx-react-lite"

function App() {
  return (
    <div className="App">
      {/*把store中的count渲染一下 */}
      {/* 点击触发 */}
      {/* 使用计算属性 */}
      {counterStore.filterList.join('-')}
      {/* 定义一个方法 */}
      <button onClick={counterStore.addCount}>
        {counterStore.count}
      </button>
      <button onClick={counterStore.addList}>修改数组</button>
    </div>
  );
}

export default observer(App);

3.模块化(复杂用法 大型项目)

子模块1

//在store文件夹下新建counter.Store.js
import { makeAutoObservable } from "mobx"

class CounterStore {
    // 1.定义数据
    count = 0

    // 定义一个原始数据 list
    list = [1, 2, 3, 4, 5, 6]
    constructor() {
        // 2.把数据弄成响应式
        makeAutoObservable(this)
    }
    // 定义计算属性
    get filterList() {
        return this.list.filter(item => item > 2)
    }
    // 修改list
    addList = () => {
        this.list.push(7, 8, 9)
    }
    // 3.定义action函数(修改数据)
    addCount = () => {
        this.count++
    }
}

// 4.实例化 然后导出给react使用
export { CounterStore }

子模块2

 //在store文件夹下新建list.Store.js
import { makeAutoObservable } from "mobx"

class ListStore{
    list=['react','vue']
    constructor(){
        makeAutoObservable(this)
    }

    addList=()=>{
        this.list.push('angular')
    }
}

export {ListStore}

根模块(组合子模块)

//在store文件加下新建index.js
// 组合子模块
// 封装统一导出的供业务使用的方法
import { ListStore } from "./list.Store";
import { CounterStore } from "./counter.Store";
import React from "react";

// 1.声明一个rootStore
class RootStore{
 constructor(){
    // 对子模块进行实例化操作
    // 将来实例化根store时,根store有两个属性分别对应各自子模块实例对象
    this.CounterStore=new CounterStore()
    this.ListStore=new ListStore()
 }
}

// 实例化操作
const rootStore =new RootStore()
// 使用react context机制 完成统一方法封装
// Context.Provider value={传递的数据}
// 查找机制:useContext 优先从Provider value找 如果找不到就会找createContext()方法传递过来的默认参数
const context =React.createContext(rootStore)
// 这个方法作用:通过useContex拿到rootStore实例对象
// useStore()  ->  rootStore
const useStore=()=>React.useContext(context)

export {useStore}

使用

//App.js中使用
import { useStore } from "./store";
//导入中间件链接mobx与react完成响应式变化
import { observer } from "mobx-react-lite"

function App() {
  const rootStore = useStore()
  // 注意:解构赋值 到store实例对象就可以了 防止破坏响应式
  const {counterStore,ListStore}=useStore()
  console.log({ rootStore });
  return (
    <div className="App">
      {rootStore.ListStore.list.join('-')}

      <button onClick={rootStore.CounterStore.addCount}>
        {rootStore.CounterStore.count}
      </button>

      <button onClick={rootStore.ListStore.addList}>
        修改数组
      </button>
    </div>
  );
}
//包裹App
export default observer(App);

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值