react学习(十) React 中的 context

在平时工作中的某些场景下,你可能想在整个组件树中传递数据,但却不想手动地通过 props 属性在每一层传递属性,contextAPI 应用而生。

如果在你的项目中使用主题,基本是每个组件都需要;或者你在项目中使用多语言,也是每个组件都需要支持,这都是典型的可以通过 context 操作的例子

使用示例

我们实现一个多个组件,共享同一个颜色的示例,通过按钮点击切换颜色,如下:

我们有五个部分,外层的 pannel 组件,header 组件,title 组件,main 组件还有 content 组件。我们在随便一层组件中执行 color 切换函数,因为 setColor 方法已经通过 context 传递进去了。样式很简单,代码如下:

// src/index.js
import React from "react";
import ReactDOM from "react-dom";

// 创建上下文对象
const ColorContext = React.createContext()

const style = {
  margin: '5px',
  padding: '5px'
}
// 我们使用不同的方式创建组件

function Title() {
  return (
     // 函数组件使用方式,children 是一个函数
    <ColorContext.Consumer>
      {
        (contextValue) => {
          return <div style={{border: `5px solid ${contextValue.color}`}}>title</div>
        }
      }
    </ColorContext.Consumer>
  )
}


class Header extends React.Component {
  // 类组件绑定静态方法,默认给实例绑定 context 属性
  static contextType = ColorContext
  render() {
    // 也可以使用 comsumer 组件
    return (
      <div style={{border: `5px solid ${this.context.color}`}}>header <Title /></div>
    )
  }
}

function Content() {
  return (
    <ColorContext.Consumer>
      // 内部是函数形式
      {
        (contextValue) => {
          return (
            <>
            // 我们在这里控制颜色的改变
            <div style={{border: `5px solid ${contextValue.color}`}}>Content</div>
            <button onClick={() => {contextValue.changeColor('red')}}>red</button>
            <button onClick={() => {contextValue.changeColor('green')}}>green</button>
            </>
          )
        }
      }
    </ColorContext.Consumer>
  )
}
class Main extends React.Component {
  // 类组件获取方式
  static contextType = ColorContext

  render() {
    return (
      <div style={{border: `5px solid ${this.context.color}`}}>main <Content /></div>
    )
  }
}
class Panel extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      color: 'black'
    }

  }

  changeColor = (color) => {
    this.setState({color})
  }
  render() {
    const contextValue = {
      color: this.state.color,
      changeColor:this.changeColor
    }
    return (
      // 通过value向下传递
      <ColorContext.Provider value={contextValue}>
        <div style={{...style, width: '250px', border: `5px solid ${this.state.color}`}}>
          Panel
          <Header />
          <Main />
        </div>
      </ColorContext.Provider>
    )
  }
}

由上面可知,Provider 组件是一个提供数据的,数据存放在 value 中。Consumer 组件和 contextType 是消费数据的。而组件我们之前也实现过,更具不同的类型, 单独使用方法处理。

实现

定义类型:

// src/constants.js
export const REACT_PROVIDER = Symbol('react.provider')
// context 和 consumer 都是 context 类型,小伙伴们可自行打印官方的库查看
export const REACT_CONTEXT = Symbol('react.context')

React 中有个 createContext 方法:

// src/react.js
// 我们的写法效仿的是我们使用官方库打印出来的结果
function createContext() {
  const context = {
    $$typeof: REACT_CONTEXT,
    _currentValue: undefined, // 值是绑定在 context 中的 _currentValue 属性上
  }
  // 这里使用了递归引用,你中有我我中有你
  context.Provider = {
    $$typeof: REACT_PROVIDER,
    _context: context
  }
  
  context.Consumer = {
    $$typeof: REACT_CONTEXT,
    _context: context
  }
  return context
}


const React = {
  ...
  createContext
}

对于内容渲染我们要分两种情况考虑,一个是直接渲染的情况,一个是更新渲染的情况。第一种是 createDOM 中判断处理,第二种是在 updateElement 中处理,当然还有 forceUpdate 更新方法。

mount 处理

// src/react-dom.js  createDOM

...
if (type && type.$$typeof === REACT_PROVIDER) {
  return mountProviderComponent(vdom)  
} else if (type && type.$$typeof === REACT_CONTEXT) {
  return mountContextComponent(vdom)
}
...


function mountProviderComponent(vdom) {
  const {props, type} = vdom
  const context = type._context // Provider._context
  context._current = props.value // 通过value属性提供值
  const renderVdom = props.chidlren
  vdom.oldRenderVdom = renderVdom
  return createDOM(renderVdom) // 递归处理子,这里限制了一个根子节点
}

funtion mountContextComponent(vdom) {
  const {props, type} = vdom
  const context = type._context// Consomer._context
  const renderVdom = props.children(context._currentValue) // consumer 组件子是一个函数,这里的值上一步provider 已经赋值了,引用类型
  vdom.oldRenderVdom = renderVdom
  return createDOM(renderVdom)
}

类组件需要处理 contextType 静态方法

// src/react-dom.js  mountClassComponent
...
if (type.contextType) {
  // 添加 context 属性
  classInstance.context = type.contextType._currentValue
}
...

这里可能有朋友有疑问,为什么 type 一会这样,一会这么判断。这是 babeljsx 解析的结果,typeof type === string, 就是我们正常的 html 标签。如果是函数类型的话,可能是类组件或者函数组件。在这里 type 就指代的 ProviderConsumer 对象,需要具体情况具体分析。

这里一个 ColorContext 只能处理颜色的逻辑,如果还有其他的共享逻辑怎么办呢?我们可以对 ProviderConsumer 进行多层嵌套,使用方法是一样的。因为子也是递归处理,再根据类型找到对应的处理函数。如果使用的组件在不同的页面,我们需要把 ColorContext 进行导出,文件中自行引入。

