Yapei Li

专注于前端领域

0%

React Context

上下文(Context) : React.createContext

上下文(Context) 提供了一种通过组件树传递数据的方法,无需在每个级别手动传递 props 属性。

不必在树的每个层级显式传递一个 prop

Context 旨在共享一个组件树内可被视为 “全局” 的数据

适用于 组件直接将数据传递给 孙子级组件

当一些数据需要在不同的嵌套级别上被许多组件访问时,首先考虑使用 Context

两个概念:Provider(提供者)Consumer(消费者)

不使用Context,直接使用props一级级传递:

1
2
3
4
5
6
7
//父级
class App extends React.Component {
render() {
return <Toolbar theme="dark" />;
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
//子级
function Toolbar(props) {
// The Toolbar component must take an extra "theme" prop
// and pass it to the ThemedButton. This can become painful
// if every single button in the app needs to know the theme
// because it would have to be passed through all components.
return (
<div>
<ThemedButton theme={props.theme} />
</div>
);
}

1
2
3
4
5
6
//孙子级
class ThemedButton 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
//父组件
// 为theme 属性创建上下文(默认为“light”)。
const ThemeContext = React.createContext('light');

class App extends React.Component {
render() {
// 使用Provider将当前主题传递给下面的树
// 任何组件都可以读取它,无论它有多深
// 在这个例子中,我们将“dark”作为当前值。
return (
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
);
}
}

1
2
3
4
5
6
7
8
9
//子级
// 中间的组件不再需要明确地传递theme 属性
function Toolbar(props) {
return (
<div>
<ThemedButton />
</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
//孙子级
class ThemedButton extends React.Component {
// 分配contextType以读取theme 属性上下文
//必须这么写
//this.contextType = ThemeContext; 报错

static contextType = ThemeContext;
render() {
return <Button theme={this.context} />;
}
}

//下面写法也行
ThemedButton.contextType = ThemeContext;

//还可以用 Consumer写法
class ThemedButton extends React.Component {
render() {
return (
<ThemeContext.Consumer>
// value 等同于 Provider 的 value
{value => <div>{value}</div>}
</ThemeContext.Consumer>
)
}
}

Context API

React.createContext

创建一个 Context 对象

defaultValue 参数 仅 当 consumer(使用者) 在树中没有匹配的 Provider(提供则) 时使用它

defaultValue:默认值,可以是任何数据类型,

1
2
3
4
5
6
const MyContext = React.createContext(defaultValue);

const ThemeContext = React.createContext({
theme: themes.dark,
toggleTheme: () => {},
});

Context.Provider 提供者

接受一个 value 属性传递给使用组件(覆盖创建时初始化的 数据),这个 consumer(使用者) 组件 为 Provider(提供者) 的后代组件

一个 Provider 可以连接到许多 consumers

每当 Provider(提供者) 的 value 属性发生变化时,所有作为 Provider(提供者) 后代的 consumer(使用者) 组件 都将重新渲染

value值可以是组件的state,方便动态改变上下文

value值可以是组件的一个方法,方便深层嵌套的组件动态改变该组件的数据

1
<MyContext.Provider value={/* some value */}>

Context.Consumer

一个可以订阅 context 变化的 React 组件。 这允许您订阅 函数式组件 中的 context

接收一个 函数作为子节点。

该函数接收当前 context 值并返回一个 React 节点。

传递给函数的 value 参数将等于组件树中上层这个 context 最接近的 Providervalue 属性。

如果上层没有提供这个 contextProvidervalue参数将等于传递给 createContext()defaultValue

1
2
3
4
<MyContext.Consumer>
/* value 等同于 Provider 的 value */
{value => /* 根据上下文值渲染内容 */}
</MyContext.Consumer>

Class(类组件).contextType:

类组件上的 contextType 属性可以绑定由 React.createContext() 创建的 Context 对象。

在组件中使用this.context 使用该 Context 类型的最近的当前值

可以在任何生命周期方法中引用它,包括 render 函数。

接收方式 MyClass.contextType = MyContext;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class MyClass extends React.Component {
componentDidMount() {
let value = this.context;
/* perform a side-effect at mount using the value of MyContext */
}
componentDidUpdate() {
let value = this.context;
/* ... */
}
componentWillUnmount() {
let value = this.context;
/* ... */
}
render() {
let value = this.context;
/* render something based on the value of MyContext */
}
}
MyClass.contextType = MyContext;

从嵌套组件更新 context(context传递一个函数)

theme-context.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
export const themes = {
light: {
foreground: '#000000',
background: '#eeeeee',
},
dark: {
foreground: '#ffffff',
background: '#222222',
},
};

// 单独创建一个 创建ThemeContext 的文件,方便多个文件访问同一个context
export const ThemeContext = React.createContext({
theme: themes.dark,
toggleTheme: () => {},
});
theme-toggler-button.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//引入 context  方便下边使用 context.Consumer API
import {ThemeContext} from './theme-context';

function ThemeTogglerButton() {
// Toggler Button不仅从上下文接收主题,还接收toggleTheme函数
//接收的是 APP组件的 ThemeContext.Provider 中的 value属性
return (
<ThemeContext.Consumer>
//{theme, toggleTheme} 利用解构赋值 取出value中的各项值
{({theme, toggleTheme}) => (
<button
onClick={toggleTheme}
style={{backgroundColor: theme.background}}>
Toggle Theme
</button>
)}
</ThemeContext.Consumer>
);
}

export default ThemeTogglerButton;
app.js
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
//引入 context  方便下边使用 context.Provider API
import {ThemeContext, themes} from './theme-context';
import ThemeTogglerButton from './theme-toggler-button';

class App extends React.Component {
constructor(props) {
super(props);

this.toggleTheme = () => {
this.setState(state => ({
theme:
state.theme === themes.dark
? themes.light
: themes.dark,
}));
};

//State还包含updater函数,因此它将传递给上下文提供程序
this.state = {
theme: themes.light,
toggleTheme: this.toggleTheme,
};
}

render() {
// 将上下文设置为 state进行传递(其中包括函数)
return (
<ThemeContext.Provider value={this.state}>
<Content />
</ThemeContext.Provider>
);
}
}

function Content() {
return (
<div>
<ThemeTogglerButton />
</div>
);
}

ReactDOM.render(<App />, document.root);

使用多个 context

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// Theme context, default to light theme
const ThemeContext = React.createContext('light');

// Signed-in user context
const UserContext = React.createContext({
name: 'Guest',
});

class App extends React.Component {
render() {
const {signedInUser, theme} = this.props;

// App component that provides initial context values
return (
<ThemeContext.Provider value={theme}>
<UserContext.Provider value={signedInUser}>
<Layout />
</UserContext.Provider>
</ThemeContext.Provider>
);
}
}

1
2
3
4
5
6
7
8
function Layout() {
return (
<div>
<Sidebar />
<Content />
</div>
);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// A component may consume multiple contexts
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
29
30
31
32
33
34
//ToggleContext.js

import React, { createContext } from 'react'
// 1. 使用 createContext 创建上下文
const ToggleContext = createContext({
toggle: true,
handleToggle: () => {}
})
// 2. 创建 Provider
export class ToggleProvider extends React.Component {

// 注意书写顺序;handleToggle 作为箭头函数不能 bind 因此需要写在上面;如果不喜欢这样的顺序则可以书写普通函数放在下面但记得 bind
handleToggle = () => {
this.setState({ toggle: !this.state.toggle })
}

// 2-1. 重写 ToggleContext 的 state
state = {
toggle: true,
handleToggle: this.handleToggle
}

render() {
// 2-2. 通过 Provider 组件的 value 将 state 提供出去
return (
<ToggleContext.Provider value={this.state}>
{this.props.children}
</ToggleContext.Provider>
)
}
}

// 3. 创建 Consumer
export const ToggleConsumer = ToggleContext.Consumer

上面的代码主要分为三大部分:

1
2
3
4
5
6
7
8
// 创建 Context
const ToggleContext = createContext()

// 创建 Provider
export class ToggleProvider extends React.Component {}

// 创建 Consumer
export cnost ToggleConsumer = ToggleContext.Consumer
步骤解析
  • 首先,我们需要引入 createContext 上下文并调用,传入我们希望在其他层级组件中使用的 state 和改变 state 的方法,注意这里的 state 和方法只是一个“骨架”(或者说预设值),后面的 Provider 会覆盖
  • 接下来创建 Provider 这里头维护真正的 state,并通过 render 函数里面的 Context.Provider 组件的 value 属性传递和覆盖这些方法与属性
  • 然后创建 Consumer,直接导出 Context.Consumer 给外部使用即可

使用 Provider

使用 Provider 比较简单直接包裹在父组件上层即可(直接作为父组件的父级)

下边的 App组件只是个示例,可以是任何 父组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import React from 'react';
import { ToggleProvider } from './ToggleContext' // 1. 获取 Provider

function App() {
// 2-1. 使用 ToggleProvider 组件
// 2-2. 如果有其他组件一样可以共享 state
return (
<ToggleProvider>
<Switcher></Switcher>
{/* 其他组件仍然可以通过 props 访问到共享的 state */}
</ToggleProvider>
);
}

// ...
export default App;

使用 Consumer

  • 通过 Consumer 直接使用 props 传递的 state 属性在 render 函数中渲染即可
  • 如果需要调用方法,则可调用 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
import React from 'react';
import { ToggleProvider, ToggleConsumer } from './ToggleContext' // 1. 获取 Provider 和 Consumer

function App() {
return (
<ToggleProvider>
<Switcher></Switcher>
</ToggleProvider>
);
}

const Switcher = () => {
return <Pannel />
}

const Pannel = () => {
// 在多个层级内直接通过 props 获取 state 和方法,调用方法改变 state
return (
<ToggleConsumer>
{({ toggle, handleToggle }) => <div onClick={() => handleToggle()}>{ toggle ? '✔' : '❌'}</div>}
</ToggleConsumer>
)
}

export default App;