ReactContext详细介绍(状态共享、数据传递)
⽬录
Context 是什么?
Context 提供了⼀个⽆需为每层组件⼿动添加 props,就能在组件树间进⾏数据传递的⽅法。在⼀个典型的 React 应⽤中,数据是通过props 属性⾃上⽽下(由⽗及⼦)进⾏传递的,但这种做法对于某些类型的属性⽽⾔是极其繁琐的(例如:地区偏好,UI 主题),这些属性是应⽤程序中许多组件都需要的。Context 提供了⼀种在组件之间共享此类值的⽅式,⽽不必显式地通过组件树的逐层传递 props。
Context 什么时候⽤?
Context 设计⽬的是为了共享那些对于⼀个组件树⽽⾔是“全局”的数据,例如当前认证的⽤户、主题或⾸选语⾔。举个例⼦,在下⾯的代码中,我们通过⼀个 “theme” 属性⼿动调整⼀个按钮组件的样式
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 {
render() {
return <Button theme={this.props.theme} />;
}
}
// 通过props传递:App -> Toolbar -> ThemedButton
// 如果嵌套很深,那么需要逐层传递props,即使中间不需要该props,显得很繁琐
使⽤ context, 我们可以避免通过中间元素传递 props
// Context 可以让我们⽆须明确地传遍每⼀个组件,就能将值深⼊传递进组件树。
// 为当前的 theme 创建⼀个 context("light"为默认值)。
const ThemeContext = ateContext('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={t} />;
}
}
// 也可以使⽤ tType = ThemeContext;
API介绍
const MyContext = ateContext(defaultValue);
创建⼀个 Context 对象。当 React 渲染⼀个订阅了这个 Context 对象的组件,这个组件会从组件树中离⾃⾝最近的那个匹配
的 Provider 中读取到当前的 context 值。
只有当组件所处的树中没有匹配到 Provider 时,其 defaultValue 参数才会⽣效。这有助于在不使⽤ Provider 包装组件的情况下对组件进⾏测试。注意:将 undefined 传递给 Provider 的 value 时,消费组件的 defaultValue 不会⽣效。
Context.Provider
<MyContext.Provider value={/* 某个值 */}>
每个 Context 对象都会返回⼀个 Provider React 组件,它允许消费组件订阅 context 的变化。
Provider 接收⼀个 value 属性,传递给消费组件。⼀个 Provider 可以和多个消费组件有对应关系。多个 Provider 也可以嵌套使⽤,⾥层的会覆盖外层的数据。
当 Provider 的 value 值发⽣变化时,它内部的所有消费组件都会重新渲染。Provider 及其内部 consumer 组件都不受制
于 shouldComponentUpdate 函数,因此当 consumer 组件在其祖先组件退出更新的情况下也能更新。
挂载在 class 上的 contextType 属性会被重赋值为⼀个由  创建的 Context 对象。这能让你使⽤ t 来消费最近 Context 上的那个值。你可以在任何⽣命周期中访问到它,包括 render 函数中
import MyContext from './MyContext';
class MyClass extends React.Component {
componentDidMount() {
let value = t;
/* 在组件挂载完成后,使⽤ MyContext 组件的值来执⾏⼀些有副作⽤的操作 */
}
componentDidUpdate() {
let value = t;
/* ... */
}
componentWillUnmount() {
let value = t;
/* ... */
}
render() {
let value = t;
/
* 基于 MyContext 组件的值进⾏渲染 */
}
// 或者如上边例⼦⼀样使⽤ static contextType = MyContext;
}
Context.Consumer
import MyContext from './MyContext';
function ToolList() {
return (
<MyContext.Consumer
{value => /* 基于 context 值进⾏渲染*/}
</MyContext.Consumer>
)
}
这⾥,React 组件也可以订阅到 context 变更。这能让你在中完成订阅 context。
这需要这种做法。这个函数接收当前的 context 值,返回⼀个 React 节点。传递给函数的 value 值等同于往上组件树离这个 context 最近的 Provider 提供的 value 值。如果没有对应的 Provider,value 参数等同于传递给 createContext() 的 defaultValue。
Context.displayName
context 对象接受⼀个名为 displayName 的 property,类型为字符串。React DevTools 使⽤该字符串来确定 context 要显⽰的内容。如下述组件在 DevTools 中将显⽰为 MyDisplayName
const MyContext = ateContext(/* some value */);
MyContext.displayName = 'MyDisplayName';
<MyContext.Provider>  // "MyDisplayName.Provider" 在 DevTools 中
<MyContext.Consumer>  // "MyDisplayName.Consumer" 在 DevTools 中
⽰例
动态 Context
对于上⾯的 theme 例⼦,使⽤动态值(dynamic values)后更复杂的⽤法theme-context.js
export const themes = {
light: {
foreground: '#000000',
background: '#eeeeee',
},
dark: {
foreground: '#ffffff',
background: '#222222',
},
};
export const ThemeContext = ateContext(themes.dark);  // 该处为默认值themed-button.js
import { ThemeContext } from './theme-context';
class ThemedButton extends React.Component {
render() {
let props = this.props;
// 获取到ThemeContext中的默认值
let theme = t;
return (
<button
{...props}
style={{backgroundColor: theme.background}}
/>
);
}
// static contextType = ThemeContext;
}
export default ThemedButton;
app.js
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.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={leTheme} />
</ThemeContext.Provider>
<Section>
<ThemedButton />
</Section>
</Page>
);
}
}
react组件之间通信
// 使⽤ThemeContext.Provider包裹的组件,可以消费到ThemeContext中的value
// 即Toolbar、ThemedButton中都可以使⽤t来获取到value
// 注意观察,更新state的⽅法是通过props向下传递,由⼦孙组件触发更新,下⾯会讲到通过context的⽅式传递更新函数
在嵌套组件中更新 Context
在上⾯的例⼦中,我们通过 props 的⽅式向下传递⼀个更新函数,从⽽改变 App 中 themes 的值。我们知道,从⼀个在组件树中嵌套很深的组件中更新 context 是很有必要的。在这种场景下,你可以通过 context 传递⼀个函数,使得 consumers 组件更新 context
theme-context.js
// 确保传递给 createContext 的默认值数据结构是调⽤的组件(consumers)所能匹配的!
export const ThemeContext = ateContext({
theme: themes.dark,
toggleTheme: () => {},  // 定义更新主题的⽅法,向下传递
});

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。