1. React简介

React 是 Facebook 在 2011 年开发的前端 JavaScript 库。它遵循基于组件的方法,有助于构建可重用的UI组件。

它使用虚拟DOM而非真实DOM

虚拟DOM与真实DOM比较:

  1. 虚拟DOM本质是一个js的对象
  2. 虚拟DOM比较“轻”,真实DOM比较“重”(自带属性很多)
  3. 虚拟DOM最终会被转化为真实DOM

2. JSX语法规则

JSX 是JavaScript XML 的简写。是 React 使用的一种文件。

  1. 定义虚拟DOM时,不要写引号
  2. 标签中混入JS表达式时要用{ }
  3. 样式的类名指定不要用class,要用className
  4. 内联样式要用**{{key: value}}**形式去写, 外部花括号表示里面要写js表达式, 内层花括号表示对象
  5. 虚拟DOM必须只有一个根标签
  6. 标签必须闭合
  7. 标签首字母
    1. 若小写字母,则React会将标签转为html同名元素
    2. 若大写字母开头,则react则去渲染同名组件
 1<script type="text/babel">
 2    const myID = "title";
 3    const myData = "Hello React";
 4
 5    // 1. 创建虚拟DOM
 6    const VDOM = (  /* 不写引号 */
 7    <div>
 8        <h1 className="title" id ={myID}>
 9            <span style={{color: "white", fontSize:"30px"}}>{myData}</span>
10        </h1>
11        <h1 className="title" id ={myID + '1'}>
12            <span style={{color: "white", fontSize:"30px"}}>{myData}</span>
13        </h1>
14        <input type="text"></input>
15    </div>
16    ) 
17    // 2. 渲染虚拟DOM到页面
18    ReactDOM.render(VDOM, document.getElementById('test'));
19</script>

3. React面向组件编程

3.1. 函数式组件

1<script type="text/babel">
2    // 1. 创建函数式组件
3    function Demo(){
4        return <h1>Hello React</h1>
5    }
6
7    ReactDOM.render(<Demo/>, document.getElementById("test"));
8</script>

3.2 类式组件

 1<script type="text/babel">
 2    // 1. 创建类式组件
 3    class Demo2 extends React.Component{
 4        render(){
 5            return <h1>Hello React!!!</h1>
 6        }
 7    }
 8    // 2. 渲染组件到页面
 9    ReactDOM.render(<Demo2/>, document.getElementById("test"));
10</script>

4. React组件三大核心属性

4.1. state

状态(state)不可直接更改,需要借助setState进行更新,且更新是合并的形式,不是替换。

setState()以后组件会调用render()重新渲染

 1<script type="text/babel">
 2    // 1. 创建类式组件
 3    class Weather extends React.Component{
 4        constructor(props){
 5            super(props);
 6            // 初始化状态
 7            this.state = {
 8                isHot: true
 9            };
10			// 改变this指向
11            this.changeWeather = this.changeWeather.bind(this);
12        }
13        render(){
14            return <h1 onClick={this.changeWeather}>今天天气很{this.state.isHot ? '炎热' : '凉爽'}</h1>
15        }
16        changeWeather(){
17            // 由于changeWeather是作为onclick的回调,不是通过实例调用的,是直接调用
18            // 类中的方法默认开启局部严格模式
19            // 所以this为 undefined
20            const isHot = this.state.isHot;
21            this.setState({
22                isHot: !isHot
23            });
24        }
25    }
26    // 2. 渲染组件到页面
27    ReactDOM.render(<Weather/>, document.getElementById("test"));
28</script>

state简化写法:

 1<script type="text/babel">
 2    // 1. 创建类式组件
 3    class Weather extends React.Component{
 4        // 初始化状态
 5        state = {
 6            isHot: true,
 7            wind: '微风'
 8        };
 9
10        render(){
11            return <h1 onClick={this.changeWeather}>今天天气很{this.state.isHot ? '炎热' : '凉爽'}, { this.state.wind }</h1>
12        }
13
14        // 自定义方法
15        // 箭头函数this取决于外部this
16        changeWeather = ()=>{
17            const isHot = this.state.isHot;
18            this.setState({
19                isHot: !isHot,
20            });
21        }
22    }
23    // 2. 渲染组件到页面
24    ReactDOM.render(<Weather/>, document.getElementById("test"));
25</script>

4.2. props

props用于外部传值,一但定义不允许在组件内进行更改.

组件标签内以属性定义方式进行传值

 1<script type="text/babel">
 2    // 1. 创建类式组件
 3    class Person extends React.Component{
 4        render(){
 5            console.log(this);
 6            return (
 7                <ul>
 8                    <li>姓名: {this.props.name}</li>
 9                    <li>性别: 女</li>
10                    <li>年龄: 18</li>
11                </ul>
12            )
13        }
14    }
15    // 2. 渲染组件到页面
16    ReactDOM.render(<Person name="Jin"/>, document.getElementById("test"));
17</script>

批量传递标签属性:

 1<script type="text/babel">
 2    // 1. 创建类式组件
 3    class Person extends React.Component{
 4        render(){
 5            console.log(this);
 6            return (
 7                <ul>
 8                    <li>姓名: {this.props.name}</li>
 9                    <li>性别: {this.props.sex}</li>
10                    <li>年龄: {this.props.age}</li>
11                </ul>
12            )
13        }
14    }
15    const p = {name: "jin", sex:"男", age: 19};
16    // 2. 渲染组件到页面
17    ReactDOM.render(<Person {...p}/>, document.getElementById("test"));
18</script>

props简写形式,标签类型限制,默认属性定义:

 1<script type="text/babel">
 2    // 1. 创建类式组件
 3    class Person extends React.Component{
 4        // 对标签属性进行限制
 5        static propTypes = {
 6            name: PropTypes.string.isRequired, // 限制必传 且为字符串
 7            sex: PropTypes.string,
 8            age: PropTypes.number,
 9            speak: PropTypes.func,
10        };
11        // 指定默认标签属性
12        static defaultProps = {
13            sex: "男",
14            age: 100
15        }
16
17        render(){
18            console.log(this);
19            return (
20                <ul>
21                    <li>姓名: {this.props.name}</li>
22                    <li>性别: {this.props.sex}</li>
23                    <li>年龄: {this.props.age}</li>
24                </ul>
25            )
26        }
27    }
28    const p = {name: "jin", sex:"男", age: 19};
29    // 2. 渲染组件到页面
30    ReactDOM.render(<Person {...p}/>, document.getElementById("test"));
31    ReactDOM.render(<Person name="Tom"  sex="woman"/>, document.getElementById("test1"));
32</script>

constructor中props问题

1// 不接受props 构造器中可能获取不到props值.
2// constructor(){
3//     super();
4//     console.log(this.props);    -------   undefined
5// }
6constructor(props){
7    super(props);
8    console.log(this.props);
9}

4.3. refs

组件内的html标签可以定义ref属性来标识自己,React会将其收集到组件实例的refs属性内.

利用refs可以获取到ref属性标识的dom节点

