一、何时应用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>
);
}
}
发表回复