ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
[toc] [https://reactjs.org/docs/context.html](https://reactjs.org/docs/context.html) `Context API`, 提供了一种在`组件树`中快速传递数据的方法。 在传统的React应用中,数据一般是从上至下通过`props`传递的(\<Component prop1=xxx\>),但是这种方式如果组件嵌套很深,我们就需要一层接一层的往下传,就会显得非常蠢。 So,`Context API`就是为了解决这个问题孕育而生的。 ## when to Use Contex 在组件树中,通过`Context API` 分享的数据,可以被当成“全局”变量一样的存在。反之,我们通过`Context API`分享的数据最好具有全局性,比如一些类似和主题相关、用户认证相关、语言偏好什么的数据。 --- 下面的代码中我们通过手动传递一个`theme`属性来使Button组件获得其父组件传递给它的样式相关数据。 ``` class App extends React.Component{ render(){ return <Toolbar theme="dark"/> } } function Toolbar(props){ return ( <div> <ThemedButton theme={props.theme}> </div> ) } function ThemedButton(props){ return <Button theme={props.theme}> } ``` 这种方式灰常麻烦,首先必须往Toolbar组件的props中添加一个`theme`,接着如果过这个组件中有很多个Button,我们还需要挨个将theme传递给每一个button。 接下来,我们来看`contex`是如何在组件树中传递数据的 ``` //首先创建一个context,并且给一个默认值'light' const ThemeContext = React.createContext('lignt'); class App extends React.Component{ render(){ return ( //用Provider组件传递数据 //任何下层(不论多么深的层级)的组件皆可读到这些数据 //现在value为dark不再为light <ThemeContext.Provider value='dark'> <Toolbar/> </ThemeContext.Provider> ) } } function Toolbar(props){ return ( <div> ` ` <ThemedButton/> </div> ) } function ThemedButton(props){ return ( //通过consumer组件来读取theme context //react会找到离该组件最近的provider并使用其提供的value //这个栗子中 theme为dark <ThemeContext.Consumer> {theme=><Button {...props} theme={theme}/>} </ThemeContext.Consumer> ) } ``` >[warning] Context API 适用于层级比较深,同一层级有很多个组件需要接受数据的情况 ## API ### React.createContext ``` const {Provider,Consumer} = React.createContext(defaultValue); ``` `defaultValue`只会在一个`Consumer`组件在当前组件树中匹配不到任何上级的`Provider`组件时才会生效。 这个会对没有包裹的的独立的测试组件有所帮助。 Note: passing undefined as a Provider value does not cause Consumers to use defaultValue. ### Provider ``` <Provider value={/* some value */}> ``` `Provider`可以**嵌套**,`Consumer`会自动获取离其最近的Provider提供的value ### Consumer ``` <Consumer> {value => /* render something based on the context value */} </Consumer> ``` A React component that subscribes to context changes. 所有的`Consumers`在`Provider`提供的value发生改变时都会重新渲染。 Changes are determined by comparing the new and old values using the same algorithm as [Object.is](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is#Description). (This can cause some issues when passing objects as value: see [Caveats](https://reactjs.org/docs/context.html#caveats).) >Note > >For more information about this pattern, see [render props](https://reactjs.org/docs/render-props.html). ## Examples ``` export const themes = { light: { foreground: '#000000', background: '#eeeeee', }, dark: { foreground: '#ffffff', background: '#222222', }, }; export const ThemeContext = React.createContext( themes.dark // default value ); ``` ``` import {ThemeContext} from './theme-context'; function ThemedButton(props) { return ( <ThemeContext.Consumer> {theme => ( <button {...props} style={{backgroundColor: theme.background}} /> )} </ThemeContext.Consumer> ); } export default ThemedButton; ``` ``` import {ThemeContext, themes} from './theme-context'; import ThemedButton from './themed-button'; // An intermediate component that uses the 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() { // The ThemedButton button inside the ThemeProvider // uses the theme from state while the one outside uses // the default dark theme return ( <Page> <ThemeContext.Provider value={this.state.theme}> <Toolbar changeTheme={this.toggleTheme} /> </ThemeContext.Provider> <Section> <ThemedButton /> </Section> </Page> ); } } ReactDOM.render(<App />, document.root); ``` ### Updating Context from a Nested Component >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: () => {}, }); ``` `defaultValue`传递了一个对象,这对象包含一个`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; ``` 注意:在createContext中默认的value就决定了consumer中能拿到哪些东东,也就是说这个默认的value不仅是个默认值还决定了在组件树中那些东东能够传递 >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); ``` ### Consuming Multiple Contexts To keep context re-rendering fast, React needs to make each context consumer a separate node in the tree. ``` // 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> ); } ``` If two or more context values are often used together, you might want to consider creating your own render prop component that provides both. ### Accessing Context in Lifecycle Methods Accessing values from context in lifecycle methods is a relatively common use case. Instead of adding context to every lifecycle method, you just need to pass it as a prop, and then work with it just like you’d normally work with a prop. ``` class Button extends React.Component { componentDidMount() { // ThemeContext value is this.props.theme } componentDidUpdate(prevProps, prevState) { // Previous ThemeContext value is prevProps.theme // New ThemeContext value is this.props.theme } render() { const {theme, children} = this.props; return ( <button className={theme ? 'dark' : 'light'}> {children} </button> ); } } export default props => ( <ThemeContext.Consumer> {theme => <Button {...props} theme={theme} />} </ThemeContext.Consumer> ); ``` ### Consuming Context with a HOC(高阶组件) Some types of contexts are consumed by many components (e.g. theme or localization). It can be tedious to explicitly wrap each dependency with a <Context.Consumer> element. A higher-order component can help with this. For example, a button component might consume a theme context like so: ``` const ThemeContext = React.createContext('light'); function ThemedButton(props) { return ( <ThemeContext.Consumer> {theme => <button className={theme} {...props} />} </ThemeContext.Consumer> ); } ``` That’s alright for a few components, but what if we wanted to use the theme context in a lot of places? We could create a higher-order component called withTheme: ``` const ThemeContext = React.createContext('light'); // This function takes a component... export function withTheme(Component) { // ...and returns another component... return function ThemedComponent(props) { // ... and renders the wrapped component with the context theme! // Notice that we pass through any additional props as well return ( <ThemeContext.Consumer> {theme => <Component {...props} theme={theme} />} </ThemeContext.Consumer> ); }; } ``` Now any component that depends on the theme context can easily subscribe to it using the withTheme function we’ve created: ``` function Button({theme, ...rest}) { return <button className={theme} {...rest} />; } const ThemedButton = withTheme(Button); ``` ### Forwarding Refs to Context Consumers One issue with the render prop API is that refs don’t automatically get passed to wrapped elements. To get around this, use React.forwardRef: >fancy-button.js > ``` class FancyButton extends React.Component { focus() { // ... } // ... } // Use context to pass the current "theme" to FancyButton. // Use forwardRef to pass refs to FancyButton as well. export default React.forwardRef((props, ref) => ( <ThemeContext.Consumer> {theme => ( <FancyButton {...props} theme={theme} ref={ref} /> )} </ThemeContext.Consumer> )); ``` >app.js > ``` import FancyButton from './fancy-button'; const ref = React.createRef(); // Our ref will point to the FancyButton component, // And not the ThemeContext.Consumer that wraps it. // This means we can call FancyButton methods like ref.current.focus() <FancyButton ref={ref} onClick={handleClick}> Click me! </FancyButton>; ``` ## Caveats Because context uses reference identity to determine when to re-render, there are some gotchas that could trigger unintentional renders in consumers when a provider’s parent re-renders. For example, the code below will re-render all consumers every time the Provider re-renders because a new object is always created for value: ``` class App extends React.Component { render() { return ( <Provider value={{something: 'something'}}> <Toolbar /> </Provider> ); } } ``` To get around this, lift the value into the parent’s state: ``` class App extends React.Component { constructor(props) { super(props); this.state = { value: {something: 'something'}, }; } render() { return ( <Provider value={this.state.value}> <Toolbar /> </Provider> ); } } ``` >Note React previously shipped with an experimental context API. The old API will be supported in all 16.x releases, but applications using it should migrate to the new version. The legacy API will be removed in a future major React version. Read the legacy context docs here.