不应该过度使用ref

  1. 字符串形式ref (不推荐)

     1<script type="text/babel">
     2    // 1. 创建类式组件
     3    class Demo extends React.Component{
     4        render(){
     5            return (
     6                <div>
     7                    <input ref="input1" type="text" placeholder="点击按钮提示数据" />
     8                    <button ref="btn1" onClick={this.showData}>点我提示左侧数据</button>
     9                    <input ref="input2" type="text" placeholder="失去焦点提示数据" />
    10                </div>
    11            )
    12        }
    13
    14        showData = ()=>{
    15            console.dir(this.refs.input1);
    16        }
    17    }
    18    // 2. 渲染组件到页面
    19    ReactDOM.render(<Demo />, document.getElementById("test"));
    20</script>
    
  2. 回调形式ref

    如果回调函数是以内联方式定义的,则在组件更新过程中它会被执行两次,第一次传入参数为null,第二次会传入参数DOM元素.(不是啥大问题)

    通过将ref的回调函数定义成class的绑定函数(实例方法)可以避免上述问题.

     1<script type="text/babel">
     2    // 1. 创建类式组件
     3    class Demo extends React.Component{
     4        state = {
     5            isHot: true
     6        }
     7        render(){
     8            console.log(this);
     9            return (
    10                <div>
    11                    <input ref={(currentNode)=>{
    12                            // 回调参数为dom节点本身
    13                            this.input1 = currentNode;
    14                            console.log('被调用');
    15                        }} 
    16                        type="text" placeholder="点击按钮提示数据" />
    17                    <button ref={(currentNode)=>{
    18                            // 回调参数为dom节点本身
    19                            this.btn1 = currentNode;
    20                        }}
    21                        onClick={this.showData}>点我提示左侧数据</button>
    22                    <input ref={(currentNode)=>{
    23                            // 回调参数为dom节点本身
    24                            this.input2 = currentNode;
    25                        }}
    26                        type="text" placeholder="失去焦点提示数据" />
    27                    <br />
    28                    <span>今天天气很{this.state.isHot ? "炎热" : "凉爽"}!!</span>
    29                </div>
    30            )
    31        }
    32
    33        showData = ()=>{
    34            console.log(this.input1.value);
    35            const { isHot } = this.state;
    36            this.setState({isHot: !isHot});
    37        }
    38    }
    39    // 2. 渲染组件到页面
    40    ReactDOM.render(<Demo />, document.getElementById("test"));
    41</script>
    
  3. createRef

    React.createRef()函数调用后返回一个容器, 该容器用于存储ref所标识的dom节点, 需要多少个节点就创建多少个ref容器

     1<script type="text/babel">
     2    // 1. 创建类式组件
     3    class Demo extends React.Component{
     4        /* 
     5           React.createRef()调用后可以返回一个容器,该容器用于存储ref所标识的dom节点
     6           该容器只允许存一个节点
     7        */
     8        input1 = React.createRef();
     9        render(){
    10            return (
    11                <div>
    12                    <input ref={this.input1} type="text" placeholder="点击按钮提示数据" />
    13                    <button ref="btn1" onClick={this.showData}>点我提示左侧数据</button>
    14                    <input ref="input2" type="text" placeholder="失去焦点提示数据" />
    15                </div>
    16            )
    17        }
    18
    19        showData = ()=>{
    20            console.log(this.input1);
    21        }
    22    }
    23    // 2. 渲染组件到页面
    24    ReactDOM.render(<Demo />, document.getElementById("test"));
    25</script>
    

5. React中的事件处理

  1. 通过onXxx属性指定事件处理函数(注意大小写)

    a. React使用的是自定义(合成)事件,而不是使用原生DOM事件 ————为了更好的兼容性

    b. React中的事件是通过事件委托方式处理的(委托给最外层元素) ————为了高效

  2. 通过event.target得到发生事件的DOM元素对象

 1<script type="text/babel">
 2    // 1. 创建类式组件
 3    class Demo extends React.Component{
 4        input1 = React.createRef();
 5        render(){
 6            console.log(this);
 7            return (
 8                <div>
 9                    <input ref={this.input1} type="text" placeholder="点击按钮提示数据" />
10                    <button onClick={this.showData}>点我提示左侧数据</button>
11                    <input onBlur={this.showData2} type="text" placeholder="失去焦点提示数据" />
12                </div>
13            )
14        }
15
16        showData = ()=>{
17            console.log(this.input1.current.value);
18        }
19
20        showData2 = (event)=>{
21            console.log(event.target.value);
22        }
23    }
24    // 2. 渲染组件到页面
25    ReactDOM.render(<Demo />, document.getElementById("test"));
26</script>

5.1 非受控组件

顾名思义,非受控组件即不受状态控制,获取数据即操作DOM,即用即取

以做一个用户登录表单提交为例

实现方法可以有利用ref获取dom节点,在登录提交时获取对应节点值,但ref不建议过度使用

 1<script type="text/babel">
 2    // 1. 创建类式组件
 3    class Login extends React.Component{
 4        render(){
 5            return (
 6                <form onSubmit={this.handleSubmit}>
 7                    用户名: <input ref={(c)=>this.username = c} type="text" name="username"/>
 8                    密码: <input ref={(c)=>this.password = c} type="password" name="password"/>
 9                    <button> 登录 </button>
10                </form>
11            )
12        }
13
14        handleSubmit = (event)=>{
15            event.preventDefault();
16            console.log(this.username.value, this.password.value);
17        }
18    }
19    // 2. 渲染组件到页面
20    ReactDOM.render(<Login />, document.getElementById("test"));
21</script>

5.2 受控组件

受控组件必须要有value和onChange,onChange用于获取value,保存在状态中,在需要时从状态中去获取值

 1<script type="text/babel">
 2    // 1. 创建类式组件
 3    class Login extends React.Component{
 4        state = {
 5            username: "",
 6            password: ""
 7        }
 8        render(){
 9            console.log(this);
10            return (
11                <form onSubmit={this.handleSubmit}>
12                    用户名: <input value="111" onChange={this.saveUsername} type="text" name="username"/>
13                    密码: <input onChange={this.savePassword} type="password" name="password"/>
14                    <button> 登录 </button>
15                </form>
16            )
17        }
18
19        handleSubmit = (event)=>{
20            event.preventDefault();
21            console.log(this.username.value, this.password.value);
22        }
23
24        saveUsername = (event)=>{
25            console.log(event.target.value);
26            this.setState({username: event.target.value});
27        }
28
29        savePassword = ()=>{
30            this.setState({password: event.target.value});
31        }
32    }
33    // 2. 渲染组件到页面
34    ReactDOM.render(<Login />, document.getElementById("test"));
35</script>

5.3 函数柯里化

函数柯里化:通过函数调用继续返回函数的方式,实现多次接收参数最后统一处理的函数编码形式。

上述onChange每一个事件都要绑定一个不同函数,显然面对多条input时会造成代码冗余

因此,可以利用函数闭包,接收数据类型参数,返回真正调用的函数,根据数据类型参数来对对应状态进行赋值

 1<script type="text/babel">
 2    // 1. 创建类式组件
 3    class Login extends React.Component{
 4        state = {
 5            username: "",
 6            password: ""
 7        }
 8        render(){
 9            console.log(this);
10            return (
11                <form onSubmit={this.handleSubmit}>
12                    用户名: <input onChange={this.saveFormData('username')} type="text" name="username"/>
13                    密码: <input onChange={this.saveFormData('password')} type="password" name="password"/>
14                    <button> 登录 </button>
15                </form>
16            )
17        }
18
19        handleSubmit = (event)=>{
20            event.preventDefault();
21            console.log(this.state.username, this.state.password);
22        }
23
24        saveFormData = (dataType)=>{
25            // this.setState({username: event.target.value});
26            return (event)=>{
27                console.log(dataType);
28                console.log(event.target.value);
29                this.setState({[dataType]: event.target.value});
30            }
31        }
32    }
33    // 2. 渲染组件到页面
34    ReactDOM.render(<Login />, document.getElementById("test"));
35</script>

6. React组件的生命周期

React组件包含一系列钩子函数(生命周期回调函数),会在特定的时刻调用。

image-20220407170133979

6.1. React生命周期(旧)

初始化阶段 —— 由React.render()触发

  1. constructor

    构造函数构建组件实例

  2. 组件将要被挂载

    componentWillMount(){}

  3. 初始化渲染

    render()

  4. 组件挂载完毕

    componentDidMount(){}

    注意:比较常用,一般用于做一些初始化的事,例如:开启定时器,发送网络请求,订阅消息

  5. 组件即将卸载

    componentWillUnmount(){}

    注意:比较常用,一般用于做一些收尾的事,例如:关闭定时器,取消订阅消息

  6. 卸载组件

    ReactDOM.unmountComponentAtNode(document.getElementById(“test”))

