React全家桶学习笔记
1. React简介
React 是 Facebook 在 2011 年开发的前端 JavaScript 库。它遵循基于组件的方法,有助于构建可重用的UI组件。
它使用虚拟DOM而非真实DOM
虚拟DOM与真实DOM比较:
- 虚拟DOM本质是一个js的对象
 - 虚拟DOM比较“轻”,真实DOM比较“重”(自带属性很多)
 - 虚拟DOM最终会被转化为真实DOM
 
2. JSX语法规则
JSX 是JavaScript XML 的简写。是 React 使用的一种文件。
- 定义虚拟DOM时,不要写引号
 - 标签中混入JS表达式时要用{ }
 - 样式的类名指定不要用class,要用className
 - 内联样式要用**{{key: value}}**形式去写, 外部花括号表示里面要写js表达式, 内层花括号表示对象
 - 虚拟DOM必须只有一个根标签
 - 标签必须闭合
 - 标签首字母
- 若小写字母,则React会将标签转为html同名元素
 - 若大写字母开头,则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
- 
字符串形式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> - 
回调形式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> - 
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中的事件处理
- 
通过onXxx属性指定事件处理函数(注意大小写)
a. React使用的是自定义(合成)事件,而不是使用原生DOM事件 ————为了更好的兼容性
b. React中的事件是通过事件委托方式处理的(委托给最外层元素) ————为了高效
 - 
通过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组件包含一系列钩子函数(生命周期回调函数),会在特定的时刻调用。
6.1. React生命周期(旧)
初始化阶段 —— 由React.render()触发
- 
constructor
构造函数构建组件实例
 - 
组件将要被挂载
componentWillMount(){}
 - 
初始化渲染
render()
 - 
组件挂载完毕
componentDidMount(){}
注意:比较常用,一般用于做一些初始化的事,例如:开启定时器,发送网络请求,订阅消息
 - 
组件即将卸载
componentWillUnmount(){}
注意:比较常用,一般用于做一些收尾的事,例如:关闭定时器,取消订阅消息
 - 
卸载组件
ReactDOM.unmountComponentAtNode(document.getElementById(“test”))
 
Update
- 
componentWillReceiveProps(props)
组件将要接收到props,触发后续更新
注意:第一次接收到props不会触发
 - 
setState()
设置state,触发后续更新
 - 
forceUpdate()
强制更新, 比正常跟新少了一个shouldUpdate
 - 
组件是否应该更新
shouldComponentUpdate(nextProps, nextState){}
 - 
组件将要更新
componentWillUpdate(){}
 - 
组件更新完毕
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调用顺序:
getDerivedStateFromProps()shouldComponentUpdate()render()getSnapshotBeforeUpdate()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算法
- 
虚拟DOM中key的作用
a. 简单地说,key是虚拟dom对象的标识
b. 当状态中的数据发生变化时,React会根据【新数据】生成【新的虚拟dom】随后,React进行【新虚拟dom】和【旧虚拟dom】的diff比较
 比较规则下:
- 
旧虚拟dom中找到了与新虚拟dom相同的key
若虚拟dom的内容没变,则直接使用之前的真实dom。
若虚拟dom的内容变了,则生成新的真实dom,随后替换掉页面中之前的真实dom
 - 
就虚拟dom中没有找到与新的虚拟dom相同的key
根据数据创建新的真实dom,随后渲染到页面
 
 - 
 - 
用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代理配置
- 
单个服务器代理
直接在package.json中加上
”proxy“:"http://localhost:xxxx" - 
多代理配置
在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概念
- 
单页Web应用(single page web application, SPA)
 - 
整个应用只有一个完整的页面
 - 
点击页面中的链接浏览器不会刷新页面,只会做页面的局部更新
 - 
数据都需要通过ajax获取,并在前端异步展示
 
12.2. 路由概念
- 一个路由就是一个映射关系(key: value)
 - key为路径, value可能是function或者component
 
路由分类:
- 
后端路由
比如node中,value是function,用于处理客户端提交的请求
app.get(key, function(req, res){})
 - 
前端路由
浏览器路由,value是component,用于展示页面内容
 
