共计 6930 个字符,预计需要花费 18 分钟才能阅读完成。
一、何时应用 Context
就一句话,当数据须要往下传递的层数很多或者须要向很多子孙组件传递数据的时候思考应用 Context
二、应用 Context 之前的思考
Context 次要利用场景在于_很多 _ 不同层级的组件须要拜访同样一些的数据 。请审慎应用, 因为这会使得组件的复用性变差。
三、Context 的 API
1.React.createContext
const MyContext = React.createContext(defaultValue); //defauleValue 是默认值
作用:
(1)创立一个 Context 对象。当 React 渲染一个订阅了这个 Context 对象的组件, 这个组件会从组件树中离本身最近的那个匹配的 Provider 中读取到以后的 context 值。
(2)只有 当组件所处的树中 没有匹配到 Provider 时,其 defaultValue 参数才会失效 。这有助于在不应用 Provider 包装组件的状况下对组件进行测试。留神: 将 undefined 传递给 Provider 的 value 时,生产组件的 defaultValue 不会失效。
2.Context.Provider
<MyContext.Provider value={/* 某个值 */}>
作用:
(1)每个 Context 对象都会 返回一个 Provider React 组件 , 它容许生产组件订阅 context 的变动。
(2)Provider 承受一个 value 属性,传递给生产组件。一个 Provider 能够和多个生产组件有对应关系。多个 Provider 也能够嵌套应用,里层的会笼罩外层的数据。
(3)当 Provider 的 value 值产生扭转时,它外部的所有生产组件都会从新渲染 。Provider 及其外部 consumer 组件都不受制于 shouldComponentUpdate 函数, 因而当 consumer 组件在其先人组件退出更新的状况下也能更新。
(4)通过新旧值检测来确定变动,应用了与 Object.is 雷同的算法。
3.Class.contextType
class MyClass extends React.Component {
// 能够应用 static 来初始化 contextType
//static contextType = MyContext;
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 上的那个值 。你能够 在任何生命周期中拜访到它,包含 render 函数中。
4.Context.Consumer
<MyContext.Consumer>
{value => /* 基于 context 值进行渲染 */}
</MyContext.Consumer>
这种办法须要一个函数作为子元素(function as a child)。这个函数 接管以后的 context 值,并返回一个 React 节点 。传递给函数的 value 值等等价于 组件树上方离这个 context 最近的 Provider 提供的 value 值 。 如果没有对应的 Provider,value 参数等同于传递给 createContext() 的 defaultValue。
5.Context.displayName
context 对象 承受一个名为 displayName 的 property,类型为字符串。React DevTools 应用该字符串来确定 context 要显示的内容。
示例,下述组件在 DevTools 中将显示为 MyDisplayName:
const MyContext = React.createContext(/* some value */);
MyContext.displayName = 'MyDisplayName';
<MyContext.Provider> // "MyDisplayName.Provider" 在 DevTools 中
<MyContext.Consumer> // "MyDisplayName.Consumer" 在 DevTools 中
四、示例
1. 应用 Context 代替 props 进行组件中数据的传递
(1)应用 props 进行组件中的数据传递
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} />;
}
}
其实就是一个 theme 值在 App -> Toolbar -> ThemeButton 中进行传递,应用 context,咱们能够防止在两头元素传递 props。
(2)应用 Context 进行组件中的数据传递
// 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} />; //this.context 代表的就是 Provider 传递下来的 value 值
}
}
2. 动静 Context
一个更加简单的计划是对下面的 theme 例子应用动静值(dynamic values):
(1)theme-context.js
export const themes = {
light: {
foreground: '#000000',
background: '#eeeeee',
},
dark: {
foreground: '#ffffff',
background: '#222222',
},
};
// 将 context 对象抛出去
export const ThemeContext = React.createContext(themes.dark // 默认值);
(2)theme-button.js
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;
(3)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.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);
3. 在嵌套组件中更新 Context
从一个在组件树中嵌套很深的组件中更新 context 是很有必要的。在这种场景下,你能够通过 context 传递一个函数,使得 consumers 组件更新 context:
(1)theme-context.js
// 确保传递给 createContext 的默认值数据结构是调用的组件(consumers)所能匹配的!export const ThemeContext = React.createContext({
theme: themes.dark,
toggleTheme: () => {},
});
(2)theme-toggler-button.js
import {ThemeContext} from './theme-context';
function ThemeTogglerButton() {
// Theme Toggler 按钮不仅仅只获取 theme 值,它也从 context 中获取到一个 toggleTheme 函数
return (
// 承受数据的组件应用一层 Context.Consumer 包裹
<ThemeContext.Consumer>
// 外面包裹的是一个函数
{({theme, toggleTheme}) => (<button onClick={toggleTheme}
style={{backgroundColor: theme.background}}>
Toggle Theme
</button>
)}
</ThemeContext.Consumer>
);
}
export default ThemeTogglerButton;
(3)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);
4. 生产多个 Context
为了确保 context 疾速进行重渲染,React 须要使每一个 consumers 组件的 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>
);
}
如果两个或者更多的 context 值常常被一起应用,那你可能要考虑一下另外创立你本人的渲染组件,以提供这些值。
5. 注意事项
因为 context 会应用参考标识(reference identity)来决定何时进行渲染 ,这里可能会有一些陷阱, 当 provider 的父组件进行重渲染时,可能会在 consumers 组件中触发意外的渲染 。举个例子,当每一次 Provider 重渲染时,以下的代码会重渲染所有上面的 consumers 组件, 因为 value 属性总是被赋值为新的对象:
class App extends React.Component {render() {
return (
// 父组件每一次渲染 value 都会被新创建
<MyContext.Provider value={{something: 'something'}}>
<Toolbar />
</MyContext.Provider>
);
}
}
为了避免这种景象,将 value 状态晋升到父节点的 state 中:
class App extends React.Component {constructor(props) {super(props);
this.state = {value: {something: 'something'},
};
}
render() {
return (<Provider value={this.state.value}>
<Toolbar />
</Provider>
);
}
}