Update

  1. componentWillReceiveProps(props)

    组件将要接收到props,触发后续更新

    注意:第一次接收到props不会触发

  2. setState()

    设置state,触发后续更新

  3. forceUpdate()

    强制更新, 比正常跟新少了一个shouldUpdate

  4. 组件是否应该更新

    shouldComponentUpdate(nextProps, nextState){}

  5. 组件将要更新

    componentWillUpdate(){}

  6. 组件更新完毕

    componentDidUpdate(){}

 1<script type="text/babel">
 2    // 1. 创建类式组件
 3    class Count extends React.Component{
 4        constructor(props){
 5            console.log("constructor");
 6            super(props);
 7            this.state = {
 8                count: 0
 9            }
10        }
11
12        componentWillMount(){
13            console.log("componentWillMount");
14        }
15
16        componentDidMount(){
17            console.log("componentDidMount");
18        }
19
20        componentWillUnmount(){
21            console.log("componentWillUnmount");
22        }
23
24        shouldComponentUpdate(){
25            console.log("shouldComponentUpdate");
26            return true;
27        }
28
29        componentWillUpdate(){
30            console.log("componentWillUpdate");
31        }
32
33        componentDidUpdate(){
34            console.log("componentDidUpdate");
35        }
36
37        render(){
38            console.log("Render");
39            return (
40                <div>
41                    <h2>当前求和: {this.state.count}</h2>
42                    <button onClick={this.add}>点我加1</button>
43                    <button onClick={this.death}>卸载组件</button>
44                    <button onClick={this.force}>强制更新</button>
45                </div>
46            )
47        }
48
49        add = ()=>{
50            let count = this.state.count;
51            this.setState({count: count + 1})
52        }
53
54        death = ()=>{
55            ReactDOM.unmountComponentAtNode(document.getElementById("test"));
56        }
57
58        force = ()=>{
59            this.forceUpdate();
60        }
61    }
62
63    class A extends React.Component{
64        state = {
65            carName: "奔驰"
66        }
67
68        render(){
69            return (
70                <div>
71                    <div>我是A组件</div>
72                    <button onClick={this.changeCar}>换车</button>
73                    <B carName={this.state.carName}/>
74                </div>
75            )
76        }
77
78        changeCar = ()=>{
79            let carName = this.state.carName;
80            this.setState({carName: carName == "奔驰" ? "奥拓" : "奔驰"});
81        }
82    }
83
84    class B extends React.Component{
85        componentWillReceiveProps(props){
86            console.log("componentWillReceiveProps", props);
87        }
88
89        render(){
90            return (
91                <div>我是B组件,接收到的车是{this.props.carName}</div>
92            )
93        }
94    }
95    // 2. 渲染组件到页面
96    ReactDOM.render(<A />, document.getElementById("test"));
97</script>

6.2. React生命周期(新)

新版本生命周期和旧版本没太大区别,主要区别如下:

  • componentWillMount()
  • componentWillUpdate()
  • componentWillReceiveProps()

以上三个在新版本中需要加上UNSAFE_前缀使用,如UNSAFE_componentWillMount()

这里的UNSAFE不是指安全性,而是指使用这些生命周期函数的代码在未来版本中可能会出现bug,React未来打算实现异步渲染。

同时新增了下列两个生命周期函数:

  • getDerivedStateFromProps(): 在调用 render 方法之前调用,并且在初始挂载及后续更新时都会被调用。返回一个state状态对象或者null,根据 shouldComponentUpdate() 的返回值,判断 React 组件的输出是否受当前 state 或 props 更改的影响。

    此方法适用于一些极其特殊的时刻,state的值在任何时候都取决于props

  • getSnapshotBeforeUpdate(): 在更新时调用,最近一次渲染输出(提交到 DOM 节点)之前调用,获取快照,此函数任何返回值都将作为参数传递给componentDidUpdate(),此方法也不常用

新版Update调用顺序:

  1. getDerivedStateFromProps()
  2. shouldComponentUpdate()
  3. render()
  4. getSnapshotBeforeUpdate()
  5. componentDidUpdate(preProps, preState, snapshotValue)

getSnapshotBeforeUpdate()用法示例:

 1<script type="text/babel">
 2    // 1. 创建类式组件
 3    class NewsList extends React.Component{
 4        state = {
 5            newsArr:[]
 6        }
 7
 8        componentDidMount(){
 9            setInterval(() => {
10                const newsArr = this.state.newsArr;
11                const news = '新闻' + (newsArr.length + 1);
12                this.setState({newsArr : [news, ...newsArr]});
13            }, 1000);
14        }
15
16        getSnapshotBeforeUpdate(){
17            // 返回更改前内容区高度
18            return this.refs.list.scrollHeight;
19        }
20
21        componentDidUpdate(preProps, preState, snapshotVal){
22            this.refs.list.scrollTop += this.refs.list.scrollHeight - snapshotVal;
23        }
24
25        render(){
26            return(
27                <div className="list" ref="list">
28                    {
29                        this.state.newsArr.map((val, index)=>{
30                            return <div className="news" key={index}>{val}</div>
31                        })
32                    }
33                </div>
34            )
35        }
36    }
37    // 2. 渲染组件到页面
38    ReactDOM.render(<NewsList />, document.getElementById("test"));
39</script>

7. DOM的diff算法

  1. 虚拟DOM中key的作用

    a. 简单地说,key是虚拟dom对象的标识

    b. 当状态中的数据发生变化时,React会根据【新数据】生成【新的虚拟dom】随后,React进行【新虚拟dom】和【旧虚拟dom】的diff比较

    ​ 比较规则下:

    1. 旧虚拟dom中找到了与新虚拟dom相同的key

      若虚拟dom的内容没变,则直接使用之前的真实dom。

      若虚拟dom的内容变了,则生成新的真实dom,随后替换掉页面中之前的真实dom

    2. 就虚拟dom中没有找到与新的虚拟dom相同的key

      根据数据创建新的真实dom,随后渲染到页面

  2. 用index作为key可能会引发的问题

    a. 若对数据进行:逆序添加,逆序删除等破坏顺序的操作(例如,在数组首位添加删除元素):

    ​ 会产生没有必要的真实DOM的更新(因为index变了)

    b. 若结构中还包含输入类的dom:

    ​ 会产生错误DOM更新

    c. 若仅用于渲染列表用于展示,则用index没有问题,但是数据量大进行逆序操作会引发效率问题

8. React脚手架使用

8.1. 创建项目并启动

  • 全局安装

    npm install -g create-react-app

  • 创建项目

    create-react-app hello-react

  • 启动项目

    npm start

  • 打包项目

    npm run build

React项目内组件一般用jsx后缀结尾(用js也能用,一般为了区分)

9. React父子组件通信

9.1. 父组件给子组件传递数据

通过props方式传递

9.2. 子组件给父组件传递数据

也是通过props方式,但是父组件要先定义一个修改数据的函数,将该函数传递给子组件,子组件调用函数对父组件数据进行修改

 1class App extends React.Component{
 2    state = {
 3        todos:[
 4            {id: '001', name: '吃饭' , done: true},
 5            {id: '002', name: '睡觉' , done: true},
 6            {id: '003', name: '敲代码' , done: false},
 7            {id: '004', name: '逛街' , done: true}
 8        ]
 9    }
10
11    addTodo = (todo)=>{
12        this.setState({todos: [...this.state.todos, todo]});
13    }
14
15    deleteTodo = (id)=>{
16        var newTodos = this.state.todos.filter((todo)=>{
17            return todo.id !== id;
18        })
19        this.setState({todos: newTodos});
20    }
21
22    changeTodo = (id, done)=>{
23        const newTodos = this.state.todos.map((todo)=>{
24            if(todo.id === id){
25                todo.done = done;
26            }
27            return todo;
28        })
29        this.setState({todos: newTodos});
30    }
31
32    checkAllTodos = (done)=>{
33        const {todos} = this.state;
34        const newTodos = todos.map((todo)=>{
35            return {...todo, done: done};
36        })
37        this.setState({todos: newTodos});
38    }
39    
40    clearAllDone = ()=>{
41        const {todos} = this.state;
42        const newTodos = todos.filter((todo)=>{
43            return !todo.done;
44        })
45        this.setState({todos: newTodos});
46    }
47
48    render(){
49        return (
50            <div className="todo-container">
51                <div className="todo-wrap">
52                <Header addTodo={this.addTodo}></Header>
53                <List todos={this.state.todos} changeTodo={this.changeTodo} deleteTodo={this.deleteTodo}></List>
54                <Footer todos={this.state.todos} checkAllTodos={this.checkAllTodos} clearAllDone={this.clearAllDone}>						</Footer>
55                </div>
56            </div>
57        )
58    }
59}

