react Api - createContext
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) => {});
尽量理解和归纳吧,不能归纳熟悉一下也好。后面复习的时候再画一个流程图处理一下。
参考
- React Context 原理理解: blog.csdn.net/weixin_4565…
- 官网:react.dev/reference/r…
转载自:https://juejin.cn/post/7234750572191727653