From 5a9584afcf432df37fce465a921856ec12ecc1cf Mon Sep 17 00:00:00 2001 From: wxzhang Date: Sat, 24 Aug 2024 22:03:07 +0800 Subject: [PATCH] update --- example/src/forms/network/form.tsx | 10 ++-- example/src/forms/network/index.tsx | 11 +++- packages/core/src/action.tsx | 44 ++++++---------- packages/core/src/form.tsx | 21 ++++++-- packages/core/src/submit.tsx | 27 +++++++++- packages/reactive/src/action.ts | 52 ------------------- packages/reactive/src/computed/async.ts | 23 ++++---- packages/reactive/src/computed/install.ts | 12 ++--- packages/reactive/src/computed/sync.ts | 15 +++--- packages/reactive/src/extends.ts | 6 +-- packages/reactive/src/reactives/helux.ts | 4 +- packages/reactive/src/reactives/types.ts | 2 +- .../reactive/src/{context.ts => scope.ts} | 44 ++++++++-------- packages/reactive/src/store/store.ts | 28 +++++----- packages/reactive/src/store/types.ts | 3 +- packages/reactive/src/utils/getVal.ts | 16 ++++-- packages/reactive/src/utils/isMap.ts | 2 +- packages/reactive/src/watch/install.ts | 2 +- 18 files changed, 156 insertions(+), 166 deletions(-) delete mode 100644 packages/reactive/src/action.ts rename packages/reactive/src/{context.ts => scope.ts} (71%) diff --git a/example/src/forms/network/form.tsx b/example/src/forms/network/form.tsx index a06a8c8..012c9ae 100644 --- a/example/src/forms/network/form.tsx +++ b/example/src/forms/network/form.tsx @@ -194,8 +194,7 @@ const Network = createForm({ enable: (root: any) => { return root.fields.wifi.ssid.value.length > 3 }, - execute: action(async (scope:any,{abortSignal}) => { - console.log(scope) + execute: action(async (_:any,{abortSignal}) => { return new Promise((resolve,reject)=>{ setTimeout(()=>{ resolve(count++) @@ -221,8 +220,10 @@ const Network = createForm({ }, ping: { title: "测试网络连通性", - scope: "wifi", // 表示该动作的上下文是wifi这个子表单 - enable: (wifi: any) => wifi.ssid.value.length > 0, + scope:"fields.wifi", + enable: computed((wifi: any) => { + return wifi.ssid.value.length > 0 + },{scope:"fields.wifi"}), execute: async (a: Dict) => { await delay(2000); console.log(a); @@ -230,6 +231,7 @@ const Network = createForm({ }, // 向导表单:上一步 previous:{ + enable: (wifi: any) => wifi.ssid.value.length > 0, execute:async ()=>{ return 1 diff --git a/example/src/forms/network/index.tsx b/example/src/forms/network/index.tsx index 85d04ac..04d6da3 100644 --- a/example/src/forms/network/index.tsx +++ b/example/src/forms/network/index.tsx @@ -5,7 +5,8 @@ import {Card,JsonViewer, Button,Divider,Field,Input } from "@speedform/demo-com const NetworkForm = ()=>{ - Network.state.fields.wifi.password.value + // @ts-ignore + globalThis.Network = Network return
@@ -112,6 +113,14 @@ const NetworkForm = ()=>{ }} + name="ping" > + {({title,visible,loading,enable,run,error,progress})=>{ + return <> + enable= {String(enable)} + + + }} + name="fields.wifi.cancelableSubmit" > {({title,visible,loading,enable,run,cancel,error,progress})=>{ return <> diff --git a/packages/core/src/action.tsx b/packages/core/src/action.tsx index c86c203..0c6d333 100644 --- a/packages/core/src/action.tsx +++ b/packages/core/src/action.tsx @@ -33,7 +33,7 @@ import { ReactNode, useCallback, useRef, RefObject,useState} from "react"; import React from "react"; -import type { FormDefine, FormStore, RequiredFormOptions } from "./form"; +import type { FormDefine, FormStore } from "./form"; import { AsyncComputedDefine, AsyncComputedGetter, AsyncComputedObject, ComputedDescriptorDefine, ComputedOptions, ComputedParams, Dict, RuntimeComputedOptions, computed, getValueByPath} from '@speedform/reactive'; import { omit } from "flex-tools/object/omit"; import { getFormData } from "./serialize"; @@ -148,6 +148,8 @@ export type ActionProps + * + * * @param this * @param store * @returns @@ -260,10 +265,8 @@ export function createActionComponent(store:FormStore const [state] = store.useState() let { name:actionKey } = props // 如果动作是声明在actions里面可以省略actions前缀 - if(!actionKey.includes(".")) actionKey = `actions.${actionKey}` - - const actionState = getValueByPath(state,actionKey,".") - + if(!actionKey.includes(".")) actionKey = `actions.${actionKey}` + const actionState = getValueByPath(state,actionKey) if(actionState==null){ store.options.log(`Action ${actionKey} is not defined`,"error") return <>{props.children} @@ -273,10 +276,8 @@ export function createActionComponent(store:FormStore const actionCanceller = useActionCanceller(state,actionKey) // 用来引用当前动作 const ref = useRef(null) - // 创建动作组件的Props const actionRenderProps = createActionRenderProps(actionState,actionRunner,actionCanceller,ref) - // 执行渲染动作组件 if(typeof(props.render)==='function'){ return @@ -288,6 +289,8 @@ export function createActionComponent(store:FormStore }else{ return } + }else{ + return <> } } return React.memo(Action,(oldProps:any, newProps:any)=>{ @@ -302,6 +305,7 @@ export function createActionComponent(store:FormStore * 该函数实现以下功能: * - 从store中获取动作的状态数据传递给Action的getter函数 * * + * * @param getter * @param options */ @@ -311,32 +315,16 @@ export function action(getter: AsyncComputedGett // 比如 getSnap(state.fields.xxx.xxx)也是返回整个state的快照 const data = getFormData(Object.assign({},scope)) return await (getter as unknown as AsyncComputedGetter)(data,opts) - },[],Object.assign({},options,{async:true})) + },[ + // 不依赖于任意表单数据,需要手动触发执行 + ],Object.assign({},options,{ + async:true + })) } -export type SubmitAsyncComputedGetter = AsyncComputedGetter -export type SubmitActionOptions = ComputedOptions - -/** - * - * 特殊的对象传入一个FormData对象 - * 声明一个提交动作 - * submit动作总是返回一个FormData对象 - * - * @param getter - * @param options - * @returns - */ -export function submit(getter: SubmitAsyncComputedGetter,options?: SubmitActionOptions){ - return action(async (data:Dict,opts)=>{ - const formData = new FormData() - return await (getter as unknown as SubmitAsyncComputedGetter)(formData,opts) - },options) - -} export type UseActionType = (executor:AsyncComputedGetter,options?:ComputedOptions & {name?:string})=>FormActionState['execute'] diff --git a/packages/core/src/form.tsx b/packages/core/src/form.tsx index aff4d19..c4f1cb4 100644 --- a/packages/core/src/form.tsx +++ b/packages/core/src/form.tsx @@ -227,10 +227,20 @@ function setFormDefault(define:T){ * - immediate=false : 不会自动执行,需要手动调用action.execute.run()来执行 * - 让scope默认指向fields,这样就可以直接使用fields下的字段,而不需要fields前缀 * + * actions:{ + * ping:{ + * scope:"wifi", // 当指定scope时,execute.scope由scope指定 + * execute:computed(async (wifi: any) => { + * // .... + * }) + * } + * } + * */ function createActionHook(valuePath:string[],options:ComputedOptions){ if(valuePath.length>1 && valuePath[valuePath.length-1]=='execute'){ - options.immediate = false // 默认不自动执行,需要手动调用action.execute.run()来执行 + // 默认不自动执行,需要手动调用action.execute.run()来执行 + options.immediate = false // 如果没有指定scope,则默认指向fields,这样就可以直接使用fields下的字段,而不需要fields前缀 if(options.scope){ if(Array.isArray(options.scope)){ @@ -239,8 +249,8 @@ function createActionHook(valuePath:string[],options:ComputedOptions){ options.scope.unshift(FIELDS_STATE_KEY) } } - }else{ - options.scope = [FIELDS_STATE_KEY] + }else{// 如果没有指定scope,则默认指向fields, + options.scope = [FIELDS_STATE_KEY] } options.noReentry = true // 禁止重入 } @@ -313,13 +323,14 @@ export function createForm(schema: State,op createActionHook(valuePath,options) }, onComputedDraft(draft,{computedType,valuePath}){ - // 针对计属性 + // 针对计算属性 // 修改fields下的所有计算函数的作用域根,使之总是指向fields开头 // 这样可以保证在计算函数中,当scope->Root时,总是指向fields,否则就需要state.fields.xxx.xxx if(computedType==='Computed' && valuePath.length >0 && valuePath[0]==FIELDS_STATE_KEY){ return draft.fields } - } + }, + immediate:true // 默认立即执行完成所有计算属性的初始化 }); /** diff --git a/packages/core/src/submit.tsx b/packages/core/src/submit.tsx index d2f661e..722e2b8 100644 --- a/packages/core/src/submit.tsx +++ b/packages/core/src/submit.tsx @@ -9,7 +9,7 @@ * */ import React, { useCallback, useRef } from 'react' -import { Dict, getValueByPath } from "@speedform/reactive"; +import { AsyncComputedGetter, ComputedOptions, Dict, getValueByPath } from "@speedform/reactive"; import type { FormSchemaBase, FormStore, RequiredFormOptions } from "./form"; import { CSSProperties, ReactElement, ReactNode } from "react"; import { isFieldGroup, isFieldList, isFieldValue } from "./utils"; @@ -244,4 +244,27 @@ export const $submit = { console.log("scope=",scope,"options=",options) debugger }) -} \ No newline at end of file +} + + +export type SubmitAsyncComputedGetter = AsyncComputedGetter +export type SubmitActionOptions = ComputedOptions + + +/** + * + * 特殊的对象传入一个FormData对象 + * 声明一个提交动作 + * submit动作总是返回一个FormData对象 + * + * @param getter + * @param options + * @returns + */ +export function submit(getter: SubmitAsyncComputedGetter,options?: SubmitActionOptions){ + return action(async (data:Dict,opts)=>{ + const formData = new FormData() + return await (getter as unknown as SubmitAsyncComputedGetter)(formData,opts) + },options) + +} \ No newline at end of file diff --git a/packages/reactive/src/action.ts b/packages/reactive/src/action.ts deleted file mode 100644 index 0e3479f..0000000 --- a/packages/reactive/src/action.ts +++ /dev/null @@ -1,52 +0,0 @@ -// import type { StoreOptions, Dict, IStore } from "./store/types" -// import { Dict, StateUpdater } from "./types" -// import { isPromise } from "./utils" - - - -// export type ActionDefines = Record | AsyncAction> -// export type Action = (...args:Args)=>StateUpdater -// export type AsyncAction = (...args:Args)=>Promise> -// export type Actions = { -// [key in keyof Fields]: Fields[key] extends (...args:any[])=>Promise ? -// AsyncAction> : ( -// Fields[key] extends (...args:any)=>any ? Action> : never -// ) -// } - - -// /** -// * 创建Action -// * @param actions -// * @param state -// * @param api -// * @returns -// */ -// export function createActions(actions:T['actions'],store:IStore,options?:StoreOptions){ -// return Object.entries(actions||{}).reduce((results:any,[key,action])=>{ -// results[key] = createAction(action as Action ,store.stateCtx.setState) -// return results -// },{}) as Actions -// } - - -// export function createAction(action: Action,setState:any){ -// const updateState = (updater:any)=>{ -// if(typeof(updater)=='function'){ -// setState((draft:any)=>{ -// updater(draft) -// }) -// } -// } -// return (...args:any)=>{ -// const result = action(...args) -// if(isPromise(result)){ -// // @ts-ignore -// return result.then((updater:any)=>{ -// updateState(updater) -// }) -// }else{ -// updateState(result) -// } -// } -// } \ No newline at end of file diff --git a/packages/reactive/src/computed/async.ts b/packages/reactive/src/computed/async.ts index 85d33f5..81b3d4c 100644 --- a/packages/reactive/src/computed/async.ts +++ b/packages/reactive/src/computed/async.ts @@ -12,7 +12,7 @@ import type { IStore } from "../store/types"; import { skipComputed, joinValuePath, getError, getDepValues,getVal, setVal, getComputedId } from "../utils"; import { delay } from 'flex-tools/async/delay'; import { OBJECT_PATH_DELIMITER } from '../consts'; -import { getComputedScope } from '../context'; +import { getComputedScope } from '../scope'; import { AsyncComputedObject, ComputedOptions, ComputedParams, ComputedProgressbar } from './types'; import type { ComputedDescriptorDefine, ComputedRunContext } from './types'; import { IReactiveReadHookParams } from '../reactives/types'; @@ -74,7 +74,6 @@ async function executeComputedGetter(draft:any,computedRunContex const { timeout=0,retry=[0,0],selfReactiveable } = computedOptions const setState = selfReactiveable ? selfReactiveable.setState.bind(selfReactiveable) : store.setState // - const thisDraft = draft const scopeDraft = getComputedScope(store,computedOptions,{draft,dependValues,valuePath,computedType:"Computed"} ) const [retryCount,retryInterval] = Array.isArray(retry) ? retry : [Number(retry),0] @@ -135,7 +134,7 @@ async function executeComputedGetter(draft:any,computedRunContex } } // 执行计算函数 - computedResult = await getter.call(thisDraft, scopeDraft,computedParams); + computedResult = await getter.call(store, scopeDraft,computedParams); if(hasAbort) throw new Error("Abort") if(!hasTimeout){ Object.assign(afterUpdated,{result:computedResult,error:null,timeout:0}) @@ -179,7 +178,7 @@ async function executeComputedGetter(draft:any,computedRunContex -function createComputed(computedRunContext:ComputedRunContext,computedOptions:ComputedOptions,store:IStore){ +function createComputed(computedRunContext:ComputedRunContext,computedOptions:ComputedOptions,store:IStore){ const { valuePath, id:computedId,deps,desc:computedDesc } = computedRunContext const { selfReactiveable,initial,noReentry } = computedOptions @@ -218,7 +217,7 @@ function createComputed(computedRunContext:ComputedRunContext,co computedRunContext.isComputedRunning=true computedRunContext.dependValues = values // 即所依赖项的值 try{ - const r= await executeComputedGetter(draft,computedRunContext,finalComputedOptions,store) + const r= await executeComputedGetter(draft,computedRunContext,finalComputedOptions,store) return r }finally{ computedRunContext.isComputedRunning=false @@ -234,14 +233,14 @@ function createComputed(computedRunContext:ComputedRunContext,co * @param stateCtx * @param params */ -export function createAsyncComputedMutate(computedParams:IReactiveReadHookParams,store:IStore) :ComputedObject> { +export function createAsyncComputedMutate(computedParams:IReactiveReadHookParams,store:IStore) :ComputedObject> | undefined { // 1. 参数检查 const { path:valuePath, parent ,value } = computedParams; // // 排除掉所有非own属性,例如valueOf等 - // if (parent && !Object.hasOwn(parent, valuePath[valuePath.length - 1])) { - // return; - // } + if (parent && !Object.hasOwn(parent, valuePath[valuePath.length - 1])) { + return; + } // 2. 获取到计算属性描述信息: 包括getter和配置。 此时value是一个函数 let { getter, options: computedOptions } = value() as ComputedDescriptorDefine @@ -264,7 +263,7 @@ export function createAsyncComputedMutate(computedParams: // 计算对象的id和name,name用于打印日志时提供更多信息 const computedId = getComputedId(valuePath,computedOptions) computedOptions.id = computedId - const computedDesc = `${computedId}_${valuePath.join(OBJECT_PATH_DELIMITER)}` + const computedDesc = valuePath.join(OBJECT_PATH_DELIMITER) store.options.log(`Create async computed: ${computedDesc} (depends=${depends.length==0 ? 'None' : joinValuePath(depends)})`); @@ -279,13 +278,13 @@ export function createAsyncComputedMutate(computedParams: deps : depends, getter } - createComputed(computedRunContext,computedOptions,store) + createComputed(computedRunContext,computedOptions,store) // 移花接木原地替换 if(!selfReactiveable) computedParams.replaceValue(getVal(store.state, valuePath)); // 8. 创建计算对象实例 - const computedObject = new ComputedObject>(store,selfReactiveable,valuePath,computedOptions) + const computedObject = new ComputedObject>(store,selfReactiveable,valuePath,computedOptions) if(computedOptions.save) store.computedObjects.set(computedId,computedObject) return computedObject } diff --git a/packages/reactive/src/computed/install.ts b/packages/reactive/src/computed/install.ts index 5df4651..df219e0 100644 --- a/packages/reactive/src/computed/install.ts +++ b/packages/reactive/src/computed/install.ts @@ -13,15 +13,15 @@ import { Dict } from "../types"; */ -export function installComputed(params:IReactiveReadHookParams,store:IStore):ComputedObject | ComputedObject> { +export function installComputed(params:IReactiveReadHookParams,store:IStore):ComputedObject | ComputedObject> | undefined { const descriptor = params.value - let computedObject:ComputedObject | ComputedObject> + let computedObject:ComputedObject | ComputedObject> |undefined //@ts-ignore if (descriptor.__COMPUTED__=='async') { - computedObject = createAsyncComputedMutate(params,store); + computedObject = createAsyncComputedMutate(params,store); //@ts-ignore }else if (descriptor.__COMPUTED__=='sync') { - computedObject = createComputedMutate(params,store); + computedObject = createComputedMutate(params,store); }else if (isAsyncFunction(descriptor)) { // 简单的异步计算函数,没有通过computed函数创建,此时由于没有指定依赖,所以只会执行一次 params.value = () => ({ getter: descriptor, @@ -32,7 +32,7 @@ export function installComputed(params:IReactiveReadHookPa enable : true, }, }); - computedObject = createAsyncComputedMutate(params,store); + computedObject = createAsyncComputedMutate(params,store); }else { // 简单的同步计算函数,没有通过computed函数创建 params.value = () => ({ getter: descriptor, @@ -42,7 +42,7 @@ export function installComputed(params:IReactiveReadHookPa } }) // 直接声明同步计算函数,使用全局配置的计算上下文 - computedObject = createComputedMutate(params,store); + computedObject = createComputedMutate(params,store); } // 当创建计算完毕后的回调 if(computedObject){ diff --git a/packages/reactive/src/computed/sync.ts b/packages/reactive/src/computed/sync.ts index e7d84b2..284fdb1 100644 --- a/packages/reactive/src/computed/sync.ts +++ b/packages/reactive/src/computed/sync.ts @@ -3,7 +3,7 @@ */ import { getComputedId, getVal, setVal } from "../utils"; import { ComputedDescriptorParams, ComputedGetter, ComputedOptions, ComputedRunContext } from './types'; -import { getComputedScope } from '../context'; +import { getComputedScope } from '../scope'; import { IStore } from '../store/types'; import { IReactiveReadHookParams } from "../reactives/types"; import { ComputedObject } from "./computedObject"; @@ -38,7 +38,7 @@ function createComputed(computedRunContext:ComputedRunContext,co // 2. 执行getter函数 let computedResult = computedOptions.initial; try { - computedResult = (getter as ComputedGetter).call(thisDraft,scopeDraft); + computedResult = (getter as ComputedGetter).call(store,scopeDraft); store.emit("computed:done",{ path:valuePath,id: computedId,value:computedResult}) } catch (e: any) {// 如果执行计算函数出错,则调用 store.emit("computed:error", { path:valuePath,id: computedId, error: e }) @@ -64,13 +64,13 @@ function createComputed(computedRunContext:ComputedRunContext,co * @param computedParams */ -export function createComputedMutate(computedParams:IReactiveReadHookParams,store:IStore) :ComputedObject { +export function createComputedMutate(computedParams:IReactiveReadHookParams,store:IStore) :ComputedObject | undefined { // 1. 获取计算属性的描述 const {path:valuePath, parent,value } = computedParams; - // if (parent && !Object.hasOwn(parent, valuePath[valuePath.length - 1])) { - // return; // 排除掉所有非own属性,例如valueOf等 - // } + if (parent && !Object.hasOwn(parent, valuePath[valuePath.length - 1])) { + return; // 排除掉所有非own属性,例如valueOf等 + } // 2. 获取到计算属性描述信息: 包括getter和配置。 此时value是一个函数 let { getter, options: computedOptions } = value() as ComputedDescriptorParams @@ -89,7 +89,6 @@ export function createComputedMutate(computedParams:IReac const computedId = getComputedId(valuePath,computedOptions) const computedDesc = valuePath.join(OBJECT_PATH_DELIMITER) - store.options.log(`Create sync computed: ${computedDesc}`); const computedRunContext:ComputedRunContext = { @@ -109,7 +108,7 @@ export function createComputedMutate(computedParams:IReac if(!selfReactiveable) computedParams.replaceValue(getVal(store.state, valuePath)); // 5. 创建计算对象实例 - const computedObject = new ComputedObject(store,selfReactiveable,valuePath,computedOptions) + const computedObject = new ComputedObject(store,selfReactiveable,valuePath,computedOptions) if(computedOptions.save) store.computedObjects.set(computedId,computedObject) return computedObject } \ No newline at end of file diff --git a/packages/reactive/src/extends.ts b/packages/reactive/src/extends.ts index 1d7c9a7..6d320b4 100644 --- a/packages/reactive/src/extends.ts +++ b/packages/reactive/src/extends.ts @@ -32,16 +32,16 @@ import { Dict } from "./types"; // } -export function installExtends(computedParams:IReactiveReadHookParams,store:IStore) { +export function installExtends(computedParams:IReactiveReadHookParams,store:IStore) { // 拦截读取state的操作,在第一次读取时, const { path, value } = computedParams; const key = joinValuePath(path); if ( typeof value === "function" && !store._replacedKeys[key] && !isSkipComputed(value) ) { store._replacedKeys[key] = true; if(value.__COMPUTED__=='watch'){ - installWatch(computedParams,store) + installWatch(computedParams,store) }else{ - installComputed(computedParams,store) + installComputed(computedParams,store) } } } diff --git a/packages/reactive/src/reactives/helux.ts b/packages/reactive/src/reactives/helux.ts index 78f7e35..bcf8650 100644 --- a/packages/reactive/src/reactives/helux.ts +++ b/packages/reactive/src/reactives/helux.ts @@ -125,12 +125,12 @@ export class HeluxReactiveable extends Reactiveable void, depends?: (string | string[])[] | undefined) { + createWatch(listener: (changedPaths: string[][]) => void, depends?: (string | string[])[] | (()=>any)) { // @ts-ignore const { unwatch } = watch(({triggerReasons})=>{ const valuePaths:string[][] = triggerReasons.map((reason:any)=>reason.keyPath) listener(valuePaths) - },()=>{ + },typeof(depends)==='function' ? depends : ()=>{ return depends?.length==0 ? [this._stateCtx.state] : depends?.map(dep=>getValueByPath( this._stateCtx.state,dep)) }) return unwatch diff --git a/packages/reactive/src/reactives/types.ts b/packages/reactive/src/reactives/types.ts index a3f8676..4232ff0 100644 --- a/packages/reactive/src/reactives/types.ts +++ b/packages/reactive/src/reactives/types.ts @@ -105,7 +105,7 @@ export class Reactiveable{ * @param depends 依赖的字段,为空时监听所有的状态变化 * @returns 返回取消监听的方法 */ - createWatch(listener:(changedPaths:string[][])=>void,depends?:(string | string[])[]):()=>void{ + createWatch(listener:(changedPaths:string[][])=>void,depends?:(string | string[])[] | (()=>any)):()=>void{ throw new Error("createWatch not implemented.") } diff --git a/packages/reactive/src/context.ts b/packages/reactive/src/scope.ts similarity index 71% rename from packages/reactive/src/context.ts rename to packages/reactive/src/scope.ts index 104fa3f..fa566da 100644 --- a/packages/reactive/src/context.ts +++ b/packages/reactive/src/scope.ts @@ -38,7 +38,7 @@ import { Dict } from "./types"; * @param computedThis * @param storeCtxOption */ -function getScopeOptions(state: any,computedScope?: ComputedScope,storeScope?: ComputedScope) { +function getScopeOptions(state: any,valuePath:string[],computedScope?: ComputedScope,storeScope?: ComputedScope) { let scope = computedScope == undefined ? storeScope : computedScope; if (typeof scope == "function") { try { scope = scope.call(state, state) } catch { } @@ -70,6 +70,7 @@ export function getComputedScope(store:IStore,computed const { draft,dependValues, valuePath, computedType } = ctx; let rootDraft = draft; + // 1. 执行hook:可以在hook函数中修改计算函数的根上下文以及相关配置参数 if (typeof store.options.onComputedDraft == "function") { @@ -81,34 +82,35 @@ export function getComputedScope(store:IStore,computed const parentPath = valuePath.length>=1 ? valuePath.slice(0, valuePath.length - 1) : []; // 2. 读取计算函数的上下文配置参数 - const contexRef = getScopeOptions(draft,computedOptions.scope, (store.options.scope && store.options.scope(computedType))) + const scopeRef = getScopeOptions(draft,valuePath,computedOptions.scope, (store.options.scope && store.options.scope(computedType,valuePath))) // 3. 根据配置参数获取计算函数的上下文对象 try { - if(contexRef === ComputedScopeRef.Current) { + if(scopeRef === ComputedScopeRef.Current) { return getValueByPath(draft, parentPath); - }else if (contexRef === ComputedScopeRef.Parent) { + }else if (scopeRef === ComputedScopeRef.Parent) { return getValueByPath(draft,valuePath.slice(0, valuePath.length - 2)); - }else if (contexRef === ComputedScopeRef.Root) { + }else if (scopeRef === ComputedScopeRef.Root) { return rootDraft; - }else if (contexRef === ComputedScopeRef.Depends) { // 异步计算的依赖值 + }else if (scopeRef === ComputedScopeRef.Depends) { // 异步计算的依赖值 return Array.isArray(dependValues) ? dependValues.map(dep=>typeof(dep)=='function' ? dep() : dep) : []; - }else if (typeof contexRef == "string") { // 当前对象的指定键 - return getValueByPath(draft, getRelValuePath(valuePath,contexRef)) - }else if (Array.isArray(contexRef)) { // 从根对象开始的完整路径 - if(contexRef.length>0 && contexRef[0].startsWith("@")){ - const finalKeys = getValueByPath(draft, [...contexRef[0].substring(1).split(OBJECT_PATH_DELIMITER),...contexRef.slice(1)]); - return getValueByPath(draft,finalKeys); - }else{ - return getValueByPath(draft, contexRef); - } - }else if (typeof contexRef == "number") { - const endIndex = contexRef > valuePath.length - 2 ? valuePath.length - contexRef - 1 : 0; - return getValueByPath(draft, valuePath.slice(0, endIndex)); - }else { - return draft; + }else{ + if (typeof scopeRef == "string") { + if(scopeRef.startsWith("@")){ + return getComputedScope(store,{...computedOptions,scope:scopeRef.slice(1)},{draft,dependValues,valuePath,computedType}) + }else{ + return getValueByPath(draft, getRelValuePath(valuePath,scopeRef)); + } + }else if (Array.isArray(scopeRef)) { // 从根对象开始的完整路径 + return getValueByPath(draft, scopeRef); + }else if (typeof scopeRef == "number") { + const endIndex = scopeRef > valuePath.length - 2 ? valuePath.length - scopeRef - 1 : 0; + return getValueByPath(draft, valuePath.slice(0, endIndex)); + }else { + return draft; + } } }catch (e) { - return draft; + return draft; } } diff --git a/packages/reactive/src/store/store.ts b/packages/reactive/src/store/store.ts index 9f2bde3..a25ab2c 100644 --- a/packages/reactive/src/store/store.ts +++ b/packages/reactive/src/store/store.ts @@ -14,7 +14,7 @@ import { setEventEmitter } from '../events'; -export function createStore(data:T,options?:Partial>){ +export function createStore(data:State,options?:Partial>){ // 1.初始化配置参数 const opts = Object.assign({ id : getRndId(), @@ -22,7 +22,7 @@ export function createStore(data:T,options?:PartialComputedScopeRef.Current, - },options) as StoreOptions + },options) as StoreOptions opts.log = (...args:any[])=>{ if(opts.debug) (log as any)(...args) @@ -30,42 +30,42 @@ export function createStore(data:T,options?:Partial = { + const store:IStore = { options: opts, _replacedKeys:{} // 用来保存已经替换过的key } setEventEmitter(store) - store.computedObjects = new ComputedObjects(store as IStore), - store.watchObjects = new WatchObjects(store as IStore) + store.computedObjects = new ComputedObjects(store as IStore), + store.watchObjects = new WatchObjects(store as IStore) // 3. 创建响应式对象, 此处使用helux - store.reactiveable = new HeluxReactiveable(data,{ + store.reactiveable = new HeluxReactiveable(data,{ id:opts.id, onRead: (params) => { - installExtends(params,store as IStore); + installExtends(params,store as IStore); } - }) as Reactiveable + }) as Reactiveable store.state = store.reactiveable.state store.emit("created") - store.useState = createUseState(store) - store.setState = createSetState(store) + store.useState = createUseState(store) + store.setState = createSetState(store) store.enableComputed = (value:boolean=true)=>store.options.enableComputed = value store.sync = store.reactiveable.sync.bind(store.reactiveable) // store.sync = store.stateCtx.sync // 侦听 - store.watch = createWatch(store) - store.useWatch = createUseWatch(store) - store.watch = createWatch(store) + store.watch = createWatch(store) + store.useWatch = createUseWatch(store) + store.watch = createWatch(store) // 创建计算对象的函数 // store.computed = createComputed<>(store) // store.useComputed = createComputed<>(store) // 3. 创建计算对象的函数 - store.createComputed = computedObjectCreator(store) + store.createComputed = computedObjectCreator(store) // // @ts-ignore // extendObjects.computedObjects.new = createComputed diff --git a/packages/reactive/src/store/types.ts b/packages/reactive/src/store/types.ts index 459af1e..f4c8936 100644 --- a/packages/reactive/src/store/types.ts +++ b/packages/reactive/src/store/types.ts @@ -43,11 +43,10 @@ export interface StoreOptions{ /** * 计算函数的默认上下文,即传入的给计算函数的draft对象是根state还是所在的对象或父对象 * 如果未指定时,同步计算的上下文指向current,异步指定的上下文指向root - * computedThis:(computedType:StateComputedType)=>ComputedContext * @param computedType * @returns */ - scope:(computedType:StateComputedType)=> ComputedScope + scope:(computedType:StateComputedType,valuePath:string[])=> ComputedScope /** * 提供一个响应式核心 */ diff --git a/packages/reactive/src/utils/getVal.ts b/packages/reactive/src/utils/getVal.ts index 1bff250..208768b 100644 --- a/packages/reactive/src/utils/getVal.ts +++ b/packages/reactive/src/utils/getVal.ts @@ -6,8 +6,18 @@ export function getVal(obj: any, keyPath: string[]): any { let val; let parent = obj; keyPath.forEach((key) => { - val = isMap(parent) ? getMapVal(parent, key) : parent[key]; - parent = val; + if(isMap(parent)){ + val = getMapVal(parent, key) + }else{ + if(key in parent){ + val = parent[key] + }else{ + throw new Error(`key ${key} not in object ${parent}`) + } + } }); return val; - } \ No newline at end of file + } + + + \ No newline at end of file diff --git a/packages/reactive/src/utils/isMap.ts b/packages/reactive/src/utils/isMap.ts index d4f2bbd..a7b8dcc 100644 --- a/packages/reactive/src/utils/isMap.ts +++ b/packages/reactive/src/utils/isMap.ts @@ -1,3 +1,3 @@ export function isMap(mayMap: any) { - return toString.call(mayMap) === '[object Map]';; + return toString.call(mayMap) === '[object Map]'; } diff --git a/packages/reactive/src/watch/install.ts b/packages/reactive/src/watch/install.ts index 271d3ec..0530c38 100644 --- a/packages/reactive/src/watch/install.ts +++ b/packages/reactive/src/watch/install.ts @@ -11,7 +11,7 @@ import { Dict } from "../types" * @param store * @param watchTo */ -export function installWatch(params:IReactiveReadHookParams,store:IStore) { +export function installWatch(params:IReactiveReadHookParams,store:IStore) { store.options.log(`install watch for <${params.path.length==0 ? "Dynamic" : params.path.join(OBJECT_PATH_DELIMITER)}>`)