10. React兄弟组件通信

消息订阅与发布

工具库: PubSubJS

适用于任意组件间通信

 1import PubSub from 'pubsub-js'
 2
 3// 消息订阅 用于需要获取数据的组件
 4var token = PubSub.subscribe('MY TOPIC', mySubscriber);
 5
 6// 消息发布 用于需要传递数据的组件
 7PubSub.publish('MY TOPIC', 'hello world!');
 8
 9// 取消订阅
10PubSub.unsubscribe(token);

11. React代理配置

  1. 单个服务器代理

    直接在package.json中加上 ”proxy“:"http://localhost:xxxx"

  2. 多代理配置

    在src下建立setupProxy.js文件

     1// CommonJS语法
     2const {createProxyMiddleware} = require('http-proxy-middleware');
     3
     4module.exports = function(app){
     5    app.use(
     6        createProxyMiddleware('/api1', {        // 遇见前缀为 /api1 的请求, 就会触发该代理配置
     7            target: 'http://localhost:5000',    // 请求转发给谁
     8            changeOrigin: true,                 // 控制服务器接受到的请求头中Host字段的值 为true时 服务器收到请求头为localhost:5000 												//								   为flase时 服务器收到请求头为localhost:3000
     9            pathRewrite: {'^/api1':''}          // 重写请求路径
    10        })
    11    ),
    12    app.use(
    13        createProxyMiddleware('/api2', {
    14            target: 'http://localhost:5001',
    15            changeOrigin: true,
    16            pathRewrite: {'^/api2':''}
    17        })
    18    )
    19}
    

12. React路由

12.1. SPA概念

  1. 单页Web应用(single page web application, SPA)

  2. 整个应用只有一个完整的页面

  3. 点击页面中的链接浏览器不会刷新页面,只会做页面的局部更新

  4. 数据都需要通过ajax获取,并在前端异步展示

12.2. 路由概念

  1. 一个路由就是一个映射关系(key: value)
  2. key为路径, value可能是function或者component

路由分类:

  1. 后端路由

    比如node中,value是function,用于处理客户端提交的请求

    app.get(key, function(req, res){})

  2. 前端路由

    浏览器路由,value是component,用于展示页面内容

12.3. react-router

react-router包括有Web,Native,Any

