React

React

文档 http://www.zhufengpeixun.cn/2020/html/62.1.react-basic.html

React当中的元素就是普通的js对象,它描述了界面上元素的样子,react-dom会在render的时候根据对象的描述生成真实的dom结构。
react元素是构成react应用的最小单位。
react中的元素也就是虚拟dom。

react 源码 github.com => 搜索react => packages => react => src => ReactElement.js

react forceUpdate | react flush

e.preventDefault(); // 阻止默认表单提交事件

jsx

JavaScript+xml 是一种把js和html混合书写的一种语法。
JSX其实只是一种语法糖,最终会通过babeljs转译成createElement语法,以下代码等价。

1
2
3
4
5
6
7
8
9
10
11
ReactDOM.render(
<h1>Hello</h1>,
document.getElementById('root')
);

React.createElement("h1", {
className: "title",
style: {
color: 'red'
}
}, "hello");

this

事件的处理 关于this的处理
一般来说我们希望在回调函数里让this=当前组件

  • 1、使用箭头函数 this就会永远指向类的实例
  • 2、如果用的是普通函数,this = undefined
    • 2.1 可以使用bind
    • 2.2 可以使用匿名函数 ()=>funName()

解决this指针的三种方法

  • this.add.bind(this); 把add方法里面的this指针绑定为组件实例
  • 使用匿名函数 ()=>this.add();
  • 1
    2
    3
    4
    // 给类的实例增加一个add的属性,而这个属性的this绑死为组件的实例
    add = () => {...}
    ...
    onClick={this.add}

ref

ref的用法 + 受控组件、非受控组件

reference = 引用 如果我们希望在代码中获取到React元素渲染到页面上的真实DOM的话

ref用法

1
2
3
this.a = React.createRef(); // {current: null}

<input ref={this.a} />

受控和非受控

非受控组件:指DOM元素的值存在于DOM元素内部,这个值跟react是相互独立的,不受react控制,这被称为非受控组件。
受控:设置 value={this….} onChange来改变value的值

生命周期

父组件包裹子组件 父组件 Counter 子组件 SubCounter
当父组件改变数据时,触发时 父子组件生命周期顺序如下:

Counter shouldComponentUpdate
Counter componentWillUpdate
Counter render
SubCounter componentWillReceiveProps
SubCounter shouldComponentUpdate
SubCounter componentWillUpdate
SubCounter render
SubCounter componentDidUpdate
Counter componentDidUpdate

如图 props、state、forceUpdate 改变时都会触发

  • static getDerivedStateFromProps
1
2
3
4
// 从属性对象中获取派发的状态,但返回的对象将会成为新的状态对象,如果不改变状态,则可以返回null
static getDerivedStateFromProps(nextProps,prevState)){

}

getDerivedStateFromProps 的存在只有一个目的:让组件在props变化时更新state。

  • shouldComponentUpdate
1
2
3
shouldComponentUpdate(nextProps, nextState){

}
  • render

  • getSnapshotBeforeUpdate

1
2
3
4
5
6
7
// 在更新前获取DOM的快照
getSnapshotBeforeUpdate(prevProps, prevState){
    return ......xxxxx; // 此返回值会传给componentDidUpdate最后一个参数
}
componentDidUpdate(prevProps,prevState,prevXXXX){

}
  • 更新DOM

  • componentDidUpdate

1
2
3
componentDidUpdate(prevProps, prevState, prevXXXX){

}

状态

状态提升

多个组件需要共享同一个状态的话,就需要把他们的状态提升到他们共同的父组件中。

setState

1
this.setState({});

修改状态;重新render

setState 可能是异步的

出于性能方面的考虑,React 可以将多次的 setState() 调用合并为一次

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
constructor(){
this.state = {number:0}
}
add = () =>{
this.setState({number:this.state.number+1});
console.log(this.state.number); // 0
this.setState({number:this.state.number+1});
console.log(this.state.number); // 0
setTimeout( ()=>{
this.setState({number:this.state.number+1});
console.log(this.state.number); // 2
this.setState({number:this.state.number+1});
console.log(this.state.number); // 3
})
}
// 页面上显示 3

源码

dirtyComponents 脏组件,就是组件的状态和界面显示不一致

1
2
3
4
5
6
7
8
// 源码
if( !batchingStrategy.isBatchingUpdates){ // 为 false 则不批量更新,立即处理
batchingStrategy.batchUpdates(enqueusUpdate, component);
return;
}

