Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Doc #138

Merged
merged 4 commits into from
Jan 16, 2024
Merged

Doc #138

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,16 @@

click helux-core [change log](./packages/helux-core/CHANGELOG.md) to see more details

[released] - 2024-01-16

- fix issue [136](https://github.com/heluxjs/helux/issues/136)

- fix issue [125](https://github.com/heluxjs/helux/issues/125)

- fix issue [137](https://github.com/heluxjs/helux/issues/125) 新增`fnScope.delPathAoa`来记录需要移除的依赖项,让 mutate 回调的 draft 读依赖记录更精确,避免误判为死循环

- 优化`defineFullDerive`类型,自动推导出`params.state`累心

[released] - 2023-11-24

- 接入 `vitest`
Expand Down
13 changes: 7 additions & 6 deletions docs/docs/playground/demos/Playground/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ import Console from './Console';
import * as codes from './codes';
import './index.less';

function getCode(name: string, subName: string) {
const codeDict: any = codes;
return codeDict[name]?.[subName] || '';
}

const scope = { helux, React, ...helux };
const subNames: Record<string, string> = {
atom: 'primitive',
Expand All @@ -19,14 +24,10 @@ const cachedSubNames: Record<string, string> = {};
const obj = qs.parse(window.location.search, { ignoreQueryPrefix: true });
const name = obj.n || 'atom';
const subName = obj.s || 'primitive';

function getCode(name: string, subName: string) {
const codeDict: any = codes;
return codeDict[name]?.[subName] || '';
}
const code = getCode(name, subName);

export default () => {
const [info, setInfo] = React.useState({ name, subName, code: getCode('atom', 'primitive'), });
const [info, setInfo] = React.useState({ name, subName, code });
const changeCode = (name: string) => {
const subName = cachedSubNames[name] || subNames[name] || 'primitive';
setInfo({ name, subName, code: getCode(name, subName) });
Expand Down
2 changes: 1 addition & 1 deletion docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
"animated-scroll-to": "^2.3.0",
"classnames": "^2.5.0",
"console-feed": "^3.5.0",
"helux": "4.1.6",
"helux": "4.2.0",
"lodash": "^4.17.21",
"lodash.throttle": "^4.1.1",
"prism-react-renderer": "^2.3.1",
Expand Down
13 changes: 13 additions & 0 deletions packages/helux-core/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
# @helux/core

## 4.2.0

### Minor Changes

- e63fbea: build(4.2.0): see change log 2024-01-16

### Patch Changes

- Updated dependencies [e63fbea]
- @helux/hooks-impl@4.2.0
- @helux/types@4.2.0
- @helux/utils@4.2.0

## 4.1.6

### Patch Changes
Expand Down
2 changes: 1 addition & 1 deletion packages/helux-core/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@helux/core",
"version": "4.1.6",
"version": "4.2.0",
"description": "A reactive atomic state engine for React like.",
"bugs": {
"url": "https://github.com/heluxjs/helux/issues"
Expand Down
2 changes: 1 addition & 1 deletion packages/helux-core/src/consts/user.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { VER as limuVer } from 'limu';

export const VER = '4.1.6';
export const VER = '4.2.0';

export const LIMU_VER = limuVer;

Expand Down
4 changes: 2 additions & 2 deletions packages/helux-core/src/factory/common/fnScope.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,9 +105,9 @@ export function getFnCtxByObj<T = Dict>(obj: T) {
}

export function getRunningFn() {
const { runningFnKey, depKeys, runningSharedKey, isIgnore } = getFnScope();
const { runningFnKey, depKeys, runningSharedKey, isIgnore, delPathAoa } = getFnScope();
const fnCtx = !runningFnKey ? null : getFnCtx(runningFnKey);
return { fnCtx, depKeys, isIgnore, runningSharedKey };
return { fnCtx, depKeys, delPathAoa, isIgnore, runningSharedKey };
}

export function hasRunningFn() {
Expand Down
21 changes: 16 additions & 5 deletions packages/helux-core/src/factory/creator/notify.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,13 @@ export function execDepFns(opts: ICommitOpts) {
FN_DEP_KEYS.set(depKeys);
}

const findDirtyFnKeys = (key: string, forSharedKey = false) => {
// 通过根值去查询 fnCtx,内部再根据自己的依赖比较后得出需要执行的函数
const { firstLevelFnKeys, asyncFnKeys } = getDepFnStats(internal, key, runCountStats, forSharedKey);
dirtyFnKeys = dirtyFnKeys.concat(firstLevelFnKeys);
dirtyAsyncFnKeys = dirtyAsyncFnKeys.concat(asyncFnKeys);
};

const analyzeDepKey = (key: string) => {
// 值相等就忽略
if (!diffVal(internal, key)) {
Expand Down Expand Up @@ -72,11 +79,8 @@ export function execDepFns(opts: ICommitOpts) {
validInsKeys.push(insKey);
}
}

dirtyInsKeys = dirtyInsKeys.concat(validInsKeys);
const { firstLevelFnKeys, asyncFnKeys } = getDepFnStats(internal, key, runCountStats);
dirtyFnKeys = dirtyFnKeys.concat(firstLevelFnKeys);
dirtyAsyncFnKeys = dirtyAsyncFnKeys.concat(asyncFnKeys);
findDirtyFnKeys(key);
};
depKeys.forEach((k) => analyzeDepKey(k));
// 分析 rootValKey 结果刻意放 depKeys.forEach 之后执行,是需要复用 sharedScope.isStateChanged 结果,有以下2个作用
Expand All @@ -88,6 +92,9 @@ export function execDepFns(opts: ICommitOpts) {
if (!depKeys.includes(rootValKey)) {
analyzeDepKey(rootValKey);
}
// fix issue 136
findDirtyFnKeys(rootValKey, true);

// clear cached diff result
clearDiff();
// find id's ins keys
Expand All @@ -106,7 +113,11 @@ export function execDepFns(opts: ICommitOpts) {
// start mark async derive fn computing
dirtyAsyncFnKeys.forEach((fnKey) => markComputing(fnKey, runCountStats[fnKey]));
// start execute derive/watch fns
dirtyFnKeys.forEach((fnKey) => runFn(fnKey, { depKeys, sn, from, triggerReasons, internal, desc, isFirstCall, fromFnKey }));
const watchFnKeys: string[] = [];
const runOptions = { depKeys, sn, from, triggerReasons, watchFnKeys, skipWatch: true, internal, desc, isFirstCall, fromFnKey };
dirtyFnKeys.forEach((fnKey) => runFn(fnKey, runOptions));
const runOptionsOfWatch = { depKeys, sn, from, triggerReasons, internal, desc, isFirstCall, fromFnKey };
watchFnKeys.forEach((fnKey) => runFn(fnKey, runOptionsOfWatch));

// start trigger rerender
dirtyInsKeys.forEach((insKey) => updateIns(insCtxMap, insKey, sn));
Expand Down
8 changes: 7 additions & 1 deletion packages/helux-core/src/factory/creator/operateState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ function putId(keyIds: KeyIdsDict, options: { writeKey: string; ids: NumStrSymbo
export function handleOperate(opParams: IOperateParams, opts: { internal: TInternal; mutateCtx: IMutateCtx }) {
const { isChanged, fullKeyPath, keyPath, parentType, value } = opParams;
const { internal, mutateCtx } = opts;
const { arrKeyDict, isReactive, readKeys } = mutateCtx;
const { arrKeyDict, isReactive, readKeys, from } = mutateCtx;
const { sharedKey } = internal;
const arrLike = isArrLike(parentType);
const currReactive = REACTIVE_META.current();
Expand Down Expand Up @@ -75,6 +75,12 @@ export function handleOperate(opParams: IOperateParams, opts: { internal: TInter
return;
}

// 来自于 mutate 回调里的 draft 读写
if (MUTATE === from) {
const { delPathAoa, fnCtx } = getRunningFn();
fnCtx && delPathAoa.push(keyPath);
}

// 无任何变化的写操作
if (!isChanged) {
return;
Expand Down
11 changes: 11 additions & 0 deletions packages/helux-core/src/factory/root.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,22 @@ function buildFnScope() {
isIgnore: false,
/** 函数运行结束收集到的读依赖 depKeys */
depKeys: [] as string[],
/**
* del path array of array
* 需要移除的 depKeys,解决 mutate 回调里 draft 里深层次读取修改的依赖收集不正确问题
* ```
* // 这里 get 收集到了 a,这个 a 需要移除,否则会造成死循环依赖误判
* draft.a.val = state.someKey + 1;
* ```
*/
delPathAoa: [] as string[][],
/** globalId to Array<insKey> */
GID_INSKEYS_MAP: new Map<NumStrSymbol, number[]>(),
FNKEY_STATIC_CTX_MAP: new Map<string, IFnCtx>(),
FNKEY_HOOK_CTX_MAP: new Map<string, IFnCtx>(),
DEPKEY_FNKEYS_MAP: new Map<string, string[]>(),
/** sharedKeyStr to fnKeys */
SKEY_FNKEYS_MAP: new Map<string, string[]>(),
UNMOUNT_INFO_MAP: new Map<string, IUnmountInfo>(),
/** 记录第一次运行的各个函数,辅助推导出计算状态 */
DEPKEY_COMPUTING_FNKEYS_MAP: new Map<string, string[]>(),
Expand Down
15 changes: 13 additions & 2 deletions packages/helux-core/src/helpers/fnCtx.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { includeOne, matchDictKey, nodupPush } from '@helux/utils';
import { delListItem, includeOne, matchDictKey, nodupPush } from '@helux/utils';
import { newFnCtx } from '../factory/common/ctor';
import { getCtxMap, getFnCtx, getFnKey, markFnKey } from '../factory/common/fnScope';
import { getFnScope } from '../factory/common/speedup';
import { getDepKeyByPath } from '../factory/common/util';
import type { Dict, Fn, IFnCtx, ScopeType } from '../types/base';
import { delFnDep, delHistoryUnmoutFnCtx } from './fnDep';

Expand Down Expand Up @@ -29,7 +30,7 @@ export function markFnEnd() {
const fnCtx = getFnCtx(runningFnKey);
let targetKeys: string[] = [];
if (fnCtx) {
const { depKeys: afterRunDepKeys } = fnScope;
const { depKeys: afterRunDepKeys, delPathAoa, runningSharedKey } = fnScope;
const { depKeys } = fnCtx;

const dict: Dict<number> = {};
Expand All @@ -44,11 +45,21 @@ export function markFnEnd() {
});
const validDepKeys = Object.keys(dict);
validDepKeys.forEach((depKey) => nodupPush(depKeys, depKey));

// 移除认为是冗余的依赖
delPathAoa.forEach((pathArr) => {
const len = pathArr.length;
for (let i = 1; i <= len; i++) {
const toDel = getDepKeyByPath(pathArr.slice(0, i), runningSharedKey);
delListItem(depKeys, toDel);
}
});
targetKeys = depKeys.slice(); // 返回收集到依赖,辅助判断死循环之用
}

fnScope.runningFnKey = '';
fnScope.depKeys = [];
fnScope.delPathAoa = [];
fnScope.runningSharedKey = 0;
return targetKeys;
}
Expand Down
23 changes: 16 additions & 7 deletions packages/helux-core/src/helpers/fnDep.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { nodupPush, safeMapGet } from '@helux/utils';
import { EXPIRE_MS, NOT_MOUNT, PROTO_KEY, SIZE_LIMIT, UNMOUNT } from '../consts';
import { DERIVE, EXPIRE_MS, NOT_MOUNT, PROTO_KEY, SIZE_LIMIT, UNMOUNT } from '../consts';
import { delFnDepData, getFnCtx, getRunningFn, opUpstreamFnKey } from '../factory/common/fnScope';
import { hasChangedNode } from '../factory/common/sharedScope';
import { getFnScope } from '../factory/common/speedup';
Expand All @@ -24,7 +24,7 @@ export function recordFnDepKeys(inputDepKeys: string[], options: { sharedKey?: n
DEPS_CB.current()(inputDepKeys);
return;
}
const { DEPKEY_FNKEYS_MAP } = getFnScope();
const { DEPKEY_FNKEYS_MAP, SKEY_FNKEYS_MAP } = getFnScope();
const { belongCtx, sharedKey } = options;

if (sharedKey) {
Expand Down Expand Up @@ -55,6 +55,9 @@ export function recordFnDepKeys(inputDepKeys: string[], options: { sharedKey?: n

const fnKeys = safeMapGet(DEPKEY_FNKEYS_MAP, depKey, []);
nodupPush(fnKeys, fnKey);
const [sKey] = depKey.split('/');
const fnKeysOfSkey = safeMapGet(SKEY_FNKEYS_MAP, sKey, []);
nodupPush(fnKeysOfSkey, fnKey);
});
}

Expand Down Expand Up @@ -99,9 +102,10 @@ export function getDepSharedStateFeature(fn: IFnCtx) {
/**
* 获得依赖的第一层函数、异步函数
*/
export function getDepFnStats(internal: TInternal, depKey: string, runCountStats: Dict<number>) {
const { DEPKEY_FNKEYS_MAP } = getFnScope();
const fnKeys = DEPKEY_FNKEYS_MAP.get(depKey) || [];
export function getDepFnStats(internal: TInternal, depKey: string, runCountStats: Dict<number>, isSharedKey = false) {
const { DEPKEY_FNKEYS_MAP, SKEY_FNKEYS_MAP } = getFnScope();
const map = isSharedKey ? SKEY_FNKEYS_MAP : DEPKEY_FNKEYS_MAP;
const fnKeys = map.get(depKey) || [];
const firstLevelFnKeys: string[] = [];
const asyncFnKeys: string[] = [];

Expand All @@ -113,11 +117,16 @@ export function getDepFnStats(internal: TInternal, depKey: string, runCountStats
if (fnCtx.isFirstLevel) {
firstLevelFnKeys.push(fnKey);
}
if (fnCtx.isAsync) {
if (fnCtx.isAsync && fnCtx.fnType === DERIVE) {
asyncFnKeys.push(fnKey);
}

const count = runCountStats[fnKey]; // 每个函数将要运行的次数统计
runCountStats[fnKey] = count === undefined ? 1 : count + 1;
if (count === undefined) {
runCountStats[fnKey] = 1;
} else if (!isSharedKey) {
runCountStats[fnKey] = count + 1;
}
}
});

Expand Down
13 changes: 11 additions & 2 deletions packages/helux-core/src/helpers/fnRunner.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { enureReturnArr, isPromise, noopVoid, tryAlert } from '@helux/utils';
import { enureReturnArr, isPromise, nodupPush, noopVoid, tryAlert } from '@helux/utils';
import { ASYNC_TYPE, FROM, WATCH } from '../consts';
import { fakeFnCtx, fakeInternal } from '../factory/common/fake';
import { delComputingFnKey, getFnCtx, getFnCtxByObj, putComputingFnKey } from '../factory/common/fnScope';
Expand All @@ -20,6 +20,8 @@ interface IRunFnOpt {
force?: boolean;
isFirstCall?: boolean;
triggerReasons?: TriggerReason[];
watchFnKeys?: string[];
skipWatch?: boolean;
err?: any;
internal?: TInternal;
desc?: any;
Expand Down Expand Up @@ -131,6 +133,8 @@ export function runFn(fnKey: string, options: IRunFnOpt = {}) {
forceTask = false,
throwErr = false,
triggerReasons = [],
watchFnKeys = [],
skipWatch = false,
sn = 0,
err,
unbox = false,
Expand All @@ -146,6 +150,10 @@ export function runFn(fnKey: string, options: IRunFnOpt = {}) {
return resultTuple(new Error(`not a valid watch or derive cb for key ${fnKey}`));
}
if (fnCtx.fnType === WATCH) {
// watch 需要合并后再外部独立执行
if (skipWatch) {
return nodupPush(watchFnKeys, fnCtx.fnKey);
}
return runWatch(fnCtx, options);
}

Expand Down Expand Up @@ -184,8 +192,9 @@ export function runFn(fnKey: string, options: IRunFnOpt = {}) {
fnCtx.setLoading(false, err);
}
triggerUpdate();
const runOptions = { isFirstCall, sn, triggerReasons, err, watchFnKeys, skipWatch };
fnCtx.nextLevelFnKeys.forEach((key) => {
runFn(key, { isFirstCall, sn, triggerReasons, err });
runFn(key, runOptions);
});
};

Expand Down
2 changes: 1 addition & 1 deletion packages/helux-core/src/hooks/useLocalForceUpdate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { CoreApiCtx } from '../types/api-ctx';
/**
* 强制更新当前组件
*/
export function useLocalForceUpdate<T = any>(apiCtx: CoreApiCtx): () => void {
export function useLocalForceUpdate(apiCtx: CoreApiCtx): () => void {
const updater = apiCtx.hookImpl.useForceUpdate();
return updater;
}
4 changes: 2 additions & 2 deletions packages/helux-core/src/types/api.d.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
|------------------------------------------------------------------------------------------------
| helux-core@4.1.6
| helux-core@4.2.0
| A state library core that integrates atom, signal, collection dep, derive and watch,
| it supports all react like frameworks ( including react 18 ).
|------------------------------------------------------------------------------------------------
Expand Down Expand Up @@ -68,7 +68,7 @@ import type {
} from './base';

export declare const cst: {
VER: '4.1.6';
VER: '4.2.0';
LIMU_VER: string;
EVENT_NAME: {
/** 共享状态创建时的事件 */
Expand Down
7 changes: 5 additions & 2 deletions packages/helux-core/src/types/base.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -768,10 +768,13 @@ type DefineMutateDerive<T extends SharedState = SharedState> = <I = SharedDict>(
type DefineFullDerive<T extends SharedState = SharedState> = <DR extends DepsResultDict | undefined = undefined>(
throwErr?: boolean,
) => <
D extends /**
/**
* 如果透传了 DR 约束返回结果类型和 deps 返回类型,则使用 DR 来约束
* 加上 & Dict 是为了支持用户配置 DR 之外的其他结果,不严格要求所有结果 key 都需要在 DR 里定义类型
*/ DR extends DepsResultDict ? MultiDeriveFn<DR> & Dict<DeriveFn | IDeriveFnItem> : Dict<DeriveFn | IDeriveFnItem>,
*/
D extends DR extends DepsResultDict
? MultiDeriveFn<DR> & Dict<DeriveFn<any, any, T> | IDeriveFnItem<any, any, T>>
: Dict<DeriveFn<any, any, T> | IDeriveFnItem<any, any, T>>,
>(
deriveFnDict: D | ((boundStateInfo: IBoundStateInfo<T>) => D),
) => {
Expand Down
2 changes: 1 addition & 1 deletion packages/helux-demo-utils/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
"dependencies": {
"@types/react": ">=16.0.0",
"@types/react-dom": ">=16.0.0",
"helux": "^4.1.6",
"helux": "^4.2.0",
"react": ">=16.10.2",
"react-dom": ">=16.10.2"
},
Expand Down
Loading