在这里学习使用web上的react路由使用,需要下载react-router-dom

  1. 最基本路由使用

    <Link>标签编写路由路径

    <Route>标签注册路由组件

    最外部需要包一个 <BroserRouter>或者 <HashRouter>,路由才会生效

     1export default class App extends Component {
     2  render() {
     3    return (
     4      <div>
     5        <div className="row">
     6          <div className="col-xs-offset-2 col-xs-8">
     7            <div className="page-header"><h2>React Router Demo</h2></div>
     8          </div>
     9        </div>
    10        <div className="row">
    11          <div className="col-xs-2 col-xs-offset-2">
    12            <div className="list-group">
    13              {/* 编写路由链接 */}
    14              <Link className='list-group-item' to="/about">About</Link>
    15              <Link className='list-group-item' to="/home">Home</Link>
    16            </div>
    17          </div>
    18          <div className="col-xs-6">
    19            <div className="panel">
    20              <div className="panel-body">
    21                {/* 注册路由 */}
    22                <Route path="/about" component={About}/>
    23                <Route path="/home" component={Home}/>
    24              </div>
    25            </div>
    26          </div>
    27        </div>
    28      </div>
    29    )
    30  }
    31}
    
    1const root = createRoot(document.getElementById('root'));
    2root.render(<BrowserRouter><App></App></BrowserRouter>);
    
  2. NavLink使用

    <NavLink>可以看作是 <Link>的升级版,在路由被激活时,会追加一个激活后的样式,默认追加类名为active

     1export default class App extends Component {
     2  render() {
     3    return (
     4      <div>
     5        <Header/>
     6        <div className="row">
     7          <div className="col-xs-2 col-xs-offset-2">
     8            <div className="list-group">
     9              {/* 编写路由链接 */}
    10              <NavLink activeClassName='active' className='list-group-item' to="/about">About</NavLink>
    11              <NavLink activeClassName='active' className='list-group-item' to="/home">Home</NavLink>
    12            </div>
    13          </div>
    14          <div className="col-xs-6">
    15            <div className="panel">
    16              <div className="panel-body">
    17                {/* 注册路由 */}
    18                <Route path="/about" component={About}/>
    19                <Route path="/home" component={Home}/>
    20              </div>
    21            </div>
    22          </div>
    23        </div>
    24      </div>
    25    )
    26  }
    27}
    
  3. 封装NavLink组件

    原生NavLink组件每一次编写都要指定activeClassName,className等属性,造成代码冗余,因此需要对其进行封装,每次使用只需要指定to属性即可。

    注意: React组件标签间值会自动作为key为children的属性传入组件props内

     1export default class App extends Component {
     2  render() {
     3    return (
     4      <div>
     5        <Header/>
     6        <div className="row">
     7          <div className="col-xs-2 col-xs-offset-2">
     8            <div className="list-group">
     9              <MyNavLink to="/about">About</MyNavLink>
    10              <MyNavLink to="/home">Home</MyNavLink>
    11            </div>
    12          </div>
    13          <div className="col-xs-6">
    14            <div className="panel">
    15              <div className="panel-body">
    16                {/* 注册路由 */}
    17                <Route path="/about" component={About}/>
    18                <Route path="/home" component={Home}/>
    19              </div>
    20            </div>
    21          </div>
    22        </div>
    23      </div>
    24    )
    25  }
    26}
    

    封装NavLink:

    1export default class MyNavLink extends Component {
    2  render() {
    3    return (
    4        <NavLink activeClassName='active' className='list-group-item' {...this.props}></NavLink>
    5    )
    6  }
    7}
    
  4. Switch使用

    如果一个路由路径指定了多个组件,则多个组件会一起展示

    用Switch标签包裹后即可实现单一匹配

    1<div className="panel-body">
    2    {/* 注册路由 */}
    3    <Switch>
    4        <Route path="/about" component={About}/>
    5        <Route path="/home" component={Home}/>
    6        <Route path="/home" component={Test}/>
    7    </Switch>
    8</div>
    
  5. 多级路由路径下刷新浏览器样式丢失问题

    1// 1. 采用public绝对路径
    2<link rel="stylesheet" href="%PUBLIC_URL%/css/bootstrap.css">
    3
    4// 2.相对路径不要带. 
    5<link rel="stylesheet" href="/css/bootstrap.css">
    6
    7// 3.采用HashRouter
    8const root = createRoot(document.getElementById('root'));
    9root.render(<HashRouter><App></App></HashRouter>);
    
  6. 路由的模糊匹配与严格匹配

    默认模糊匹配

    开启严格匹配:

    严格匹配不建议随意开启,有时会导致无法继续二级路由

     1export default class App extends Component {
     2  render() {
     3    return (
     4      <div>
     5        <Header/>
     6        <div className="row">
     7          <div className="col-xs-2 col-xs-offset-2">
     8            <div className="list-group">
     9              <MyNavLink to="/about">About</MyNavLink>
    10              <MyNavLink to="/home/a/b">Home</MyNavLink>
    11            </div>
    12          </div>
    13          <div className="col-xs-6">
    14            <div className="panel">
    15              <div className="panel-body">
    16                {/* 注册路由 */}
    17                <Switch>
    18                  <Route exact={true} path="/about" component={About}/>
    19                  {/* 不开启严格匹配则该路由对NavLink指定的路径将会生效 */} 
    20                  <Route exact={true} path="/home" component={Home}/>
    21                </Switch>
    22              </div>
    23            </div>
    24          </div>
    25        </div>
    26      </div>
    27    )
    28  }
    29}
    
  7. Redirect使用

    Redirect组件用于重定向,一般用于页面打开时默认展示路由

    当Route一个都没有匹配时,前往Redirect指定的路由路径

     1export default class App extends Component {
     2  render() {
     3    return (
     4      <div>
     5        <Header/>
     6        <div className="row">
     7          <div className="col-xs-2 col-xs-offset-2">
     8            <div className="list-group">
     9              <MyNavLink to="/about">About</MyNavLink>
    10              <MyNavLink to="/home">Home</MyNavLink>
    11            </div>
    12          </div>
    13          <div className="col-xs-6">
    14            <div className="panel">
    15              <div className="panel-body">
    16                {/* 注册路由 */}
    17                <Switch>
    18                  <Route path="/about" component={About}/>
    19                  <Route path="/home" component={Home}/>
    20                  <Redirect to="/About"></Redirect>
    21                </Switch>
    22              </div>
    23            </div>
    24          </div>
    25        </div>
    26      </div>
    27    )
    28  }
    29}
    
  8. 嵌套路由

    注意不要随便开启严格模式

    嵌套路由是按照路由注册顺序依次进行匹配的

  9. 向路由组件传递params参数

    在定义路由时以模板字符串形式定义参数内容(value);

    在注册路由时声明接收params参数,以key形式声明

    参数将会保存在路由组件内props属性内

     1export default class Message extends Component {
     2  state = {
     3      messageArr: [
     4          {id: '001', title: '消息1'},
     5          {id: '002', title: '消息2'},
     6          {id: '003', title: '消息3'},
     7      ]
     8  }
     9  render() {
    10    return (
    11      <div>
    12        <ul>
    13          {
    14              this.state.messageArr.map((message)=>{
    15                  return (
    16                    <li key={message.id}>
    17                        {/* 向路由组件传递params参数 */}
    18                        <Link to={`/home/message/detail/${message.id}/${message.title}`}>{message.title}</Link>
    19                    </li>
    20                  )
    21              })
    22          }
    23        </ul>
    24        <hr></hr>
    25        {/* 声明接收params参数 */}
    26        <Route path="/home/message/detail/:id/:title" component={Detail}></Route>
    27      </div>
    28    )
    29  }
    30}
    

    image-20220412112955796

  10. 向路由组件传递search参数

    以query的形式在路由路径内声明即可,

    参数保存在路由组件props的location内

    需要通过qs模块进行解析

     1export default class Message extends Component {
     2  state = {
     3      messageArr: [
     4          {id: '001', title: '消息1'},
     5          {id: '002', title: '消息2'},
     6          {id: '003', title: '消息3'},
     7      ]
     8  }
     9  render() {
    10    return (
    11      <div>
    12        <ul>
    13          {
    14              this.state.messageArr.map((message)=>{
    15                  return (
    16                    <li key={message.id}>
    17                        {/* 向路由组件传递params参数 */}
    18                        {/* <Link to={`/home/message/detail/${message.id}/${message.title}`}>{message.title}</Link> */}
    19
    20                        {/* 向路由组件传递search参数 */}
    21                        <Link to={`/home/message/detail/?id=${message.id}&title=${message.title}`}>{message.title}</Link>
    22                    </li>
    23                  )
    24              })
    25          }
    26        </ul>
    27        <hr></hr>
    28        {/* 声明接收params参数 */}
    29        {/* <Route path="/home/message/detail/:id/:title" component={Detail}></Route> */}
    30
    31        {/* search参数无需声明接收 */}
    32        <Route path="/home/message/detail" component={Detail}></Route>
    33      </div>
    34    )
    35  }
    36}
    
     1export default class Detail extends Component {
     2  render() {
     3    const {id, title} = qs.parse(this.props.location.search.slice(1));
     4    return (
     5      <div>
     6          <ul>
     7              <li>{id}</li>
     8              <li>{title}</li>
     9              <li>Message</li>
    10          </ul>
    11      </div>
    12    )
    13  }
    14}
    

    image-20220412114756638

  11. 向路由组件传递state参数

    以对象形式定义 <Link> 标签内的to属性,声明pathname以及state

    在路由组件内通过location属性内的state属性获取

    参数不会在浏览器地址栏上显示,但是刷新可以正常显示,因为location也是作为hostory属性的一部分

     1export default class Message extends Component {
     2  state = {
     3      messageArr: [
     4          {id: '001', title: '消息1'},
     5          {id: '002', title: '消息2'},
     6          {id: '003', title: '消息3'},
     7      ]
     8  }
     9  render() {
    10    return (
    11      <div>
    12        <ul>
    13          {
    14              this.state.messageArr.map((message)=>{
    15                  return (
    16                    <li key={message.id}>
    17                        {/* 向路由组件传递params参数 */}
    18                        {/* <Link to={`/home/message/detail/${message.id}/${message.title}`}>{message.title}</Link> */}
    19
    20                        {/* 向路由组件传递search参数 */}
    21                        {/* <Link to={`/home/message/detail/?id=${message.id}&title=${message.title}`}>{message.title}</Link> */}
    22
    23                        {/* 向路由组件传递state参数 */}
    24                        <Link to={{pathname: '/home/message/detail', state:{id:message.id, title:message.title}}}>{message.title}</Link>
    25                    </li>
    26                  )
    27              })
    28          }
    29        </ul>
    30        <hr></hr>
    31        {/* 声明接收params参数 */}
    32        {/* <Route path="/home/message/detail/:id/:title" component={Detail}></Route> */}
    33
    34        {/* search参数无需声明接收 */}
    35        <Route path="/home/message/detail" component={Detail}></Route>
    36      </div>
    37    )
    38  }
    39}
    

    image-20220412115509427

  12. push和replace模式

    默认路由跳转是push模式,可以在 <Link> 标签内指定开启replace模式

    1<Link replace={true} to='/home/message/detail'>{message.title}</Link>
    
  13. 编程式路由导航

    有时候需要在没有 <Link> 标签的情况下实现路由导航

    借助路由组件内 this.props.history 属性以编程方式进行路由的跳转

     1export default class Message extends Component {
     2  state = {
     3      messageArr: [
     4          {id: '001', title: '消息1'},
     5          {id: '002', title: '消息2'},
     6          {id: '003', title: '消息3'},
     7      ]
     8  }
     9  render() {
    10    return (
    11      <div>
    12        <ul>
    13          {
    14              this.state.messageArr.map((message)=>{
    15                  return (
    16                    <li key={message.id}>
    17                        {/* 向路由组件传递params参数 */}
    18                        <Link to={`/home/message/detail/${message.id}/${message.title}`}>{message.title}</Link>
    19                        <button onClick={()=>{this.pushShow(message.id, message.title)}}>push查看</button>
    20                        <button onClick={()=>{this.replaceShow(message.id, message.title)}}>replace查看</button>
    21
    22                        {/* 向路由组件传递search参数 */}
    23                        {/* <Link to={`/home/message/detail/?id=${message.id}&title=${message.title}`}>{message.title}</Link> */}
    24
    25                        {/* 向路由组件传递state参数 */}
    26                        {/* <Link to={{pathname: '/home/message/detail', state:{id:message.id, title:message.title}}}>{message.title}</Link> */}
    27                    </li>
    28                  )
    29              })
    30          }
    31        </ul>
    32        <hr></hr>
    33        {/* 声明接收params参数 */}
    34        {/* <Route path="/home/message/detail/:id/:title" component={Detail}></Route> */}
    35
    36        {/* search参数无需声明接收 */}
    37        <Route path="/home/message/detail" component={Detail}></Route>
    38
    39        <button onClick={this.back}>回退</button>
    40        <button onClick={this.forward}>前进</button>
    41      </div>
    42    )
    43  }
    44
    45  back = ()=>{
    46      this.props.history.goBack();
    47  }
    48
    49  forward = ()=>{
    50      this.props.history.goForward();
    51  }
    52
    53  pushShow = (id, title)=>{
    54    //   this.props.history.push(`/home/message/detail/${id}/${title}`);
    55    // this.props.history.push(`/home/message/detail/?id=${id}&title=${title}`);
    56    this.props.history.push(`/home/message/detail`, {id, title});
    57  }
    58
    59  // 实现replace跳转到Detail组件
    60  replaceShow = (id, title)=>{
    61    //   this.props.history.replace(`/home/message/detail/${id}/${title}`);
    62    // this.props.history.replace(`/home/message/detail/?id=${id}&title=${title}`);
    63    this.props.history.replace(`/home/message/detail/`, {id, title});
    64  }
    65}
    
  14. withRouter使用

    只有路由组件才有 this.props.history 属性

    对于一般组件如果想要实现路由跳转,需要使用 withRouter 函数进行跳转

    withRouter可以加工一般组件,让以班组间具有路由组件所特有的API,withRouter的返回值是一个新组件

     1class Header extends Component {
     2  render() {
     3      console.log(this);
     4    return (
     5        <div className="row">
     6            <div className="col-xs-offset-2 col-xs-8">
     7                <div className="page-header"><h2>React Router Demo</h2></div>
     8                <button onClick={this.back}>回退</button>
     9                <button onClick={this.forward}>前进</button>
    10            </div>
    11        </div>
    12    )
    13  }
    14
    15  back = ()=>{
    16      this.props.history.goBack();
    17  }
    18
    19  forward = ()=>{
    20      this.props.history.goForward();
    21  }
    22}
    23
    24export default withRouter(Header)
    
  15. BrowserRouter 和 HashRouter的区别

    1. 底层原理不一样

      BrowserRouter使用的是H5的history API,不兼容IE9及以下版本,

      HashRouter使用的是URL的哈希值

    2. url表现形式不一样

    3. 刷新后对路由state参数的影响

      BrowserRouter没有任何影响,因为state保存在hostory对象中

      HashRouter刷新后会导致路由state参数丢失

