From 19b582bcfccd165ad9846b2b7cb13d832449a45c Mon Sep 17 00:00:00 2001 From: fantasticsoul Date: Sat, 6 Jan 2024 22:09:44 +0800 Subject: [PATCH] build(4.0.3): see 4.0.3 changelog --- docs/docs/api/base/index.md | 1 + docs/docs/api/base/watch-effect.md | 28 +++++++ docs/docs/api/base/watch.md | 8 +- docs/docs/api/hooks/index.md | 1 + docs/docs/api/hooks/use-watch-effect.md | 49 +++++++++++ docs/docs/api/hooks/use-watch.md | 4 + docs/docs/api/index.md | 2 + docs/docs/guide/atom.md | 2 +- docs/docs/guide/watch.md | 55 +++++++++++++ package.json | 2 +- packages-legacy/helux-preact/src/index.ts | 2 + packages/helux-core/src/api.ts | 5 +- packages/helux-core/src/consts/user.ts | 2 +- .../helux-core/src/factory/createWatch.ts | 28 +++++-- .../helux-core/src/factory/creator/parse.ts | 5 +- .../src/hooks/common/useWatchLogic.ts | 81 +++++++++++++------ packages/helux-core/src/hooks/index.ts | 2 +- packages/helux-core/src/hooks/useWatch.ts | 2 +- packages/helux-core/src/types/api.d.ts | 33 ++++++-- packages/helux-core/src/types/base.d.ts | 20 +++-- packages/helux/src/index.ts | 2 + pnpm-lock.yaml | 8 +- 22 files changed, 289 insertions(+), 53 deletions(-) create mode 100644 docs/docs/api/base/watch-effect.md create mode 100644 docs/docs/api/hooks/use-watch-effect.md diff --git a/docs/docs/api/base/index.md b/docs/docs/api/base/index.md index 2fa3589f..787cc3fd 100644 --- a/docs/docs/api/base/index.md +++ b/docs/docs/api/base/index.md @@ -26,6 +26,7 @@ nav: - [runMutateTask](/api/base/run-mutate-task) 人工触发可变派生函数异步任务 - [action](/api/base/action) 创建修改状态的 action 同步或异步函数 - [watch](/api/base/watch) 创建观察数据变化的监听函数 +- [watchEffect](/api/base/watch-effect) 创建观察数据变化的监听函数,立即运行并在首次运行时收集到依赖 - [syncer](/api/base/syncer) 浅层次对象的同步函数生成器,辅助双向绑定 - [sync](/api/base/sync) 深层次对象的同步函数生成器,辅助双向绑定 - [emit](/api/base/emit) 发射事件 diff --git a/docs/docs/api/base/watch-effect.md b/docs/docs/api/base/watch-effect.md new file mode 100644 index 00000000..ace80a61 --- /dev/null +++ b/docs/docs/api/base/watch-effect.md @@ -0,0 +1,28 @@ +--- +group: + title: 帮助 + order: 4 +order: 0 +--- + +# watchEffect + +`watchEffect`用于观察数据变化,并做对应的处理逻辑,观察的粒度可以任意定制。 + +区别于`watch`,`watchEffect`回调会立即执行,自动对首次运行时函数内读取到的值完成变化监听。 + +:::info +本章节展示基础用法,更多用法查阅[指南/观察](/guide/watch)或[基础api/观察](/api/base/watch) +::: + +## 基础用法 + +```ts +import { watchEffect, getSnap } from ' helux '; +const [priceState, setPrice] = share({ a: 1 }); + +// 观察 priceState.a 的变化 +watchEffect(() => { + console.log(`found price.a changed from ${getSnap(priceState).a} to ${priceState.a}`); +}); +``` diff --git a/docs/docs/api/base/watch.md b/docs/docs/api/base/watch.md index 5760af7b..38b5abb6 100644 --- a/docs/docs/api/base/watch.md +++ b/docs/docs/api/base/watch.md @@ -27,7 +27,11 @@ watch 可观察共享状态跟对象的变化,第二位参数可写为`()=>[]` ## 死循环 -设置`immediate`为 true 时,watch 回调首次执行会自动收集依赖,此时如果存在读取自己修改自己的新闻,则会造成死循环 +设置`immediate`为 true 时,watch 回调首次执行会自动收集依赖,此时如果存在读取自己修改自己的行为,会造成死循环。 + +:::tip +死循环产生后,框架会定位到具体的函数位置并告知原因,用户可打开控制台查看 +::: ```ts import { watch, atom } from 'helux'; @@ -36,7 +40,7 @@ const [state, setAtom] = atom({ a: 1 }); watch( () => { - // ❌ 单独 a 修改 a,触发死循环 + // ❌ 读取 a 修改 a,触发死循环 setAtom((draft) => { draft.a += 1; }); diff --git a/docs/docs/api/hooks/index.md b/docs/docs/api/hooks/index.md index 38c7082b..4b7aa45f 100644 --- a/docs/docs/api/hooks/index.md +++ b/docs/docs/api/hooks/index.md @@ -13,6 +13,7 @@ order: 0 - [useDerived](/api/hooks/use-derived) 使用全量派生结果 - [useOnEvent](/api/hooks/use-on-event) 使用事件监听 - [useWatch](/api/hooks/use-watch) 使用观察 +- [useWatchEffect](/api/hooks/use-watch-effect) 使用观察,立即运行并在首次运行时收集到依赖 - [useGlobalId](/api/hooks/use-global-id) 使用`globalId` - [useMutate](/api/hooks/use-mutate) 使用可变本地状态 - [useService](/api/hooks/use-service) 使用服务 diff --git a/docs/docs/api/hooks/use-watch-effect.md b/docs/docs/api/hooks/use-watch-effect.md new file mode 100644 index 00000000..7460399a --- /dev/null +++ b/docs/docs/api/hooks/use-watch-effect.md @@ -0,0 +1,49 @@ +--- +order: 4 +--- + +# useWatchEffect + +`useWatchEffect` 功能同 `watchEffect``一样,区别在于 `useWatchEffect` 会立即执行回调,自动对首次运行时函数内读取到的值完成变化监听。 + +:::info +其他使用方式可参考[watchEffect](/api/hooks/use-effect) +::: + + +## 基础用法 + +:::info +`useWatchEffect`回调的首次运行的执行时机是在组件挂载完毕后才执行 +::: + +```tsx +/** + * defaultShowCode: true + */ +import { share, useMutable, useWatchEffect, getSnap } from 'helux'; + +const [priceState, setState, ctx] = share({ a: 1, b: { b1: { b2: 200 } } }); + +function changeA() { + setState((draft) => { + draft.a += 1; + }); +} + +export default function Comp(props: any) { + const [ state, setState] = useMutable({tip:'1'}) + useWatchEffect(() => { + setState(draft=>{ + draft.tip = `priceState.a changed from ${getSnap(priceState).a} to ${priceState.a}`; + }); + }); + + return ( +
+ +

