From ca55e4846e80cdb7b90d86161a287e97f4138f14 Mon Sep 17 00:00:00 2001 From: Muyao Date: Sun, 26 Dec 2021 11:34:12 +0800 Subject: [PATCH] feat(reactive-vue): add observer option scheduler (#2672) --- .../src/__tests__/observer.spec.ts | 48 +++++++++++++++++++ .../reactive-vue/src/hooks/useObserver.ts | 38 ++++++++++----- packages/reactive-vue/src/observer/index.ts | 6 +-- .../src/observer/observerInVue2.ts | 8 +++- .../src/observer/observerInVue3.ts | 2 +- packages/reactive-vue/src/types.ts | 1 + packages/reactive/docs/api/vue/observer.md | 13 +++++ .../reactive/docs/api/vue/observer.zh-CN.md | 13 +++++ 8 files changed, 110 insertions(+), 19 deletions(-) diff --git a/packages/reactive-vue/src/__tests__/observer.spec.ts b/packages/reactive-vue/src/__tests__/observer.spec.ts index 3b43cb3a458..ae1119fff2f 100644 --- a/packages/reactive-vue/src/__tests__/observer.spec.ts +++ b/packages/reactive-vue/src/__tests__/observer.spec.ts @@ -70,3 +70,51 @@ test('observer: component with setup', async () => { expect(wrapper.find('button').text()).toBe('12') wrapper.destroy() }) + +test('observer: component scheduler', async () => { + let schedulerRequest = null + + const model = observable({ + age: 10, + setAge() { + model.age++ + }, + }) + const Component = observer( + { + data() { + return { + model, + } + }, + render(this: any, h: CreateElement) { + return h('button', { + on: { click: this.model.setAge }, + domProps: { textContent: this.model.age }, + }) + }, + }, + { + scheduler: (update) => { + clearTimeout(schedulerRequest) + schedulerRequest = setTimeout(() => { + update() + }, 100) + }, + } + ) + const wrapper = shallowMount(Component) + + expect(wrapper.find('button').text()).toBe('10') + + wrapper.find('button').trigger('click') + await new Promise((r) => setTimeout(r, 150)) + expect(wrapper.find('button').text()).toBe('11') + + // test second render + wrapper.find('button').trigger('click') + await new Promise((r) => setTimeout(r, 150)) + expect(wrapper.find('button').text()).toBe('12') + + wrapper.destroy() +}) diff --git a/packages/reactive-vue/src/hooks/useObserver.ts b/packages/reactive-vue/src/hooks/useObserver.ts index 1fc6954eb53..97923ae8e63 100644 --- a/packages/reactive-vue/src/hooks/useObserver.ts +++ b/packages/reactive-vue/src/hooks/useObserver.ts @@ -1,18 +1,20 @@ -import { autorun } from '@formily/reactive' +import { Tracker } from '@formily/reactive' import { getCurrentInstance, onBeforeUnmount, isVue3 } from 'vue-demi' +import { IObserverOptions } from '../types' /* istanbul ignore next */ -export const useObserver = () => { +export const useObserver = (options?: IObserverOptions) => { if (isVue3) { const vm = getCurrentInstance() - - let dispose: () => void | undefined - - onBeforeUnmount(() => { - if (dispose) { - dispose() + let tracker: Tracker = null + const disposeTracker = () => { + if (tracker) { + tracker.dispose() + tracker = null } - }) + } + + onBeforeUnmount(disposeTracker) Object.defineProperty(vm, 'update', { get() { @@ -20,10 +22,20 @@ export const useObserver = () => { return vm['_updateEffect'] || {} }, set(newValue) { - if (dispose) { - dispose() - } - dispose = autorun(newValue) + disposeTracker() + + const update = () => tracker.track(newValue) + + tracker = new Tracker(() => { + if (options?.scheduler) { + options?.scheduler?.(update) + } else { + update() + } + }) + + update() + vm['_updateEffect'] = newValue }, }) diff --git a/packages/reactive-vue/src/observer/index.ts b/packages/reactive-vue/src/observer/index.ts index 885062e0d4e..a2aaf5de38d 100644 --- a/packages/reactive-vue/src/observer/index.ts +++ b/packages/reactive-vue/src/observer/index.ts @@ -4,10 +4,8 @@ import { observer as observerV3 } from './observerInVue3' import collectData from './collectData' import { IObserverOptions } from '../types' -export function observer( - baseComponent: C, - options?: IObserverOptions & { forwardRef: true } -): C { +export function observer(baseComponent: C, options?: IObserverOptions): C { + /* istanbul ignore else */ if (isVue2) { return observerV2(baseComponent, options) } else { diff --git a/packages/reactive-vue/src/observer/observerInVue2.ts b/packages/reactive-vue/src/observer/observerInVue2.ts index 21c7d70e566..bd0e3703fa0 100644 --- a/packages/reactive-vue/src/observer/observerInVue2.ts +++ b/packages/reactive-vue/src/observer/observerInVue2.ts @@ -69,7 +69,13 @@ function observer(Component: any, observerOptions?: IObserverOptions): any { return this } - const tracker = new Tracker(reactiveRender) + const tracker = new Tracker(() => { + if (observerOptions?.scheduler) { + observerOptions?.scheduler?.(reactiveRender) + } else { + reactiveRender() + } + }) this[disposerSymbol] = tracker.dispose diff --git a/packages/reactive-vue/src/observer/observerInVue3.ts b/packages/reactive-vue/src/observer/observerInVue3.ts index f81a7890a43..e987424519c 100644 --- a/packages/reactive-vue/src/observer/observerInVue3.ts +++ b/packages/reactive-vue/src/observer/observerInVue3.ts @@ -9,7 +9,7 @@ export const observer = function (opts: any, options?: IObserverOptions): any { name, ...opts, setup(props: Record, context: any) { - useObserver() + useObserver(options) return opts?.setup?.(props, context) }, } diff --git a/packages/reactive-vue/src/types.ts b/packages/reactive-vue/src/types.ts index f6639264351..a237fe849d0 100644 --- a/packages/reactive-vue/src/types.ts +++ b/packages/reactive-vue/src/types.ts @@ -1,3 +1,4 @@ export interface IObserverOptions { name?: string + scheduler?: (updater: () => void) => void } diff --git a/packages/reactive/docs/api/vue/observer.md b/packages/reactive/docs/api/vue/observer.md index 98ba4753e22..d25311ed445 100644 --- a/packages/reactive/docs/api/vue/observer.md +++ b/packages/reactive/docs/api/vue/observer.md @@ -4,6 +4,19 @@ In Vue, the component rendering method is changed to Reaction, and dependencies are collected every time the view is re-rendered, and dependencies are updated automatically to re-render. +### Signature + +```ts +interface IObserverOptions { + scheduler?: (updater: () => void) => void //The scheduler, you can manually control the timing of the update + name?: string //name of the packaged component +} + +interface observer { + (component: T, options?: IObserverOptions): T +} +``` + ## Example ```html diff --git a/packages/reactive/docs/api/vue/observer.zh-CN.md b/packages/reactive/docs/api/vue/observer.zh-CN.md index d319ddb9844..62830484a64 100644 --- a/packages/reactive/docs/api/vue/observer.zh-CN.md +++ b/packages/reactive/docs/api/vue/observer.zh-CN.md @@ -4,6 +4,19 @@ 在 Vue 中,将组件渲染方法变成 Reaction,每次视图重新渲染就会收集依赖,依赖更新会自动重渲染。 +### 签名 + +```ts +interface IObserverOptions { + scheduler?: (updater: () => void) => void //调度器,可以手动控制更新时机 + name?: string //包装后的组件的name +} + +interface observer { + (component: T, options?: IObserverOptions): T +} +``` + ## 用例 ```html