Context

344 阅读3分钟

React-context

Context

Context 提供了一个无需为每层组件手动添加 props,就能在组件树间进行数据传递的方法。

Context 设计目的是为了共享那些对于一个组件树而言是“全局”的数据,例如当前认证的用户、主题或首选语言。

在下面的代码中,我们通过一个 “theme” 属性手动调整一个按钮组件的样式,使用 context, 我们可以避免通过中间元素传递 props:

// Context 可以让我们无须明确地传遍每一个组件,就能将值深入传递进组件树。
// 为当前的 theme 创建一个 context(“light”为默认值)。
const ThemeContext = React.createContext('light');
class App extends React.Component {
  render() {
    // 使用一个 Provider 来将当前的 theme 传递给以下的组件树。
    // 无论多深,任何组件都能读取这个值。
    // 在这个例子中,我们将 “dark” 作为当前的值传递下去。
    return (
      <ThemeContext.Provider value="dark">
        <Toolbar />
      </ThemeContext.Provider>
    );
  }
}

// 中间的组件再也不必指明往下传递 theme 了。
function Toolbar() {
  return (
    <div>
      <ThemedButton />
    </div>
  );
}

class ThemedButton extends React.Component {
  // 指定 contextType 读取当前的 theme context。
  // React 会往上找到最近的 theme Provider,然后使用它的值。
  // 在这个例子中,当前的 theme 值为 “dark”。
  static contextType = ThemeContext;
  render() {
    return <Button theme={this.context} />;
  }
}

API

React.createContext

const MyContext = React.createContext(defaultValue);

创建一个 Context 对象。当 React 渲染一个订阅了这个 Context 对象的组件,这个组件会从组件树中离自身最近的那个匹配的 Provider 中读取到当前的 context 值。

只有当组件所处的树中没有匹配到 Provider 时,其 defaultValue 参数才会生效。这有助于在不使用 Provider 包装组件的情况下对组件进行测试。注意:将 undefined 传递给 Provider 的 value 时,消费组件的 defaultValue 不会生效

Context.Provider

<MyContext.Provider value={/* 某个值 */}>

每个 Context 对象都会返回一个 Provider React 组件,它允许消费组件订阅 context 的变化。

Provider 接收一个 value 属性,传递给消费组件。一个 Provider 可以和多个消费组件有对应关系。多个 Provider 也可以嵌套使用,里层的会覆盖外层的数据。

当 Provider 的 value 值发生变化时,它内部的所有消费组件都会重新渲染。Provider 及其内部 consumer 组件都不受制于 shouldComponentUpdate 函数,因此当 consumer 组件在其祖先组件退出更新的情况下也能更新。

通过新旧值检测来确定变化,使用了与 Object.is 相同的算法






React Hooks-useContext

useContext

const value = useContext(MyContext);

接收一个 context 对象(React.createContext 的返回值)并返回该 context 的当前值。当前的 context 值由上层组件中距离当前组件最近的 <MyContext.Provider>value prop 决定。

当组件上层最近的 <MyContext.Provider> 更新时,该 Hook 会触发重渲染,并使用最新传递给 MyContext provider 的 context value 值。

useContext 的参数必须是 context 对象本身:

  • 正确: useContext(MyContext)
  • 错误: useContext(MyContext.Consumer)
  • 错误: useContext(MyContext.Provider)

示例

interface IThemeProps{
  [key: string]: { color: string; background: string;}
}

const themes: IThemeProps = {
  'light': {
    color: '#000',
    background: '#eee',
  },
  'dark': {
    color: '#fff',
    background: '#222',
  }
}

const ThemesContext = React.createContext(themes.light)

上例中,通过Context创建了一个全局的样式:深色dark和浅色light

以下是有关ThemesContext类型定义

interface Context<T> {
    Provider: Provider<T>;
    Consumer: Consumer<T>;
    displayName?: string;
}

Context接口定义了两个属性:Provider数据的提供者和Consumer数据的消费者。

继续完善

import React from 'react';
import LikeButton from './components/LikeButton'

interface IThemeProps{
  [key: string]: { color: string; background: string;}
}

const themes: IThemeProps = {
  'light': {
    color: '#000',
    background: '#eee',
  },
  'dark': {
    color: '#fff',
    background: '#222',
  }
}

export const ThemesContext = React.createContext(themes.light)
function App() {
  return (
    <ThemeContext.Provider value={themes.light}>
      <LikeButton />
    </ThemeContext.Provider>
  );
}

这时在它里面的组件LikeButton想要使用全局创建的Context,只要时Provider包裹的组件啊都可以快捷的使用:

import React, { useState, useContext } from 'react'
import { ThemesContext } from '../App'

const LikeButton: React.FC = () => {
const theme = useContext(ThemesContext)
const style = {
    color: theme.color,
    background: theme.background
}
    const [like, setLike] = useState(0)
    return (
        <button style={style} onClick={() => { setLike(like + 1) }}>
            {like}👍
        </button>
    )
}
export default LikeButton

上例中,我们首先引入 ThemesContext,然后通过useContext来获取Context定义的theme的值,再将style设置给button。即完成了数据的传递。