12.4. 路由组件与一般组件的区别

  1. 路由组件会默认收到路由器传递的props:history,location,match
  2. 两者写法不同

13. redux

redux是一个专门用于做状态管理的JS库(不是react插件库)

作用:集中式管理react应用中多个组件共享的状态

redux原理图

13.1. redux三个核心概念

  1. action

    • 动作的对象
    • 包含两个属性
      • type:表示属性,值为字符串,唯一
      • data:数据属性,可选
    • 例子:{type: “ADD_STUDENT”, data:{name: “J”, age: 18}}
  2. reducer

    • 用于初始化状态,加工状态
    • 加工时,根据旧的state和action,产生新的state的纯函数
  3. store

    将state,action,reducer联系在一起的对象

13.2. redux核心API

  • createstore()

作用:创建包含指定reducer的store对象

  • store对象
  1. 作用: redux库最核心的管理对象

  2. 它内部维护着:

    1. state

    2. reducer

  3. 核心方法:

    1. getState()

    2. dispatch(action)

    3. subscribe(listener)

  4. 具体编码:

    1. store.getState()

    2. store.dispatch({type:‘INCREMENT’, number})

    3. store.subscribe(render)

  • applyMiddleware()

    作用:应用上基于redux的中间件(插件库)

  • combineReducers()

作用:合并多个reducer函数

14. react-redux

一个react的插件库,专门用于简化在react应用中使用redux

react-redux将所有组件分为两大类:

  • UI组件

    只负责UI呈现

    不使用redux的API

    通过props接收数据

  • 容器组件

    负责管理数据和业务逻辑

    使用redux的API

react-redux模型图

基本使用:

  1. 如何创建一个容器组件

    靠react-redux的connect函数

    mapStateToProps 返回store中的state对象传递给UI组件

    mapDispatchToProps 返回操作状态的方法对象dispatch传递给UI组件调用

     1// 引入Count的UI组件
     2import CountUI from "../../components/Count";
     3// 引入connect用于连接UI组件和redux
     4import { connect } from "react-redux";
     5import { createDecrementAction, createIncrementAction, createIncrementAsyncAction } from "../../redux/count_action";
     6
     7// a函数的返回值作为状态props传递给CountUI组件 state ———— redux保存的状态值
     8function mapStateToProps(state){
     9    return {count: state};
    10}
    11
    12function mapDispatchToProps(dispatch){
    13    return {
    14    increment: (data)=>{
    15        dispatch(createIncrementAction(data));
    16    },
    17    decrement:(data)=>{
    18        dispatch(createDecrementAction(data));
    19    },
    20    asyncIncrement: (data, time)=>{
    21        dispatch(createIncrementAsyncAction(data, time));
    22    }}
    23}
    24
    25const CountContainer = connect(mapStateToProps, mapDispatchToProps)(CountUI);
    26
    27export default CountContainer;
    
  2. 容器组件中的store是靠props传递进去的

     1import React, { Component } from 'react'
     2// import Count from './components/Count'
     3import CountContainer from './containers/Count'
     4import store from './redux/store'
     5
     6export default class App extends Component {
     7  render() {
     8    return (
     9      <div>
    10        <CountContainer store={store}></CountContainer>
    11      </div>
    12    )
    13  }
    14}
    
  3. Provider批量传递store

    当需要给多个容器组件传递store时,在外部套一个 <Provider> 标签,给它传递一个store属性即可对所有容器组件进行传递

    1const root = createRoot(document.getElementById('root'));
    2root.render(<Provider store={store}><App></App></Provider>);
    
  4. 容器组件和UI组件可以整合成一个文件

  5. combineReducer合并多个reducer

     1// 引入createStore,专门用于创建redux中最为核心的store对象
     2import { createStore, applyMiddleware, combineReducers } from "redux"
     3// 引入为Count组件服务的reducer
     4import countReducer from './reducers/count'
     5import personReducer from "./reducers/person";
     6import thunk from "redux-thunk"
     7
     8// 保存的对象键值就是以后需要用到的状态对象名
     9const allReducer = combineReducers(
    10    {
    11        count: countReducer, 
    12        persons: personReducer
    13    }
    14);
    15
    16const store = createStore(allReducer, applyMiddleware(thunk));
    17
    18// 暴露一个全局唯一store对象
    19export default store
    
  6. redux-devtools-extension

    redux开发者工具

    1import {composeWithDevTools} from 'redux-devtools-extension'
    2const store = createStore(allReducer, composeWithDevTools(applyMiddleware(thunk)))
    

15. React扩展

15.1. setState

setState更新状态两种方法:

 1	(1). setState(stateObj, [callback])------对象式的setState
 2            1.stateObj为状态改变对象(该对象可以体现出状态的更改)
 3            2.callback是可选的回调函数, 它在状态更新完毕、界面也更新后(render调用后)才被调用
 4					
 5	(2). setState(updater, [callback])------函数式的setState
 6            1.updater为返回stateChange对象的函数。
 7            2.updater可以接收到state和props。
 8            4.callback是可选的回调函数, 它在状态更新、界面也更新后(render调用后)才被调用。
 9总结:
10		1.对象式的setState是函数式的setState的简写方式(语法糖)
11		2.使用原则:
12				(1).如果新状态不依赖于原状态 ===> 使用对象方式
13				(2).如果新状态依赖于原状态 ===> 使用函数方式
14				(3).如果需要在setState()执行后获取最新的状态数据, 
15					要在第二个callback函数中读取