tip: {state.tip}

+
+ ); +} +``` \ No newline at end of file diff --git a/docs/docs/api/hooks/use-watch.md b/docs/docs/api/hooks/use-watch.md index 7963c67f..2b843faa 100644 --- a/docs/docs/api/hooks/use-watch.md +++ b/docs/docs/api/hooks/use-watch.md @@ -34,6 +34,10 @@ interface IWatchOptions { type WatchFnDeps = () => any[] | undefined; ``` +:::info +`useWatch`回调的首次运行的执行时机是在组件挂载完毕后才执行 +::: + ## 基础用法 ### 观察原始类型 atom diff --git a/docs/docs/api/index.md b/docs/docs/api/index.md index 0a058439..2d734dc3 100644 --- a/docs/docs/api/index.md +++ b/docs/docs/api/index.md @@ -27,6 +27,7 @@ nav: > - [runMutateTask](/api/base/run-mutate-task) 人工触发可变派生函数异步任务 > - [action](/api/base/action) 创建修改状态的 action 同步或异步函数 > - [watch](/api/base/watch) 创建观察数据变化的监听函数 +> - [watchEffect](/api/base/watch-effect) 创建观察数据变化的监听函数,立即运行并在首次运行时收集到依赖 > - [syncer](/api/base/syncer) 浅层次对象的同步函数生成器,辅助双向绑定 > - [sync](/api/base/sync) 深层次对象的同步函数生成器,辅助双向绑定 > - [emit](/api/base/emit) 发射事件 @@ -56,6 +57,7 @@ ctx.aciton()(/** action 函数定义 */) > - [useDerived](/api/hooks/use-derived) 使用全量派生结果 > - [useOnEvent](/api/hooks/use-on-event) 使用事件监听 > - [useWatch](/api/hooks/use-watch) 使用观察 +> - [useWatchEffect](/api/hooks/use-watch-effect) 使用观察,立即运行并在首次运行时收集到依赖 > - [useGlobalId](/api/hooks/use-global-id) 使用`globalId` > - [useMutate](/api/hooks/use-mutate) 使用可变本地状态 > - [useService](/api/hooks/use-service) 使用服务 diff --git a/docs/docs/guide/atom.md b/docs/docs/guide/atom.md index 076514b0..35ad7cb4 100644 --- a/docs/docs/guide/atom.md +++ b/docs/docs/guide/atom.md @@ -59,7 +59,7 @@ const { state: numAtom, setState: setAtom } = atomCtx; 钩子 `useAtom` 返回一个元组,使用方式大体对齐 `react.useState` 接口,唯一的区别是`setter`提供的回调参数是一个草稿对象,可基于草稿对象直接修改,这个差异点下面会再次提到。 -:::info{title=推荐通过 actions 修改} +:::info{title='通过 actions 修改'} 推荐了解和使用[模块化/defineAtcions](/guide/modular#defineactions)定义修改方法,有利于维护或扩展 ::: diff --git a/docs/docs/guide/watch.md b/docs/docs/guide/watch.md index 3878ac79..56e5dce0 100644 --- a/docs/docs/guide/watch.md +++ b/docs/docs/guide/watch.md @@ -11,6 +11,8 @@ order: 8 ## 组件外观察变化 +### watch + 使用`watch`可观察 atom 对象自身变化或任意多个子节点的变化。 观察函数立即执行,首次执行时收集到相关依赖 @@ -86,8 +88,24 @@ watch( ); ``` +### watchEffect + +`watchEffect`回调会立即执行,自动对首次运行时函数内读取到的值完成变化监听 + +```ts +import { watchEffect, getSnap } from ' helux '; +const [priceState, setPrice] = share({ a: 1 }); + +// 观察 priceState.a 的变化 +watchEffect(() => { + console.log(`found price.a changed from ${getSnap(priceState).a} to ${priceState.a}`); +}); +``` + ## 组件内观察变化 +### useWatch + 提供`useWatch`让客户在组件内部观察变化 :::success{title=自动销毁观察监听} @@ -176,3 +194,40 @@ export default () => ( ); ``` + +### useWatchEffect + +在组件中使用 `useWatchEffect` 来完成状态变化监听,会在组件销毁时自动取消监听。 + +`useWatchEffect` 功能同 `watchEffect``一样,区别在于 `useWatchEffect` 会立即执行回调,自动对首次运行时函数内读取到的值完成变化监听。 + +```tsx +/** + * defaultShowCode: true + */ +import { share, useMutable, useWatchEffect, getSnap } from 'helux'; + +const [priceState, setState, ctx] = share({ a: 1, b: { b1: { b2: 200 } } }); + +function changeA() { + setState((draft) => { + draft.a += 1; + }); +} + +export default function Comp(props: any) { + const [ state, setState] = useMutable({tip:'1'}) + useWatchEffect(() => { + setState(draft=>{ + draft.tip = `priceState.a changed from ${getSnap(priceState).a} to ${priceState.a}`; + }); + }); + + return ( +
+ +

