From 7ae0a9237752246583dfecb6353e095f3a958831 Mon Sep 17 00:00:00 2001 From: janrywang Date: Thu, 11 Mar 2021 19:06:34 +0800 Subject: [PATCH] test(project): update mobx => @formily/reactive --- packages/core/src/models/Field.ts | 8 ++-- packages/core/src/models/Form.ts | 4 +- packages/core/src/models/Graph.ts | 4 +- packages/core/src/models/VoidField.ts | 8 ++-- packages/react/src/__tests__/field.spec.tsx | 6 ++- .../src/hooks/useForceUpdate.ts | 44 ++++++++++++++++++- .../reactive-react/src/hooks/useObserver.ts | 22 +++------- packages/reactive/README.md | 33 -------------- .../reactive/src/__tests__/define.spec.ts | 14 +++--- packages/reactive/src/annotations/computed.ts | 5 ++- packages/reactive/src/model.ts | 10 +++-- 11 files changed, 82 insertions(+), 76 deletions(-) diff --git a/packages/core/src/models/Field.ts b/packages/core/src/models/Field.ts index 8e52c8f9936..f4a852ad5fc 100644 --- a/packages/core/src/models/Field.ts +++ b/packages/core/src/models/Field.ts @@ -14,7 +14,7 @@ import { parseValidatorDescriptions, } from '@formily/validator' import { - defineModel, + define, observable, reaction, batch, @@ -145,7 +145,7 @@ export class Field< } protected makeObservable() { - defineModel(this, { + define(this, { title: observable.ref, description: observable.ref, dataSource: observable.ref, @@ -163,8 +163,8 @@ export class Field< inputValues: observable.ref, decoratorType: observable.ref, componentType: observable.ref, - decoratorProps: observable.shallow, - componentProps: observable.shallow, + decoratorProps: observable, + componentProps: observable, validator: observable, feedbacks: observable, component: observable.computed, diff --git a/packages/core/src/models/Form.ts b/packages/core/src/models/Form.ts index f4b7a2e476b..851252331d9 100644 --- a/packages/core/src/models/Form.ts +++ b/packages/core/src/models/Form.ts @@ -1,5 +1,5 @@ import { - defineModel, + define, observable, batch, toJS, @@ -120,7 +120,7 @@ export class Form { } protected makeObservable() { - defineModel(this, { + define(this, { fields: observable.shallow, initialized: observable.ref, validating: observable.ref, diff --git a/packages/core/src/models/Graph.ts b/packages/core/src/models/Graph.ts index dacf5edbefb..96104711f0e 100644 --- a/packages/core/src/models/Graph.ts +++ b/packages/core/src/models/Graph.ts @@ -1,4 +1,4 @@ -import { defineModel, batch } from '@formily/reactive' +import { define, batch } from '@formily/reactive' import { each, FormPath } from '@formily/shared' import { IFormGraph } from '../types' import { Form } from './Form' @@ -14,7 +14,7 @@ export class Graph { constructor(form: Form) { this.form = form - defineModel(this, { + define(this, { setGraph: batch, }) } diff --git a/packages/core/src/models/VoidField.ts b/packages/core/src/models/VoidField.ts index b61afaacf6c..6835fc470e0 100644 --- a/packages/core/src/models/VoidField.ts +++ b/packages/core/src/models/VoidField.ts @@ -5,7 +5,7 @@ import { isValid, toArr, } from '@formily/shared' -import { defineModel, observable, autorun, batch } from '@formily/reactive' +import { define, observable, autorun, batch } from '@formily/reactive' import { JSXComponent, JSXComponenntProps, @@ -93,7 +93,7 @@ export class VoidField { } protected makeObservable() { - defineModel(this, { + define(this, { title: observable.ref, description: observable.ref, selfDisplay: observable.ref, @@ -102,9 +102,9 @@ export class VoidField { mounted: observable.ref, unmounted: observable.ref, decoratorType: observable.ref, - decoratorProps: observable.shallow, componentType: observable.ref, - componentProps: observable.shallow, + decoratorProps: observable, + componentProps: observable, display: observable.computed, pattern: observable.computed, hidden: observable.computed, diff --git a/packages/react/src/__tests__/field.spec.tsx b/packages/react/src/__tests__/field.spec.tsx index d1cf1f2afe6..e3d62b9f12c 100644 --- a/packages/react/src/__tests__/field.spec.tsx +++ b/packages/react/src/__tests__/field.spec.tsx @@ -140,7 +140,7 @@ test('useAttch', () => { expect(form.query('bb').take().mounted).toBeTruthy() }) -test('useFormEffects', () => { +test('useFormEffects', async () => { const form = createForm() const CustomField = observer((props: { tag?: string }) => { const field = useField() @@ -164,7 +164,9 @@ test('useFormEffects', () => { aa.setValue('123') } }) - expect(queryByTestId('custom-value').textContent).toEqual('123') + await waitFor(() => { + expect(queryByTestId('custom-value').textContent).toEqual('123') + }) rerender( diff --git a/packages/reactive-react/src/hooks/useForceUpdate.ts b/packages/reactive-react/src/hooks/useForceUpdate.ts index b6a5075f497..7812742acb0 100644 --- a/packages/reactive-react/src/hooks/useForceUpdate.ts +++ b/packages/reactive-react/src/hooks/useForceUpdate.ts @@ -1,13 +1,53 @@ -import { useCallback, useState } from 'react' +import React, { useCallback, useEffect, useRef, useState } from 'react' +import ReactDOM from 'react-dom' + +const batchUpdate = + React['batchUpdate'] || + ReactDOM['batchUpdate'] || + ReactDOM['unstable_batchedUpdates'] || + ((callback: any) => callback()) const EMPTY_ARRAY: any[] = [] +const RENDER_COUNT = { value: 0 } +const RENDER_QUEUE = new Set<() => void>() export function useForceUpdate() { const [, setTick] = useState(0) + const unmountRef = useRef(false) const update = useCallback(() => { - setTick((tick) => tick + 1) + if (RENDER_COUNT.value === 0) { + if (unmountRef.current) return + setTick((tick) => { + return tick + 1 + }) + } else { + if (!RENDER_QUEUE.has(update)) { + RENDER_QUEUE.add(update) + } + } }, EMPTY_ARRAY) + RENDER_COUNT.value++ + + useEffect(() => { + RENDER_COUNT.value-- + if (RENDER_COUNT.value === 0) { + batchUpdate(() => { + RENDER_QUEUE.forEach((update) => { + RENDER_QUEUE.delete(update) + update() + }) + }) + } + }) + + useEffect(() => { + unmountRef.current = false + return () => { + unmountRef.current = true + } + }, []) + return update } diff --git a/packages/reactive-react/src/hooks/useObserver.ts b/packages/reactive-react/src/hooks/useObserver.ts index b83edb81c57..1b7fad32544 100644 --- a/packages/reactive-react/src/hooks/useObserver.ts +++ b/packages/reactive-react/src/hooks/useObserver.ts @@ -1,16 +1,10 @@ import React from 'react' -import ReactDOM from 'react-dom' import { Tracker } from '@formily/reactive' import { isFn } from '@formily/shared' import { GarbageCollector } from '../gc' import { IObserverOptions } from '../types' import { useForceUpdate } from './useForceUpdate' -const batchUpdate = - React['batchUpdate'] || - ReactDOM['batchUpdate'] || - ReactDOM['unstable_batchedUpdates'] - class AutoCollector {} export const useObserver = any>( @@ -18,23 +12,19 @@ export const useObserver = any>( options?: IObserverOptions ): ReturnType => { const forceUpdate = useForceUpdate() + const unmountRef = React.useRef(false) const gcRef = React.useRef() + const tracker = React.useMemo(() => { - const updater = () => { - if (isFn(batchUpdate)) { - batchUpdate(() => forceUpdate()) - } else { - forceUpdate() - } - } return new Tracker(() => { if (isFn(options?.scheduler)) { - options.scheduler(updater) + options.scheduler(forceUpdate) } else { - updater() + forceUpdate() } }) }, []) + //StrictMode/ConcurrentMode会导致组件无法正确触发Unmount,所以只能自己做垃圾回收 if (!gcRef.current) { const target = new AutoCollector() @@ -47,8 +37,10 @@ export const useObserver = any>( } React.useEffect(() => { + unmountRef.current = false gcRef.current.close() return () => { + unmountRef.current = true if (tracker) { tracker.dispose() } diff --git a/packages/reactive/README.md b/packages/reactive/README.md index 0fe60c6ab46..e72af1bdb9a 100644 --- a/packages/reactive/README.md +++ b/packages/reactive/README.md @@ -3,36 +3,3 @@ > Web Reactive Library Like Mobx ## QuikStart - -```tsx -import React from 'react' -import { createForm, onFieldChange } from '@formily/core' - -const attach = void }>(target: T): T => { - target.onMount() - return target -} - -const form = attach(createForm({})) -const field = attach( - form.createField({ - name: 'aa', - required: true, - }) -) -field.onInput('').then(() => { - console.log(field.value, field.errors) -}) - -export default () => ( - -) -``` diff --git a/packages/reactive/src/__tests__/define.spec.ts b/packages/reactive/src/__tests__/define.spec.ts index 6daefb782e1..59170bd9909 100644 --- a/packages/reactive/src/__tests__/define.spec.ts +++ b/packages/reactive/src/__tests__/define.spec.ts @@ -1,4 +1,4 @@ -import { defineModel, observable, autorun } from '..' +import { define, observable, autorun } from '..' import { observe } from '../observe' import { FormPath } from '@formily/shared' import { batch } from '../batch' @@ -8,7 +8,7 @@ describe('makeObservable', () => { const target: any = { aa: {}, } - defineModel(target, { + define(target, { aa: observable, }) const handler = jest.fn() @@ -29,7 +29,7 @@ describe('makeObservable', () => { const target: any = { aa: {}, } - defineModel(target, { + define(target, { aa: observable.shallow, }) const handler = jest.fn() @@ -50,7 +50,7 @@ describe('makeObservable', () => { }) test('box annotation', () => { const target: any = {} - defineModel(target, { + define(target, { aa: observable.box, }) const handler = jest.fn() @@ -68,7 +68,7 @@ describe('makeObservable', () => { }) test('ref annotation', () => { const target: any = {} - defineModel(target, { + define(target, { aa: observable.ref, }) const handler = jest.fn() @@ -92,7 +92,7 @@ describe('makeObservable', () => { target.aa.cc = 312 }, } - defineModel(target, { + define(target, { aa: observable, setData: batch, }) @@ -113,7 +113,7 @@ describe('makeObservable', () => { return this.aa + this.bb }, } - defineModel(target, { + define(target, { aa: observable, bb: observable, cc: observable.computed, diff --git a/packages/reactive/src/annotations/computed.ts b/packages/reactive/src/annotations/computed.ts index b686f628b42..1dd9910bb5b 100644 --- a/packages/reactive/src/annotations/computed.ts +++ b/packages/reactive/src/annotations/computed.ts @@ -20,8 +20,9 @@ export interface IComputed { export const computed: IComputed = createAnnotation( ({ target, key, value }) => { + const initialValue = Symbol('initialValue') const store = { - value: undefined, + value: initialValue, } const proxy = { @@ -61,7 +62,7 @@ export const computed: IComputed = createAnnotation( function compute() { const oldValue = store.value store.value = getter?.call?.(context) - if (oldValue === store.value) return + if (oldValue === store.value || oldValue === initialValue) return batchStart() runReactionsFromTargetKey({ target: context, diff --git a/packages/reactive/src/model.ts b/packages/reactive/src/model.ts index 409a21b6d65..ea5a971face 100644 --- a/packages/reactive/src/model.ts +++ b/packages/reactive/src/model.ts @@ -2,20 +2,24 @@ import { each, isFn, reduce } from '@formily/shared' import { buildTreeNode } from './traverse' import { observable } from './observable' import { createObservable, getObservableMaker } from './internals' -import { isObservable, isAnnotation } from './shared' +import { isObservable, isAnnotation, isSupportObservable } from './shared' import { Annotations } from './types' import { batch } from './batch' +import { ProxyRaw, RawProxy } from './environment' -export function defineModel( +export function define( target: Target, annotations?: Annotations, traverse = createObservable ) { if (isObservable(target)) return target + if (!isSupportObservable(target)) return target buildTreeNode({ value: target, traverse, }) + ProxyRaw.set(target, target) + RawProxy.set(target, target) return observable(target, ({ target, value }) => { if (target) return target each(annotations, (annotation, key) => { @@ -42,5 +46,5 @@ export function model(target: Target) { } return buf }, {}) - return defineModel(target, annotations) + return define(target, annotations) }