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> ); } }