React冷门但很好用的知识点

介绍

最近在重读React官方文档,盘点一些不常用但有用的知识点。如果有啥说的不好的地方,欢迎指正!

推荐个翻译的不错的React文档

getSnapshotBeforeUpdate()

getSnapshotBeforeUpdate()为React生命周期函数, 在render()之前调用。它使得组件能在发生更改之前从 DOM 中捕获一些信息(例如,滚动位置)。此生命周期的任何返回值将作为参数传递给 componentDidUpdate()

处理聊天滚动的示例:

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
class ScrollingList extends React.Component {
constructor(props) {
super(props);
this.listRef = React.createRef();
}

getSnapshotBeforeUpdate(prevProps, prevState) {
// 我们是否在 list 中添加新的 items ?
// 捕获滚动位置以便我们稍后调整滚动位置。
if (prevProps.list.length < this.props.list.length) {
const list = this.listRef.current;
return list.scrollHeight - list.scrollTop;
}
return null;
}

componentDidUpdate(prevProps, prevState, snapshot) {
// 如果我们 snapshot 有值,说明我们刚刚添加了新的 items,
// 调整滚动位置使得这些新 items 不会将旧的 items 推出视图。
//(这里的 snapshot 是 getSnapshotBeforeUpdate 的返回值)
if (snapshot !== null) {
const list = this.listRef.current;
list.scrollTop = list.scrollHeight - snapshot;
}
}

render() {
return (
<div ref={this.listRef}>{/* ...contents... */}</div>
);
}
}

forceUpdate()

默认情况下,当组件的 stateprops 发生变化时,组件将重新渲染。如果 render() 方法依赖于其他数据,则可以调用 forceUpdate() 强制让组件重新渲染。

调用 forceUpdate() 将致使组件调用 render() 方法,此操作会跳过该组件的 shouldComponentUpdate()。但其子组件会触发正常的生命周期方法,包括 shouldComponentUpdate() 方法。如果标记发生变化,React 仍将只更新 DOM。

简单来说forceUpdate()方法可以强制更新组件,所以不建议使用!

key

key顾名思义为组件的标识,当key改变时组件会重新注册!但我们使用最多的还是遍历!

1
2
3
4
5
6
7
const todos = [1, 2, 3, 4, 5];
const todoItems = todos.map((todo, index) =>
// Only do this if items have no stable IDs
<li key={index}>
{todo.text}
</li>
);

其实用index作为Key有问题,可以看这篇博客

状态提升

官方解释:通常,多个组件需要反映相同的变化数据,这时我们建议将共享状态提升到最近的共同父组件中去。

简单来说,就是两个子组件都使用父组件的一个参数,通过props把改参数传入子组件,并把改变该参数的方法一并传入子组件,通过调用该方法改变父组件共用的参数!

举个例子🌰

现在有ComponentA以及ComponentB,他们各有一个输入框,当我在其中一个输入框输入英文单词时,在另一个组件展示转换后的大小写。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class ComponentA extends React.Component {
// 当input输入时,在另一个组件显示大的大小写转换
handleChange(e) => {
const { onChangeWordUpper } = this.props;
onChangeWordUpper(e.target.value);
};

render() {
const { word } = this.props;
return (
<h2>ComponentA enter: {word}</h2>
<input onChange={this.handleChange} />
);
}
}
`
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class ComponentB extends React.Component {
// 当input输入时,在另一个组件显示大的大小写转换
handleChange(e) => {
const { onChangeWordLower } = this.props;
onChangeWordLower(e.target.value);
};

render() {
const { word } = this.props;
return (
<h2>ComponentB enter: {word}</h2>
<input onChange={this.handleChange} />
);
}
}
`

父组件componentFather

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
class ComponentFather extends React.Component {
state = {
word: '';
}

// 转换为大写
onChangeWordUpper(value) => {
const word = value.toUpperCase()
this.setState({ word });
}

// 转换为小写
onChangeWordLower(value) => {
const word = value.toLowerCase()()
this.setState({ word });
}

render() {
const { word } = this.state;
const componentAProps = {
word,
onChangeWordUpper: this.onChangeWordUpper,
};
const componentBProps = {
word,
onChangeWordUpper: this.onChangeWordLower,
};
return (
<ComponentA {...componentAProps} />
<ComponentB {...componentBProps} />
);
}
}

Fragments

Fragments大家应该都很熟悉!

React 中的一个常见模式是一个组件返回多个元素。Fragments 允许你将子列表分组,而无需向 DOM 添加额外节点

1
2
3
4
<React.Fragment>
<div>somethings</div>
<div>somethings</div>
</React.Fragment>

可以简写为

1
2
3
4
<>
<div>somethings</div>
<div>somethings</div>
</>

children prop

当一个组件无法知晓子组件的内容时,children prop就很有用。

它可以把子组件整个传递下去!

举个例子🌰

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 父组件
function Header(props) {
return (
<div>
{props.children}
</div>
);
}

function Page() {
return (
<Header>
<h1>children - 1</h1>
<h1>children - 2</h1>
</Header>
);
}

这样Header组件接受的children就是两个h1标签

Context

Context应该是很常用的Api,防止有人没使用过并巩固一下我的知识点,还是写一下。

举个例子🌰

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
import React, { createContext } from "react";
import ReactDOM from "react-dom";

const ThemeContext = createContext("red");

class App extends React.Component {
state = {
color: "green"
};

handleChangeColor = () => {
if(this.state.color === 'green') {
this.setState({ color: "red" });
} else {
this.setState({ color: "green" });
}
};

render() {
// 使用一个 Provider 来将当前的 color 传递给以下的组件树。
// 无论多深,任何组件都能读取这个值。
// 在这个例子中,我们将 “dark” 作为当前的值传递下去。
return (
<ThemeContext.Provider value={this.state.color}>
<Toolbar />
<br />
<button onClick={this.handleChangeColor}>changeColor</button>
</ThemeContext.Provider>
);
}
}

// 中间的组件再也不必指明往下传递 color 了。
function Toolbar(props) {
return <ThemedSpan />;
}

class ThemedSpan extends React.Component {
// 指定 contextType 读取当前的 color context。
// React 会往上找到最近的 color Provider,然后使用它的值。
// 在这个例子中,当前的 color 值为 “green”。
static contextType = ThemeContext;
render() {
return <Span color={this.context} />;
}
}

class Span extends React.Component {
static contextType = ThemeContext;
render() {
return (
<span style={{ backgroundColor: this.props.color }}>{this.context}</span>
);
}
}

ReactDOM.render(<App />, document.getElementById("container"));

注意事项:Provider的value我放在了state里。这样当value更新时不会导致其他组件渲染。

假设,value传入的是一个对象。

1
2
3
4
5
// bad
const obj = {a: 1};
<ThemeContext.Provider value={obj}>
<Toolbar />
</ThemeContext.Provider>

当obj改变,但obj.a 还是 1,实际上,a的value并没有改变,但是obj !== obj。

但是this.state.obj === this.state.obj

1
2
3
4
// good
<ThemeContext.Provider value={this.state.obj}>
<Toolbar />
</ThemeContext.Provider>

codesandbox地址

未完待续

Copyright © 2017 - 2019 Timbok's Blog All Rights Reserved.

访客数 : | 访问量 :