本文是对官方文档学习的记录
定义
Context提供了一个无需为每层组件手动添加props,就能在组件树间进行数据传递的方法。
何时使用Context
Context设计的目的是为了共享那些对于一个组件树而言是全局的数据,例如当前认证的用户、主题或者首选语言。
举个例子,当我们改变主题色时,这个button在APP->Toolbar->ThemedButton下,如果使用props进行数据传递,那么就需要为每一层组件都添加props。
// 定义主题色为dark,向下传递
class App extends React.Component {
render() {
return <Toolbar theme="dark" />;
}
}
function Toolbar(props) {
// Toolbar 组件接受一个额外的“theme”属性,然后传递给 ThemedButton 组件。
// 如果应用中每一个单独的按钮都需要知道 theme 的值,这会是件很麻烦的事,
// 因为必须将这个值层层传递所有组件。
return (
<div>
<ThemedButton theme={props.theme} />
</div>
);
}
class ThemedButton extends React.Component {
// ThemedButton 组件接受一个额外的“theme”属性。
render() {
return <Button theme={this.props.theme} />;
}
}
使用Context,可以为我们避免中间props的数据传递:
// Context 可以让我们避免将值传遍每一个组件,就能将值深入传递进组件树。
// 为当前的 theme 创建一个 context(“light”为默认值)。
const ThemeContext = React.createContext('light');
class App extends React.Component {
render() {
// 使用一个 Provider 来将当前的 theme 传递给以下的组件树。
// 无论多深,任何组件都能读取这个值。
// 在这个例子中,我们将 “dark” 作为当前的值传递下去。
return (
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
);
}
}
// 中间的组件再也不必指明往下传递 theme 了。
function Toolbar() {
return (
<div>
<ThemedButton />
</div>
);
}
class ThemedButton extends React.Component {
// 指定 contextType 读取当前的 theme context。
// React 会往上找到最近的 theme Provider,然后使用它的值。
// 在这个例子中,当前的 theme 值为 “dark”。
static contextType = ThemeContext;
render() {
return <Button theme={this.context} />;
}
}
API
React.createContext
const MyContext = React.createContext(defaultValue);
创建一个Context对象。当React渲染一个订阅了这个Context对象的组件,这个组件会从组件树离自身最近的那个匹配的 Provider
中读取当前的context值。
只有当组件所处的树中没有匹配到Provider时(没有使用Provider包裹),defaultValue
参数才会生效。
注意:将undefined
传递给Provider的value时,消费组件的defaultValue
不会生效。
Context.Provider
<MyContext.Provider value={}>
每个Context对象都会返回一个Provider组件,它允许消费组价订阅context的变化。
Provider接收一个value
属性传递给消费组件。
一个Provider可以对应多个消费组件。
多个Provider可以嵌套使用,里层的会覆盖外层的数据。
当 Provider 的 value
值发生变化时,它内部的所有消费组件都会重新渲染。从 Provider 到其内部 consumer 组件的传播不受制于 shouldComponentUpdate 函数,因此当 consumer 组件在其祖先组件跳过更新的情况下也能更新。
Class.contextType
class MyClass extends React.Component {
componentDidMount() {
let value = this.context;
/* 在组件挂载完成后,使用 MyContext 组件的值来执行一些有副作用的操作 */
}
componentDidUpdate() {
let value = this.context;
/* ... */
}
componentWillUnmount() {
let value = this.context;
/* ... */
}
render() {
let value = this.context;
/* 基于 MyContext 组件的值进行渲染 */
}
}
MyClass.contextType = MyContext;
挂载在class上的contextType
属性赋值为由React.createContext()
创建的Context对象。这样可以让你使用this.context
来获取最近Context上的值。
也可以使用static
来初始化contextType
属性
class MyClass extends React.Component {
static contextType = MyContext;
render() {
let value = this.context;
/* 基于这个值进行渲染工作 */
}
}
Context.Consumer
<MyContext.Consumer>
{value => /* 基于 context 值进行渲染*/}
</MyContext.Consumer>
一个 React 组件可以订阅 context 的变更,此组件可以让你在函数式组件中可以订阅 context。
函数value
值等同于往上组件树离这个context最近的 Provider 提供的value
值。如果没有对应的 Provider,value
参数等同于传递给 createContext()
的 defaultValue
。
useContext
const value = useContext(MyContext);
传入React.createContext
创建的对象,返回最近 Provider 的value
值。当Provider组件更新时,会触发重渲染。
我们可以看出 useContext 就相当于class组件中的 static contextType = MyContext
或者 <MyContext.Consumer>
示例
动态Context
对上面的theme例子进行改造,使其能够动态的改变主题色:
theme-context.js
创建Context对象
export const themes = {
light: {
foreground: '#000000',
background: '#eeeeee',
},
dark: {
foreground: '#ffffff',
background: '#222222',
},
};
export const ThemeContext = React.createContext(
themes.dark // 默认值
);
themed-button.js
深层组件读取context的值
import {ThemeContext} from './theme-context';
class ThemedButton extends React.Component {
render() {
let props = this.props;
let theme = this.context;
return (
<button
{...props}
style={{backgroundColor: theme.background}}
/>
);
}
}
ThemedButton.contextType = ThemeContext;
export default ThemedButton;
app.js
使用Provider组件包裹,允许深层组件进行订阅
import {ThemeContext, themes} from './theme-context';
import ThemedButton from './themed-button';
// 一个使用 ThemedButton 的中间组件
function Toolbar(props) {
return (
<ThemedButton onClick={props.changeTheme}>
Change Theme
</ThemedButton>
);
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
theme: themes.light,
};
this.toggleTheme = () => {
this.setState(state => ({
theme:
state.theme === themes.dark
? themes.light
: themes.dark,
}));
};
}
render() {
// 在 ThemeProvider 内部的 ThemedButton 按钮组件使用 state 中的 theme 值,
// 而外部的组件使用默认的 theme 值
return (
<Page>
<ThemeContext.Provider value={this.state.theme}>
<Toolbar changeTheme={this.toggleTheme} />
</ThemeContext.Provider>
<Section>
<ThemedButton />
</Section>
</Page>
);
}
}
ReactDOM.render(<App />, document.root);
在嵌套组件中更新Context
想要在组件树中嵌套很深的组件中更新 context 是很有必要的。这种场景下,你可以通过 context 传递一个函数,使得 consumer 组件可以更新 context:
theme-context.js
export const ThemeContext = React.createContext({
theme: themes.dark,
toggleTheme: () => {},
});
theme-toggler-button.js
import {ThemeContext} from './theme-context';
function ThemeTogglerButton() {
// Theme Toggler 按钮不仅仅只获取 theme 值,它也从 context 中获取到一个 toggleTheme 函数
return (
<ThemeContext.Consumer>
{({theme, toggleTheme}) => (
<button onClick={toggleTheme}
style={{backgroundColor: theme.background}}>
Toggle Theme
</button>
)}
</ThemeContext.Consumer>
);
}
export default ThemeTogglerButton;
app.js
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 也包含了更新函数,因此它会被传递进 context provider。
this.state = {
theme: themes.light,
toggleTheme: this.toggleTheme,
};
}
render() {
// 整个 state 都被传递进 provider
return (
<ThemeContext.Provider value={this.state}>
<Content />
</ThemeContext.Provider>
);
}
}
function Content() {
return (
<div>
<ThemeTogglerButton />
</div>
);
}
ReactDOM.render(<App />, document.root);
消费多个Context
为了确保 context 快速的进行重渲染,我们要新建多个Context。
// 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>
);
}