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<