熟悉 React
的同学们都知道,每次数据更新都会重新渲染 fiber
树,匹配渲染优先级组件及其所有子组件都会重新渲染,存在心智负担,可能开发中很多同学会掏出 useMemo
,useCallback
, memo
, pureComponent
, sholdComponentUpdate
组合拳来优化避免组件重复渲染。
组件为什么会被重复渲染?
首先来段示例,当 App
组件触发会影响 Child
重新渲染。
function Child() {
console.log('Child::render');
return <div>child</div>
}
funcction App() {
const [data, setData] = useState(0);
return (
<>
<p>{ data }</p>
<button onClick={handleClick}>click</button>
<Child/>
</>
);
function handleClick() {
setData(data => data + 1);
}
}
有哪些方法可以避免Child渲染呢?
const Child1 = () => {
console.log('Child1::render');
return <div>child</div>
}
const child1 = <Child1/>;
const Child6 = memo(() => {
console.log('Child6::render');
return <div>child6</div>
})
funcction App({ children, render }) {
const [data, setData] = useState(0);
return (
<>
<p>{ data }</p>
<button onClick={handleClick}>click</button>
{ child1 }
{
useMemo(() => {
console.log('Child2::render');
return <div>child2</div>
}, [])
}
{
useState(() => {
console.log('Child3::render');
return <div>child3</div>
})[0]
}
{ children }
{ render }
<Child6/>
</>
);
function handleClick() {
setData(data => data + 1);
}
}
let child5 = () => {
console.log('Child5::render');
return <div>child5</div>
}
<App render={child5}>
{ React.createElement(() => {
console.log('Chil4::render');
return <div>child4</div>
}) }
</App>
// 首次渲染
print -> star
Child2::render
Child3::render
Child1::render
Child4::render
Child6::render
print -> end
通过点击 button
可以观察到控制台并无 Child
打印内容输出, 子组件并没有被重复渲染; 也就说除了常规包裹 useMemo
与 memo
外,还有很多其他的方式也可以达到类似效果; 可能会有同学疑惑(后面会有解释)
children
不是子节点吗,为什么没有重新渲染useState
为什么也可以避免Child
渲染- 打印顺序是什么鬼?
React
组件会根据 state(useState|setState), props
, context
来判断当前 fiber
是否可以复用 找到源码中 beginWork
方法, beginWork
主要用于生成子 filber
以及打上对应 flags
function beginWork(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes,
): Fiber | null {
const updateLanes = workInProgress.lanes;
// current 存在 update更新
if (current !== null) {
const oldProps = current.memoizedProps;
const newProps = workInProgress.pendingProps;
// 判断新旧props是否全等 或者 上下文是否有变化
if (
oldProps !== newProps ||
hasLegacyContextChanged()
) {
didReceiveUpdate = true;
// 当渲染优先级不包含workInProgress的优先级,复用旧fiber
} else if (!includesSomeLane(renderLanes, updateLanes)) {
didReceiveUpdate = false;
...
// 复用fiber
return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
} else {
if ((current.flags & ForceUpdateForLegacySuspense) !== NoFlags) {
...
didReceiveUpdate = true;
} else {
...
didReceiveUpdate = false;
}
}
} else {
didReceiveUpdate = false;
}
乍一看不是很合理吗,那么为何App组件更新会导致Child组件刷新呢?
首先我们得知道workInProgress.pendingProps到底是啥?
// 源码中
const pendingProps = element.props;
// 那么这个element又是啥呢?
// 格式如下
{
_owner:null
_store:{validated: true}
$$typeof:Symbol(react.element)
key:null
props:{}
ref:null
type:() => {\n console.log('Child::RENDER');\n return /*#__PURE...
}
// 是不是很熟悉,这就是常写得Jsx数据
// 可以打印看看
const Child1 = () => {
console.log('Child1::render');
return <div>child</div>;
};
const child1 = <Child1 />;
console.log(child1);
// 换个方式可以证明
let props1 = child1.props;
let props2 = child1.props;
console.log(props1 === props2);
print -> true
// props3 与 props4 生成得Jsx 不是同一引用因此不相等
let props3 = (<Child1 />).props;
let props4 = (<Child1 />).props;
console.log(props3 === props4);
print -> false
// 这个概念类似
const a = { "test": 1 };
const b = { "test": 1'};
console.log(a === b);
print -> false
const c = a; // "c" 仅仅是 "a" 的引用
console.log(a === c);
print -> true
因此当 App
组件更新时 重新生成 JSX
,Child
组件对应得 props
内存地址也发生了变化,故会重新渲染.
这也就解释了
- 同一引用得
Child1
组件不会受到影响。 children
与render
属性也是一样的, 引用未变。Child5
与Child6
组件是在App
外部生成的JSX
并赋值给App
中的children
与render
属性。- 所以
App
组件再次渲染子组件便会重新生成JSX
(换句话说只会影响内部的子元素) - 因为
App
外层没有变更,因此App
不会重新生成JSX
,children
与render
内存地址固然不变咯。
useMemo, memo为何可以避免组件重新渲染?
可能大伙都很熟悉这几个 API
, 对于 useMemo
缓存变量,memo
包裹的组件浅比较 props
来判断组件是否需要重新渲染。
memo
// 项目中很多这么使用的(可能有问题,下面会说)
const Test = memo(() => <div>???</div>)
// memo还有第二个参数“手动档”进行新旧props比较
const Test = memo(() => <div>???</div>, (prev, next) => prev !== next)
// 那么 memo 是怎么保障组件渲染的呢
// 源码 updateMemoComponent 中
const prevProps = currentChild.memoizedProps;
// compare 为 “手动档” 回调
let compare = Component.compare;
compare = compare !== null ? compare : shallowEqual;
if (compare(prevProps, nextProps) && current.ref === workInProgress.ref) {
// 复用 fiber
return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
}
// 也就是存在定义的比较函数时使用,否则使用默认比较shallowEqual
// 命中则复用不会重新渲染
所以这也就解释了上面为什么说 memo
包裹的 Child6
不会被重新渲染。 再看看默认比较规则 shallowEqual
:
- 比较内存
- 不是对象
- 比较参数长度
- 遍历参数比较
function shallowEqual(objA: mixed, objB: mixed): boolean {
if (is(objA, objB)) {
return true;
}
if (
typeof objA !== 'object' ||
objA === null ||
typeof objB !== 'object' ||
objB === null
) {
return false;
}
const keysA = Object.keys(objA);
const keysB = Object.keys(objB);
if (keysA.length !== keysB.length) {
return false;
}
// Test for A's keys different from B.
for (let i = 0; i < keysA.length; i++) {
if (
!hasOwnProperty.call(objB, keysA[i]) ||
!is(objA[keysA[i]], objB[keysA[i]])
) {
return false;
}
}
return true;
}
问: 给所有组件都包裹memo合适吗?
上面大家都可以看到 memo
的好处,但是凡是都有代价的,对比正常的组件多了一层浅比较逻辑不说。 对于参数比较少的组件来说,使用默认规则可能开销不那么大。
// 举个极端的例子,这要走默认比较不得老要命了😉
// 下次看到建议直接打死
const Demo = memo(() => <div>demo</div>)
let records = {
...100 arguments
}
<Demo {...records}/>
useMemo
// useMemo 用来缓存原始变量
// 举个栗子
let originData = {
test: '???',
};
const Test = () => {
const [data, setData] = useState(0);
let cacheData = useMemo(() => originData, []);
console.log(cacheData === originData);
return <div onClick={() => {
setData(data => data + 1)
}}>click</div>
}
// Test 组件每次渲染 都会打印 true
// 上面说了<Child/> 就是JSX对象
// 所以useMemo也能缓存JSX对象引用, 保障新旧props地址相同
问:为什么useMemo可以缓存数据?
其实可以根据上面的示例猜测一下,(执行回调,缓存回调变量??) 细说的话这个问题其实和 Hooks
原理是差不多的。
首次渲染时的 useMemo
:
export function useMemo<T>(
create: () => T,
deps: Array<mixed> | void | null,
): T {
const dispatcher = resolveDispatcher();
return dispatcher.useMemo(create, deps);
}
// dispatcher.useMemo 最终会调用 mountMemo
function mountMemo<T>(
nextCreate: () => T,
deps: Array<mixed> | void | null,
): T {
const hook = mountWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
const nextValue = nextCreate();
hook.memoizedState = [nextValue, nextDeps];
return nextValue;
}
// 可以看到 nextValue (也就是需要缓存的变量或者JSX对象)被存入hook.memoizedState中
function mountWorkInProgressHook(): Hook {
const hook: Hook = {
memoizedState: null,
baseState: null,
baseQueue: null,
queue: null,
next: null,
};
// workInProgressHook 表示组件所创建的hooks链表
// 为null的话表示当前hook是该组件的第一个hook
if (workInProgressHook === null) {
// This is the first hook in the list
currentlyRenderingFiber.memoizedState = workInProgressHook = hook;
} else {
// Append to the end of the list
workInProgressHook = workInProgressHook.next = hook;
}
return workInProgressHook;
}
// mountWorkInProgressHook
// 创建一个hook
// 是第一个hook, 赋值memoizedState = workInProgressHook = hook
// 不是的话就next连接起来
// 举个栗子
useA(); // workInProgressHook = hookA
useB(); // workInProgressHook = hookA.next -> hookB
//上面的 currentlyRenderingFiber 在 renderWithHooks 中赋值 workInProgress(当前组件fiber)
export function renderWithHooks<Props, SecondArg>(
current: Fiber | null,
workInProgress: Fiber,
Component: (p: Props, arg: SecondArg) => any,
props: Props,
secondArg: SecondArg,
nextRenderLanes: Lanes,
): any {
renderLanes = nextRenderLanes;
currentlyRenderingFiber = workInProgress;
...
let children = Component(props, secondArg);
...
}
再看看update阶段的useMemo
// dispatcher.useMemo 最终会调用 updateMemo
function updateMemo<T>(
nextCreate: () => T,
deps: Array<mixed> | void | null,
): T {
// 当前hook
const hook = updateWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
// 首次渲染缓存的值
const prevState = hook.memoizedState;
if (prevState !== null) {
if (nextDeps !== null) {
const prevDeps: Array<mixed> | null = prevState[1];
// 比较依赖
if (areHookInputsEqual(nextDeps, prevDeps)) {
// 复用旧值
return prevState[0];
}
}
}
// 计算新值
const nextValue = nextCreate();
hook.memoizedState = [nextValue, nextDeps];
return nextValue;
}
// updateMemo 整个逻辑还是很简单的
// 再看看比较依赖areHookInputsEqual
function areHookInputsEqual(
nextDeps: Array<mixed>,
prevDeps: Array<mixed> | null,
) {
if (prevDeps === null) {
return false;
}
for (let i = 0; i < prevDeps.length && i < nextDeps.length; i++) {
if (is(nextDeps[i], prevDeps[i])) {
continue;
}
return false;
}
return true;
}
大家也可以下面例子验证 useMemo
存储位置,控制台查看 memoizedState
中 memo
缓存的数据。
function App() {
return (
<>
{useMemo(() => {
console.log('Child2::render');
return <div>child2</div>;
}, [])}
</>
);
}
let dom = document.getElementById('root')
let current = dom.__reactContainer$g605vp1tnct
console.log(current.child.memoizedState.memoizedState);
print -> [JSX对象, 依赖]
所以当 App
组件再次渲染时,倘若 useMemo
依赖项没有变更,便会复用(使用上次内存地址,进而新旧 props
也会全等)。
那么useMemo 相同效果的好兄弟 “pureComponents” & “sholdComponentUpdate”; 他们的作用真的相等吗?
pureComponents
类组件只需要继承 pureComponents
便可以避免 render
渲染,是不是神似 memo
, 那你知道他们的区别吗?
class Test extends PureComponent {
render() {
console.log('Testsssssssssss');
return "Test"
}
}
// 源码中 PureComponent 实现
function PureComponent(props, context, updater) {
this.props = props;
this.context = context;
this.refs = emptyObject;
this.updater = updater || ReactNoopUpdateQueue;
}
const pureComponentPrototype = (PureComponent.prototype = new ComponentDummy());
pureComponentPrototype.constructor = PureComponent;
// 继承Component
Object.assign(pureComponentPrototype, Component.prototype);
// 这里留意一下 isPureReactComponent 标识
pureComponentPrototype.isPureReactComponent = true;
可以在 updateClassInstance
中看到全貌,省略了部分代码 看上去代码量很多,其实每环逻辑很清晰, 主要为 componentDidUpdate
和 getSnapshotBeforeUpdate
添加标记 返回是否需要重新渲染布尔标识(true -> 需要渲染 | false -> 复用)
- 判断新旧
props
与data
内存地址是否一致,上下文是否变化- 没有变化复用放回
false
, 为相关生命周期打添加对应标识
- 没有变化复用放回
- 计算
shouldUpdate
shouldUpdate
为false
时代表复用,为相关生命周期打添加对应标识(与上面 1-a 一致)shouldUpdate
为true
代表需要重新render
,存在componentWillUpdate
orUNSAFE_componentWillUpdate
执行。
function updateClassInstance(
current: Fiber,
workInProgress: Fiber,
ctor: any,
newProps: any,
renderLanes: Lanes,
): boolean {
const instance = workInProgress.stateNode;
const unresolvedOldProps = workInProgress.memoizedProps;
const oldProps =
workInProgress.type === workInProgress.elementType
? unresolvedOldProps
: resolveDefaultProps(workInProgress.type, unresolvedOldProps);
instance.props = oldProps;
const unresolvedNewProps = workInProgress.pendingProps;
const oldContext = instance.context;
const contextType = ctor.contextType;
let nextContext = emptyContextObject;
...
const oldState = workInProgress.memoizedState;
let newState = (instance.state = oldState);
newState = workInProgress.memoizedState;
if (
unresolvedOldProps === unresolvedNewProps &&
oldState === newState &&
!hasContextChanged() &&
!checkHasForceUpdateAfterProcessing()
) {
if (typeof instance.componentDidUpdate === 'function') {
if (
unresolvedOldProps !== current.memoizedProps ||
oldState !== current.memoizedState
) {
workInProgress.flags |= Update;
}
}
if (typeof instance.getSnapshotBeforeUpdate === 'function') {
if (
unresolvedOldProps !== current.memoizedProps ||
oldState !== current.memoizedState
) {
workInProgress.flags |= Snapshot;
}
}
return false;
}
...
// 重新计算 shouldUpdate
// 需要留意 checkShouldComponentUpdate
const shouldUpdate =
checkHasForceUpdateAfterProcessing() ||
checkShouldComponentUpdate(
workInProgress,
ctor,
oldProps,
newProps,
oldState,
newState,
nextContext,
);
// 重新渲染 render
// 存在 componentWillUpdate or UNSAFE_componentWillUpdate 执行
// 为 componentDidUpdate and getSnapshotBeforeUpdate 添加标记
if (shouldUpdate) {
if (
!hasNewLifecycles &&
(typeof instance.UNSAFE_componentWillUpdate === 'function' ||
typeof instance.componentWillUpdate === 'function')
) {
if (typeof instance.componentWillUpdate === 'function') {
instance.componentWillUpdate(newProps, newState, nextContext);
}
if (typeof instance.UNSAFE_componentWillUpdate === 'function') {
instance.UNSAFE_componentWillUpdate(newProps, newState, nextContext);
}
}
if (typeof instance.componentDidUpdate === 'function') {
workInProgress.flags |= Update;
}
if (typeof instance.getSnapshotBeforeUpdate === 'function') {
workInProgress.flags |= Snapshot;
}
} else {
// 复用
// 为 componentDidUpdate and getSnapshotBeforeUpdate 添加标记
if (typeof instance.componentDidUpdate === 'function') {
if (
unresolvedOldProps !== current.memoizedProps ||
oldState !== current.memoizedState
) {
workInProgress.flags |= Update;
}
}
if (typeof instance.getSnapshotBeforeUpdate === 'function') {
if (
unresolvedOldProps !== current.memoizedProps ||
oldState !== current.memoizedState
) {
workInProgress.flags |= Snapshot;
}
}
workInProgress.memoizedProps = newProps;
workInProgress.memoizedState = newState;
}
instance.props = newProps;
instance.state = newState;
instance.context = nextContext;
return shouldUpdate;
}
checkShouldComponentUpdate
中逻辑非常简单:
- 存在
shouldComponentUpdate
调用(shouldComponentUpdate
优先级高于pureComponents
) - 否则判断是否带有
isPureReactComponent
标识- 使用默认比较
shallowEqual
(老演员了与上面memo
一致) - 除了比较
props
外还会比较state
满足一个条件即可
- 使用默认比较
- 返回是否渲染状态标识。
function checkShouldComponentUpdate(
workInProgress,
ctor,
oldProps,
newProps,
oldState,
newState,
nextContext,
) {
const instance = workInProgress.stateNode;
if (typeof instance.shouldComponentUpdate === 'function') {
const shouldUpdate = instance.shouldComponentUpdate(
newProps,
newState,
nextContext,
);
return shouldUpdate;
}
if (ctor.prototype && ctor.prototype.isPureReactComponent) {
return (
!shallowEqual(oldProps, newProps) || !shallowEqual(oldState, newState)
);
}
return true;
}
上面也验证了 PureComponents
与 memo
的差异性:
- 比较规则的差异性
- 除了比较
props
还会比较state
shouldComponentUpdate
传参与memo
回调也不相同
- 除了比较
- 而
shouldComponentUpdate
更像是memo
的第二个回调- 优先级高于默认
shallowEqual
比较
- 优先级高于默认
所以这么一看 memo
像是 PureComponents
+ shouldComponentUpdate
的浓缩版😄。
shouldUpdate 渲染标识是怎么影响渲染的?
在 finishClassComponent
中会根据 shouldUpdate
标识来判断复用 这个 bailoutOnAlreadyFinishedWord
(也是老演员了,上面复用都有出现,,大家感兴趣可以自行了解,此处不展开)复用 Filber
。
// 异常
const didCaptureError = (workInProgress.flags & DidCapture) !== NoFlags;
if (!shouldUpdate && !didCaptureError) {
if (hasContext) {
invalidateContextProvider(workInProgress, Component, false);
}
// 复用
return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
}
// 存在异常
if (
didCaptureError &&
typeof Component.getDerivedStateFromError !== 'function'
) {
nextChildren = null;
if (enableProfilerTimer) {
stopProfilerTimerIfRunning(workInProgress);
}
} else {
// 重新 render
nextChildren = instance.render();
}
...
好现在在回溯之前的问题: 给所有的 Class
组件包裹 PureComponents
合适吗?
其实与 上面给所有组件包裹
memo
的问题是一样的,甚至PureComponents
默认比较某些情况性能还是比memo
默认比较更差,比如100+的新旧props
都相等时,会遍历比较一次后又会遍历比较新旧state
, 倘若state
也是100+ 呢?🤪
其他优化
项目中经常可以看到组件里写组件的方式,这里举例并不是说这样写不行,对于某些场景会有性能差异。
const Item2 = () => {
useEffect(() => {
console.log('Item2 is Mount')
}, []);
console.log('Item2 is Render')
return (
<p>Item------</p>
);
}
function App() {
const [data, setData] = useState(0);
const Item = () => {
useEffect(() => {
console.log('Item is Mount')
}, []);
console.log('Item is Render')
return (
<p>Item------</p>
);
}
return (
<div>
{
Array.from({length: 10}).map((_, index) => <Item key={index}/>)
}
{
Array.from({length: 10}).map((_, index) => <Item2 key={index}/>)
}
<button onClick={hanldeClick}>莫碍老子</button>
</div>
);
}
--------------------
每当点击触发App更新时, 打印输出如下
click 1次
print ->
Item is Render
Item2 is Render
Item2 is Mount
click 2次
print ->
Item is Render
Item2 is Render
Item2 is Mount
Item is Render
Item2 is Render
Item2 is Mount
每当点击时。你会发现每次 Item
组件内的 useEffect
都会重新执行, 而 Item2
组件不会这是为何?
- 唉最开始不是说,
App
组件更新,子组件的JSX
都会重新创建,导致内存地址不同,进而渲染子组件- 按照这个逻辑来说,
Item
与Item2
组件不应该会有差异才对呀
- 按照这个逻辑来说,
- 看上去
Item
组件每次都初始化了一般,这是为何?
这个解释起来有点麻烦,需要了解 Diff
, Hooks 原理
以及 commit渲染流
。
在 reconcileChildrenArray
(也叫 Array Diff
)-> updateSlot
-> updateElement
中 可以找到想要的答案。
function updateElement(
returnFiber: Fiber,
current: Fiber | null,
element: ReactElement,
lanes: Lanes,
): Fiber {
if (current !== null) {
if (
current.elementType === element.type
) {
// 克隆复用 Fiber
const existing = useFiber(current, element.props);
existing.ref = coerceRef(returnFiber, current, element);
existing.return = returnFiber;
return existing;
} else if (enableBlocksAPI && current.tag === Block) {
...
}
}
// 重新创建 Fiber
const created = createFiberFromElement(element, returnFiber.mode, lanes);
created.ref = coerceRef(returnFiber, current, element);
created.return = returnFiber;
return created;
}
上面只需要留意 current.elementType === element.type
current
表示旧的Fiber
单元,element
也就是最开始介绍过的JSX
对象,那么这个逻辑就表示判断 Item 组件引用地址,如果相同克隆复用Fiber
, 不同便会新增Fiber
;而对于
hooks
分为俩个阶段,首次渲染初始化创建hooks
链表,以及update
阶段移动链表获取当前每个对应的hook
计算结果。而hooks
链表被挂载在组件的Fiber
上。 因此当重新创建Fiber
时,上次初始化的hooks
链表并没有得到保留,进而renderWithHooks
再次进入执行Item
组件时,任然是创建Hooks链表
,commit
阶段调度 (这里后续文章会说明)。这也就解释了为什么
Item
组件会每次都会执行useEffect
回调,这里差异化除了创建Fiber
的开销外,如果存在Hooks
还有Hooks
链表每次创建的开销。
把逻辑全套在setData会有什么问题?
可能会觉得不会有影响,因为执行点击回调时 从点击到视图改变时间维度不会因为你将逻辑抽离而改变,不过是逻辑执行时机的改变:
- 一个是点击回调执行逻辑后设置值。
- 一个是传入回调后到
renderWithHooks
, 调用App component
,hooks update
计算值的差别。 emmm, 你这说确实没错,倘若你开启了并发模式,那便会有差异了(可以想想)
// 举个栗子
// 如果一个hooks 有多个update 单元
// 这里的lang 表示优先级, action 表示对应需要计算的值
hooks -> queue ->
update1 -> update2 -> update3
lang=3 lang=1 lang=3
action=1 action=(d) => d + 1 action=(d) => 100+more
// 可以看到 update3 为逻辑回调
// 假设当前渲染优先级为3, 那么本次满足条件应该执行的是 update1 + update3
遍历update链表
update1 -> update2 -> update3
1
^
update1满足渲染条件
newState = 1;
newBaseState = 1;
update1 -> update2 -> update3
d => d + 1
^
update2不满足渲染条件
baseFirst = update2
newState = 1;
newBaseState = 1;
update1 -> update2 -> update3
d => 100more
^
update3满足渲染条件
baseFirst = update2 -> update3
newState = (1) => 100more 的结果;
newBaseState = 1;
那么页面上data会暂时展示 newState的结果。
下次更新会执行上次跳过的链表以newBaseState为基准, baseFirst(update2 -> update3)
因此你会发现,update3 这个逻辑回调单元被执行计算了俩次
至于hooks链表计算详细流程有机会后续文章补充😎
useContent 不正当使用
这里给个结论,具体参考 useContent
章节; 当上下文更新时会额外深度优先遍历 Fiber
匹配消费者的 Context
, 因此当 Provider
嵌套的内容越多(比如根元素上),遍历的成本就越高;组件内耦合使用更为合适。
那么有什么合适的方式大范围传值呢?
用过 Vue
的同学都知道 MVVM
的原理,对属性进行劫持,在 VNode
创建阶段获取属性,为其添加对应组件的 updateComponent
,属性更新 setter
调度任务执行 updateComponent
创建 VNode Tree
比较更新视图。
通过结合 Proxy
, 就可以再状态变更时收集所有的消费者,批量更新 (市面上也有相关的库自行了解)。
小demo
function Store(data) {
this.data = data;
let id = 1;
let promise = Promise.resolve();
let lineComponent = null;
this.forceUpdate = null;
let updateMap = null;
let proxy = new Proxy(data, {
get(obj, prop) {
if (componentMap) {
if (componentMap.has(lineComponent)) {
let list = componentMap.get(lineComponent);
list.add(prop);
} else {
componentMap.set(lineComponent, new Set([prop]));
}
}
return obj[prop];
},
set: (target, key, value) => {
if (target[key] != value && updateMap) {
updateMap.set(key, [target[key], value])
target[key] = value;
}
return true;
}
});
let componentMap = new Map();
this.useUpdate = function useUpdate() {
let [_, set] = React.useState(0);
this.forceUpdate = () => set(a => a + 1);
return (key, val) => {
updateMap = new Map();
proxy[key] = val;
promise.then(this.forceUpdate);
};
}
this.useStore = function(cb) {
return cb(proxy);
}
this.Provider = function Provider({ value, children }) {
return (
<>
{ children }
</>
);
}
let defaultEqual = function(equal, prev, next) {
let list = componentMap.get(lineComponent) || [];
for (let i of list) {
if (updateMap && updateMap.has(i)) {
return false;
}
}
if (equal) {
return equal(prev, next);
}
for (let i in next) {
if (Object.is(next[i], prev[i])) {
continue;
}
return false
}
return true;
}
this.Component = (callback, equal) => {
id+=1;
lineComponent = null;
let JSX = React.memo(callback, defaultEqual.bind(null, equal));
lineComponent = JSX.type;
return JSX;
}
}
使用
let store = new Store({
name: 'wujie',
age: '2-'
})
const Demo = store.Component(() => {
let { name, age } = store.useStore((data) => ({
name: data.name,
age: data.age
}));
let set = store.useUpdate();
return (
<div onClick={() => {
set('name',11)
set('name',33)
set('age',50)
}}>data --- { name }--{age}</div>
);
})
const root = (
<store.Provider>
{
React.createElement(() => {
let [a, b] = React.useState(0);
console.log('render.....')
return (
<div>
<p onClick={() => b(a + 1)}>a---{a}</p>
<Demo/>
</div>
);
})
}
</store.Provider>
)
文档信息
- 本文作者:scriptoverture
- 本文链接:https://scriptoverture.github.io/blog/2024/09/22/React-%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96/
- 版权声明:自由转载-非商用-非衍生-保持署名(创意共享3.0许可证)