react 高级指引之Context

xiaoxiao2021-04-19  180

Context 通过组件树提供了一个传递数据的方法,从而避免了在每一个层级手动的传递 props 属性.

Context 的使用场景: Context 设计目的是为共享那些被认为对于一个组件树而言是 "全局"的数据.例如当前ti认证的用户,主题或或首选语言.

function ThemedButton(props) { return <Button theme={props.theme} />; } //中间组件 function Toolbar(props) { //Toolbar 组件必须添加一个额外的 theme 属性 // 然后传递他给 themedButton 组件 return ( <div> <ThemedButton theme={props.theme} /> </div> ); } class App extends React.Component { render() { return <Toolbar theme="dark" />; } }

使用 context , 可以避免通过中间元素传递 props:

//创建一个 theme Context , 默认 theme 的值为 light const ThemeContext = React.createContext('light'); function ThemedButton (props) { //ThemedButton 组件从 context 接收 theme return ( <ThemeContext.Consumer> { theme => <Button { ...props } theme={theme} /> } <ThemeContext.Consumer> ); } //中间组件 function Toolbar(props) { return ( <div> <ThemedButton /> </div> ); } class App extends React.Component { render() { return ( <ThemeContext.Provider value="dark"> <Toolbar /> </ThemeContext.Provider> ) } }

注意: 你要仅仅为了避免在几个层级下的组件传递 props 而使用 context . 他是被用于在多个层级的多个组件需要访问相同数据的情景.

API React.createContext:

const { Provider, Consumer } = React.createContext(defaultValue);

创建一对 { Provider, Consumer }. 当 React 渲染context 组件 Consumer 时,他将从组件树的上层中最接近的匹配的 Provider 读取当前的 context 值. 如果上层的组件树没有一个匹配的 Provider , 而此时你需要渲染一个 Consumer 组件,那么你可以用到 defaultValue. 这有助于在不封装他们的情况下对组件进行测试. Provider

<Provider value={ /* some value */ }>

react 组件允许 Consumers 订阅context 的改变 接收一个value 属性传递给 Provider 的后代 Consumers. 一个 Provider 可以联系到多个 Consumers. Providers 可以被嵌套以覆盖组件树内更深层次的值. Consumer

<Consumer> { value => /* render something based on the context value */ } </Consumer>

一个可以订阅context 变化的 React 组件. 接收一个函数作为子节点.函数接收当前context 的值并返回一个 React 节点. 传递给函数的value 将等于组件树中上层 context 的最近的 Provider的 value属性. 如果context 没有Provider .那么value 参数将等于被传递给 createContext() 的 defaultValue. 每当 Provider 的值发送改变时, 作为 Provider 后代的所有 Consumers 都会重新渲染. 从 Provider 到其后代的Consumers 传播不受 shouldComponentUpdate方法约束,因此即使祖先组件退出更新时,后代Consumer 也会被更新.

在生命周期方法中访问 Context 在生命周期方法中从上下文访问值是一种相对常见的用例.而不是将上下文添加到每一个生命周期方法中,只需要将它作为一个 props 传递,然后像通常使用 props 一样去使用它:

class Button extends React.Component { componentDidMount() { // ThemeContext value is this.props.theme } componentDidUpdate(prevProps,prevState) { //Prevous 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> )

告诫: 因为 context 使用 reference indentity 确定何时重新渲染, 在 Consumer 中, 当一个 Provider 的父节点重新渲染的时候,有一些问题可能触发意外的渲染.例如下面的代码,所有的 Consumer 在 Provider 重新渲染时,每次都将重新渲染,因为一个新的对象总是被创建对应 Provider 里的 value .

class App extends React.Component { render () { return ( <Provider value={{ something: 'something' }}> <Toolbar /> </Provider> ) } }

为了防止这样,提升value 到父节点的state 里.

class App extends React.Component { constructor (props) { this.state = { value: { something: 'something' }, }; } render() { return ( <Provider value={this.state.value}> <Toolbar /> </Provider> ) } }
Processed: 0.019, SQL: 10