// 否则 批量更新,存储到dirtyComponent中
dirtyComponent.push(component);
1
2
3
window.trigger = function(event,method){
event.target.component[method].call(event.target.component); // 不是源码
}

实现原理和react一样,在react中会把所有的事件都委托给全局统一实现,通过事件源去区分,去调用对应的方法。
通过事件委托来实现的。

在react中,当你要开启一个事件的时候,当你要执行一个回调函数的时候,它会进入到批量更新状态。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
window.trigger = function(event,name){
// 当事件函数(即这里的add方法)执行的时候,先置为true,开启批量更新模式
batchingStrategy.isBatchingUpdates = true;
let component = event.target.component;
component[name].bind(component,event);
// 当事件结束的时候(这里指 add执行结束,且此时还未执行setTimeou异步方法,所以前2个皆为 0 0 ) 置为false
batchingStrategy.isBatchingUpdates = false;
// 进行批量更新,把所有的脏组件根据自己的状态和属性重新渲染
batchingStrategy.batchedUpdates();
}
add = () =>{
this.setState({number:this.state.number+1});
console.log(this.state.number); // 0
this.setState({number:this.state.number+1});
console.log(this.state.number); // 0
// setTimeout里面的代码比较特殊,不会走批量更新,会立刻进行更新
setTimeout( ()=>{
this.setState({number:this.state.number+1});
console.log(this.state.number); // 2
this.setState({number:this.state.number+1});
console.log(this.state.number); // 3
})
}

上面line12 - line15 ,将多次传入的对象进行合并处理,以产生一个新的最终的 state 对象,这种合并类似于:

1
2
3
4
5
6
const newState = Object.assign(
{},
state0,
state1,
state2
);

然后再将得到的 “newState” 通过调用 setState 方法进行更新,所以,如果多次调用 setState 方法时传入的对象有相同的 key,那么最后一次调用时所传入的对象的那个 key 的值将成为最终的更新值,在最后一次调用前的值都将被覆盖。

优化window.trigger中的方法 事务

  • 一个所谓的 Transaction 就是将需要执行的 method 使用 wrapper 封装起来,再通过 Transaction 提供的 perform 方法执行
  • 而在 perform 之前,先执行所有 wrapper 中的 initialize 方法;perform 完成之后(即 method 执行后)再执行所有的 close 方法一组 initialize 及 close 方法称为一个 wrapper
     *                       wrappers (injected at creation time)
    *                                      +        +
    *                                      |        |
    *                    +-----------------|--------|--------------+
    *                    |                 v        |              |
    *                    |      +---------------+   |              |
    *                    |   +--|    wrapper1   |---|----+         |
    *                    |   |  +---------------+   v    |         |
    *                    |   |          +-------------+  |         |
    *                    |   |     +----|   wrapper2  |--------+   |
    *                    |   |     |    +-------------+  |     |   |
    *                    |   |     |                     |     |   |
    *                    |   v     v                     v     v   | wrapper
    *                    | +---+ +---+   +---------+   +---+ +---+ | invariants
    * perform(anyMethod) | |   | |   |   |         |   |   | |   | | maintained
    * +----------------->|-|---|-|---|-->|anyMethod|---|---|-|---|-|-------->
    *                    | |   | |   |   |         |   |   | |   | |
    *                    | |   | |   |   |         |   |   | |   | |
    *                    | |   | |   |   |         |   |   | |   | |
    *                    | +---+ +---+   +---------+   +---+ +---+ |
    *                    |  initialize                    close    |
    *                    +-----------------------------------------+
    * 
    
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Transaction {
constructor(wrapper){
this.wrapper = wrapper;
}
perform(func){
this.wrapper.initialize();
func.call();
this.wrapper.close();
}

}

let transaction = new Transaction({
initialize() {
batchingStrategy.isBatchingUpdates = true;
},
close() {
batchingStrategy.isBatchingUpdates = false;
batchingStrategy.batchedUpdates();
}
});
window.trigger = function(event,name){
let component = event.target.component;
transaction.perform(component[name].bind(component,event));
}

1、异步更新state,将短时间内的多个setState合并成一个
2、为了解决异步更新导致的问题,增加另一种形式的setState:接受一个函数作为参数,在函数中可以得到前一个状态并返回下一个状态

1
2
3
4
5
6
7
add = () =>{
this.setState((state)=>{number: state.number+1});
this.setState((state)=>{number: state.number+1});
this.setState((state)=>{number: state.number+1});
// 这样 页面上会输出3
// 如果想从上一个状态计算下一个状态,需要传递一个函数而非对象
}