15.2. lazyLoad

路由组件的lazyLoad

  • 路由组件利用lazy函数动态加载
1const Home = lazy(()=>import('./Home'))
2const About = lazy(()=>import('./About'))
  • 通过 <Suspense>标签自定义一个Loading界面
1<div className="panel-body">
2    {/* 注册路由 */}
3    <Suspense fallback={<h1>Loading...</h1>}>
4        <Route path="/about" component={About}/>
5        <Route path="/home" component={Home}/>
6    </Suspense>
7</div>

15.2. Hooks

React Hooks可以让函数式组件使用state以及其他React特性

  • 三个常用Hook
1(1). State Hook: React.useState()
2(2). Effect Hook: React.useEffect()
3(3). Ref Hook: React.useRef()
  • State Hook
1(1). State Hook让函数组件也可以有state状态, 并进行状态数据的读写操作
2(2). 语法: const [xxx, setXxx] = React.useState(initValue)  
3(3). useState()说明:
4        参数: 第一次初始化指定的值在内部作缓存
5        返回值: 包含2个元素的数组, 第1个为内部当前状态值, 第2个为更新状态值的函数
6(4). setXxx()2种写法:
7        setXxx(newValue): 参数为非函数值, 直接指定新的状态值, 内部用其覆盖原来的状态值
8        setXxx(value => newValue): 参数为函数, 接收原本的状态值, 返回新的状态值, 内部用其覆盖原来的状态值
  • Effect Hook

    Effect Hooks用于模拟类式组件中生命周期钩子

 1(1). Effect Hook 可以让你在函数组件中执行副作用操作(用于模拟类组件中的生命周期钩子)
 2(2). React中的副作用操作:
 3        发ajax请求数据获取
 4        设置订阅 / 启动定时器
 5        手动更改真实DOM
 6(3). 语法和说明: 
 7        useEffect(() => { 
 8          // 在此可以执行任何带副作用操作
 9          return () => { // 在组件卸载前执行
10            // 在此做一些收尾工作, 比如清除定时器/取消订阅等
11          }
12        }, [stateValue]) // 监测stateValue改变则调用回调, 如果指定的是[], 回调函数只会在第一次render()后执行
13    
14(4). 可以把 useEffect Hook 看做如下三个函数的组合
15        componentDidMount()
16        componentDidUpdate()
17    	componentWillUnmount() 
  • Ref Hook
1(1). Ref Hook可以在函数组件中存储/查找组件内的标签或任意其它数据
2(2). 语法: const refContainer = useRef()
3(3). 作用:保存标签对象,功能与React.createRef()一样

15.3. Fragment

用于忽略节点,最终不会在html中渲染

可以不用必须有一个真实的DOM根标签了

1<Fragment><Fragment>
2<></>

15.4. Context

一种组件间通信方式, 常用于【祖组件】与【后代组件】间通信(父子组件间props即可)

 11) 创建Context容器对象:
 2	const XxxContext = React.createContext()  
 3	
 42) 渲染子组时,外面包裹xxxContext.Provider, 通过value属性给后代组件传递数据:
 5	<xxxContext.Provider value={数据}>
 6		子组件
 7    </xxxContext.Provider>
 8    
 93) 后代组件读取数据:
10
11	//第一种方式:仅适用于类组件 
12	  static contextType = xxxContext  // 声明接收context
13	  this.context // 读取context中的value数据
14	  
15	//第二种方式: 函数组件与类组件都可以
16	  <xxxContext.Consumer>
17	    {
18	      value => ( // value就是context中的value数据
19	        要显示的内容
20	      )
21	    }
22	  </xxxContext.Consumer>
 1// 创建一个用于保存变量的Context对象
 2const MyContext = React.createContext();
 3
 4export default class A extends Component {
 5    state = {username: 'Tom', age:18}
 6  render() {
 7    const {username, age} = this.state;
 8    return (
 9      <div>
10          <h3>我是A组件</h3>
11          <h4>我的用户名是: {this.state.username}</h4>
12          <MyContext.Provider value={{username, age}}>
13          <B></B>
14          </MyContext.Provider>
15      </div>
16    )
17  }
18}
19
20class B extends Component {
21    render() {
22        return (
23            <div>
24                <h3>我是子组件</h3>
25                <h4>我从A组件收到的用户名是: </h4>
26                <C></C>
27            </div>
28        )
29    }
30}
31
32class C extends Component {
33    // 声明接收context
34    static contextType = MyContext;
35
36    render() {
37        console.log(this);
38        return (
39            <div>
40                <h3>我是孙组件</h3>
41                <h4>我从A组件收到的用户名是: {this.context.username}, 年龄是{this.context.age}</h4>
42                <h4>我从A组件收到的用户名是:
43                <MyContext.Consumer>
44                    {value=>{return `${value.username},年龄是${value.age}`}}
45                </MyContext.Consumer>
46                </h4>
47            </div>
48        )
49    }
50}

15.5. 组件优化

Component的2个问题

  1. 只要执行setState(),即使不改变状态数据, 组件也会重新render() ==> 效率低

  2. 只当前组件重新render(), 就会自动重新render子组件,纵使子组件没有用到父组件的任何数据 ==> 效率低

效率高的做法

只有当组件的state或props数据发生改变时才重新render()

原因

Component中的shouldComponentUpdate()总是返回true

解决

办法1: 
	重写shouldComponentUpdate()方法
	比较新旧state或props数据, 如果有变化才返回true, 如果没有返回false
办法2:  
	使用PureComponent
	PureComponent重写了shouldComponentUpdate(), 只有state或props数据有变化才返回true
	** 注意: ** 
		只是进行state和props数据的浅比较, 如果只是数据对象内部数据变了, 返回false  
		不要直接修改state数据, 而是要产生新数据
		
项目中一般使用PureComponent来优化

    class Demo extends PurComponent{
    	render(){
    		return (...)
    	}
    }

15.6. render props

如何向组件内部动态传入带内容的结构(标签)?

Vue中: 
	使用slot技术, 也就是通过组件标签体传入结构  <A><B/></A>
React中:
	使用children props: 通过组件标签体传入结构
	使用render props: 通过组件标签属性传入结构,而且可以携带数据,一般用render函数属性

children props

<A>
  <B>xxxx</B>
</A>
{this.props.children}
问题: 如果B组件需要A组件内的数据, ==> 做不到 

render props

<A render={(data) => <C data={data}></C>}></A>
A组件: {this.props.render(内部state数据)}
C组件: 读取A组件传入的数据显示 {this.props.data} 

15.7. Error boundary

理解:

错误边界(Error boundary):用来捕获后代组件错误,渲染出备用页面

特点:

只能捕获后代组件生命周期产生的错误,不能捕获自己组件产生的错误和其他组件在合成事件、定时器中产生的错误

使用方式:

getDerivedStateFromError配合componentDidCatch

 1// 定义在父组件内
 2// 生命周期函数,一旦后台组件报错,就会触发
 3static getDerivedStateFromError(error) {
 4    console.log(error);
 5    // 在render之前触发
 6    // 返回新的state
 7    return {
 8        hasError: true,
 9    };
10}
11
12componentDidCatch(error, info) {
13    // 统计页面的错误。发送请求发送到后台去
14    console.log(error, info);
15}

15.8. 组件通信方式总结

组件间的关系:

  • 父子组件
  • 兄弟组件(非嵌套组件)
  • 祖孙组件(跨级组件)

几种通信方式:

	1.props:
		(1).children props
		(2).render props
	2.消息订阅-发布:
		pubs-sub、event等等
	3.集中式管理:
		redux、dva等等
	4.conText:
		生产者-消费者模式

比较好的搭配方式:

	父子组件:props
	兄弟组件:消息订阅-发布、集中式管理
	祖孙组件(跨级组件):消息订阅-发布、集中式管理、conText(开发用的少,封装插件用的多)

16. ReactRouter 6 使用

