Context提供了一种跨组件访问数据的方法。它无需在组件树间逐层传递属性,也能够方便的访问其余组件的数据node
在经典的React应用中,数据是父组件经过props向子组件传递的。可是在某些特定场合,有些数据须要在各个组件之间共享。 Context 为咱们提供一种组件之间共享数据的方式,能够避免数据在组件树上逐层传递算法
Context能够在组件树的组件之间共享“全局”数据。例如:登录的用户信息,用户选择的主题、语言等等。下面的例子中,咱们“手动”自上而下传递theme属性,用来设定Button的样式。数组
class App extends React.Component { render() { return <Toolbar theme="dark"></Toolbar>; } } 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}></ThemedButton> </div> ); } class ThemedButton extends React.Component { render() { return <Button theme={this.props.theme}></Button>; } }
使用 Context ,咱们能够避免经过多个中间组件传递propsapp
// Context lets us pass a value deep into the component tree // without explicitly threading it through every component. // Create a context for the current theme (with "light" as the default). const ThemeContext = React.createContext('light'); class App extends React.Component { render() { // Use a Provider to pass the current theme to the tree below. // Any component can read it, no matter how deep it is. // In this example, we're passing "dark" as the current value. return ( <ThemeContext.Provider value="dark"> <Toolbar></Toolbar> </ThemeContext.Provider> ); } } // A component in the middle doesn't have to // pass the theme down explicitly anymore. function Toolbar(props) { return ( <div> <ThemedButton /> </div> ); } class ThemedButton extends React.Component { // Assign a contextType to read the current theme context. // React will find the closest theme Provider above and use its value. // In this example, the current theme is "dark". static contextType = ThemeContext; render() { return <Button theme={this.context} />; } }
有时候,有些数据须要被不少组件访问,并且这些组件在组件树的不一样层上。 Context 可使咱们以“广播”的形式,在各个组件中共享数据的改变ide
const MyContext = React.createContext(defaultValue);
建立一个新的 Context 对象。当React渲染一个组件,且该组件注册了 Context 时,它将读取父组件中,距离该组件最近的Provider组件的 Context 值函数
defaultValue 只有 在“Consumer”组件找不到Provider组件时,才会被使用。this
Context.Provider编码
<MyContext.Provider value={/* some value */}>
每一个 Context 对象都携带一个名叫Provider的React组件。Provider可使得“Consumer”组件监听context的变动code
经过向Provider的后代Consumer组件传递value的prop,一个Provider能够与多个Consumer组件创建联系。component
全部的后代Consumer组件在Provider的value属性更新后,都会被从新渲染。这个更新从Provider到其后代Consumer组件之间传播,可是并不会触发shouldComponentUpdate方法。因此即便Consumer组件的祖先组件没有更新,Consumer组件也会更新
Context使用与Object.is相同的算法来对比value的新、旧值,以断定其value是否被更新了
当向value传递对象时,这种断定value是否改变的方式可能会引发问题。请参加.
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;
为class的contextTpe属性赋值一个 Context 对象后,咱们能够经过this.context在组件的各个声明周期函数中获取到当前的 Context 对象的方法
经过这种方式,每一个组件只能注册一个context对象。若是须要读取多个context的value值,参加Consuming Multiple Contexts.
若是编码中使用了ES实验中的语法,那么可使用类的静态(static)成员来初始化contextTYpe.代码以下:
class MyClass extends React.Component { static contextType = MyContext; render() { let value = this.context; /* render something based on the value */ } }
<MyContext.Consumer> {value => /* render something based on the context value */} </MyContext.Consumer>
Consumer是一个监听context变化的React组件。它使得咱们能够在一个函数组件中,监听contxt的改变。
Consumer组件要求其子元素为一个函数。该函数的参数接收当前的context的value值,要求返回一个React节点(node) 传递给该函数的参数value等于距离此 Consumner 最近的外层Provider组件的context值。若是没有外层的Provider组件,则等于调用createContext()时传递的参数值(context的默认值)。
更多关于“子元素为一个函数”的信息,请参加render props
开发中,咱们常常须要在某些嵌套结构很深的组件上更新context的value值。此时,咱们能够向下传递一个函数,用它来更新context的value。代码以下:
theme-context.js
// Make sure the shape of the default value passed to // createContext matches the shape that the consumers expect! export const ThemeContext = React.createContext({ theme: themes.dark, toggleTheme: () => {}, });
theme-toggler-button.js
import {ThemeContext} from './theme-context'; function ThemeTogglerButton() { // The Theme Toggler Button receives not only the theme // but also a toggleTheme function from the context 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 also contains the updater function so it will // be passed down into the context provider this.state = { theme: themes.light, toggleTheme: this.toggleTheme, }; } render() { // The entire state is passed to the provider return ( <ThemeContext.Provider value={this.state}> <Content /> </ThemeContext.Provider> ); } } function Content() { return ( <div> <ThemeTogglerButton /> </div> ); } ReactDOM.render(<App />, document.root);
为了保持React的快速渲染,咱们须要将每一个consumer组件编写成一个独立的组件节点(node)
// 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> ); } } function Layout() { return ( <div> <Sidebar /> <Content /> </div> ); } // 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常常一块儿使用,咱们须要考虑建立一个render prop component一并提供两个Context
由于context使用引用标示符(reference identity)来判断什么时候须要从新渲染,因此有些状况下,当provider的父元素从新渲染时,会触发consumer的非内部渲染。例以下面代码,在每次Provider从新渲染时,会从新渲染全部的consumer组件。由于会一直建立一个新的对象赋值给value(value一直在变)
class App extends React.Component { render() { return ( <Provider value={{something: 'something'}}> <Toolbar /> </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> ); } }