举例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
class App extends Component {
state = {
count: 0
};

componentDidMount() {
// 生命周期中调用
this.setState({ count: this.state.count + 1 });
console.log("lifecycle: " + this.state.count); // 0
setTimeout(() => {
// setTimeout中调用
this.setState({ count: this.state.count + 1 });
console.log("setTimeout: " + this.state.count); // 2
}, 0);
document.getElementById("div2").addEventListener("click", this.increment2);
}

increment = () => {
// 合成事件中调用
this.setState({ count: this.state.count + 1 });
console.log("react event: " + this.state.count); // 2=>2
};

increment2 = () => {
// 原生事件中调用
this.setState({ count: this.state.count + 1 });
console.log("dom event: " + this.state.count); // 2=>3
};

render() {
return (
<div className="App">
<h2>couont: {this.state.count}</h2>
<div id="div1" onClick={this.increment}>
click me and count+1
</div>
<div id="div2">click me and count+1</div>
</div>
);
}
}

探讨前,我们先简单了解下react的事件机制:react为了解决跨平台,兼容性问题,自己封装了一套事件机制,代理了原生的事件,像在jsx中常见的onClick、onChange这些都是合成事件。

那么以上4种方式调用setState(),后面紧接着去取最新的state,按之前讲的异步原理,应该是取不到的。然而,setTimeout中调用以及原生事件中调用的话,是可以立马获取到最新的state的。根本原因在于,setState并不是真正意义上的异步操作,它只是模拟了异步的行为。React中会去维护一个标识(isBatchingUpdates),判断是直接更新还是先暂存state进队列。setTimeout以及原生事件都会直接去更新state,因此可以立即得到最新state。而合成事件和React生命周期函数中,是受React控制的,其会将isBatchingUpdates设置为 true,从而走的是类似异步的那一套。

在 setTimeout 中去 setState 并不算是一个单独的场景,它是随着你外层去决定的,因为你可以在合成事件中 setTimeout ,可以在钩子函数中 setTimeout ,也可以在原生事件setTimeout,但是不管是哪个场景下,基于event loop的模型下, setTimeout 中里去 setState 总能拿到最新的state值。

小结:
1、setState 只在合成事件和钩子函数中是“异步”的,在原生事件和 setTimeout 中都是同步的。
2、setState的“异步”并不是说内部由异步代码实现,其实本身执行的过程和代码都是同步的,只是合成事件和钩子函数的调用顺序在更新之前,导致在合成事件和钩子函数中没法立马拿到更新后的值,形式了所谓的“异步”,当然可以通过第二个参数 setState(partialState, callback) 中的callback拿到更新后的结果。
3、setState 的批量更新优化也是建立在“异步”(合成事件、钩子函数)之上的,在原生事件和setTimeout 中不会批量更新,在“异步”中如果对同一个值进行多次 setState , setState 的批量更新策略会对其进行覆盖,取最后一次的执行,如果是同时 setState 多个不同的值,在更新时会对其进行合并批量更新。

小测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class App extends React.Component {
state = { val: 0 }

componentDidMount() {
this.setState((state) => {
console.log(this.state.val)
return { val: this.state.val + 1 }
})

this.setState((state) => {
console.log(this.state.val)
return { val: this.state.val + 1 }
})

setTimeout(_ => {
this.setState({ val: this.state.val + 1 })
console.log(this.state.val);

this.setState({ val: this.state.val + 1 })
console.log(this.state.val)
}, 0)
}

render() {
return <div>{this.state.val}</div>
}
}
export default App;

0 0 2 3
【提问】line11 为什么在使用函数式 setState 进行状态更新后,在后一个里面还是不能通过 this.state.age 拿到最新的值?
【回答】源码这样解释,执行第二个 setState 里面的函数时,由第一个 setState 所产生的最新的 state 并没有合并到 this 对象上面去,所以此时通过 this 获取不到最新的状态,故而拿到的 this.state.val 的值为 0

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class App extends React.Component {
state = { val: 0 }

componentDidMount() {
this.setState((state) => {
console.log(this.state.val)
return { val: state.val + 1 }
})

this.setState((state) => {
console.log(this.state.val)
return { val: state.val + 1 }
})

setTimeout(_ => {
this.setState({ val: this.state.val + 1 })
console.log(this.state.val);

this.setState({ val: this.state.val + 1 })
console.log(this.state.val)
}, 0)
}

render() {
return <div>{this.state.val}</div>
}
}
export default App;