与ReactRouter5相比,改变了:

  1. 内置组件变化,移除了 <Switch/> ,新增了 <Routes/> ;移除了 <Redirect>,新增 <Navigate>
  2. 语法变化: component={About}变为 element={<About/>}
  3. 新增多个hook:useParamsuseNavigateuseMatchuseLocation
  4. 官方明确推荐函数式组件

16.1. NavLink使用以及重定向

 1export default function App() {
 2  return (
 3    <div>
 4      <div className="row">
 5        <div className="col-xs-offset-2 col-xs-8">
 6          <div className="page-header"><h2>React Router Demo</h2></div>
 7        </div>
 8      </div>
 9      <div className="row">
10        <div className="col-xs-2 col-xs-offset-2">
11          <div className="list-group">
12            <NavLink className={(isActive)=>{ return isActive.isActive ? 'list-group-item active' : 'list-group-item' }} to="/home">Home</NavLink>
13            <NavLink className="list-group-item" to="/about">About</NavLink>
14          </div>
15        </div>
16        <div className="col-xs-6">
17          <div className="panel">
18            <div className="panel-body">
19              <Routes>
20                <Route path="/home" element={<Home></Home>}></Route>
21                <Route path="/about" element={<About></About>}></Route>
22                <Route path="/" element={<Navigate to="/about"></Navigate>}></Route>
23              </Routes>
24            </div>
25          </div>
26        </div>
27      </div>
28    </div>
29  )
30}

16.2. 路由表

  1. 建立一个单独文件用于存储路由表
 1import About from "../pages/About";
 2import Home from "../pages/Home";
 3import { Navigate } from "react-router-dom";
 4
 5export default[
 6    {
 7      path: '/about',
 8      element:<About></About>
 9    },
10    {
11      path: '/home',
12      element:<Home></Home>
13    },
14    {
15      path: '/',
16      element:<Navigate to='/about'/>
17    }
18  ];
  1. 使用useRoutes创建路由表
 1export default function App() {
 2  const element = useRoutes(routes)
 3  return (
 4    <div>
 5      <div className="row">
 6        <div className="col-xs-offset-2 col-xs-8">
 7          <div className="page-header"><h2>React Router Demo</h2></div>
 8        </div>
 9      </div>
10      <div className="row">
11        <div className="col-xs-2 col-xs-offset-2">
12          <div className="list-group">
13            <NavLink className={(isActive)=>{ return isActive.isActive ? 'list-group-item active' : 'list-group-item' }} to="/home">Home</NavLink>
14            <NavLink className="list-group-item" to="/about">About</NavLink>
15          </div>
16        </div>
17        <div className="col-xs-6">
18          <div className="panel">
19            <div className="panel-body">
20             {element}
21            </div>
22          </div>
23        </div>
24      </div>
25    </div>
26  )
27}

16.3. 嵌套路由

路由表

 1export default[
 2    {
 3      path: '/about',
 4      element:<About></About>
 5    },
 6    {
 7      path: '/home',
 8      element:<Home></Home>,
 9      children: [
10          {
11              path:'news',
12              element: <News></News>
13          },
14          {
15            path:'message',
16            element: <Message></Message>
17        }
18      ]
19    },
20    {
21      path: '/',
22      element:<Navigate to='/about'/>
23    }
24  ];

二级组件展示

 1export default function Home() {
 2  return (
 3    <div>
 4        <h2>我是Home组件</h2>
 5        <ul className="nav nav-tabs">
 6            <li>
 7            <NavLink className="list-group-item" to="news">News</NavLink>
 8            </li>
 9            <li>
10            <NavLink className="list-group-item" to="message">Message</NavLink>
11            </li>
12        </ul>
13        <Outlet></Outlet>
14    </div>
15  )
16}

16.4. 路由传参

Link指定路由路径传参

 1export default function Message() {
 2  
 3  const [messages] = useState([
 4        {id: '001', title: '消息1', content: '111'},
 5        {id: '002', title: '消息2', content: '222'},
 6        {id: '003', title: '消息3', content: '333'},
 7        {id: '004', title: '消息4', content: '444'},
 8    ])
 9  return (
10    <div>
11        <h2>Home组件内容</h2>
12        <div>
13            <div>
14                <ul>
15                    {
16                        messages.map((message)=>{
17                            return (
18                                <li key={message.id}>
19                                    {/* search */}
20                                    {/* <Link to={`detail?id=${message.id}&title=${message.title}&content=${message.content}`}>{message.title}</Link> */}
21                                    {/* state */}
22                                    <Link to='detail' state={{id: message.id, title: message.title, content: message.content}}>{message.title}</Link>
23                                </li>
24                            )
25                        })
26                    }
27                </ul>
28            </div>
29            <Outlet></Outlet>
30        </div>
31    </div>
32  )
33}

路由组件接收参数

 1
 2export default function Detail() {
 3    // params传参
 4    // const params = useParams();
 5    
 6    // search传参
 7    // const [searchParams, setSearch] = useSearchParams();
 8
 9    // state传参
10    const {id, title, content} = useLocation().state;
11
12  return (
13    <div>
14        <ul>
15            {/* params参数 */}
16            {/* <li>{params.id}</li>
17            <li>{params.title}</li>
18            <li>{params.content}</li> */}
19
20            {/* search参数 */}
21            {/* <li>{searchParams.get('id')}</li>
22            <li>{searchParams.get('title')}</li>
23            <li>{searchParams.get('content')}</li> */}
24
25            {/* state参数 */}
26            <li>{id}</li>
27            <li>{title}</li>
28            <li>{content}</li>
29        </ul>
30    </div>
31  )
32}

16.5. 编程式路由导航

利用useNavigate实现路由跳转

 1export default function Message() {
 2  
 3  const [messages] = useState([
 4        {id: '001', title: '消息1', content: '111'},
 5        {id: '002', title: '消息2', content: '222'},
 6        {id: '003', title: '消息3', content: '333'},
 7        {id: '004', title: '消息4', content: '444'},
 8  ])
 9
10  const navigate = useNavigate();
11  function showData(message){
12    navigate('detail', {
13        replace: false,
14        state: {
15            id: message.id, 
16            title: message.title, 
17            content: message.content
18        }
19    });
20  }
21
22  return (
23    <div>
24        <h2>Home组件内容</h2>
25        <div>
26            <div>
27                <ul>
28                    {
29                        messages.map((message)=>{
30                            return (
31                                <li key={message.id}>
32                                    {/* search */}
33                                    {/* <Link to={`detail?id=${message.id}&title=${message.title}&content=${message.content}`}>{message.title}</Link> */}
34                                    {/* state */}
35                                    <Link to='detail' state={{id: message.id, title: message.title, content: message.content}}>{message.title}</Link>
36                                    <button onClick={()=>{showData(message)}}>展示详情</button>
37                                </li>
38                            )
39                        })
40                    }
41                </ul>
42            </div>
43            <Outlet></Outlet>
44        </div>
45    </div>
46  )
47}

一般组件内实现路由前进后退

 1export default function Header() {
 2  const navigate = useNavigate();
 3  
 4  function back(){
 5    navigate(-1);
 6  }
 7
 8  function forward(){
 9    navigate(1);
10  }
11
12  return (
13    <div className="col-xs-offset-2 col-xs-8">
14        <div className="page-header"><h2>React Router Demo</h2>
15        </div>
16        <button onClick={back}>后退</button>
17        <button onClick={forward}>前进</button>
18    </div>
19  )
20}

16.6. useInRouterContext()

返回一个bool值,判断当前组件是否处于路由环境中

1var s = useInRouterContext()

16.7. useNavigationType()

返回当前导航类型(用户是如何来到当前页面的)

返回值:POPPUSHREPLACE

16.8. useOutlet()

用来呈现当前组件中渲染的嵌套路由

1const result = useOutlet();
2// 如果当前嵌套路由未挂载 则返回null
3// 否则返回当前挂载嵌套路由