12.3. react-router
react-router包括有Web,Native,Any
在这里学习使用web上的react路由使用,需要下载react-router-dom
- 
最基本路由使用
<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>); - 
NavLink使用
<NavLink>可以看作是<Link>的升级版,在路由被激活时,会追加一个激活后的样式,默认追加类名为active1export 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} - 
封装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} - 
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> - 
多级路由路径下刷新浏览器样式丢失问题
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>); - 
路由的模糊匹配与严格匹配
默认模糊匹配
开启严格匹配:
严格匹配不建议随意开启,有时会导致无法继续二级路由
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} - 
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} - 
嵌套路由
注意不要随便开启严格模式
嵌套路由是按照路由注册顺序依次进行匹配的
 - 
向路由组件传递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} - 
向路由组件传递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} - 
向路由组件传递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} - 
push和replace模式
默认路由跳转是push模式,可以在
<Link>标签内指定开启replace模式1<Link replace={true} to='/home/message/detail'>{message.title}</Link> - 
编程式路由导航
有时候需要在没有
<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} - 
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) - 
BrowserRouter 和 HashRouter的区别
- 
底层原理不一样
BrowserRouter使用的是H5的history API,不兼容IE9及以下版本,
HashRouter使用的是URL的哈希值
 - 
url表现形式不一样
 - 
刷新后对路由state参数的影响
BrowserRouter没有任何影响,因为state保存在hostory对象中
HashRouter刷新后会导致路由state参数丢失
 
 - 
 
12.4. 路由组件与一般组件的区别
- 路由组件会默认收到路由器传递的props:history,location,match
 - 两者写法不同
 
13. redux
redux是一个专门用于做状态管理的JS库(不是react插件库)
作用:集中式管理react应用中多个组件共享的状态
13.1. redux三个核心概念
- 
action
- 动作的对象
 - 包含两个属性
- type:表示属性,值为字符串,唯一
 - data:数据属性,可选
 
 - 例子:{type: “ADD_STUDENT”, data:{name: “J”, age: 18}}
 
 - 
reducer
- 用于初始化状态,加工状态
 - 加工时,根据旧的state和action,产生新的state的纯函数
 
 - 
store
将state,action,reducer联系在一起的对象
 
13.2. redux核心API
- createstore()
 
作用:创建包含指定reducer的store对象
- store对象
 
- 
作用: redux库最核心的管理对象
 - 
它内部维护着:
- 
state
 - 
reducer
 
 - 
 - 
核心方法:
- 
getState()
 - 
dispatch(action)
 - 
subscribe(listener)
 
 - 
 - 
具体编码:
- 
store.getState()
 - 
store.dispatch({type:‘INCREMENT’, number})
 - 
store.subscribe(render)
 
 - 
 
- 
applyMiddleware()
作用:应用上基于redux的中间件(插件库)
 - 
combineReducers()
 
作用:合并多个reducer函数
14. react-redux
一个react的插件库,专门用于简化在react应用中使用redux
react-redux将所有组件分为两大类:
- 
UI组件
只负责UI呈现
不使用redux的API
通过props接收数据
 - 
容器组件
负责管理数据和业务逻辑
使用redux的API
 
基本使用:
- 
如何创建一个容器组件
靠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; - 
容器组件中的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} - 
Provider批量传递store
当需要给多个容器组件传递store时,在外部套一个
<Provider>标签,给它传递一个store属性即可对所有容器组件进行传递1const root = createRoot(document.getElementById('root')); 2root.render(<Provider store={store}><App></App></Provider>); - 
容器组件和UI组件可以整合成一个文件
 - 
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 - 
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个问题
- 
只要执行setState(),即使不改变状态数据, 组件也会重新render() ==> 效率低
 - 
只当前组件重新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相比,改变了:
- 内置组件变化,移除了 
<Switch/>,新增了<Routes/>;移除了<Redirect>,新增<Navigate> - 语法变化: 
component={About}变为element={<About/>} - 新增多个hook:
useParams、useNavigate、useMatch、useLocation等 - 官方明确推荐函数式组件
 
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. 路由表
- 建立一个单独文件用于存储路由表
 
 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  ];
- 使用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()
返回当前导航类型(用户是如何来到当前页面的)
返回值:POP 、PUSH、REPLACE
16.8. useOutlet()
用来呈现当前组件中渲染的嵌套路由
1const result = useOutlet();
2// 如果当前嵌套路由未挂载 则返回null
3// 否则返回当前挂载嵌套路由
- 原文作者:小小呆呆没有脑袋
 - 原文链接:https://www.gujin.store/post/2022-4-1-reactlearn/
 - 版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可,非商业转载请注明出处(作者,原文链接),商业转载请联系作者获得授权。