0 0 3 4

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class App extends React.Component {
state = { val: 0 }

componentDidMount() {
this.setState((state) => {
console.log(state.val)
return { val: state.val + 1 }
})

this.setState((state) => {
console.log(state.val)
return { val: state.val + 1 }
})

setTimeout(_ => {
this.setState({ val: this.state.val + 1 })
console.log(this.state.val);

this.setState({ val: this.state.val + 1 })
console.log(this.state.val)
}, 0)
}

render() {
return <div>{this.state.val}</div>
}
}
export default App;

0 1 3 4

Context

Context 提供了一个无需为每层组件手动添加 props,就能在组件树间进行数据传递的方法。

1
2
let Context = React.createContext(); // 返回一个context实例
// Context.Provider 负责提供数据 Context.Consumer 负责获取数据

https://zh-hans.reactjs.org/docs/context.html

从示例 http://react.html.cn/docs/context.html#%E5%8A%A8%E6%80%81-context 可以看出没有被包裹的改变同一个值,没有改变

注意下告诫
http://react.html.cn/docs/context.html#%E5%91%8A%E8%AF%AB

等学了react-redux的 之后可以在反过来看看Context中的 childContextTypes getChildContext

PureComponent

  • 当一个组件的props或state变更,React 会将最新返回的元素与之前渲染的元素进行对比,以此决定是否有必要更新真实的 DOM,当它们不相同时 React 会更新该 DOM。
  • 如果渲染的组件非常多时可以通过覆盖生命周期方法 shouldComponentUpdate 来进行优化
  • shouldComponentUpdate 方法会在重新渲染前被触发。其默认实现是返回 true,如果组件不需要更新,可以在shouldComponentUpdate中返回 false 来跳过整个渲染过程。其包括该组件的 render 调用以及之后的操作
  • PureComponent通过prop和state的浅比较来实现shouldComponentUpdate
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
import React, { Component } from 'react';

class Title extends Component {
render() {
console.log("render Title");
return (
<div>
{this.props.title}
</div>
)
}
}

class Counter extends Component{
render() {
console.log("render Counter");
return (
<div>
{this.props.number}
</div>
)
}
}

class App extends Component {
constructor(props){
super(props);
this.state = {
title: '这里是标题',
number:0
}
this.inputRef = React.createRef();
}
add = ()=>{
this.setState({
number: this.state.number + parseInt(this.inputRef.current.value)
})
}
render() {
console.log("render App");
return (
<div className="App">
<Title title={this.state.title}></Title>
<Counter number={this.state.number}></Counter>
<input ref={this.inputRef} />
<button onClick={this.add}>+</button>
</div>
);
}
}

export default App;

App 组件包含2个子组件 Title 组件 和 Counter 组件
当点击 + 时,三个组件都刷新,当父组件的值改变,子组件也render

1
2
3
render App
render Title
render Counter

问题

  • 当改变数字的时候 title 没有改变,<Title> 组件不应该更新
  • 当 + 0,实际数字没有改变,3个组件都不应该更新

优化

所有组件都继承 PureComponent

这个是React原生的 import React, { Component, PureComponent } from 'react'

测试

input中输入 0,3个组件都不render
input中输入 1

1
2
render App
render Counter

手写PureComponent

浅比较方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 浅比较 比较obj1 和 obj2 是否相等,相等返回true,不相等返回false,只比较第一层
function shallowEqual(obj1,obj2){
if(obj1 === obj2){
return true;
}
if(typeof obj1 != 'object' || obj1 === null ||typeof obj2 != 'object' || obj2 === null ){
return false;
}
let keys1 = Object.keys(obj1);
let keys2 = Object.keys(obj2);
if(keys1.length != keys2.length){
return false;
}
for(let key of keys1){
if(!obj2.hasOwnProperty(key) || obj1[key] !== obj2[key]){
console.log("line16~")
return false;
}
}
return true;
}

let obj1 = {name:'jiafei'};
let obj2 = {name:'jiafei'};
console.log(shallowEqual(obj1,obj2)); // 返回 true

let obj1 = {attr:{name:'jiafei'}};
let obj2 = {attr:{name:'jiafei'}};
console.log(shallowEqual(obj1,obj2));
// 返回 line16~ false
// 这里走到了line16 {name:'jiafei'}这个对象引用的地址不一样,所以不相等,这里只比较了第一层

