返回介绍

Context 上下文

发布于 2024-06-23 21:09:01 字数 8500 浏览 0 评论 0 收藏 0

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

在一个典型的 React 应用中,数据是通过 props 属性由上向下(由父及子)的进行传递的,但这对于某些类型的属性而言是极其繁琐的(例如:地区偏好,UI 主题),这是应用程序中许多组件都所需要的。 Context 提供了一种在组件之间共享此类值的方式,而不必通过组件树的每个层级显式地传递 props

简单说就是,当你不想在组件树中通过逐层传递 props 或者 state 的方式来传递数据时,可以使用 Context 来实现跨层级的组件数据传递。

  • 使用 props 或者 state 传递数据,数据自顶下流。

数据自顶下流

  • 使用 Context,可以跨越组件进行数据传递

跨越组件进行数据传递

何时使用 Context

Context 设计目的是为共享那些被认为对于一个组件树而言是“全局”的数据,例如当前认证的用户、主题或首选语言。例如,在下面的代码中,我们通过一个“theme”属性手动调整一个按钮组件的样式:

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,它是被用于在多个层级的多个组件需要访问相同数据的情景。

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

注意

关于此案例的更多信息, 请看 render props .

每当 Provider 的值发生改变时, 作为 Provider 后代的所有 Consumers 都会重新渲染。 从 Provider 到其后代的 Consumers 传播不受 shouldComponentUpdate 方法的约束,因此即使祖先组件退出更新时,后代 Consumer 也会被更新。

通过使用与 Object.is 相同的算法比较新值和旧值来确定变化。

注意

(这在传递对象作为 value 时会引发一些问题 Caveats .)

动态 Context

一个更加复杂的例子:

import React from 'react';
import ReactDOM from 'react-dom';

const ThemeContext = React.createContext({
  background: 'red',
  color: 'white'
});

通过静态方法 React.createContext() 创建一个 Context 对象,这个 Context 对象包含两个组件, <Provider /><Consumer />

class App extends React.Component {
  render () {
    return (
      <ThemeContext.Provider value={ {background: 'green', color: 'white'}}>
        <Header />
      </ThemeContext.Provider>
    );
  }
}

复制代码 <Provider /> 的 value 相当于现在的 getChildContext()

class Header extends React.Component {
  render () {
    return (
      <Title>Hello React Context API</Title>
    );
  }
}
class Title extends React.Component {
  render () {
    return (
      <ThemeContext.Consumer>
        {context => (
          <h1 style={ {background: context.background, color: context.color}}>
            {this.props.children}
          </h1>
        )}
      </ThemeContext.Consumer>
    );
  }
}

复制代码 <Consumer />children 必须是一个函数,通过函数的参数获取 <Provider /> 提供的 Context

几个可以直接获取 Context 的地方

实际上,除了实例的 context 属性( this.context ), React 组件还有很多个地方可以直接访问父组件提供的 Context 。比如构造方法:

constructor(props, context)

比如生命周期:

componentWillReceiveProps(nextProps, nextContext)
shouldComponentUpdate(nextProps, nextState, nextContext)
componetWillUpdate(nextProps, nextState, nextContext)

对于面向函数的无状态组件,可以通过函数的参数直接访问组件的 Context。

const StatelessComponent = (props, context) => (
  ......
)

作用于多个上下文

为了保持 context 快速进行二次渲染, React 需要使每一个 Consumer 在组件树中成为一个单独的节点。

// 主题上下文, 默认 light
const ThemeContext = React.createContext('light');

// 登陆用户上下文
const UserContext = React.createContext();

// 一个依赖于两个上下文的中间组件
function Toolbar(props) {
  return (
    <ThemeContext.Consumer>
      {theme => (
        <UserContext.Consumer>
          {user => (
            <ProfilePage user={user} theme={theme} />
          )}
        </UserContext.Consumer>
      )}
    </ThemeContext.Consumer>
  );
}

class App extends React.Component {
  render() {
    const {signedInUser, theme} = this.props;

    // App 组件提供上下文的初始值
    return (
      <ThemeContext.Provider value={theme}>
        <UserContext.Provider value={signedInUser}>
          <Toolbar />
        </UserContext.Provider>
      </ThemeContext.Provider>
    );
  }
}

如果两个或者多个上下文的值经常被一起使用,也许你需要考虑你自己渲染属性的组件提供给它们。

实例

import React, { Component } from 'react'

const LocalContext = React.createContext();

const { Provider, Consumer } = LocalContext; 

function Container(props) {
  return <Title />
}

function Title(props) {
  return (
    <div>
      <Consumer>
        { context => {
          return (
            <div>
              {context.name} - { context.age}
            </div>
          )
        }}
      </Consumer>
    </div>
  )
}

class DM extends Component {
  componentDidMount() {
    console.log(this.props);
  }

  render() {
    return (
      <div>
        <Consumer>
          {
            user => (<div>
              <p>{ user.name }</p>
              <p>{ user.age }</p>
            </div>
            )
          }
        </Consumer>
        ---{ this.props.name }----
      </div>
    )
  }

}

class ContextDemo extends Component {
  constructor (props, context) {
    super(props, context)
    this.state = {
      User: {
        age: 19,
        name: 'aicoder'
      }
    }
  }


  render () {
    return (
      <div>
        <Provider value={ this.state.User }>
          <Container></Container>
          <DM></DM>
        </Provider>
        <hr/>
        <input
          onClick={
            () => this.setState(preState => {
              return {User: { ...preState.User, age: preState.User.age + 1 }}
            })
          }
          className="button is-primary"
          value={ this.state.User.name }
          type="button"
          />
      </div>
    )
  }
}

export default ContextDemo

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文