diff --git a/packages/react/src/__tests__/field.spec.tsx b/packages/react/src/__tests__/field.spec.tsx index 4dd88e364b1..ad4a4dc39ae 100644 --- a/packages/react/src/__tests__/field.spec.tsx +++ b/packages/react/src/__tests__/field.spec.tsx @@ -131,7 +131,7 @@ test('ReactiveField', () => { render({() =>
}
) }) -test('useAttach', () => { +test('useAttach basic', async () => { const form = createForm() const MyComponent = (props: any) => { return ( @@ -143,8 +143,10 @@ test('useAttach', () => { const { rerender } = render() expect(form.query('aa').take().mounted).toBeTruthy() rerender() - expect(form.query('aa').take().mounted).toBeFalsy() - expect(form.query('bb').take().mounted).toBeTruthy() + await waitFor(() => { + expect(form.query('aa').take().mounted).toBeFalsy() + expect(form.query('bb').take().mounted).toBeTruthy() + }) }) test('useAttach with array field', async () => { diff --git a/packages/react/src/hooks/useAttach.ts b/packages/react/src/hooks/useAttach.ts index fae7ad9269c..6c3dcf926ad 100644 --- a/packages/react/src/hooks/useAttach.ts +++ b/packages/react/src/hooks/useAttach.ts @@ -1,12 +1,11 @@ -import { useEffect } from 'react' - +import { unstable_useCompatEffect } from '@formily/reactive-react' interface IRecycleTarget { onMount: () => void onUnmount: () => void } export const useAttach = (target: T): T => { - useEffect(() => { + unstable_useCompatEffect(() => { target.onMount() return () => target.onUnmount() }, [target]) diff --git a/packages/react/src/hooks/useFormEffects.ts b/packages/react/src/hooks/useFormEffects.ts index 98e11c86ffc..cc2552fef38 100644 --- a/packages/react/src/hooks/useFormEffects.ts +++ b/packages/react/src/hooks/useFormEffects.ts @@ -1,22 +1,17 @@ -import { useLayoutEffect, useMemo } from 'react' +import { unstable_useCompatFactory } from '@formily/reactive-react' import { Form } from '@formily/core' import { uid } from '@formily/shared' import { useForm } from './useForm' export const useFormEffects = (effects?: (form: Form) => void) => { const form = useForm() - const ref = useMemo(() => { + unstable_useCompatFactory(() => { const id = uid() form.addEffects(id, effects) - const request = setTimeout(() => { - form.removeEffects(id) - }, 100) - return { id, request } - }, []) - useLayoutEffect(() => { - clearTimeout(ref.request) - return () => { - form.removeEffects(ref.id) + return { + dispose() { + form.removeEffects(id) + }, } - }, []) + }) } diff --git a/packages/reactive-react/src/hooks/index.ts b/packages/reactive-react/src/hooks/index.ts index d0b6e5be858..8c8e0b9eb04 100644 --- a/packages/reactive-react/src/hooks/index.ts +++ b/packages/reactive-react/src/hooks/index.ts @@ -1,2 +1,13 @@ -export * from './useForceUpdate' -export * from './useObserver' +import { useForceUpdate } from './useForceUpdate' +import { useCompatEffect } from './useCompatEffect' +import { useCompatFactory } from './useCompatFactory' +import { useDidUpdate } from './useDidUpdate' +import { useLayoutEffect } from './useLayoutEffect' +import { useObserver } from './useObserver' + +export const unstable_useForceUpdate = useForceUpdate +export const unstable_useCompatEffect = useCompatEffect +export const unstable_useCompatFactory = useCompatFactory +export const unstable_useDidUpdate = useDidUpdate +export const unstable_useLayoutEffect = useLayoutEffect +export const unstable_useObserver = useObserver diff --git a/packages/reactive-react/src/hooks/useCompatEffect.ts b/packages/reactive-react/src/hooks/useCompatEffect.ts new file mode 100644 index 00000000000..3458d0c0ae4 --- /dev/null +++ b/packages/reactive-react/src/hooks/useCompatEffect.ts @@ -0,0 +1,39 @@ +import { useEffect, useRef, EffectCallback, DependencyList } from 'react' +import { immediate } from '../shared' + +const isArr = Array.isArray + +const isEqualDeps = (target: any, source: any) => { + const arrA = isArr(target) + const arrB = isArr(source) + if (arrA !== arrB) return false + if (arrA) { + if (target.length !== source.length) return false + return target.every((val, index) => val === source[index]) + } + return target === source +} + +export const useCompatEffect = ( + effect: EffectCallback, + deps?: DependencyList +) => { + const depsRef = useRef(null) + const mountedRef = useRef(false) + useEffect(() => { + mountedRef.current = true + const dispose = effect() + return () => { + mountedRef.current = false + if (!isEqualDeps(depsRef.current, deps)) { + if (dispose) dispose() + return + } + immediate(() => { + if (mountedRef.current) return + if (dispose) dispose() + }) + } + }, deps) + depsRef.current = deps +} diff --git a/packages/reactive-react/src/hooks/useCompatFactory.ts b/packages/reactive-react/src/hooks/useCompatFactory.ts new file mode 100644 index 00000000000..baffbabd4d8 --- /dev/null +++ b/packages/reactive-react/src/hooks/useCompatFactory.ts @@ -0,0 +1,42 @@ +import React from 'react' +import { GarbageCollector } from '../shared' +import { useCompatEffect } from './useCompatEffect' + +class ObjectToBeRetainedByReact {} + +function objectToBeRetainedByReactFactory() { + return new ObjectToBeRetainedByReact() +} + +export const useCompatFactory = void }>( + factory: () => T +): T => { + const instRef = React.useRef(null) + const gcRef = React.useRef() + const [objectRetainedByReact] = React.useState( + objectToBeRetainedByReactFactory + ) + if (!instRef.current) { + instRef.current = factory() + } + //StrictMode/ConcurrentMode会导致组件无法正确触发UnMount,所以只能自己做垃圾回收 + if (!gcRef.current) { + gcRef.current = new GarbageCollector(() => { + if (instRef.current) { + instRef.current.dispose() + } + }) + gcRef.current.open(objectRetainedByReact) + } + + useCompatEffect(() => { + gcRef.current.close() + return () => { + if (instRef.current) { + instRef.current.dispose() + instRef.current = null + } + } + }, []) + return instRef.current +} diff --git a/packages/reactive-react/src/hooks/useObserver.ts b/packages/reactive-react/src/hooks/useObserver.ts index 9a2aead221c..787e30faa29 100644 --- a/packages/reactive-react/src/hooks/useObserver.ts +++ b/packages/reactive-react/src/hooks/useObserver.ts @@ -1,59 +1,22 @@ -import React from 'react' import { Tracker } from '@formily/reactive' -import { GarbageCollector, immediate } from '../shared' import { IObserverOptions } from '../types' import { useForceUpdate } from './useForceUpdate' - -class ObjectToBeRetainedByReact {} - -function objectToBeRetainedByReactFactory() { - return new ObjectToBeRetainedByReact() -} +import { useCompatFactory } from './useCompatFactory' export const useObserver = any>( view: T, options?: IObserverOptions ): ReturnType => { const forceUpdate = useForceUpdate() - const mountedRef = React.useRef(false) - const trackerRef = React.useRef(null) - const gcRef = React.useRef() - const [objectRetainedByReact] = React.useState( - objectToBeRetainedByReactFactory + const tracker = useCompatFactory( + () => + new Tracker(() => { + if (typeof options?.scheduler === 'function') { + options.scheduler(forceUpdate) + } else { + forceUpdate() + } + }, options?.displayName) ) - if (!trackerRef.current) { - trackerRef.current = new Tracker(() => { - if (typeof options?.scheduler === 'function') { - options.scheduler(forceUpdate) - } else { - forceUpdate() - } - }, options?.displayName) - } - - //StrictMode/ConcurrentMode会导致组件无法正确触发UnMount,所以只能自己做垃圾回收 - if (!gcRef.current) { - gcRef.current = new GarbageCollector(() => { - if (trackerRef.current) { - trackerRef.current.dispose() - } - }) - gcRef.current.open(objectRetainedByReact) - } - - React.useEffect(() => { - const dispose = () => { - if (trackerRef.current && !mountedRef.current) { - trackerRef.current.dispose() - trackerRef.current = null - } - } - mountedRef.current = true - gcRef.current.close() - return () => { - mountedRef.current = false - immediate(dispose) - } - }, []) - return trackerRef.current.track(view) + return tracker.track(view) } diff --git a/packages/reactive-react/src/index.ts b/packages/reactive-react/src/index.ts index 198914a10ce..670ef4f7817 100644 --- a/packages/reactive-react/src/index.ts +++ b/packages/reactive-react/src/index.ts @@ -1,2 +1,3 @@ export * from './observer' +export * from './hooks' export * from './types' diff --git a/packages/reactive-react/src/observer.ts b/packages/reactive-react/src/observer.ts index 9d31b79af49..94015bb1cbe 100644 --- a/packages/reactive-react/src/observer.ts +++ b/packages/reactive-react/src/observer.ts @@ -1,6 +1,6 @@ import React, { forwardRef, memo, Fragment } from 'react' import hoistNonReactStatics from 'hoist-non-react-statics' -import { useObserver } from './hooks' +import { useObserver } from './hooks/useObserver' import { IObserverOptions, IObserverProps, ReactFC } from './types' export function observer<