改成深比较

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
for(let key of keys1){
// if(!obj2.hasOwnProperty(key) || obj1[key] !== obj2[key]){
// console.log("line30~")
// return false;
// }
// 递归改成深比较
if(obj2.hasOwnProperty(key)){
if(obj1[key] != obj2[key]){
if( typeof obj1[key] === 'object' && typeof obj2[key] === 'object'){
return shallowEqual(obj1[key],obj2[key])
}
}
}else{
return false;
}
}
let obj1 = {attr:{name:'jiafei'}};
let obj2 = {attr:{name:'jiafei'}};
console.log(shallowEqual(obj1,obj2));
// 返回 true

PureComponent 要注意的问题

当state中

1
2
3
4
this.state = {
title: '这里是标题',
number:{count: 0}
}

时,改变state时这样写

1
2
3
4
this.state.number.count = this.state.number.count + parseInt(this.inputRef.current.value);
this.setState({
number: this.state.number
})

那么,改变input中的值3个组件都不render,因为

1
2
3
if(!obj2.hasOwnProperty(key) || obj1[key] !== obj2[key]){
return false;
}

这里{count: ...} 这个对象指向的引用地址没有改变
可以这样改

1
2
3
4
this.state.number.count = this.state.number.count + parseInt(this.inputRef.current.value);
this.setState({
number: {...this.state.number,count: this.state.number.count}
})

每次都创建新的number对象,所以能够更新

函数组件的PureComponent

<Title>组件变成函数组件时

1
2
3
4
5
6
7
8
function Title(props){
console.log("render Title Func");
return (
<div>
{props.title}
</div>
)
}

可以看到每次改变input中的值,都会刷新 render Title Func

解决:

1
2
3
4
5
6
7
8
9
function Title(props){
console.log("render Title Func");
return (
<div>
{props.title}
</div>
)
}
Title = React.memo(Title);

memo的原理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function memo(Func){
return class extends PureComponent{
render(){
return <Func {...this.props} />
}
}
}
function memo(Func){
return class extends PureComponent{
render(){
return Func(this.props);
}
}
}

额外补充

1
2
3
4
5
6
7
8
9
let null1 = null;
let null2 = null;
console.log(null1 === null2);
// true

let obj1 = {};
let obj2 = {};
console.log(obj1 === obj2);
// false

react组件通信

1、属性传递;2、context redux内部也是context实现的。

看下react中文文档 http://react.html.cn/docs/context.html 这里的例子可以看官网的 好好看看官方的这个例子

代码拆分 http://react.html.cn/docs/code-splitting.html

高阶组件

  • 高阶组件就是一个函数,传给它一个组件,它返回一个新的组件
  • 高阶组件的作用其实就是为了组件之间的代码复用

示例一

下面有2个逻辑相似的组件 如下
UserNameInput.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import React, { Component } from 'react';
class UserNameInput extends Component {
constructor(props){
super(props);
this.state = {
value: ''
}
}
componentDidMount() {
this.setState({
value: localStorage.getItem('username')
})
}
render() {
return (
<div>
{this.state.value}
</div>
);
}
}
export default UserNameInput;

EmailInput.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import React, { Component } from 'react';
class EmailInput extends Component {
constructor(props){
super(props);
this.state = {
value: ''
}
}
componentDidMount() {
this.setState({
value: localStorage.getItem('email')
})
}
render() {
return (
<div>
{this.state.value}
</div>
);
}
}
export default EmailInput;

这2个组件都是获取一个值然后展示,其中line3 ~ 13 的逻辑可以复用

将公共逻辑部分封装成一个高阶组件
withLocal.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import React from 'react';

export default function(Component,name){
return class extends React.Component{
constructor(props){
super(props);
this.state = {
value: ''
}
}
componentDidMount() {
this.setState({
value: localStorage.getItem(name)
})
}
render(){
return <Component {...this.props} value={this.state.value} />;
}
}
}

那么原来的组件,还改变成如下:
UserNameInput.js

1
2
3
4
5
6
7
8
9
10
11
12
13
import React, { Component } from 'react';
import withLocal from './withLocal';

class UserNameInput extends Component {
render() {
return (
<div>
{this.props.value}
</div>
);
}
}
export default withLocal(UserNameInput,'username');

EmailInput.js

1
2
3
4
5
6
7
8
9
10
11
12
13
import React, { Component } from 'react';
import withLocal from './withLocal';

class EmailInput extends Component {
render() {
return (
<div>
{this.props.value}
</div>
);
}
}
export default withLocal(EmailInput,'email');

