本文最后更新于:2023年1月12日 下午
前言
React
为我们提供了Context
用于将数据进行广播,消费者只需使用特定的方法就能拿到广播的数据,不用再采用层层递传props
的方式。如果组件之间需要共享数据,也可以采用Context
的方式,如react-router
中的Router
组件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| ... render() { return ( <RouterContext.Provider value={{ history: this.props.history, location: this.state.location, match: Router.computeRootMatch(this.state.location.pathname), staticContext: this.props.staticContext }} > <HistoryContext.Provider children={this.props.children || null} value={this.props.history} /> </RouterContext.Provider> ); } ...
|
使用Provider
包裹组件后,后续就能直接从context
中获取最新的history
等数据。
在什么情况下需要使用Context
: https://react.docschina.org/docs/context.html#when-to-use-context
使用之前的考虑: https://react.docschina.org/docs/context.html#before-you-use-context
React.createContext
用于创建一个Context
对象,其中包含Provider
和Consumer
两个组件。Provider
组件可以提供用于Consumer
组件消费的context
。同时可以在创建Context
对象时,为其指定一个默认值:
1
| const NewContext = React.createContext(defaultValue);
|
如果Consumer
组件不能在所处的树中匹配到Provider
,将会使用这个defaultValue
。
如果直接给Provider
的value
赋值为undefined
,消费组件的defaultValue
不会生效
Context.Provider
Provider
可以为消费组件提供context
,通过将要广播的数据放入value
,内部的消费组件就可以消费这个value
:
1 2 3 4 5 6
| const {Provider} = NewContext; ... <Provider value='provider'> <Button /> </Provider> ...
|
Provider
可以进行嵌套,但Consumer
只会消费最近的Provider
。
如果Provider
的value
发生变化,它内部的所有的消费组件都会重新渲染,即使使用了shouldComponentUpdate
进行了优化处理,也不会影像消费组件的重新渲染。
如果为Provider
提供的是对象,由于该父组件进行重渲染时,每次value
都会得到一个新的对象,从而consumers组件中也会发生不必要的渲染。
解决方式是将该对象保存在state
中:
1 2 3 4 5 6 7 8 9 10 11 12
| const App = () => { const [state, setState] = useState({}); useEffect(() => { ... setState({theme: '#fff'}) ... }) return( <NewContext.Provider value={state}> <Button /> </NewContext.Provider>) }
|
Class.contextType
通过给组件的contextType
属性赋值为Context
对象后,可以在该组件的内部拿到context
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| class App extends React.Component { ... componentDidMount() { ... let value = this.context ... } ... render() { ... let value = this.context ... } } App.contextType = NewContext
|
可以在组件的任何生命周期中拿到context
。也可以使用static
关键字进行初始化:
1 2 3 4 5 6 7 8
| class App extends React.Component { static contextType = NewContext render() { ... let value = this.context ... } }
|
Context.Consumer
contextType
只适用于类组件,对于函数组件,可以使用Consutmer
组件获取context
:
1 2 3 4 5 6 7 8 9 10 11 12
| const App = () => { return( <NewContext.Consumer> {context => { // 拿到context,并进行其他逻辑 // 返回React节点 return <Button style={{background: context.theme}}/> }} </NewContext.Consumer> ) }
|
Consumer
组件中需要一个函数来接收当前的context
,函数返回一个React
节点。
Context.displayName
Context
对象接收一个displayName
属性,类型为字符串,React DevTools 使用该字符串来确定 context
要显示的内容:
1 2 3 4 5
| const MyContext = React.createContext(/* some value */); MyContext.displayName = 'MyDisplayName';
<MyContext.Provider> // "MyDisplayName.Provider" 在 DevTools 中 <MyContext.Consumer> // "MyDisplayName.Consumer" 在 DevTools 中
|
useContext
useContext
是React
提供的一个hooks API
,可以用来拿到对应的Context
对象提供的context
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| // context.js export const NewContext = React.createContext(defaultValue);
// app.js import {NewContext} from './context' import {Buttons} from './buttons' const App = () => { ... return( <NewContext.Provider value={{theme: '#fff'}}> ... <Buttons /> </NewContext.Provider> ) }
// buttons.js import {Button} from './button' const Buttons = () => { ... return( ... <Button /> ... ) }
// button.js import {useContext} from 'react' import {NewContext} from './context' const Button = () => { const context = useContext(NewContext) return ( <button style={{background: context.theme}}>按钮</button> ) }
|
当组件上层最近的 <NewContext.Provider>
更新时,该 Hook 会触发重渲染,并使用最新传递给 NewContext
provider 的 context value
值。即使祖先使用 React.memo
或 shouldComponentUpdate
,也会在组件本身使用 useContext
时重新渲染。
参考
[1] https://react.docschina.org/docs/context.html
[2] https://react.docschina.org/docs/hooks-reference.html#usecontext