Context

Intro

context 可以在多个层级的组件中避免通过 props 单级的层层传递数据。

什么时候需要使用 context ?

当一个组件树需要共享一组全局数据时,如主题、首选语言或当前认证的用户。

但是要避免不要过度滥用 context,他会破坏组件的可复用性。

context 更适合**为每一层组件都提供数据**,而不是针对跨组件(子孙组件需要访问父级组件)进行数据传输。

createContext(defaultValue)

首先需要创建一个 context,createContext() 接受一个”默认值”参数,来保证在找不到 Provider 的时候,可以使用该默认值。

// context.js
import React from "react"

const ThemeColorContext = React.createContext('blue')

export {ThemeColorContext}

defaultValue

defaultValue 是最后的 fallback 选项,是静态的、永远不会修改的。

Context 对象

createContext() 返回的是一个 context 对象。Context 对象本身不持有任何信息,它通过 Context.Provider,Context.Consumer 属性来实现新的提供和消费,或者使用 useConext() hook 来消费。

Class 组件消费 context 提供的值

如果是类组件,使用 Class.contextType 来指定某一组件来消费 context 提供的值

import React from "react";
import { ThemeContext } from "../../module/context";

class Tag extends React.Component {
    render() { 
        return (
            <div style={{backgroundColor: this.context, color: '#fff', padding: '10px', borderRadius: '8px'}}>{this.props.children}</div>
        )
    }
}
// 在类的外部指定 contextType,在类的内部可以通过 this.context 来消费
Tag.contextType = ThemeColorContext 
export default Tag;

或者使用 static 关键字在类的内部指定:

class Tag extends React.Component {
    // 使用 static 来指定类的静态成员
    static contextType = ThemeColorContext
    render() { 
        return (
            <div style={{backgroundColor: this.context, color: '#fff', padding: '10px', borderRadius: '8px'}}>{this.props.children}</div>
        )
    }
}

Context.Provider

由 createContext 创建的 Context 对象,包含一个 Context.Provider 组件。

被这个组件包裹的其他对象,都可以消费由 provider 指定的 context 值。使用 value 属性来指定 context 值。

例如上面的例子,如果不提供一个 Provider,Tag 组件会使用默认的颜色,即蓝色。

而如果用一个 Provider 包裹住,Tag 会消费由 Provider 提供的值。

import Tag from './components/tag'
import './App.css'
import { ThemeColorContext } from './module/context'

export default function App() {
    return (
        <div className="container">
            <Tag>Hello World</Tag>

            <ThemeColorContext.Provider value={'red'}>
                <Tag>Hello World</Tag>
            </ThemeColorContext.Provider>
        </div>
    )
}

触发重渲染

每当 Provider 的 value 属性发生变更的时候,组件就会触发渲染。

监听 value 变化使用的是浅比较,而不比较具体的内容。因此,有一种情况会触发不必要的重渲染:即 Provider 组件发生渲染后,提供的 value 对象也发生了变化(创建了新的对象),从而触发了重渲染:

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

可以使用 state 来保存 value 值,从而保证不会触发不必要的重渲染。

多个 Provider 嵌套

Provider 可能不止一个,多个 Provider 可以一层层叠加

<ThemeContext.Provider value={theme}>
    <UserContext.Provider value={signedInUser}>
        ...
    </UserContext.Provider>
</ThemeContext.Provider>

Context.Consumer

Context.Consumer 利用 render prop。向它的 children 传入一个渲染函数,这个渲染函数接受一个 context 值,函数内部根据 context 的具体内容来返回一个 React 元素。

context 是自底向上根据最近的 Provider 决定的,如果找不到,则使用 context 的默认值。

<MyContext.Consumer>
  {context => /* 基于 context 值进行渲染*/}
</MyContext.Consumer>

新的 useContext() 实现了类似的功能,更应该考虑使用它。

useContext()

使用方法

const value = useContext(SomeContext)
import { useContext } from "react";
import { ThemeColorContext } from "../../module/context";

function Tag({children = ''}) {
    const themeColor = useContext(ThemeColorContext)

    return ( 
        <div style={{backgroundColor: themeColor, color: '#fff', padding: '10px', borderRadius: '8px'}}>{children}</div>
     )
}

export default Tag;

Last updated