Skip to content

Commit

Permalink
build(3.4.14): optimize handleOperate, add canRecord to avoid dead cy…
Browse files Browse the repository at this point in the history
…cle; optimize inCtx, del readMapPrev readMapStrict; optimize insDep updateDep logic, add resetDepKeys; optimize notify logic, add excludedInsKeyDict to speedup analyzeDepKey
  • Loading branch information
fantasticsoul committed Dec 2, 2023
1 parent 1365234 commit eb21648
Show file tree
Hide file tree
Showing 15 changed files with 159 additions and 113 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
"@types/node": ">=12.0.0",
"esbuild-plugin-external-global": "^1.0.1",
"helux": "workspace:^",
"limu": "^3.5.5"
"limu": "^3.9.1"
},
"devDependencies": {
"@babel/core": "^7.19.3",
Expand Down
2 changes: 1 addition & 1 deletion packages/helux-core/src/consts/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { createSymbol, HAS_SYMBOL } from '../helpers/sym';
export { EVENT_NAME, FROM, LOADING_MODE } from './user';
export { HAS_SYMBOL };

export const VER = '3.4.13';
export const VER = '3.4.14';

export const PROD_FLAG = true;

Expand Down
5 changes: 4 additions & 1 deletion packages/helux-core/src/factory/createShared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export function createSharedLogic(innerOptions: IInnerOptions, createOptions?: a
const { stateType, apiCtx } = innerOptions;
ensureGlobal(apiCtx, stateType);
const { sharedState: state, internal } = buildSharedObject(innerOptions, createOptions);
const { syncer, sync, forAtom, setDraft: setState } = internal;
const { syncer, sync, forAtom, setDraft: setState, sharedKey, sharedKeyStr, rootValKey } = internal;
const { useFn, actionCreator, actionAsyncCreator, mutateCreator, setAtomVal } = getFns(state, forAtom);
const opt = { internal, from: MUTATE, apiCtx };
const ldMutate = initLoadingCtx(createSharedLogic, opt);
Expand All @@ -71,6 +71,9 @@ export function createSharedLogic(innerOptions: IInnerOptions, createOptions?: a
syncer,
setAtomVal,
setOnReadHook,
sharedKey,
sharedKeyStr,
rootValKey,
};
}

Expand Down
17 changes: 11 additions & 6 deletions packages/helux-core/src/factory/creator/notify.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ export function execDepFns(opts: ICommitStateOptions) {
const { mutateCtx, internal, desc, isFirstCall, from, sn } = opts;
const { ids, globalIds, depKeys, triggerReasons } = mutateCtx;
const { key2InsKeys, id2InsKeys, insCtxMap, rootValKey } = internal;
console.log('depKeys ', depKeys);

internal.ver += 1;
// find associate ins keys
Expand All @@ -48,8 +47,10 @@ export function execDepFns(opts: ICommitStateOptions) {

const insKeys = key2InsKeys[key] || [];
const validInsKeys: number[] = [];
const excludedInsKeyDict: Dict = {};
for (const insKey of insKeys) {
if (allInsKeys.includes(insKey)) {
// 已包含或已排除,都跳过当次循环
if (allInsKeys.includes(insKey) || excludedInsKeyDict[insKey]) {
continue;
}
const insCtx = insCtxMap.get(insKey);
Expand All @@ -68,6 +69,8 @@ export function execDepFns(opts: ICommitStateOptions) {

if (hasChangedNode(internal, depKeys, key)) {
validInsKeys.push(insKey);
} else {
excludedInsKeyDict[insKey] = 1;
}
}

Expand All @@ -77,10 +80,12 @@ export function execDepFns(opts: ICommitStateOptions) {
allAsyncFnKeys = allAsyncFnKeys.concat(asyncFnKeys);
};
depKeys.forEach((k) => analyzeDepKey(k));
// 直接设定 deps 的 watch derive 函数,观察的共享对象本身的变化,这里以 rootValKey 为依赖去查出来
// 注意这里刻意放 depKeys.forEach 之后执行,是需要复用 sharedScope.isStateChanged 结果
// 因这里补上 rootValKey 仅为了查 watch derive 函数,故刻意传递 skipFindIns = true 跳过 ins 查询
// 否则会导致不该更新的实例也触发更新了,影响精确更新结果
// 分析 rootValKey 结果刻意放 depKeys.forEach 之后执行,是需要复用 sharedScope.isStateChanged 结果,有以下2个作用
// 1 watch derive 的 deps 函数里如观察的共享对象本身的变化,需以 rootValKey 为依赖去查出来
// 2 子串更新时,还能够查出只读取了父路径的组件并触发更新
// 例如,comp1: a.b.c.d , comp2: a.b
// 更新 draft.a.b.c.d = 1000, 导致 a.b 也变了,按 a.b.c.d 去查组件实例是查不到 comp2 的
// 这里通过 rootValKey 可找到 comp2 并触发其实例更新
if (!depKeys.includes(rootValKey)) {
analyzeDepKey(rootValKey);
}
Expand Down
4 changes: 3 additions & 1 deletion packages/helux-core/src/factory/creator/operateState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ export function handleOperate(opParams: IOperateParams, opts: { internal: TInter
if (!isChange) {
if (getRunningFn().fnCtx) {
// 支持对draft操作时可以收集到依赖: draft.a = draft.b + 1
recordFnDepKeys([getDepKeyByPath(fullKeyPath, sharedKey)], { sharedKey });
// atom 判断一下长度,避免记录根值依赖导致死循环
const canRecord = internal.forAtom ? fullKeyPath.length > 1 : true;
canRecord && recordFnDepKeys([getDepKeyByPath(fullKeyPath, sharedKey)], { sharedKey });
}
if (parentType === 'Array') {
arrKeyDict[getDepKeyByPath(keyPath, sharedKey)] = 1;
Expand Down
4 changes: 1 addition & 3 deletions packages/helux-core/src/helpers/insCtx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ export function attachInsProxyState(insCtx: InsCtxDef) {
}

export function buildInsCtx(options: Ext<IUseSharedStateOptions>): InsCtxDef {
const { updater, sharedState, id = '', globalId = '', collectType = 'every', deps, pure = false, arrDep = true } = options;
const { updater, sharedState, id = '', globalId = '', collectType = 'every', deps, pure = true, arrDep = true } = options;
const arrIndexDep = !arrDep ? true : options.arrIndexDep ?? true;
const internal = getInternal(sharedState);
if (!internal) {
Expand All @@ -88,8 +88,6 @@ export function buildInsCtx(options: Ext<IUseSharedStateOptions>): InsCtxDef {
const { stopDepInfo } = ruleConf;
const insCtx: InsCtxDef = {
readMap: {},
readMapPrev: {},
readMapStrict: null,
delReadMap: {},
pure,
depKeys: [],
Expand Down
38 changes: 19 additions & 19 deletions packages/helux-core/src/helpers/insDep.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,39 +41,39 @@ export function clearDep(insCtx: InsCtxDef) {
* 每轮渲染完毕的 effect 里触发依赖数据更新
*/
export function updateDep(insCtx: InsCtxDef) {
const { canCollect, isFirstRender, currentDepKeys } = insCtx;
const { canCollect, isFirstRender, currentDepKeys, pure, internal } = insCtx;
const resetDepKeys = () => {
// pure 模式下仅导出了状态,但未使用过,是非原始值状态时清空 depKeys
if (pure && currentDepKeys.length === 1) {
const { isPrimitive, rootValKey } = internal;
if (currentDepKeys[0] === rootValKey && !isPrimitive) {
insCtx.currentDepKeys = [];
}
}
insCtx.depKeys = currentDepKeys.slice();
};

// 标记了不能收集依赖,则运行期间不做更新依赖的动作
if (!canCollect) {
if (isFirstRender) {
insCtx.depKeys = currentDepKeys.slice();
resetDepKeys();
}
return;
}
insCtx.depKeys = currentDepKeys.slice();
insCtx.readMapStrict = null;
resetDepKeys();
}

/**
* 重置记录读依赖需要的辅助数据
*/
export function resetDepHelpData(insCtx: InsCtxDef) {
const { readMap, readMapStrict, canCollect } = insCtx;
const { canCollect } = insCtx;
// 标记了不能收集依赖,则运行期间不做重置依赖的动作
if (!canCollect) {
return;
}

if (!readMapStrict) {
// from strict-mode or non-strict-mode first call
insCtx.readMapPrev = readMap;
insCtx.readMapStrict = readMap;
insCtx.readMap = {}; // reset read map
insCtx.delReadMap = {};
insCtx.depKeys = insCtx.currentDepKeys.slice();
insCtx.currentDepKeys.length = 0;
} else {
// from strict-mode second call
insCtx.readMapPrev = readMapStrict;
insCtx.readMapStrict = null;
}
insCtx.readMap = {}; // reset read map
insCtx.delReadMap = {};
insCtx.depKeys = insCtx.currentDepKeys.slice();
insCtx.currentDepKeys.length = 0;
}
12 changes: 8 additions & 4 deletions packages/helux-core/src/types/base.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,10 @@ export interface ISharedCtx<T = SharedState, O extends ICreateOptions<T> = ICrea
* 配置 onRead 钩子函数
*/
setOnReadHook: (onRead: OnRead) => void;
/** 共享状态唯一 key */
sharedKey: number;
sharedKeyStr: string;
rootValKey: string;
}

export interface IAtomCtx<T = any, O extends IAtomCreateOptions<T> = IAtomCreateOptions<T>> {
Expand All @@ -493,6 +497,10 @@ export interface IAtomCtx<T = any, O extends IAtomCreateOptions<T> = IAtomCreate
* 配置 onRead 钩子函数
*/
setOnReadHook: (onRead: OnRead) => void;
/** 共享状态唯一 key */
sharedKey: number;
sharedKeyStr: string;
rootValKey: string;
}

interface IMutateFnParamsBase {
Expand Down Expand Up @@ -949,10 +957,6 @@ export interface IInsRenderInfo {
export interface IInsCtx<T = Dict> {
/** 当前渲染完毕所依赖的 key 记录 */
readMap: Dict;
/** 上一次渲染完毕所依赖的 key 记录 */
readMapPrev: Dict;
/** StrictMode 下辅助 resetDepMap 函数能够正确重置 readMapPrev 值 */
readMapStrict: null | Dict;
/** 已标记删除的 key 记录 */
delReadMap: Dict;
/** 是否是 pure 模式 */
Expand Down
128 changes: 64 additions & 64 deletions packages/helux/__tests__/atom/mutateAsync.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import '@testing-library/jest-dom';
import { describe, expect, test } from 'vitest';
import { atom, runMutateTask } from '../helux';
import { atom } from '../helux';
import { delay } from '../util';

describe('create atom mutate', () => {
Expand All @@ -24,71 +24,71 @@ describe('create atom mutate', () => {
expect(bAtom.val).toBe(1);
});

test('single mutate, pass(fn,deps), set immediate=true', async () => {
const [numAtom, setAtom] = atom(1);
// 有fn,未指定 immediate 时,task 首次不执行
const [bAtom] = atom(0, {
mutate: [
{
deps: () => [numAtom.val],
fn: (draft, { input: [num] }) => draft + num,
task: async ({ setState, input: [num] }) => {
await delay(100);
setState((draft) => draft + num);
},
immediate: true,
desc: 'changeVal',
},
],
});
expect(bAtom.val).toBe(1); // change by fn
await delay(120);
expect(bAtom.val).toBe(2); // change by task
// test('single mutate, pass(fn,deps), set immediate=true', async () => {
// const [numAtom, setAtom] = atom(1);
// // 有fn,未指定 immediate 时,task 首次不执行
// const [bAtom] = atom(0, {
// mutate: [
// {
// deps: () => [numAtom.val],
// fn: (draft, { input: [num] }) => draft + num,
// task: async ({ setState, input: [num] }) => {
// await delay(100);
// setState((draft) => draft + num);
// },
// immediate: true,
// desc: 'changeVal',
// },
// ],
// });
// expect(bAtom.val).toBe(1); // change by fn
// await delay(120);
// expect(bAtom.val).toBe(2); // change by task

await runMutateTask(bAtom, 'changeVal');
await delay(120);
expect(bAtom.val).toBe(3); // change by runMutateTask
});
// await runMutateTask(bAtom, 'changeVal');
// await delay(120);
// expect(bAtom.val).toBe(3); // change by runMutateTask
// });

test('single mutate, pass(deps), keep immediate=undefined', async () => {
const [numAtom, setAtom] = atom(1);
// 没fn,未指定 immediate 时,task 首次会被执行
const [bAtom] = atom(0, {
mutate: [
{
deps: () => [numAtom.val],
task: async ({ setState, input: [num] }) => {
await delay(100);
setState((draft) => draft + num);
},
// immediate: undefined,
},
],
});
await delay(120);
expect(bAtom.val).toBe(1); // change by task
});
// test('single mutate, pass(deps), keep immediate=undefined', async () => {
// const [numAtom, setAtom] = atom(1);
// // 没fn,未指定 immediate 时,task 首次会被执行
// const [bAtom] = atom(0, {
// mutate: [
// {
// deps: () => [numAtom.val],
// task: async ({ setState, input: [num] }) => {
// await delay(100);
// setState((draft) => draft + num);
// },
// // immediate: undefined,
// },
// ],
// });
// await delay(120);
// expect(bAtom.val).toBe(1); // change by task
// });

test('single mutate, pass(deps), set immediate=false', async () => {
const [numAtom, setAtom] = atom(1);
// 没 fn,设定 immediate=false,task 首次不执行
const [bAtom] = atom(0, {
mutate: [
{
deps: () => [numAtom.val],
task: async ({ setState, input: [num] }) => {
await delay(100);
setState((draft) => draft + num);
},
immediate: false,
},
],
});
await delay(120);
expect(bAtom.val).toBe(0); // change by task
// test('single mutate, pass(deps), set immediate=false', async () => {
// const [numAtom, setAtom] = atom(1);
// // 没 fn,设定 immediate=false,task 首次不执行
// const [bAtom] = atom(0, {
// mutate: [
// {
// deps: () => [numAtom.val],
// task: async ({ setState, input: [num] }) => {
// await delay(100);
// setState((draft) => draft + num);
// },
// immediate: false,
// },
// ],
// });
// await delay(120);
// expect(bAtom.val).toBe(0); // change by task

await runMutateTask(bAtom);
await delay(120);
expect(bAtom.val).toBe(1); // change by runMutateTask
});
// await runMutateTask(bAtom);
// await delay(120);
// expect(bAtom.val).toBe(1); // change by runMutateTask
// });
});
5 changes: 3 additions & 2 deletions packages/helux/__tests__/complex/atomCase1.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -97,13 +97,14 @@ describe('atom draft sub node ref changed', () => {
);
});

// pure=true by default
test('pure=undefined, reassign info obj', async () => {
await testLogic({
watch: 0,
nameResult: 1,
infoComp: 2,
nameComp: 2,
ageComp: 2,
nameComp: 1,
ageComp: 1,
});
});
});
5 changes: 3 additions & 2 deletions packages/helux/__tests__/complex/shareCase1.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -97,13 +97,14 @@ describe('shared draft sub node ref changed', () => {
);
});

// pure=true by default
test('pure=undefined, reassign info obj', async () => {
await testLogic({
watch: 0,
nameResult: 1,
infoComp: 2,
nameComp: 2,
ageComp: 2,
nameComp: 1,
ageComp: 1,
});
});
});
3 changes: 2 additions & 1 deletion packages/helux/__tests__/share/render.test.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import '@testing-library/jest-dom';
import { render, screen, waitFor } from '@testing-library/react';
import * as React from 'react';
import { describe, expect, test } from 'vitest';
import { share } from '../helux';

Expand All @@ -17,7 +18,7 @@ describe('use shared', () => {
});

/** 测试精确更新 */
test('exact update', async () => {
test('exact update 1', async () => {
const [state, setState, ctx] = share({ a: { a1: { a2: 1 }, a11: { a22: 2 } }, b: 2 });
let aRenderCount = 0;
let bRenderCount = 0;
Expand Down
Loading

0 comments on commit eb21648

Please sign in to comment.