前面学习 react 的时候,了解到父组件跟子组件通信只能通过 props 传递属性和回调函数,父组件通过属性控制子组件,子组件又通过属性控制子子组件,层层嵌套到最底层。但这种做法对于某些类型的属性而言是极其繁琐的(例如:地区偏好,UI 主题),这些属性是应用程序中许多组件都需要的。在这种情况下,Context 被设计出来了,专门存储共享这些数据。
如何使用Context
在使用 Context 前,我们可以先写一个列子,使用 props 层层传递属性。比如我们前端系统常常会设定一种主题,然后所有的组件都需要知道该主题的值是什么,然后根据主题显示对应的风格。
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
// 假设 App 的主题是 dark 模式
class App extends React.Component {
render() {
return <PageContext> theme="dark" />;
}
}
function PageContext(props) {
// PageContext 组件接受一个“theme”属性,然后传递给 Toolbar 和 Table 组件。
// 然后 Toolbar 再将主题传递给最终使用该属性的 Button 按钮,如果还有其他的就继续传递
// 这个过程中,PageContext,Toolbar,Table 这类容器组件实际上都不是主题的使用者,它们只是起中间传递作用。
return (
<div>
<Toolbar theme={props.theme} />
<div>
<Table theme={props.theme} >
</div>
</div>
);
}
class Toolbar extends React.Component {
render() {
return <Button theme={this.props.theme} />;
}
}
显然,如果是一个真实的页面的话,这种方式肯定非常繁琐,大量重复的传递代码,即麻烦又很不简洁。如果使用 Context 来改造的话,就是下面的样子了
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
// Context 可以让我们无须明确地传遍每一个组件,就能将值深入传递进组件树。
// 通过 React.createContext 为当前的 theme 创建一个 context(“light”为默认值)。
const ThemeContext = React.createContext('light');
// 假设 App 的主题是 dark 模式
class App extends React.Component {
render() {
// 使用一个 Provider 来将当前的 theme 传递给以下的组件树。
// 无论多深,任何 Provider 的子组件都能读取这个值,而不需要每一级都传递了。
// value 给值的话,会替换掉默认值 light
return (<ThemeContext.Provider value="dark">
<PageContext> theme="dark" />;
</ThemeContext.Provider>)
}
}
function PageContext(props) {
// Toolbar 和 Table 都不需要通过 props 传递 theme 了
return (
<div>
<Toolbar/>
<div>
<Table>
</div>
</div>
);
}
class Toolbar extends React.Component {
render() {
// 通过创建的 context 的 Consumer 取数据
// 注意数据会通过一个箭头函数传递出来
return (<ThemeContext.Consumer>
{value => <Button theme={value} />}
</ThemeContext.Consumer>);
}
}
上面的例子中展示了,只要创建一个 context ,就能通过它的 Provider 和 Consumer 传递和读取数据,省略了中间所有层次的组件,如果在实际开发中在最上层的布局组件中使用 context,则可以很方便的传递用户的id,权限,主题等通用数据,任何子组件随时可以取用,可以省略很多冗余的代码。
Context 给我的感觉有点像 java 把数据放到 ThreadLocal 中一样,不需要层层传递,只要是同一个线程就能拿到数据。
注意事项
- context 可以同时嵌套多个,子组件根据名字取对应的值
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
// Theme context,默认的 theme 是 “light” 值
const ThemeContext = React.createContext('light');
// 用户登录 context
const UserContext = React.createContext({
name: 'Guest',
});
class App extends React.Component {
render() {
const {signedInUser, theme} = this.props;
// 提供初始 context 值的 App 组件
return (
<ThemeContext.Provider value={theme}>
<UserContext.Provider value={signedInUser}>
<Layout />
</UserContext.Provider>
</ThemeContext.Provider>
);
}
}
function Layout() {
return (
<div>
<Sidebar />
<Content />
</div>
);
}
// 一个组件可能会消费多个 context
function Content() {
return (
<ThemeContext.Consumer>
{theme => (
<UserContext.Consumer>
{user => (
<ProfilePage user={user} theme={theme} />
)}
</UserContext.Consumer>
)}
</ThemeContext.Consumer>
);
}
-
context可以在传递的对象中包含回调函数,然后子组件通过回调函数修改值
-
某些写法下,Provider 重新渲染时可能会导致子组件全部渲染
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 {
render() {
return (
// value直接给一个对象,Provider每次渲染时,value都是一个新对象,会导致子组件全部渲染
<Provider value=>
<Toolbar />
</Provider>
);
}
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
value: {something: 'something'},
};
}
render() {
return (
// 将 value 状态提升到父节点的 state 里,可以避免这个问题
<Provider value={this.state.value}>
<Toolbar />
</Provider>
);
}
}