From fd5727b077c3e5826e4dee362c63e1b489875998 Mon Sep 17 00:00:00 2001 From: fantasticsoul Date: Sun, 3 Dec 2023 04:32:01 +0800 Subject: [PATCH] build(3.4.15): del notify redundant excludedInsKeyDict logic --- package.json | 2 +- packages/helux-core/src/consts/index.ts | 2 +- .../helux-core/src/factory/creator/notify.ts | 7 +- .../__tests__/hook-options/_makeArrDepTest.ts | 154 ++++++++++++++++++ .../__tests__/hook-options/arrDep.test.ts | 6 + .../helux/__tests__/hook-options/pure.test.ts | 67 ++++++++ 6 files changed, 231 insertions(+), 7 deletions(-) create mode 100644 packages/helux/__tests__/hook-options/_makeArrDepTest.ts create mode 100644 packages/helux/__tests__/hook-options/arrDep.test.ts create mode 100644 packages/helux/__tests__/hook-options/pure.test.ts diff --git a/package.json b/package.json index 5a6beefd..62cccf89 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "helux", - "version": "3.4.13", + "version": "3.4.15", "description": "A state library core that integrates atom, signal, collection dep, derive and watch, it supports all react like frameworks( including react 18 ).", "keywords": [], "author": { diff --git a/packages/helux-core/src/consts/index.ts b/packages/helux-core/src/consts/index.ts index 4ac9627c..c2cb89c1 100644 --- a/packages/helux-core/src/consts/index.ts +++ b/packages/helux-core/src/consts/index.ts @@ -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.14'; +export const VER = '3.4.15'; export const PROD_FLAG = true; diff --git a/packages/helux-core/src/factory/creator/notify.ts b/packages/helux-core/src/factory/creator/notify.ts index f242f16a..7a75626c 100644 --- a/packages/helux-core/src/factory/creator/notify.ts +++ b/packages/helux-core/src/factory/creator/notify.ts @@ -47,10 +47,9 @@ export function execDepFns(opts: ICommitStateOptions) { const insKeys = key2InsKeys[key] || []; const validInsKeys: number[] = []; - const excludedInsKeyDict: Dict = {}; for (const insKey of insKeys) { // 已包含或已排除,都跳过当次循环 - if (allInsKeys.includes(insKey) || excludedInsKeyDict[insKey]) { + if (allInsKeys.includes(insKey)) { continue; } const insCtx = insCtxMap.get(insKey); @@ -69,8 +68,6 @@ export function execDepFns(opts: ICommitStateOptions) { if (hasChangedNode(internal, depKeys, key)) { validInsKeys.push(insKey); - } else { - excludedInsKeyDict[insKey] = 1; } } @@ -85,7 +82,7 @@ export function execDepFns(opts: ICommitStateOptions) { // 2 子串更新时,还能够查出只读取了父路径的组件并触发更新 // 例如,comp1: a.b.c.d , comp2: a.b // 更新 draft.a.b.c.d = 1000, 导致 a.b 也变了,按 a.b.c.d 去查组件实例是查不到 comp2 的 - // 这里通过 rootValKey 可找到 comp2 并触发其实例更新 + // 这里通过 rootValKey 依靠 depKey&insKey 不对称记录机制可找到 comp2 并触发其实例更新 if (!depKeys.includes(rootValKey)) { analyzeDepKey(rootValKey); } diff --git a/packages/helux/__tests__/hook-options/_makeArrDepTest.ts b/packages/helux/__tests__/hook-options/_makeArrDepTest.ts new file mode 100644 index 00000000..c587a373 --- /dev/null +++ b/packages/helux/__tests__/hook-options/_makeArrDepTest.ts @@ -0,0 +1,154 @@ +import { renderHook } from '@testing-library/react'; +import { describe, expect, test } from 'vitest'; +import { atom, useAtom } from '../helux'; +import { dictFictory, getDepKey, noop } from '../util'; + +interface IOptionsBase { + arrIndexDep?: boolean; + arrDep?: boolean; +} +interface IOptions extends IOptionsBase { + getMatchObj: (rootValKey: string) => any; +} + +export function makeTest(options: { label: string; atom: typeof atom; useAtom: typeof useAtom }) { + const { label, atom, useAtom } = options; + + async function testNotReadList(options: IOptionsBase) { + const { arrIndexDep, arrDep } = options; + const [dictAtom, setDictAtom, { rootValKey }] = atom(dictFictory); + const { result } = renderHook(() => { + const [state, , info] = useAtom(dictAtom, { arrIndexDep, arrDep }); + return info.getDeps(); + }); + + expect(result.current).toMatchObject([rootValKey]); + } + + /** arrIndexDep=true by default */ + async function testReadList(options: IOptions) { + const { arrIndexDep, arrDep, getMatchObj } = options; + const [dictAtom, setDictAtom, { rootValKey }] = atom(dictFictory); + const { result } = renderHook(() => { + const [state, , info] = useAtom(dictAtom, { arrIndexDep, arrDep }); + noop(state.extra.list); + noop(state.extra.list[0]); + noop(state.extra.list[1]); + return info.getDeps(); + }); + + expect(result.current).toMatchObject(getMatchObj(rootValKey)); + } + + const getArrDepAndIndexDep = (rootValKey) => [ + getDepKey(rootValKey, 'extra.list'), + getDepKey(rootValKey, 'extra.list.0'), + getDepKey(rootValKey, 'extra.list.1'), + ]; + + const getArrDep = (rootValKey) => [getDepKey(rootValKey, 'extra.list')]; + + const getIndexDep = (rootValKey) => [getDepKey(rootValKey, 'extra.list.0'), getDepKey(rootValKey, 'extra.list.1')]; + + describe(`${label} arrDep=undefined`, () => { + test('arrIndexDep=undefined, not read list', async () => { + await testNotReadList({ arrDep: undefined, arrIndexDep: undefined }); + }); + + test('arrIndexDep=true, not read list', async () => { + await testNotReadList({ arrDep: undefined, arrIndexDep: true }); + }); + + test('arrIndexDep=false, not read list', async () => { + await testNotReadList({ arrDep: undefined, arrIndexDep: false }); + }); + + test('arrIndexDep=undefined, read list', async () => { + await testReadList({ + arrDep: undefined, + arrIndexDep: undefined, + getMatchObj: getArrDepAndIndexDep, + }); + }); + + test('arrIndexDep=true, read list', async () => { + await testReadList({ + arrDep: undefined, + arrIndexDep: true, + getMatchObj: getArrDepAndIndexDep, + }); + }); + + test('arrIndexDep=false, read list', async () => { + await testReadList({ + arrDep: undefined, + arrIndexDep: false, + getMatchObj: getArrDep, + }); + }); + }); + + describe(`${label} arrDep=true`, () => { + test('arrIndexDep=undefined, not read list', async () => { + await testNotReadList({ arrDep: true, arrIndexDep: undefined }); + }); + + test('arrIndexDep=true, not read list', async () => { + await testNotReadList({ arrDep: true, arrIndexDep: true }); + }); + + test('arrIndexDep=false, not read list', async () => { + await testNotReadList({ arrDep: true, arrIndexDep: false }); + }); + + test('arrIndexDep=undefined, read list', async () => { + await testReadList({ + arrDep: true, + arrIndexDep: undefined, + getMatchObj: getArrDepAndIndexDep, + }); + }); + + test('arrIndexDep=true, read list', async () => { + await testReadList({ + arrDep: true, + arrIndexDep: true, + getMatchObj: getArrDepAndIndexDep, + }); + }); + + test('arrIndexDep=true, read list', async () => { + await testReadList({ + arrDep: true, + arrIndexDep: false, + getMatchObj: getArrDep, + }); + }); + }); + + describe(`${label} arrDep=false`, () => { + test('arrIndexDep=true, read list', async () => { + await testReadList({ + arrDep: false, + arrIndexDep: true, + getMatchObj: getIndexDep, + }); + }); + + test('arrIndexDep=false, read list', async () => { + await testReadList({ + arrDep: false, + arrIndexDep: false, + getMatchObj: getIndexDep, + }); + }); + + test('arrIndexDep=undefined, read list', async () => { + await testReadList({ + arrDep: false, + arrIndexDep: undefined, + getMatchObj: getIndexDep, + }); + }); + }); +} diff --git a/packages/helux/__tests__/hook-options/arrDep.test.ts b/packages/helux/__tests__/hook-options/arrDep.test.ts new file mode 100644 index 00000000..a168e10b --- /dev/null +++ b/packages/helux/__tests__/hook-options/arrDep.test.ts @@ -0,0 +1,6 @@ +import { atom, share, useAtom, useShared } from '../helux'; +import { makeTest } from './_makeArrDepTest'; + +makeTest({ label: 'useAtom', atom, useAtom }); + +makeTest({ label: 'useShared', atom: share as any, useAtom: useShared as any }); diff --git a/packages/helux/__tests__/hook-options/pure.test.ts b/packages/helux/__tests__/hook-options/pure.test.ts new file mode 100644 index 00000000..5a114873 --- /dev/null +++ b/packages/helux/__tests__/hook-options/pure.test.ts @@ -0,0 +1,67 @@ +import { renderHook } from '@testing-library/react'; +import { describe, expect, test } from 'vitest'; +import { atom, useAtom } from '../helux'; +import { dictFictory, getDepKey, noop } from '../util'; + +describe('useAtom options.pure', () => { + test('pure=undefined', async () => { + const [dictAtom, setDictAtom, { rootValKey }] = atom(dictFictory); + + let renderCount = 0; + const { result } = renderHook(() => { + renderCount += 1; + const [state, , info] = useAtom(dictAtom); + return info.getDeps(); + }); + + expect(result.current).toMatchObject([rootValKey]); + expect(renderCount).toBe(1); + + setDictAtom((draft) => { + draft.a.b.c = 100; + }); + expect(renderCount).toBe(1); + }); + + test('pure=undefined, read state.a, update a.b.c', async () => { + const [dictAtom, setDictAtom, { rootValKey }] = atom(dictFictory); + + let renderCount = 0; + const { result } = renderHook(() => { + renderCount += 1; + const [state, , info] = useAtom(dictAtom); + noop(state.a); + return info.getDeps(); + }); + + expect(result.current).toMatchObject([getDepKey(rootValKey, 'a')]); + expect(renderCount).toBe(1); + + setDictAtom((draft) => { + draft.a.b.c = 100; + }); + // 更新 a.b.c ,通过 rootValKey 配合不对称记录机制,能查到依赖 a 的示例并触发更新 + expect(renderCount).toBe(2); + }); + + test('pure=undefined, read state.a.b.c, update a', async () => { + const [dictAtom, setDictAtom, { rootValKey }] = atom(dictFictory); + + let renderCount = 0; + const { result } = renderHook(() => { + renderCount += 1; + const [state, , info] = useAtom(dictAtom); + noop(state.a.b.c); + return info.getDeps(); + }); + + expect(result.current).toMatchObject([getDepKey(rootValKey, 'a.b.c')]); + expect(renderCount).toBe(1); + + setDictAtom((draft) => { + draft.a = { ...draft.a }; + }); + // 更新 a,但 a.b.c 无变化,故实例不会更新 + expect(renderCount).toBe(1); + }); +});