likes
comments
collection
share

react Api - createContext

作者站长头像
站长
· 阅读数 73

react Api - createContext

概念

Context 提供了一个无需为每层组件手动添加 props,就能在组件树间进行数据传递的方法。相当于一个全局对象主要作用就是隔代传递参数。

例子

子组件改变全局背景色 content.js :

import { createContext } from 'react';

export const ThemeContext = createContext({});

app.js

import { ThemeContext } from "./content";

import { useContext, useState } from "react";

 const Parents = () => {
    return (
      <div>
        <div>hhh</div>
        <Childs />
      </div>
    );
 };

export default function App() {
  const [color, setColor] = useState("yellow");
  return (
    <ThemeContext.Provider value={{ color, setColor }}>
      <div style={{ background: color }} >
        <h1>Hello CodeSandbox</h1>
        <Parents />
      </div>
    </ThemeContext.Provider>
  );
}

child.js

import { ThemeContext } from "./content";

import { useContext, useState } from "react";

const Childs = () => {
    const value = useContext(ThemeContext);
    return <button onClick={() => value.setColor(value.color === "blue" ? "yellow": "blue")}>{value.color}</button>;
    // 老版本获取value
    //  <ThemeContext.Consumer>
    //        {(value) => <div>{value.color}</div>}
    //      </ThemeContext.Consumer>
};

注意事项

因为 context 会根据引用标识来决定何时进行渲染(本质上是 value 属性值的浅比较),所以这里可能存在一些陷阱,当 provider 的父组件进行重渲染时,可能会在 consumers 组件中触发意外的渲染。也就是说建议传递value时useState,不然会出现反复渲染的情况。

实现原理

content对象实现方法

export function createContext(defaultValue, calculateChangedBits){
  // 对象本质
  const context = {
    $$typeof: REACT_CONTEXT_TYPE, // Consumer element类型
    _calculateChangedBits: calculateChangedBits,
    _currentValue: defaultValue,
    _currentValue2: defaultValue,
    Provider: null,
    Consumer: null,
  };
  // react element对象
  context.Provider = {
    $$typeof: REACT_PROVIDER_TYPE,
    _context: context,
  };
  // react element对象
  context.Consumer = context;

  return context;
}

Provider 提供者

Provider要传递value要传递value首先要做到两点:

  • 子孙节点取到value值。
  • value更新的时候子孙节点也做相应的更新。

第一点很简单我们只需要拿到content对象就可以拿到value的值。 第二点更新就比较复杂,我们做如下分析: provider在编译的时候会被label编译为React Element对象,再编译成分片(ContextProvider fiber)。 调度过程:如果Provider触发更新,fiber进入调度阶段,再进入到 beginWork流程:

  • 如果当前类型的 fiber 不需要更新,那么会 FinishedWork中止当前节点和子节点的更新。
  • 如果当前类型 fiber 需要更新,那么会调用不同类型 fiber 的处理方法。updateContextProvider方法
function updateContextProvider(current, workInProgress, renderLanes) {
  var providerType = workInProgress.type;
  var context = providerType._context;
  var newProps = workInProgress.pendingProps;
  var oldProps = workInProgress.memoizedProps;
  var newValue = newProps.value;
  var oldValue = oldProps.value;

  // 更新 value prop 到 context 中
  context._currentValue = nextValue;

  // 是否进行标记更新
  if (objectIs(oldValue, newValue)) {
    if (oldProps.children === newProps.children && !hasContextChanged()) {
      return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
    }
  } else {
    propagateContextChange(workInProgress, context, changedBits, renderLanes);
  }

  // ... reconciler children
}

Provider 更新,会递归所有的子组件,只要消费了 context 的子代 fiber ,都会给一个高优先级。而且向上更新父级 fiber 链上的优先级,让所有父级 fiber 都处于一个高优先级。那么接下来高优先级的 fiber 都会 beginWork 。 fiber有一个dependencies 属性,这个属性可以把当前的 fiber 和 context 建立起关联,是一个链表结构,将 fiber 对应的 context 存放在 dependencies 中。 这样只要消费了context地方建立相应的依赖就可以取到context最新的值。

Consumer消费者

其实只需做到和 fiber 和 context 建立起关联,就可以做到消费content。那如何建立关联呢?

export function readContext(context, observedBits) {
    /*创速—个 contextItem */
    const contextItem = {
        context,
        observedBits: resolved0bservedBits,
        next: null,
    };
    /*不存在最后一个 context Dependency*/
    if (lastContextDependency === null) {
        lastContextDependency = contextItem;
        currentlyRenderingFiber.dependencies = {
            expirationTime: Nowork,
            firstContext: contextItem,
            responders: null,
        };
    } else {
        /*存在的情况*/
        lastContextDependency = lastContextDependency.next = contextItem;
    }
    return isPrimaryRenderer ? context._currentValue : context._currentValue2;
}

useContent 、Consumer原理其实都是调用了这个方法,并且在编译的时候会先找到这些消费地方再去执行分片渲染。

替代方案EventEmitter

需要注意的是监听的代码需要在emit之后执行。

import { EventEmitter } from 'events';
 
const eventBus = new EventEmitter();
// 你要传递的值
eventBus.emit("event", "hello");
// 监听你传过来的值 
eventBus.on('event', (value) => {});


尽量理解和归纳吧,不能归纳熟悉一下也好。后面复习的时候再画一个流程图处理一下。

参考

转载自:https://juejin.cn/post/7234750572191727653
评论
请登录