React 高级特性
一、函数组件
没有
state
的情况下,只接受props
的情况下,可以使用函数组件代替class
组件。
- 纯函数,输入
props
,输出JSX
- 没有实例,没有生命周期,没有
state
- 不能扩展其他方法
function List(props) {
const { list } = this.props;
return (
<ul>
{list.map((item, index) => {
return (
<li key={item.id}>
<span>{item.title}</span>
</li>
);
})}
</ul>
);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
二、非受控组件
- ref
- defaultValue defaultChecked
- 手动操作 DOM 元素
(1)代码演示
class UncontrolComp extends React.Component {
constructor(props) {
super(props);
this.state = {
name: "非受控组件",
};
this.nameRef = React.createRef();
this.fileRef = React.createRef();
}
render() {
return (
<div>
{/* 获取 input 的值 */}
<input defaultValue={this.state.name} ref={this.nameRef}></input>
<span>{this.state.name}</span>
<button onClick={this.alertName}></button>
{/* 获取文件 */}
<input type="file" ref={this.fileRef}></input>
<button onClick={this.alertFile}></button>
</div>
);
}
alertName = () => {
const elem = this.nameRef.current;
alert(elem.value);
};
alertFile = () => {
const elem = this.fileRef.current;
alert(elem.files[0].name);
};
}
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
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
(2)使用场景
- 必须手动操作 DOM 元素,setState 实现不了
- 文件上传
<input type="file"></input>
- 某些富文本编辑器,需要传入 DOM 元素
(3)受控组件 vs 非受控组件
- 优先使用受控组件,符合 React 设计原则
- 必须操作 DOM 时,再使用非受控组件
三、Portals
- 组件默认会按照既定层次嵌套渲染
Portals
能将组件渲染到父组件以外
class Portal extends React.Component {
render() {
return ReactDOM.createPortal(
{/* this.props.children 相当于 vue 的slot */}
<div className="modal">{this.props.children}</div>,
{/* dom元素,将第一个参数的 dom 插入 到这个 dom 节点下 */}
document.body
)
}
}
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
类比 Vue3
的 teleport
使用场景:
- overfiow: hidden
- 父组件 z-index 值太小
- fixed 需要放到 body 第一层级
四、context
- 公共信息(语言、主题),传递给每个组件
- 用 props 太繁琐
- 用 redux 小题大做
使用
- 定义:
const ThemeContext = React.createContext('light')
- 父组件生产数据,使用
<ThemeContext.Provider value={ }></ThemeContext.Provider>
包裹 - 子组件消费数据:
- 函数组件使用
<ThemeContext.Consumer>{ value => <p>{value}</p> }</ThemeContext>
消费 - class 组件使用
ChildComp.contextType = ThemeContext
消费
代码演示
import React from "react";
// 创建 Context 填入默认值(任何一个 js 变量)
const ThemeContext = React.createContext("light");
// 底层组件 - 函数是组件
function ThemeLink(props) {
// const theme = this.context // 会报错。函数式组件没有实例,即没有 this
// 函数式组件可以使用 Consumer
return (
<ThemeContext.Consumer>
{(value) => <p>links theme is {value}</p>}
</ThemeContext.Consumer>
);
}
// 底层组件 - class 组件
class ThemedButton extends React.Component {
// 指定 contextType 读取当前的 theme context。
// static contextType = ThemeContext // 也可以用 ThemedButton.contextType = ThemeContext
render() {
const theme = this.context; // React 会往上找到最近的 theme Provider,然后使用它的值。
return (
<div>
<p>buttons theme is {theme}</p>
</div>
);
}
}
ThemedButton.contextType = ThemeContext; // 指定 contextType 读取当前的 theme context。
// 中间的组件再也不必指明往下传递 theme 了。
function Toolbar(props) {
return (
<div>
<ThemedButton />
<ThemeLink />
</div>
);
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
theme: "light",
};
}
render() {
return (
<ThemeContext.Provider value={this.state.theme}>
<Toolbar />
<hr />
<button onClick={this.changeTheme}>change theme</button>
</ThemeContext.Provider>
);
}
changeTheme = () => {
this.setState({
theme: this.state.theme === "light" ? "dark" : "light",
});
};
}
export default App;
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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
五、异步组件
- import()
- React.lazy
- React.Suspense
代码演示
import React from "react";
const ContextDemo = React.lazy(() => import("./ContextDemo"));
class App extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<p>引入一个动态组件</p>
<hr />
<React.Suspense fallback={<div>Loading...</div>}>
<ContextDemo />
</React.Suspense>
</div>
);
// 1. 强制刷新,可看到 loading (看不到就限制一下 chrome 网速)
// 2. 看 network 的 js 加载
}
}
export default App;
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
六、性能优化
1. SCU(shouldComponentUpdate 的简称)
背景:
- 默认情况下,父组件更新,子组件无条件也跟随更新(
SCU
默认返回true
)- 鉴于性能损耗问题,组件数据没变化的情况下,没必要更新,避免不必要的开销
(2)基本用法
shouldComponentUpdate(nextProps, nextState) {
if (nextState.count !== this.state.count) {
return true // 可以渲染
}
return false // 不可重复渲染
}
1
2
3
4
5
6
2
3
4
5
6
注意:SCU
必须配合不可变值使用,否则nextState.xxx
和this.state.xxx
永远相等,shouldComponentUpdate
返回的永远为false
(2)总结
SCU
默认返回true
,即 React 默认重新渲染所有子组件- 必须配合“不可变值”使用
- 可先不用
SCU
,有性能问题时在考虑使用
2. PureComponent 和 React.memo
- PureComponent,SCU 中实现了浅比较
- memo,函数组件中的 PureComponent
- 浅比较已使用大部分情况(尽量不要做深度比较)
// class 组件:PureComponent,和 Component 用法一致
class PureComp extends React.PureComponent {}
// 函数组件
function MayComponent(props) {}
function areEqual(prevProps, nextProps) {
/**
如果把 nextProps 传入 render 方法返回的结果 和
prevProps 传入 render 方法返回的结果 一致,则返回 true,否则返回 false
*/
}
export default React.memo(PureComp, areEqual);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
3. 不可变值 immutable.js
- 彻底拥抱“不可变值”
- 基于共享数据(不是深拷贝),速度快
- 有一定的学习和迁移成本,按需使用
const map1 = Immutable.Map({ a: 1, b: 2, c: 3 });
const map2 = map1.set("b", 50);
map1.get("b"); // 2
map2.get("b"); // 50
1
2
3
4
2
3
4
七、公共逻辑抽离
(1)高阶组件 HOC
参数为组件,返回值为新组件的函数(类似高阶函数、装饰器模式)
import React from "react";
// 高阶组件
const withMouse = (Component) => {
class withMouseComponent extends React.Component {
constructor(props) {
super(props);
this.state = { x: 0, y: 0 };
}
handleMouseMove = (event) => {
this.setState({
x: event.clientX,
y: event.clientY,
});
};
render() {
return (
<div style={{ height: "500px" }} onMouseMove={this.handleMouseMove}>
{/* 1. 透传所有 props 2. 增加 mouse 属性 */}
<Component {...this.props} mouse={this.state} />
</div>
);
}
}
return withMouseComponent;
};
const App = (props) => {
const a = props.a;
const { x, y } = props.mouse; // 接收 mouse 属性
return (
<div style={{ height: "500px" }}>
<h1>
The mouse position is ({x}, {y})
</h1>
<p>{a}</p>
</div>
);
};
export default withMouse(App); // 返回高阶函数
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
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
(2)Render Props
核心:将一个函数以
props
方式传递给子组件,子组件渲染时调用这个函数
import React from "react";
import PropTypes from "prop-types";
class Mouse extends React.Component {
constructor(props) {
super(props);
this.state = { x: 0, y: 0 };
}
handleMouseMove = (event) => {
this.setState({
x: event.clientX,
y: event.clientY,
});
};
render() {
return (
<div style={{ height: "500px" }} onMouseMove={this.handleMouseMove}>
{/* 将当前 state 作为 props ,传递给 render (render 是一个函数组件) */}
{this.props.render(this.state)}
</div>
);
}
}
Mouse.propTypes = {
render: PropTypes.func.isRequired, // 必须接收一个 render 属性,而且是函数
};
const App = (props) => (
<div style={{ height: "500px" }}>
<p>{props.a}</p>
<Mouse
render={
/* render 是一个函数组件 */
({ x, y }) => (
<h1>
The mouse position is ({x}, {y})
</h1>
)
}
/>
</div>
);
/**
* 即,定义了 Mouse 组件,只有获取 x y 的能力。
* 至于 Mouse 组件如何渲染,App 说了算,通过 render prop 的方式告诉 Mouse 。
*/
export default App;
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
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
(3)HOC vs Render Props
- HOC:模式简单,易于理解,但会增加组件层级
- Render Props:代码简洁,但学习成本较高
- 按需使用