tip: {state.tip}

+
+ ); +} +``` diff --git a/package.json b/package.json index 12e67aa1..3cd9f5e3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "helux", - "version": "4.0.2", + "version": "4.0.3", "description": "A state engine integrates atom, signal, derive, watch and dep tracking, supports fine-grained responsive updates, it is compatible with all react like libs (including React 18).", "keywords": [], "author": { diff --git a/packages-legacy/helux-preact/src/index.ts b/packages-legacy/helux-preact/src/index.ts index 15c10429..5ee723dc 100644 --- a/packages-legacy/helux-preact/src/index.ts +++ b/packages-legacy/helux-preact/src/index.ts @@ -18,6 +18,7 @@ export const { runDeriveTask, // watch api watch, + watchEffect, // hooks api useAtom, useAtomX, @@ -25,6 +26,7 @@ export const { useReactiveX, useDerived, useWatch, + useWatchEffect, useGlobalId, useService, useOnEvent, diff --git a/packages/helux-core/src/api.ts b/packages/helux-core/src/api.ts index 833d291a..04a2375a 100644 --- a/packages/helux-core/src/api.ts +++ b/packages/helux-core/src/api.ts @@ -9,7 +9,7 @@ import { defineDeriveFnItem, defineDeriveTask, derive, deriveDict } from './fact import { mutate, mutateDict, runMutate, runMutateTask } from './factory/createMutate'; import { atom, atomx, share, sharex } from './factory/createShared'; import { sync, syncer } from './factory/createSync'; -import { watch } from './factory/createWatch'; +import { watch, watchEffect } from './factory/createWatch'; import { currentDraftRoot } from './factory/creator/current'; import { flush, reactiveDesc } from './factory/creator/reactive'; import { init } from './factory/root'; @@ -33,6 +33,7 @@ import { useReactiveX, useService, useWatch, + useWatchEffect, } from './hooks'; import { block, dynamicBlock, signal } from './signal'; @@ -63,6 +64,7 @@ export { runDeriveTask, // watch api watch, + watchEffect, // hooks api useAtom, useAtomX, @@ -70,6 +72,7 @@ export { useReactiveX, useDerived, useWatch, + useWatchEffect, useGlobalId, useService, useOnEvent, diff --git a/packages/helux-core/src/consts/user.ts b/packages/helux-core/src/consts/user.ts index 1a469bad..e80da79c 100644 --- a/packages/helux-core/src/consts/user.ts +++ b/packages/helux-core/src/consts/user.ts @@ -1,6 +1,6 @@ import { VER as limuVer } from 'limu'; -export const VER = '4.0.1'; +export const VER = '4.0.3'; export const LIMU_VER = limuVer; diff --git a/packages/helux-core/src/factory/createWatch.ts b/packages/helux-core/src/factory/createWatch.ts index b5ff0b71..b7a896ca 100644 --- a/packages/helux-core/src/factory/createWatch.ts +++ b/packages/helux-core/src/factory/createWatch.ts @@ -5,7 +5,7 @@ import { markFnEnd, markFnStart, registerFn } from '../helpers/fnCtx'; import { delFnDep, recordFnDepKeys } from '../helpers/fnDep'; import { runFn } from '../helpers/fnRunner'; import { getInternal, getSharedKey } from '../helpers/state'; -import type { Fn, IFnCtx, IWatchFnParams, ScopeType, SharedState, WatchOptionsType } from '../types/base'; +import type { Fn, IFnCtx, IWatchFnParams, ScopeType, SharedState, WatchEffectOptionsType, WatchOptionsType } from '../types/base'; import { INS_CTX } from './creator/current'; import { parseWatchOptions } from './creator/parse'; interface ICreateWatchLogicOpts { @@ -38,6 +38,15 @@ function putSharedToDep(list: any[]) { } } +function innerWatch(forEffect: boolean, watchFn: (fnParams: IWatchFnParams) => void, options?: WatchOptionsType) { + const { deps, immediate } = parseWatchOptions(forEffect, options); + const fnCtx = createWatchLogic(watchFn, { scopeType: SCOPE_TYPE.STATIC, deps, immediate }); + return { + run: (throwErr?: boolean) => runFn(fnCtx.fnKey, { throwErr }), + unwatch: () => delFnDep(fnCtx), + }; +} + export function createWatchLogic(watchFn: (fnParams: IWatchFnParams) => any, options: ICreateWatchLogicOpts) { const { scopeType, fnCtxBase, immediate, deps = noop, label = 'watch', sharedState, isSimpleWatch } = options; if (!isFn(watchFn)) { @@ -61,11 +70,16 @@ export function createWatchLogic(watchFn: (fnParams: IWatchFnPa return fnCtx; } +/** + * watch 回调默认不立即执行,需要设置 immediate=true 才立即执行 + */ export function watch(watchFn: (fnParams: IWatchFnParams) => void, options?: WatchOptionsType) { - const { deps, immediate } = parseWatchOptions(options); - const fnCtx = createWatchLogic(watchFn, { scopeType: SCOPE_TYPE.STATIC, deps, immediate }); - return { - run: (throwErr?: boolean) => runFn(fnCtx.fnKey, { throwErr }), - unwatch: () => delFnDep(fnCtx), - }; + return innerWatch(false, watchFn, options); +} + +/** + * watch 回调立即执行 + */ +export function watchEffect(watchFn: (fnParams: IWatchFnParams) => void, options?: WatchEffectOptionsType) { + return innerWatch(true, watchFn, options); } diff --git a/packages/helux-core/src/factory/creator/parse.ts b/packages/helux-core/src/factory/creator/parse.ts index d4d08b49..429f07af 100644 --- a/packages/helux-core/src/factory/creator/parse.ts +++ b/packages/helux-core/src/factory/creator/parse.ts @@ -317,7 +317,7 @@ export function parseCreateMutateOpt(descOrOptions?: string | IRunMutateOptions) return { desc, strict, ...descOrOptions, throwErr }; } -export function parseWatchOptions(options?: WatchOptionsType) { +export function parseWatchOptions(forEffect: boolean, options?: WatchOptionsType) { let deps: WatchFnDeps = noop; let immediate: boolean = false; @@ -327,6 +327,9 @@ export function parseWatchOptions(options?: WatchOptionsType) { deps = options.deps || noop; immediate = options.immediate ?? false; } + // 如果是为 watchEffect 准备参数,则 immediate 一定为 true + immediate = forEffect ? true : immediate; + return { immediate, deps }; } diff --git a/packages/helux-core/src/hooks/common/useWatchLogic.ts b/packages/helux-core/src/hooks/common/useWatchLogic.ts index be6d211a..9212bb79 100644 --- a/packages/helux-core/src/hooks/common/useWatchLogic.ts +++ b/packages/helux-core/src/hooks/common/useWatchLogic.ts @@ -4,7 +4,7 @@ import { MOUNTED, SCOPE_TYPE } from '../../consts'; import { getDepKeyInfo } from '../../factory/common/util'; import { createWatchLogic } from '../../factory/createWatch'; import { parseWatchOptions } from '../../factory/creator/parse'; -import { buildFnCtx, delFnCtx } from '../../helpers/fnCtx'; +import { buildFnCtx, delFnCtx, markFnEnd, markFnStart } from '../../helpers/fnCtx'; import { recoverDep } from '../../helpers/fnDep'; import { getSharedState } from '../../helpers/state'; import type { CoreApiCtx } from '../../types/api-ctx'; @@ -26,6 +26,58 @@ function useFnCtxEffect(useEffect: FnVoid, fnCtx: IFnCtx) { }, [fnCtx]); } +interface IUseWatchLogicOptions { + label: string; + forEffect: boolean; + watchFn: Fn; + watchOptions: WatchOptionsType; +} + +interface IFnRefCurrent { + fn: Fn; + wrap: any; + fnKey: string; + isDeferMarked: boolean; +} + +function useWatchLogic(apiCtx: CoreApiCtx, options: IUseWatchLogicOptions) { + const { useRef, useState, useMemo, useEffect } = apiCtx.react; + const { label, forEffect, watchFn, watchOptions } = options; + const fnRef = useRef({ fn: watchFn, wrap: null, fnKey: '', isDeferMarked: false }); + const [fnCtx] = useState(() => buildFnCtx()); + // 总是绑定最新的 watchFn,让组件里的 watch 可以读取到闭包里的最新值 + // fnRef.current = watchFn; // 这样写不兼容 react dev tool + fnRef.current.fn = useMemo(() => watchFn, [watchFn]); + + if (!fnRef.current.wrap) { + const { deps, immediate } = parseWatchOptions(forEffect, watchOptions); + // 传入了局部的自定义观察函数 + fnRef.current.wrap = (params: any) => { + // 避免 strict 模式下冗余触发 + if (fnCtx.mountStatus === MOUNTED) { + fnRef.current.fn(params); + return; + } + + // 组件首次渲染,用户设置 immediate 为 true 时逻辑会走到这里,推迟到 MOUNTED 设置时再执行 + fnCtx.extra.deferedWatch = () => { + if (fnRef.current.isDeferMarked) { + fnRef.current.fn(params); + return; + } + // 对于延迟执行的函数,需要再次调用 markFnStart 才能让 fn(params) 执行收集到依赖 + fnRef.current.isDeferMarked = true; + markFnStart(fnRef.current.fnKey, 0); + fnRef.current.fn(params); + markFnEnd(); + }; + }; + const { fnKey } = createWatchLogic(fnRef.current.wrap, { scopeType: HOOK, fnCtxBase: fnCtx, deps, immediate, label }); + fnRef.current.fnKey = fnKey; + } + useFnCtxEffect(useEffect, fnCtx); +} + /** * 简化版的 useWatch,服务于 block */ @@ -47,27 +99,10 @@ export function useWatchSimpleLogic(apiCtx: CoreApiCtx, watchFn: Fn, options: IS useFnCtxEffect(useEffect, fnCtx); } -export function useWatch(apiCtx: CoreApiCtx, watchFn: Fn, options: WatchOptionsType) { - const { useRef, useState, useMemo, useEffect } = apiCtx.react; - const fnRef = useRef<{ fn: Fn; wrap: any }>({ fn: watchFn, wrap: null }); - const [fnCtx] = useState(() => buildFnCtx()); - // 总是绑定最新的 watchFn,让组件里的 watch 可以读取到闭包里的最新值 - // fnRef.current = watchFn; // 这样写不兼容 react dev tool - fnRef.current.fn = useMemo(() => watchFn, [watchFn]); +export function useWatch(apiCtx: CoreApiCtx, watchFn: Fn, watchOptions: WatchOptionsType) { + useWatchLogic(apiCtx, { label: 'useWatch', forEffect: false, watchFn, watchOptions }); +} - if (!fnRef.current.wrap) { - const { deps, immediate } = parseWatchOptions(options); - // 传入了局部的自定义观察函数 - fnRef.current.wrap = (params: any) => { - // 避免 strict 模式下冗余触发 - if (fnCtx.mountStatus === MOUNTED) { - fnRef.current.fn(params); - } else { - // 组件首次渲染,用户设置 immediate 为 true 时逻辑会走到这里,推迟到 MOUNTED 设置时再执行 - fnCtx.extra.deferedWatch = () => fnRef.current.fn(params); - } - }; - createWatchLogic(fnRef.current.wrap, { scopeType: HOOK, fnCtxBase: fnCtx, deps, immediate, label: 'useWatch' }); - } - useFnCtxEffect(useEffect, fnCtx); +export function useWatchEffect(apiCtx: CoreApiCtx, watchFn: Fn, watchOptions: WatchOptionsType) { + useWatchLogic(apiCtx, { label: 'useWatchEffect', forEffect: true, watchFn, watchOptions }); } diff --git a/packages/helux-core/src/hooks/index.ts b/packages/helux-core/src/hooks/index.ts index 714ba6cc..f9351b06 100644 --- a/packages/helux-core/src/hooks/index.ts +++ b/packages/helux-core/src/hooks/index.ts @@ -8,4 +8,4 @@ export { useMutable } from './useMutable'; export { useOnEvent } from './useOnEvent'; export { useReactive, useReactiveX } from './useReactive'; export { storeSrv, useService } from './useService'; -export { useWatch } from './useWatch'; +export { useWatch, useWatchEffect } from './useWatch'; diff --git a/packages/helux-core/src/hooks/useWatch.ts b/packages/helux-core/src/hooks/useWatch.ts index 36e1e85e..2b23f4af 100644 --- a/packages/helux-core/src/hooks/useWatch.ts +++ b/packages/helux-core/src/hooks/useWatch.ts @@ -1 +1 @@ -export { useWatch } from './common/useWatchLogic'; +export { useWatch, useWatchEffect } from './common/useWatchLogic'; diff --git a/packages/helux-core/src/types/api.d.ts b/packages/helux-core/src/types/api.d.ts index 5476e890..d938de65 100644 --- a/packages/helux-core/src/types/api.d.ts +++ b/packages/helux-core/src/types/api.d.ts @@ -1,6 +1,6 @@ /* |------------------------------------------------------------------------------------------------ -| helux-core@4.0.1 +| helux-core@4.0.3 | A state library core that integrates atom, signal, collection dep, derive and watch, | it supports all react like frameworks ( including react 18 ). |------------------------------------------------------------------------------------------------ @@ -61,11 +61,12 @@ import type { SingalVal, Syncer, SyncFnBuilder, + WatchEffectOptionsType, WatchOptionsType, } from './base'; export declare const cst: { - VER: '4.0.1'; + VER: '4.0.3'; LIMU_VER: string; EVENT_NAME: { ON_DATA_CHANGED: 'ON_DATA_CHANGED'; @@ -203,11 +204,12 @@ export function defineDeriveTask( export function defineDeriveFnItem(fnItem: F): F; /** - * 观察共享状态变化,默认 watchFn 立即执行 + * 观察共享状态变化,watch 回调默认不立即执行,需要设置 immediate=true 才立即执行, + * 因回调默认不立即执行,options 类型设计为必填,提示用户使用 watch 需要显式指定依赖 * ```ts - * // 函数内解构完成监听 + * // 立即运行,自动对首次运行时函数内读取到的值完成变化监听 * watch(()=>{ console.log(shared.val) }, { immediate: true }); - * // 第二个参数传递依赖收集回调,收集到监听key,不需要立即执行的话可设定 immediate 为 false + * // 第二个参数传递依赖收集回调,收集到监听key,不需要立即执行的话可设定 immediate 为 false 或不设置 * watch(()=>{ console.log('shared.val changed')}, ()=>[shared.val]); * // 第二个参数传递依赖收集回调,收集到监听对象,表示shared发生变化就执行watch回调 * watch(()=>{ console.log('shared changed')}, ()=>[shared]); @@ -217,7 +219,17 @@ export function defineDeriveFnItem(fnItem: F): F; */ export function watch( watchFn: (fnParams: IWatchFnParams) => void, - options?: WatchOptionsType, + options: WatchOptionsType, +): { run: (throwErr?: boolean) => void; unwatch: Fn }; + +/** + * watchEffect 和 watch 用法一样, + * 区别于 watch 的点是:watchEffect 会立即执行回调,自动对首次运行时函数内读取到的值完成变化监听, + * 因回调会立即执行,options 类型设计为选填 + */ +export function watchEffect( + watchFn: (fnParams: IWatchFnParams) => void, + options?: WatchEffectOptionsType, ): { run: (throwErr?: boolean) => void; unwatch: Fn }; /** @@ -325,8 +337,17 @@ export function useObject( initialState: T | (() => T), ): [T, (partialStateOrCb: Partial | PartialStateCb) => void, IObjApi]; +/** + * 功能同 watch,在组件中使用 useWatch 来完成状态变化监听,会在组件销毁时自动取消监听 + */ export function useWatch(watchFn: (fnParams: IWatchFnParams) => void, options: WatchOptionsType); +/** + * 功能同 watchEffect 一样,区别在于 useWatchEffect 会立即执行回调,自动对首次运行时函数内读取到的值完成变化监听 + * 在组件中使用 useWatchEffect 来完成状态变化监听,会在组件销毁时自动取消监听 + */ +export function useWatchEffect(watchFn: (fnParams: IWatchFnParams) => void, options?: WatchEffectOptionsType); + /** * 使用全局id,配合 rules[].globalIds 做定向通知更新 */ diff --git a/packages/helux-core/src/types/base.d.ts b/packages/helux-core/src/types/base.d.ts index 4d84df00..413274ec 100644 --- a/packages/helux-core/src/types/base.d.ts +++ b/packages/helux-core/src/types/base.d.ts @@ -1359,8 +1359,19 @@ export interface IWatchFnParams { export type WatchFnDeps = () => any[] | undefined; -export interface IWatchOptions { +export interface IWatchEffectOptions { + /** + * 如果是立即执行的 watch 回调,会自动将 deps 返回的依赖于回调里收集到的依赖合并 + */ deps?: WatchFnDeps; + /** + * default: false + * 是否抛出错误,默认不抛出(重执行函数可独立设定抛出),错误会发送给插件 + */ + throwErr?: boolean; +} + +export interface IWatchOptions extends IWatchEffectOptions { /** * default: false, * 如没有定义 deps 依赖,需设置 immediate,这样可以让 watch 首次执行后收集到相关依赖, @@ -1368,15 +1379,12 @@ export interface IWatchOptions { * deps 定义的和 watch 首次执行后收集到的两者合并的结果 */ immediate?: boolean; - /** - * default: false - * 是否抛出错误,默认不抛出(重执行函数可独立设定抛出),错误会发送给插件 - */ - throwErr?: boolean; } export type WatchOptionsType = WatchFnDeps | IWatchOptions; +export type WatchEffectOptionsType = WatchFnDeps | IWatchEffectOptions; + export interface IDeriveFnParamsBase { /** 函数的运行编号,每次自增1 */ sn: number; diff --git a/packages/helux/src/index.ts b/packages/helux/src/index.ts index 1c4b8dc8..81d8502b 100644 --- a/packages/helux/src/index.ts +++ b/packages/helux/src/index.ts @@ -24,6 +24,7 @@ export const { runDeriveTask, // watch api watch, + watchEffect, // hooks api useAtom, useAtomX, @@ -31,6 +32,7 @@ export const { useReactiveX, useDerived, useWatch, + useWatchEffect, useGlobalId, useService, useOnEvent, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3428e6dc..7d01405d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -189,12 +189,12 @@ importers: '@helux/types': workspace:^ '@helux/utils': workspace:^ esbuild-copy-static-files: ^0.1.0 - limu: ^3.12.0 + limu: ^3.12.1 dependencies: '@helux/hooks-impl': link:../helux-hooks-impl '@helux/types': link:../helux-types '@helux/utils': link:../helux-utils - limu: 3.12.0 + limu: 3.12.1 devDependencies: esbuild-copy-static-files: 0.1.0 @@ -12179,6 +12179,10 @@ packages: dev: false bundledDependencies: [] + /limu/3.12.1: + resolution: {integrity: sha512-AWYBWpcI3NCu3547UYUQ2pPsclKOZSSfWgdyc1axKmg12y+9PgHyqlezWEimEbL7oITD530mEsOATe6pu2Ri4A==} + dev: false + /lines-and-columns/1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} dev: true