可以看到页面上还是能显示之前localStore设置的数据

示例二

升级,从本地获取到英文名字再从服务器端拿取数据显示对应的中文名字

withAjax.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import React from 'react';

export default function(Component){
return class extends React.Component{
constructor(props){
super(props);
this.state = {
valueAjax: ''
}
}
componentDidMount() {
fetch('http://localhost:3000/translate.json').then(response=>response.json()).then(result=>{
this.setState({valueAjax:result[this.props.value]})
}).catch(err=>{ // 错误捕获
console.log(err);
})
}
render(){
return <Component value={this.state.valueAjax} />;
}
}
}

withLocal.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import React from 'react';

export default function(Component,name){
return class extends React.Component{
constructor(props){
super(props);
this.state = {
value: ''
}
}
componentDidMount() {
this.setState({
value: localStorage.getItem(name)
})
}
render(){
return <Component {...this.props} value={this.state.value} />;
}
}
}

UserNameInput.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import React, { Component } from 'react';
import withLocal from './withLocal';
import withAjax from './withAjax';

class UserNameInput extends Component {
render() {
return (
<div>
{this.props.value}
</div>
);
}
}
let UserNameInputWithAjax = withAjax(UserNameInput);
let UserNameInputWithLocal = withLocal(UserNameInputWithAjax,'username');
export default UserNameInputWithLocal;

顺序:withLocal (携带本地的英文名)=> withAjax (从服务器请求数据传递)=> UserNameInput

高阶组件一般就嵌套一层,超过二层就太复杂了。之后学到的hooks能解决这个问题(高阶组件的嵌套问题)
高阶组件应用场景:比如 react-redux、react路由、路由权限

  • react 高阶组件是一种react复用代码的封装、抽取方式,高阶组件是一个函数,接收组件并封装
    1、基于属性代理:操作组件的props
    2、基于反向继承:拦截生命周期、state、渲染过程
  • 使用高阶组件
    1、@修饰符
    2、直接调用函数
    https://blog.csdn.net/qq_29590623/article/details/88560805

render props

和高阶组件一样,都是解决逻辑复用问题,传值方式不同,本质没有区别。
一个组件 它的子组件是个函数。

Context.Consumer 实质上就是一个 render props

React.Fragment

片段(fragments) 可以让你将子元素列表添加到一个分组中,并且不会在DOM 中增加额外节点。

http://react.html.cn/docs/fragments.html

插槽Portal

Portals 提供了一种很好的方法,将子节点渲染到父组件 DOM 层次结构之外的 DOM 节点。

1
ReactDOM.createPortal(child, container)

第一个参数(child)是任何可渲染的 React 子元素,例如一个元素,字符串或 片段(fragment)
第二个参数(container)则是一个 DOM 元素

入口文件index.html 增加一个放置插槽的dom元素位置

1
<div id="modal-root"></div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import React,{Component} from 'react';
import ReactDOM from 'react-dom';
import './modal.css';

class Modal extends Component{
constructor() {
super();
this.modal=document.querySelector('#modal-root');
}
render() {
return ReactDOM.createPortal(this.props.children,this.modal);
}
}
class Page extends Component{
constructor() {
super();
this.state={show:false};
}
handleClick=() => {
this.setState({show:!this.state.show});
}
render() {
return (
<div>
<button onClick={this.handleClick}>显示模态窗口</button>
{
this.state.show&&
<Modal>
<div id="modal" className="modal">
<div className="modal-content" id="modal-content">
内容
<button onClick={this.handleClick}>关闭</button>
</div>
</div>
</Modal>
}
</div>
)
}
}

modal.css

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
.modal{
position: fixed;
left:0;
top:0;
right:0;
bottom:0;
background: rgba(0,0,0,.5);
display: block;
}

@keyframes zoom{
from{transform:scale(0);}
to{transform:scale(1);}
}

.modal .modal-content{
width:50%;
height:50%;
background: white;
border-radius: 10px;
margin:100px auto;
display:flex;
flex-direction: row;
justify-content: center;
align-items: center;
animation: zoom .6s;
}

这里的样式可以看一下,flex 、animation

React.lazy

https://zh-hans.reactjs.org/docs/code-splitting.html#reactlazy

错误边界

https://zh-hans.reactjs.org/docs/error-boundaries.html

长列表优化

未完待续

相关文章:

https://juejin.im/post/5b6f1800f265da282d45a79a
https://juejin.im/post/5aca20c96fb9a028d700e1ce
https://zh-hans.reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html