diff --git a/packages/reactive/src/__tests__/async.computed.dynamic.test.ts b/packages/reactive/src/__tests__/async.computed.dynamic.test.ts index 2e4f374..8c8d617 100644 --- a/packages/reactive/src/__tests__/async.computed.dynamic.test.ts +++ b/packages/reactive/src/__tests__/async.computed.dynamic.test.ts @@ -53,7 +53,7 @@ describe("动态创建计算属性",()=>{ }) store.setState(state=>{ - state.user.firstName = "zhang100" + //state.user.firstName = "zhang100" }) }) diff --git a/packages/reactive/src/__tests__/computed.sync.test.ts b/packages/reactive/src/__tests__/computed.sync.test.ts new file mode 100644 index 0000000..dc9143d --- /dev/null +++ b/packages/reactive/src/__tests__/computed.sync.test.ts @@ -0,0 +1,72 @@ +import { test,expect, describe, beforeAll } from "vitest" +import { createStore,ComputedScopeRef,computed, IStore } from ".." + + +const Account = { + order:{ + price:2, + count:3, + total:computed((scope)=>{ + return scope.price * scope.count + },{ + id:"total", + }) + } +} + + +describe("基本同步计算",()=>{ + + test("默认同步计算",async ()=>{ + const store = createStore({ + price:2, + count:3, + total:computed((scope)=>{ + return scope.price * scope.count + }) + }) + store.setState((draft)=>draft.count = 4) + expect(store.state.total).toBe(8) + }) +}) + +describe("Scope指向",()=>{ + + test("默认Scope指向Current=order",()=>{ + return new Promise((resolve)=>{ + const store = createStore({ + order:{ + price:2, + count:3, + total:computed((scope)=>{ + expect(scope.price).toBe(2) + expect(scope.count).toBe(3) + resolve() + }) + } + }) + store.state.order.total // 读取操作时创建计算属性 + }) + + }) + test("Scope指向Root",()=>{ + return new Promise((resolve)=>{ + const store = createStore({ + order:{ + price:2, + count:3, + total:computed((scope)=>{ + expect(scope.order.price).toBe(2) + expect(scope.order.count).toBe(3) + resolve() + return scope.order.price * scope.order.count + }) + } + },{ + computedScope:()=>ComputedScopeRef.Root + }) + + store.state.order.total // 读取操作时创建计算属性 + }) + }) +}) \ No newline at end of file diff --git a/packages/reactive/src/computed/async.ts b/packages/reactive/src/computed/async.ts index d086776..5b5647f 100644 --- a/packages/reactive/src/computed/async.ts +++ b/packages/reactive/src/computed/async.ts @@ -16,24 +16,12 @@ import { delay } from 'flex-tools/async/delay'; import { OBJECT_PATH_DELIMITER } from '../consts'; import { getComputedContextDraft, getComputedScopeDraft } from '../context'; import { AsyncComputedGetter, AsyncComputedObject, ComputedOptions, ComputedParams, ComputedProgressbar } from './types'; -import type { ComputedDescriptor } from './types'; +import type { ComputedDescriptor, ComputedRunContext } from './types'; import { IReactiveReadHookParams } from '../reactives/types'; import { ComputedObject } from './computedObject'; import { executeStoreHooks } from './utils'; - -// -export type AsyncComputedRunContext = { - id : string - name : string - valuePath : string[], - isMutateRunning: boolean - deps : (string | string[])[] // 所依赖的项的路径 - resultPath : string[] - getter : AsyncComputedGetter, - values : any[] // 依赖值,即发生变化的项的值 -} - + /** * 创建异步计算属性的数据结构 * @@ -101,7 +89,7 @@ export function setAsyncComputedObject(stateCtx:any,draft:any,resultPath:string[ * @param scopeDraft * @param options */ -async function executeComputedGetter(draft:any,computedRunContext:AsyncComputedRunContext,computedOptions:ComputedOptions,store:IStore){ +async function executeComputedGetter(draft:any,computedRunContext:ComputedRunContext,computedOptions:ComputedOptions,store:IStore){ const { valuePath,getter,resultPath } = computedRunContext; const { timeout=0,retry=[0,0],selfState } = computedOptions @@ -205,7 +193,7 @@ async function executeComputedGetter(draft:any,computedRu -function createComputed(computedRunContext:AsyncComputedRunContext,computedOptions:ComputedOptions,store:IStore){ +function createComputed(computedRunContext:ComputedRunContext,computedOptions:ComputedOptions,store:IStore){ const { valuePath, id:mutateId,deps,name:mutateName,resultPath,isMutateRunning,getter } = computedRunContext const { toComputedResult,selfState,initial,noReentry } = computedOptions @@ -243,7 +231,7 @@ function createComputed(computedRunContext:AsyncComputedR return } computedRunContext.isMutateRunning=true - computedRunContext.values = values // 即所依赖项的值 + computedRunContext.dependValues = values // 即所依赖项的值 try{ return await executeComputedGetter(draft,computedRunContext,finalComputedOptions,store) }finally{ @@ -325,12 +313,12 @@ export function createAsyncComputedMutate(computedParams store.options.log(`Create async computed: ${mutateName} (depends=${deps.length==0 ? 'None' : joinValuePath(deps)})`); // 7. 创建mutate - const computedRunContext:AsyncComputedRunContext = { + const computedRunContext:ComputedRunContext = { id : computedOptions.id || getComputedId(valuePath,computedOptions.id), name : selfState ? mutateId : valuePath.join(OBJECT_PATH_DELIMITER), resultPath : computedResultPath, isMutateRunning: false, - values : [], + dependValues : [], valuePath, deps, getter diff --git a/packages/reactive/src/computed/sync.ts b/packages/reactive/src/computed/sync.ts index c13539a..8640a5d 100644 --- a/packages/reactive/src/computed/sync.ts +++ b/packages/reactive/src/computed/sync.ts @@ -2,24 +2,16 @@ * 同步计算 */ import { StoreDefine } from "../store/types"; -import { getVal, setVal } from "../utils"; -import { ComputedDescriptorParams, ComputedGetter, ComputedOptions, RuntimeComputedOptions } from './types'; +import { getComputedId, getVal, setVal } from "../utils"; +import { ComputedDescriptorParams, ComputedGetter, ComputedOptions, ComputedRunContext, RuntimeComputedOptions } from './types'; import { getComputedContextDraft, getComputedScopeDraft } from '../context'; import { IStore } from '../store/types'; import { IReactiveReadHookParams } from "../reactives/types"; import { ComputedObject } from "./computedObject"; -import { executeStoreHooks, getMutateId } from "./utils"; - -export type ComputedRunContext = { - id : string - name : string - valuePath : string[], - isMutateRunning: boolean - deps : (string | string[])[] - resultPath : string[] - getter : ComputedGetter, - dependValues : any[] -} +import { executeStoreHooks, getComputedTargetPath, getMutateId } from "./utils"; +import { OBJECT_PATH_DELIMITER } from "../consts"; + + function createComputed(computedRunContext:ComputedRunContext,computedOptions:ComputedOptions,store:IStore){ @@ -27,13 +19,15 @@ function createComputed(computedRunContext:ComputedRunCon const { selfState } = computedOptions store.reactiveable.createComputed({ - onComputed:({draft,input})=>{ + onComputed:({draft,values})=>{ if(!computedOptions.enable){ store.options.log(`Sync computed <${mutateName}> is disabled`,'warn') return } store.options.log(`Run sync computed for : ${mutateName}`); - + + computedRunContext.dependValues = values + // 1. 根据配置参数获取计算函数的上下文对象 const thisDraft = selfState ? draft : getComputedContextDraft(store,draft,computedRunContext, computedOptions) const scopeDraft = selfState ? draft : getComputedScopeDraft(store,draft,computedRunContext, computedOptions) @@ -41,7 +35,7 @@ function createComputed(computedRunContext:ComputedRunCon // 2. 执行getter函数 let computedResult = computedOptions.initial; try { - computedResult = getter.call(thisDraft,scopeDraft); + computedResult = (getter as ComputedGetter).call(thisDraft,scopeDraft); } catch (e: any) {// 如果执行计算函数出错,则调用 if (typeof computedOptions.onError === "function") { try { @@ -79,13 +73,15 @@ export function createComputedMutate(computedParams:IRea } // 2. 获取到计算属性描述信息: 包括getter和配置。 此时value是一个函数 - let { fn: getter, options: computedOptions } = value() as ComputedDescriptorParams + let { getter, options: computedOptions } = value() as ComputedDescriptorParams // 2. 运行Hook: 当创建计算属性前时运行hook,本Hook的目的是允许重新指定computedThis或者重新包装原始计算函数 // 3.运行Hook: 用来在创建computed前运行,允许拦截更改计算函数的依赖,上下文,以及getter等 // 运行hook会修改计算配置,所以在hook运行后再读取配置 executeStoreHooks(valuePath,getter,store,computedOptions) + const { selfState } = computedOptions + const computedResultPath:string[] = getComputedTargetPath(computedParams,computedOptions) // 3. 参数解析: @@ -93,23 +89,38 @@ export function createComputedMutate(computedParams:IRea store.options.log(`Create sync computed: ${mutateName}`); + const computedRunContext:ComputedRunContext = { + id : computedOptions.id || getComputedId(valuePath,computedOptions.id), + name : selfState ? mutateId : valuePath.join(OBJECT_PATH_DELIMITER), + resultPath : computedResultPath, + isMutateRunning: false, + dependValues : [], + valuePath, + deps : [], + getter + } + + createComputed(computedRunContext,computedOptions,store) + // 移花接木原地替换 - if(!isExternal) computedParams.replaceValue(getVal(store.stateCtx.state, valuePath)); + if(!selfState) computedParams.replaceValue(getVal(store.state, valuePath)); // 5. 创建计算对象实例 - const computedObject = { - id:mutateName, - mutate, - group:computedOptions.group, - async:false, - options:computedOptions, - get enable(){return computedOptions.enable as boolean}, - set enable(val:boolean){computedOptions.enable=val}, - run:(options?:RuntimeComputedOptions)=>{ - const params = {desc:mutateId,extraArgs:options} - return isExternal ? computedTo.stateCtx.runMutateTask(params) : store.stateCtx.runMutateTask(params) - } - } + // const computedObject = { + // id:mutateName, + // mutate, + // group:computedOptions.group, + // async:false, + // options:computedOptions, + // get enable(){return computedOptions.enable as boolean}, + // set enable(val:boolean){computedOptions.enable=val}, + // run:(options?:RuntimeComputedOptions)=>{ + // const params = {desc:mutateId,extraArgs:options} + // return isExternal ? computedTo.stateCtx.runMutateTask(params) : store.stateCtx.runMutateTask(params) + // } + // } + const computedObject = new ComputedObject(store,selfState,computedOptions) + store.computedObjects.set(mutateName,computedObject) return computedObject } \ No newline at end of file diff --git a/packages/reactive/src/computed/types.ts b/packages/reactive/src/computed/types.ts index ae29f79..e97f951 100644 --- a/packages/reactive/src/computed/types.ts +++ b/packages/reactive/src/computed/types.ts @@ -226,7 +226,7 @@ export type AsyncComputed = (...args: any) => Promise; // 异步计算 // export type ComputedDescriptor = StateValueDescriptor<(scope:any) => Promise | R,ComputedOptions> export interface StateValueDescriptorParams { - fn: Fn + getter: Fn options:Options } @@ -252,3 +252,14 @@ export type ComputedTarget ={ } +// 执行计算函数时的上下文 +export type ComputedRunContext = { + id : string + name : string + valuePath : string[], + isMutateRunning: boolean + deps : (string | string[])[] + resultPath : string[] + getter : ComputedGetter | AsyncComputedGetter, + dependValues : any[] +} \ No newline at end of file diff --git a/packages/reactive/src/computed/utils.ts b/packages/reactive/src/computed/utils.ts index 5528059..6e8c6db 100644 --- a/packages/reactive/src/computed/utils.ts +++ b/packages/reactive/src/computed/utils.ts @@ -1,4 +1,6 @@ +import { switchValue } from "flex-tools/misc/switchValue"; import { OBJECT_PATH_DELIMITER } from "../consts"; +import { IReactiveReadHookParams } from "../reactives/types"; import { ComputedOptions, ComputedScopeRef, Dict, IStore } from "../types"; import { getComputedId } from "../utils"; @@ -26,4 +28,33 @@ export function getMutateId(valuePath:string[],computedOptions:ComputedOptions){ const mutateId = computedOptions.id || getComputedId(valuePath,computedOptions.id) const mutateName =computedOptions.selfState ? mutateId : valuePath.join(OBJECT_PATH_DELIMITER) return [ mutateId, mutateName ] -} \ No newline at end of file +} + + +/** + * + * 返回计算属性的目标路径 + * + * 即计算结果要写到目标state中的哪一个位置 + * + * 计算目标 + * + * @param computedParams + * @param computedOptions + * @returns + */ +export function getComputedTargetPath(computedParams:IReactiveReadHookParams,computedOptions:ComputedOptions){ + const { path:valuePath } = computedParams; + const {selfState,toComputedResult='self' } = computedOptions + + // 如果指定了selfState,即计算结果要写到外部状态中 + return selfState ? [valuePath ] : switchValue(toComputedResult,{ + self : valuePath, + root : [], + parent : valuePath.slice(0,valuePath.length-2), + current: valuePath.slice(0,valuePath.length-1), + Array : toComputedResult, // 指定一个数组,表示完整路径 + String : [...valuePath.slice(0,valuePath.length-1),String(toComputedResult).split(OBJECT_PATH_DELIMITER)], + },{defaultValue:valuePath}) + +} diff --git a/packages/reactive/src/context.ts b/packages/reactive/src/context.ts index 3fdb2ac..590526d 100644 --- a/packages/reactive/src/context.ts +++ b/packages/reactive/src/context.ts @@ -24,8 +24,7 @@ import { IOperateParams } from "helux"; import { OBJECT_PATH_DELIMITER } from "./consts"; import { type ComputedScope, ComputedScopeRef, StoreOptions, StoreDefine, IStore } from "./store/types"; import { getValueByPath } from "./utils"; -import { ComputedOptions, IComputeParams, StateComputedType } from "./computed/types"; -import { AsyncComputedRunContext } from "./computed/async"; +import { ComputedOptions, ComputedRunContext, StateComputedType } from "./computed/types"; /* * 计算函数的context可以在全局Store中通过computedThis参数指定 @@ -50,7 +49,7 @@ function getContextOptions(state: any,computedCtxOption?: ComputedScope,storeCtx export type GetComputedContextOptions ={ type:'context' | 'scope', // 要获取的是什么: context或scope computedType:StateComputedType, // 取值, 'Computed' | 'Watch - values:any[], // 当前计算函数依赖值,或watch的侦听的值 + dependValues:any[], // 当前计算函数依赖值,或watch的侦听的值 valuePath:string[], funcOptions: { // computed或者watch的配置参数 context?:any, @@ -68,11 +67,11 @@ export type GetComputedContextOptions ={ */ export function getComputedContext(draft: any,params:GetComputedContextOptions) { - const { values:depends, type, valuePath, funcOptions, storeOptions,computedType } = params; + const { dependValues, type, valuePath, funcOptions, storeOptions,computedType } = params; let rootDraft = draft; - // 1. 执行hook,允许可以修改计算函数的根上下文以及相关配置参数 + // 1. 执行hook:可以在hook函数中修改计算函数的根上下文以及相关配置参数 if (typeof storeOptions.onComputedContext == "function") { const newDraft = storeOptions.onComputedContext.call(draft,draft,{computedType,contextType:type,valuePath}); if (newDraft !== undefined) { @@ -96,7 +95,7 @@ export function getComputedContext(draft: a }else if (contexRef === ComputedScopeRef.Root) { return rootDraft; }else if (contexRef === ComputedScopeRef.Depends) { // 异步计算的依赖值 - return Array.isArray(depends) ? depends.map(dep=>typeof(dep)=='function' ? dep() : dep) : []; + return Array.isArray(dependValues) ? dependValues.map(dep=>typeof(dep)=='function' ? dep() : dep) : []; }else if (typeof contexRef == "string") { // 当前对象的指定键 return getValueByPath(draft, [...parentPath, ...contexRef.split(OBJECT_PATH_DELIMITER)]); }else if (Array.isArray(contexRef)) { // 从根对象开始的完整路径 @@ -126,10 +125,10 @@ export function getComputedContext(draft: a * @param params * @returns */ -export function getComputedRefDraft(store:IStore,draft: any,computedRunContext:AsyncComputedRunContext,computedOptions: ComputedOptions,type:'context' | 'scope') { - const { valuePath,dependValues } = computedRunContext +export function getComputedRefDraft(store:IStore,draft: any,computedRunContext:ComputedRunContext,computedOptions: ComputedOptions,type:'context' | 'scope') { + const { valuePath,dependValues:values } = computedRunContext return getComputedContext(draft,{ - input:dependValues, + dependValues: values, type, valuePath, funcOptions:computedOptions, @@ -139,9 +138,9 @@ export function getComputedRefDraft(store:IStore,draft } -export function getComputedScopeDraft(store:IStore,draft: any,computedRunContext:AsyncComputedRunContext,computedOptions: ComputedOptions) { +export function getComputedScopeDraft(store:IStore,draft: any,computedRunContext:ComputedRunContext,computedOptions: ComputedOptions) { return getComputedRefDraft(store,draft,computedRunContext,computedOptions,'scope') } -export function getComputedContextDraft(store:IStore,draft: any,computedRunContext:AsyncComputedRunContext,computedOptions: ComputedOptions) { +export function getComputedContextDraft(store:IStore,draft: any,computedRunContext:ComputedRunContext,computedOptions: ComputedOptions) { return getComputedRefDraft(store,draft,computedRunContext,computedOptions,'context') } \ No newline at end of file diff --git a/packages/reactive/src/extends.ts b/packages/reactive/src/extends.ts index b28d3ed..bb88339 100644 --- a/packages/reactive/src/extends.ts +++ b/packages/reactive/src/extends.ts @@ -8,7 +8,7 @@ */ import type { StoreDefine, IStore } from "./store/types"; -import { isSkipComputed } from "./utils"; +import { isSkipComputed, joinValuePath } from "./utils"; import { IComputeParams, installComputed } from "./computed"; import { installWatch } from "./watch"; import { IReactiveReadHookParams } from "./reactives/types"; @@ -36,7 +36,7 @@ export function installExtends(computedParams:IReactiveRe // - 为计算函数创建mutate // - 将原始属性替换为计算属性值或异步对象 const { path, value } = computedParams; - const key = path.join("."); + const key = joinValuePath(path); if ( typeof value === "function" && !store._replacedKeys[key] && !isSkipComputed(value) ) { store._replacedKeys[key] = true; if(value.__COMPUTED__=='watch'){ diff --git a/packages/reactive/src/reactives/helux.ts b/packages/reactive/src/reactives/helux.ts index 84ff2b7..c1869a4 100644 --- a/packages/reactive/src/reactives/helux.ts +++ b/packages/reactive/src/reactives/helux.ts @@ -12,12 +12,17 @@ export class HeluxReactiveable extends Reactiveable{ stopArrDep: false, moduleName:options.id ?? getRndId(), onRead:(params)=>{ - options.onRead(params as any) + options.onRead({ + path:params.fullKeyPath, + value:params.value, + parent:params.parent, + replaceValue:params.replaceValue + }) } }) } get state(){ - return this._stateCtx.state as ComputedState + return this._stateCtx.reactive as ComputedState } /** * const [ state ] = useState() @@ -93,9 +98,9 @@ export class HeluxReactiveable extends Reactiveable{ createComputed(params:CreateComputedOptions>):string{ const {onComputed,options} = params this._stateCtx.mutate({ - fn:(draft,params)=>{ + fn:(draft,{input})=>{ if(typeof(onComputed)==='function'){// @ts-ignore - onComputed({draft,setState,values:input}) + onComputed({draft,values:input}) } }, desc: options.id, diff --git a/packages/reactive/src/store/setState.ts b/packages/reactive/src/store/setState.ts index 66d802a..2694104 100644 --- a/packages/reactive/src/store/setState.ts +++ b/packages/reactive/src/store/setState.ts @@ -17,7 +17,7 @@ export function createSetState(store: IStore){ return (updater:(draft:T)=>void)=>{ // @ts-ignore - store.stateCtx.setState((draft:any)=>{ + store.reactiveable.setState((draft:any)=>{ updater(draft as any) }) } diff --git a/packages/reactive/src/store/store.ts b/packages/reactive/src/store/store.ts index 3a17fe3..a9a21ae 100644 --- a/packages/reactive/src/store/store.ts +++ b/packages/reactive/src/store/store.ts @@ -56,19 +56,19 @@ export function createStore(data:T,options? }) as Reactiveable - store.stateCtx = sharex>(data as any, { - stopArrDep: false, - moduleName: opts.id, - onRead: (params) => { - installExtends(params as any,store as IStore); - } - }); - store.state = store.stateCtx.reactive + // store.stateCtx = sharex>(data as any, { + // stopArrDep: false, + // moduleName: opts.id, + // onRead: (params) => { + // installExtends(params as any,store as IStore); + // } + // }); + store.state = store.reactiveable.state store.emit("created") store.useState = createUseState(store) store.setState = createSetState(store) store.enableComputed = (value:boolean=true)=>store.stateCtx.setEnableMutate(value) - store.sync = store.stateCtx.sync + // store.sync = store.stateCtx.sync // 侦听 store.watch = createWatch(store) store.useWatch = createUseWatch(store) diff --git a/packages/reactive/src/utils/joinValuePath.ts b/packages/reactive/src/utils/joinValuePath.ts index e968cdd..9eb8ee0 100644 --- a/packages/reactive/src/utils/joinValuePath.ts +++ b/packages/reactive/src/utils/joinValuePath.ts @@ -5,7 +5,7 @@ * @returns */ export function joinValuePath(paths?:(string | string[])[]):string{ - return (paths||[]).map((p)=>{ + return (paths||['ROOT']).map((p)=>{ return Array.isArray(p) ? p.join(".") : p }).join('_') }