前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >react学习(十) React 中的 context

react学习(十) React 中的 context

原创
作者头像
测不准
发布2022-06-09 23:40:53
2.4K0
发布2022-06-09 23:40:53
举报
文章被收录于专栏:与前端沾边与前端沾边

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

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

使用示例

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

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

代码语言:txt
复制
// 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 是消费数据的。而组件我们之前也实现过,更具不同的类型, 单独使用方法处理。

实现

定义类型:

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

React 中有个 createContext 方法:

代码语言:txt
复制
// src/react.js
// 我们的写法效仿的是我们使用官方库打印出来的结果
function createContext() {
  const context = {
    $$typeof: REACT_CONTEXT,
    _currentValue: ?developer/article/2019644/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 处理

代码语言:txt
复制
// 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 静态方法

代码语言:txt
复制
// 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 对比,同级对比,类型一样的话在比对子,同样需要对类型进行判断

代码语言:txt
复制
// 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 中也判断类组件的字段

代码语言:txt
复制
// src/Component.js

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

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

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

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 使用示例
  • 实现
    • mount 处理
      • update 处理
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
      http://www.vxiaotou.com