Skip to content

Commit

Permalink
build(3.5.0): expose reactive data
Browse files Browse the repository at this point in the history
  • Loading branch information
fantasticsoul committed Dec 6, 2023
1 parent 6fd4403 commit 33f423d
Show file tree
Hide file tree
Showing 15 changed files with 117 additions and 39 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "helux",
"version": "3.4.26",
"version": "3.5.0",
"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": {
Expand Down
2 changes: 2 additions & 0 deletions packages/helux-core/src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { useGlobalId } from './hooks/useGlobalId';
import { getActionLoading, getMutateLoading, useActionLoading, useMutateLoading } from './hooks/useLoading';
import { useMutable } from './hooks/useMutable';
import { useOnEvent } from './hooks/useOnEvent';
import { useReactive } from './hooks/useReactive';
import { storeSrv, useService } from './hooks/useService';
import { useAtom, useShared } from './hooks/useShared';
import { useWatch } from './hooks/useWatch';
Expand All @@ -43,6 +44,7 @@ export {
// hooks api
useAtom,
useShared,
useReactive,
useDerived,
useDerivedAtom,
useWatch,
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 = '3.4.26';
export const VER = '3.5.0';

export const LIMU_VER = limuVer;

Expand Down
10 changes: 7 additions & 3 deletions packages/helux-core/src/factory/common/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { getVal, isDebug, isFn, isMap, isObj, isProxyAvailable, noop, prefixValK
import { immut, IOperateParams } from 'limu';
import { ARR, KEY_SPLITER, MAP, STATE_TYPE } from '../../consts';
import { createOb } from '../../helpers/obj';
import type { Dict, ISetStateOptions, NumStrSymbol, TriggerReason } from '../../types/base';
import type { Dict, IInnerSetStateOptions, NumStrSymbol, TriggerReason } from '../../types/base';
import { DepKeyInfo } from '../../types/inner';
import type { TInternal } from '../creator/buildInternal';

Expand Down Expand Up @@ -36,6 +36,8 @@ export interface IMutateCtx {
keyPathValue: Map<string[], any>;
/** 为 atom 记录的 draft.val 引用 */
draftVal: any;
enableDraftDep: boolean;
isReactive: boolean;
}

// for hot reload of buildShared
Expand All @@ -55,8 +57,8 @@ export function tryGetLoc(moduleName: string, startCutIdx = 4) {
return loc;
}

export function newMutateCtx(options: ISetStateOptions): IMutateCtx {
const { ids = [], globalIds = [] } = options; // 用户 setState 可能设定了 ids globalIds
export function newMutateCtx(options: IInnerSetStateOptions): IMutateCtx {
const { ids = [], globalIds = [], enableDraftDep = false, isReactive = false } = options; // 用户 setState 可能设定了 ids globalIds
return {
level1Key: '',
depKeys: [],
Expand All @@ -69,6 +71,8 @@ export function newMutateCtx(options: ISetStateOptions): IMutateCtx {
keyPathValue: new Map(),
handleAtomCbReturn: true,
draftVal: null,
enableDraftDep,
isReactive,
};
}

Expand Down
3 changes: 2 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, sharedKey, sharedKeyStr, rootValKey } = internal;
const { syncer, sync, forAtom, setDraft: setState, sharedKey, sharedKeyStr, rootValKey, reactive } = internal;
const { useFn, actionCreator, actionAsyncCreator, mutateCreator, setAtomVal } = getFns(state, forAtom);
const opt = { internal, from: MUTATE, apiCtx };
const ldMutate = initLoadingCtx(createSharedLogic, opt);
Expand Down Expand Up @@ -74,6 +74,7 @@ export function createSharedLogic(innerOptions: IInnerOptions, createOptions?: a
sharedKey,
sharedKeyStr,
rootValKey,
reactive,
};
}

Expand Down
2 changes: 2 additions & 0 deletions packages/helux-core/src/factory/creator/buildInternal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ export function buildInternal(

return {
ver: 0,
// reactive will be replaced in buildReactive process later
reactive: rawState,
// snap and prevSnap will be replaced after changing state
snap: copy,
prevSnap: copy,
Expand Down
7 changes: 7 additions & 0 deletions packages/helux-core/src/factory/creator/current.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,13 @@ export function currentDraftRoot() {
return CURRENT_DRAFT_ROOT;
}

let CURRENT_INS_ON_READ: any = null;

export const INS_ON_READ = {
current: () => CURRENT_INS_ON_READ,
set: (onRead: any) => (CURRENT_INS_ON_READ = onRead),
};

export const INS_CTX = {
current: (rootVal: any) => CURRENT_INS_CTX.get(rootVal),
set: (rootVal: any, insCtx: InsCtxDef) => CURRENT_INS_CTX.set(rootVal, insCtx),
Expand Down
3 changes: 3 additions & 0 deletions packages/helux-core/src/factory/creator/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { Dict, ICreateOptions } from '../../types/base';
import { markFnExpired } from '../common/fnScope';
import { clearInternal } from '../common/internal';
import { emitShareCreated } from '../common/plugin';
import { buildReactive } from './buildReactive';
import { buildSharedState } from './buildShared';
import { clearDcLog } from './deadCycle';
import { mapSharedToInternal } from './mapShared';
Expand All @@ -23,6 +24,8 @@ export function buildSharedObject<T = Dict>(innerOptions: IInnerOptions, createO
watchAndCallMutateDict({ target: sharedState, dict: parsedOptions.mutateFnDict });

const internal = getInternal(sharedState);
// 创建顶层使用的响应式对象
internal.reactive = buildReactive(internal);
clearInternal(parsedOptions.moduleName, internal.loc);
clearDcLog(internal.usefulName);
emitShareCreated(internal);
Expand Down
5 changes: 3 additions & 2 deletions packages/helux-core/src/factory/creator/mutateDeep.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { isJsObj, isObj, noop } from '@helux/utils';
import { createDraft, finishDraft, limuUtils } from 'limu';
import { createDraft, finishDraft, IOperateParams, limuUtils } from 'limu';
import type { Dict, Ext, IInnerSetStateOptions } from '../../types/base';
import { genRenderSN } from '../common/key';
import { runMiddlewares } from '../common/middleware';
Expand Down Expand Up @@ -94,10 +94,11 @@ export function prepareDeepMutate(opts: IPrepareDeepMutateOpts) {
const mutateCtx = newMutateCtx(opts);
const commitOpts = { state: {}, mutateCtx, ...opts, desc };
const draftRoot = createDraft(internal.rawState, {
onOperate(opParams) {
onOperate: (opParams: IOperateParams) => {
handleOperate(opParams, { internal, mutateCtx });
},
});

const { forAtom, isPrimitive } = internal;
// 记录正在执行中的 draftRoot mutateCtx
DRAFT_ROOT.set(draftRoot);
Expand Down
28 changes: 24 additions & 4 deletions packages/helux-core/src/factory/creator/operateState.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import { getVal, matchDictKey, nodupPush } from '@helux/utils';
import { IOperateParams } from 'limu';
import { recordBlockDepKey } from '../../helpers/blockDep';
import { recordFnDepKeys } from '../../helpers/fnDep';
import type { KeyIdsDict, NumStrSymbol } from '../../types/base';
import { recordLastest } from '../common/blockScope';
import { getRunningFn } from '../common/fnScope';
import { cutDepKeyByStop } from '../common/stopDep';
import { getDepKeyByPath, IMutateCtx, isArrLike } from '../common/util';
import type { TInternal } from './buildInternal';
import { flush } from './buildReactive';
import { INS_ON_READ } from './current';

/**
* 如果变化命中了 rules[].ids 或 globaIds 规则,则添加到 mutateCtx.ids 或 globalIds 里
Expand All @@ -26,18 +30,29 @@ function putId(keyIds: KeyIdsDict, options: { writeKey: string; ids: NumStrSymbo
}

export function handleOperate(opParams: IOperateParams, opts: { internal: TInternal; mutateCtx: IMutateCtx }) {
const { isChanged, fullKeyPath, keyPath, parentType } = opParams;
const { isChanged, fullKeyPath, keyPath, parentType, value } = opParams;
const { internal, mutateCtx } = opts;
const { arrKeyDict } = mutateCtx;
const { arrKeyDict, isReactive } = mutateCtx;
const { sharedKey, enableDraftDep } = internal;
const arrLike = isArrLike(parentType);

if (!isChanged) {
if (enableDraftDep && getRunningFn().fnCtx) {
if (enableDraftDep || mutateCtx.enableDraftDep) {
// 支持对draft操作时可以收集到依赖: draft.a = draft.b + 1
// atom 判断一下长度,避免记录根值依赖导致死循环
const canRecord = internal.forAtom ? fullKeyPath.length > 1 : true;
canRecord && recordFnDepKeys([getDepKeyByPath(fullKeyPath, sharedKey)], { sharedKey });
if (canRecord) {
const currentOnRead = INS_ON_READ.current();
// 来自实例响应式对象的定制读行为
if (isReactive && currentOnRead) {
currentOnRead(opParams);
} else {
const depKey = getDepKeyByPath(fullKeyPath, sharedKey);
getRunningFn().fnCtx && recordFnDepKeys([depKey], { sharedKey });
recordBlockDepKey([depKey]);
recordLastest(sharedKey, value, internal.sharedState, depKey, fullKeyPath);
}
}
}
if (arrLike) {
arrKeyDict[getDepKeyByPath(keyPath, sharedKey)] = 1;
Expand Down Expand Up @@ -90,4 +105,9 @@ export function handleOperate(opParams: IOperateParams, opts: { internal: TInter
if (hasGlobalIds) {
putId(ruleConf.globalIdsDict, { ids: globalIds, writeKey, internal, opParams });
}

// 来自响应对象的变更操作,主动 flush 状态
if (isReactive) {
flush(sharedKey);
}
}
48 changes: 32 additions & 16 deletions packages/helux-core/src/helpers/insCtx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ import { genInsKey } from '../factory/common/key';
import { cutDepKeyByStop, recordArrKey } from '../factory/common/stopDep';
import { callOnRead, isArrLike, isArrLikeVal, newOpParams } from '../factory/common/util';
import type { InsCtxDef } from '../factory/creator/buildInternal';
import { buildReactive, flush } from '../factory/creator/buildReactive';
import { mapGlobalId } from '../factory/creator/globalId';
import type { Dict, Ext, IFnCtx, IUseSharedStateOptions } from '../types/base';
import type { Dict, Ext, IFnCtx, IInnerUseSharedOptions, OnOperate } from '../types/base';
import type { DepKeyInfo } from '../types/inner';
import * as fnDep from './fnDep';
import { clearDep } from './insDep';
Expand Down Expand Up @@ -45,21 +46,25 @@ export function runInsUpdater(insCtx: InsCtxDef | undefined) {
}

export function attachInsProxyState(insCtx: InsCtxDef) {
const { internal } = insCtx;
const { internal, isReactive } = insCtx;
const { rawState, isDeep, sharedKey, onRead } = internal;
if (isDeep) {
insCtx.proxyState = immut(rawState, {
onOperate: (opParams) => {
if (opParams.isBuiltInFnKey) return;
const { fullKeyPath, keyPath, parentType } = opParams;
const { rawVal, proxyValue } = callOnRead(opParams, onRead);
const depKey = prefixValKey(fullKeyPath.join(KEY_SPLITER), sharedKey);
const depKeyInfo = { depKey, keyPath: fullKeyPath, parentKeyPath: keyPath, sharedKey };
collectDep(insCtx, depKeyInfo, { parentType, rawVal });
return proxyValue;
},
compareVer: true,
});
const onOperate: OnOperate = (opParams) => {
if (opParams.isBuiltInFnKey) return;
const { fullKeyPath, keyPath, parentType } = opParams;
const { rawVal, proxyValue } = callOnRead(opParams, onRead);
const depKey = prefixValKey(fullKeyPath.join(KEY_SPLITER), sharedKey);
const depKeyInfo = { depKey, keyPath: fullKeyPath, parentKeyPath: keyPath, sharedKey };
collectDep(insCtx, depKeyInfo, { parentType, rawVal });

// 响应式对象会触发到变化行为
if (opParams.isChanged) {
flush(sharedKey);
}
return proxyValue;
};

insCtx.proxyState = isReactive ? buildReactive(internal, onOperate) : immut(rawState, { onOperate, compareVer: true });
} else {
insCtx.proxyState = createOb(rawState, {
set: () => {
Expand All @@ -81,8 +86,18 @@ export function attachInsProxyState(insCtx: InsCtxDef) {
}
}

export function buildInsCtx(options: Ext<IUseSharedStateOptions>): InsCtxDef {
const { updater, sharedState, id = '', globalId = '', collectType = 'every', deps, pure = true, arrDep = true } = options;
export function buildInsCtx(options: Ext<IInnerUseSharedOptions>): InsCtxDef {
const {
updater,
sharedState,
id = '',
globalId = '',
collectType = 'every',
deps,
pure = true,
arrDep = true,
isReactive = false,
} = options;
const arrIndexDep = !arrDep ? true : options.arrIndexDep ?? true;
const internal = getInternal(sharedState);
if (!internal) {
Expand All @@ -100,6 +115,7 @@ export function buildInsCtx(options: Ext<IUseSharedStateOptions>): InsCtxDef {
fixedDepKeys: [],
currentDepKeys: [],
isDeep,
isReactive,
insKey,
internal,
rawState,
Expand Down
15 changes: 9 additions & 6 deletions packages/helux-core/src/hooks/common/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import type { Dict, Fn, IInsRenderInfo } from '../../types/base';
* 记录一些必要的辅助数据,返回 useAtom useShared 需要的元组数据
*/
export function prepareTuple(insCtx: InsCtxDef, forAtom?: boolean): [any, Fn, IInsRenderInfo] {
const { proxyState, internal, renderInfo, canCollect } = insCtx;
const { proxyState, internal, renderInfo, canCollect, isReactive } = insCtx;
const { sharedKey, sharedKeyStr, setDraft } = internal;
renderInfo.snap = internal.snap;
// atom 自动拆箱,注意这里 proxyState.val 已触发记录根值依赖
Expand All @@ -31,7 +31,9 @@ export function prepareTuple(insCtx: InsCtxDef, forAtom?: boolean): [any, Fn, II
insCtx.recordDep({ depKey: sharedKeyStr, keyPath: [], sharedKey }, DICT);
}

return [rootVal, setDraft, renderInfo];
// 提供给 useReactive 使用的响应对象无拆箱行为
const finalRoot = isReactive ? proxyState : rootVal;
return [finalRoot, setDraft, renderInfo];
}

export function checkAtom(mayAtom: any, forAtom?: boolean) {
Expand All @@ -45,11 +47,12 @@ export function checkStateVer(insCtx: InsCtxDef) {
ver,
internal: { ver: dataVer },
} = insCtx;
if (ver !== dataVer) {
// 替换 proxyState,让把共享对象透传给 memo 组件、useEffect deps 的场景也能正常触发重新渲染
insCtx.ver = dataVer;
attachInsProxyState(insCtx);
if (ver === dataVer) {
return;
}
insCtx.ver = dataVer;
// 替换 proxyState,让把共享对象透传给 memo 组件、useEffect deps 的场景也能正常触发重新渲染
attachInsProxyState(insCtx);
}

// recover ins ctx (dep,updater etc...) for double mount behavior under react 18 strict mode
Expand Down
7 changes: 5 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@3.4.26
| helux-core@3.5.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 @@ -38,6 +38,7 @@ import type {
IBlockOptions,
ICreateOptions,
IInsRenderInfo,
InsReactiveState,
IPlugin,
IRenderInfo,
IRunMutateOptions,
Expand Down Expand Up @@ -71,7 +72,7 @@ import type {
WatchOptionsType,
} from './base';

export declare const VER: '3.4.26';
export declare const VER: '3.5.0';

export declare const LIMU_VER: string;

Expand Down Expand Up @@ -237,6 +238,8 @@ export function watch(watchFn: (fnParams: IWatchFnParams) => void, options?: Wat
*/
export function useShared<T = Dict>(sharedObject: T, options?: IUseSharedStateOptions<T>): [SharedDict<T>, SetState<T>, IInsRenderInfo];

export function useReactive<T = SharedState>(sharedState: T, options?: IUseSharedStateOptions<T>): [InsReactiveState<T>, IInsRenderInfo];

/**
* 组件使用 atom,注此接口只接受 atom 生成的对象,如传递 share 生成的对象会报错
* ```ts
Expand Down
Loading

0 comments on commit 33f423d

Please sign in to comment.