update 处理

react 更新函数即 diff 对比,同级对比,类型一样的话在比对子,同样需要对类型进行判断

// src/react-dom. updateElement
...
if (oldVdom.type.$$typeof === REACT_PROVIDER) {
  updateProviderComponent(oldVdom, newVdom)  
} else if (oldVdom.type.$$typeof === RERACT_CONTEXT) {
  updateContextComponent(oldVdom, newVdom)
}
...


function updateProviderComponent(oldVdom, newVdom) {
  const currentDOM = findDOM(oldVdom)
  const parentDOM = currentDOM.parentNode
  // 下面的操作和 mount 类型,只是加了对比
  const {type, props} = newVdom
  const context = type._context
  const renderVdom = props.children
  context._currentValue = props.value
  compareTwoVdom(parentDOM, oldVdom.oldRenderVdom, renderVdom)
  newVdom.oldRenderVdom = renderVdom
}

function updateContextComponent(oldVdom, newVdom) {
  const currentDOM = findDOM(oldVdom)
  const parentDOM = currentDOM.parentNdoe
  const {type, props} = newVdom
  const context = type._context
  const renderVdom = props.children(context._currentValue)
  compareTwoVdom(parentDOM, oldVdom.oldRenderVdom, renderVdom)
  newVdom.oldRenderVdom = renderVdom
}

点击触发 changeColor 方法,促使 render 函数重新执行,我们要在 forceUpdate 中也判断类组件的字段

// src/Component.js

forceUpdate() {
  ...
  let oldDOM = findDOM(oldREnderVdom)
  // 更新时重新获取 context, 可能value 值更新了
  if (this.constructor.contextType) {
    this.context = this.constructor.contextType._currentValue
  }
  ...
}

我们自己实现的效果如下:

本节也是代码为主,但是中间穿插文字的描述,我相信大家可以理解 context 的机制和产生的原因。下一下小节我们学习下 react 中的高阶组件。

原文地址:https://cloud.tencent.com/developer/article/2019644

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


react 中的高阶组件主要是对于 hooks 之前的类组件来说的,如果组件之中有复用的代码,需要重新创建一个父类,父类中存储公共代码,返回子类,同时把公用属性...
我们上一节了解了组件的更新机制,但是只是停留在表层上,例如我们的 setState 函数式同步执行的,我们的事件处理直接绑定在了 dom 元素上,这些都跟 re...
我们上一节了解了 react 的虚拟 dom 的格式,如何把虚拟 dom 转为真实 dom 进行挂载。其实函数是组件和类组件也是在这个基础上包裹了一层,一个是调...
react 本身提供了克隆组件的方法,但是平时开发中可能很少使用,可能是不了解。我公司的项目就没有使用,但是在很多三方库中都有使用。本小节我们来学习下如果使用该...
mobx 是一个简单可扩展的状态管理库,中文官网链接。小编在接触 react 就一直使用 mobx 库,上手简单不复杂。
我们在平常的开发中不可避免的会有很多列表渲染逻辑,在 pc 端可以使用分页进行渲染数限制,在移动端可以使用下拉加载更多。但是对于大量的列表渲染,特别像有实时数据...
本小节开始前,我们先答复下一个同学的问题。上一小节发布后,有小伙伴后台来信问到:‘小编你只讲了类组件中怎么使用 ref,那在函数式组件中怎么使用呢?’。确实我们...
上一小节我们了解了固定高度的滚动列表实现,因为是固定高度所以容器总高度和每个元素的 size、offset 很容易得到,这种场景也适合我们常见的大部分场景,例如...
上一小节我们处理了 setState 的批量更新机制,但是我们有两个遗漏点,一个是源码中的 setState 可以传入函数,同时 setState 可以传入第二...
我们知道 react 进行页面渲染或者刷新的时候,会从根节点到子节点全部执行一遍,即使子组件中没有状态的改变,也会执行。这就造成了性能不必要的浪费。之前我们了解...
在平时工作中的某些场景下,你可能想在整个组件树中传递数据,但却不想手动地通过 props 属性在每一层传递属性,contextAPI 应用而生。
楼主最近入职新单位了,恰好新单位使用的技术栈是 react,因为之前一直进行的是 vue2/vue3 和小程序开发,对于这些技术栈实现机制也有一些了解,最少面试...
我们上一节了了解了函数式组件和类组件的处理方式,本质就是处理基于 babel 处理后的 type 类型,最后还是要处理虚拟 dom。本小节我们学习下组件的更新机...
前面几节我们学习了解了 react 的渲染机制和生命周期,本节我们正式进入基本面试必考的核心地带 -- diff 算法,了解如何优化和复用 dom 操作的,还有...
我们在之前已经学习过 react 生命周期,但是在 16 版本中 will 类的生命周期进行了废除,虽然依然可以用,但是需要加上 UNSAFE 开头,表示是不安...
上一小节我们学习了 react 中类组件的优化方式,对于 hooks 为主流的函数式编程,react 也提供了优化方式 memo 方法,本小节我们来了解下它的用...
开源不易,感谢你的支持,❤ star me if you like concent ^_^
hel-micro,模块联邦sdk化,免构建、热更新、工具链无关的微模块方案 ,欢迎关注与了解
本文主题围绕concent的setup和react的五把钩子来展开,既然提到了setup就离不开composition api这个关键词,准确的说setup是由...
ReactsetState的执行是异步还是同步官方文档是这么说的setState()doesnotalwaysimmediatelyupdatethecomponent.Itmaybatchordefertheupdateuntillater.Thismakesreadingthis.staterightaftercallingsetState()apotentialpitfall.Instead,usecom