From c89b112868f131c8e2aae710dc3cf4a35143896e Mon Sep 17 00:00:00 2001 From: linbudu599 Date: Mon, 17 Jul 2023 09:42:51 +0800 Subject: [PATCH 1/3] chore: weex support for ice appear --- .changeset/dry-pets-sing.md | 5 ++ packages/appear/src/index.tsx | 47 ++++--------------- packages/appear/src/runtime.d.ts | 11 +++++ packages/appear/src/type.ts | 3 -- packages/appear/src/typings.ts | 17 +++++++ packages/appear/src/web/index.tsx | 44 +++++++++++++++++ .../src/{ => web}/intersection-observer.ts | 0 packages/appear/src/{ => web}/visibility.ts | 0 packages/appear/src/weex/index.tsx | 34 ++++++++++++++ 9 files changed, 121 insertions(+), 40 deletions(-) create mode 100644 .changeset/dry-pets-sing.md create mode 100644 packages/appear/src/runtime.d.ts delete mode 100644 packages/appear/src/type.ts create mode 100644 packages/appear/src/typings.ts create mode 100644 packages/appear/src/web/index.tsx rename packages/appear/src/{ => web}/intersection-observer.ts (100%) rename packages/appear/src/{ => web}/visibility.ts (100%) create mode 100644 packages/appear/src/weex/index.tsx diff --git a/.changeset/dry-pets-sing.md b/.changeset/dry-pets-sing.md new file mode 100644 index 0000000000..e7bbda96be --- /dev/null +++ b/.changeset/dry-pets-sing.md @@ -0,0 +1,5 @@ +--- +'@ice/appear': minor +--- + +chore: weex support for ice appear diff --git a/packages/appear/src/index.tsx b/packages/appear/src/index.tsx index 296dff26ff..8223d6947d 100644 --- a/packages/appear/src/index.tsx +++ b/packages/appear/src/index.tsx @@ -1,41 +1,14 @@ -import { Children, useRef, useEffect, useCallback } from 'react'; -import type { Ref } from 'react'; -import { isFunction } from './type'; -import { observerElement, VisibilityChangeEvent } from './visibility'; +import type * as React from 'react'; +import WebAppear from './web'; +import WeexAppear from './weex'; +import type { AppearProps } from './typings.js'; -function VisibilityChange(props: any) { - const { - onAppear, - onDisappear, - children, - } = props; +let Appear: React.ForwardRefExoticComponent>; - const defaultRef: Ref = useRef(); - const ref: Ref = (children && children.ref) ? children.ref : defaultRef; - - const listen = useCallback((eventName: string, handler: Function) => { - const { current } = ref; - // Rax components will set custom ref by useImperativeHandle. - // So We should get eventTarget by _nativeNode. - // https://github.com/raxjs/rax-components/blob/master/packages/rax-textinput/src/index.tsx#L151 - if (current && isFunction(handler)) { - const eventTarget = current._nativeNode || current; - observerElement(eventTarget as Element); - eventTarget.addEventListener(eventName, handler); - } - return () => { - const { current } = ref; - if (current) { - const eventTarget = current._nativeNode || current; - eventTarget.removeEventListener(eventName, handler); - } - }; - }, [ref]); - - useEffect(() => listen(VisibilityChangeEvent.appear, onAppear), [ref, onAppear, listen]); - useEffect(() => listen(VisibilityChangeEvent.disappear, onDisappear), [ref, onDisappear, listen]); - - return Children.only({ ...children, ref }); +if (import.meta.target === 'weex') { + Appear = WeexAppear; +} else { + Appear = WebAppear as any; } -export default VisibilityChange; +export default Appear; diff --git a/packages/appear/src/runtime.d.ts b/packages/appear/src/runtime.d.ts new file mode 100644 index 0000000000..2f3fcc3945 --- /dev/null +++ b/packages/appear/src/runtime.d.ts @@ -0,0 +1,11 @@ +/// + +interface ImportMeta { + // The build target + target: 'weex' | 'web'; +} + + +interface Node { + _nativeNode: Node; +} diff --git a/packages/appear/src/type.ts b/packages/appear/src/type.ts deleted file mode 100644 index 31715de310..0000000000 --- a/packages/appear/src/type.ts +++ /dev/null @@ -1,3 +0,0 @@ -export function isFunction(obj: any): obj is Function { - return typeof obj === 'function'; -} \ No newline at end of file diff --git a/packages/appear/src/typings.ts b/packages/appear/src/typings.ts new file mode 100644 index 0000000000..bddb24c7e2 --- /dev/null +++ b/packages/appear/src/typings.ts @@ -0,0 +1,17 @@ +import type * as React from 'react'; + +export interface AppearProps { + children: React.ReactElement; + /** + * 元素进入可视区域触发的事件 + * @param {CustomEvent} e + * @returns {void} + */ + onAppear?: (e: CustomEvent) => void; + /** + * 元素离开可视区域触发的事件 + * @param {CustomEvent} e + * @returns {void} + */ + onDisappear?: (e: CustomEvent) => void; +} diff --git a/packages/appear/src/web/index.tsx b/packages/appear/src/web/index.tsx new file mode 100644 index 0000000000..03569536db --- /dev/null +++ b/packages/appear/src/web/index.tsx @@ -0,0 +1,44 @@ +import { Children, useRef, useEffect, useCallback } from 'react'; +import type { MutableRefObject } from 'react'; + +import { observerElement, VisibilityChangeEvent } from './visibility'; + +export function isFunction(obj: any): obj is Function { + return typeof obj === 'function'; +} + +function VisibilityChange(props: any) { + const { onAppear, onDisappear, children } = props; + + const defaultRef: MutableRefObject = useRef(); + const ref: MutableRefObject = children && children.ref ? children.ref : defaultRef; + + const listen = useCallback( + (eventName: string, handler: () => {}) => { + const { current } = ref; + // Rax components will set custom ref by useImperativeHandle. + // So We should get eventTarget by _nativeNode. + // https://github.com/raxjs/rax-components/blob/master/packages/rax-textinput/src/index.tsx#L151 + if (current && isFunction(handler)) { + const eventTarget = current._nativeNode || current; + observerElement(eventTarget as Element); + eventTarget.addEventListener(eventName, handler); + } + return () => { + const { current } = ref; + if (current) { + const eventTarget = current._nativeNode || current; + eventTarget.removeEventListener(eventName, handler); + } + }; + }, + [ref], + ); + + useEffect(() => listen(VisibilityChangeEvent.appear, onAppear), [ref, onAppear, listen]); + useEffect(() => listen(VisibilityChangeEvent.disappear, onDisappear), [ref, onDisappear, listen]); + + return Children.only({ ...children, ref }); +} + +export default VisibilityChange; diff --git a/packages/appear/src/intersection-observer.ts b/packages/appear/src/web/intersection-observer.ts similarity index 100% rename from packages/appear/src/intersection-observer.ts rename to packages/appear/src/web/intersection-observer.ts diff --git a/packages/appear/src/visibility.ts b/packages/appear/src/web/visibility.ts similarity index 100% rename from packages/appear/src/visibility.ts rename to packages/appear/src/web/visibility.ts diff --git a/packages/appear/src/weex/index.tsx b/packages/appear/src/weex/index.tsx new file mode 100644 index 0000000000..6f70319bd7 --- /dev/null +++ b/packages/appear/src/weex/index.tsx @@ -0,0 +1,34 @@ +import { useEffect, useRef, forwardRef, cloneElement, Children } from 'react'; +import type { AppearProps } from '../typings'; + +const WeexAppear = forwardRef((props, ref) => { + const childrenRef = ref || useRef(null); + const { children, onAppear, onDisappear } = props; + + useEffect(() => { + onAppear && + typeof childrenRef === 'object' && + childrenRef.current?.addEventListener('appear', (e: CustomEvent) => onAppear(e)); + return () => { + onAppear && + typeof childrenRef === 'object' && + childrenRef.current?.removeEventListener('appear', (e: CustomEvent) => onAppear(e)); + }; + }, []); + + useEffect(() => { + onDisappear && + typeof childrenRef === 'object' && + childrenRef.current?.addEventListener('disappear', (e: CustomEvent) => onDisappear(e)); + + return () => { + onDisappear && + typeof childrenRef === 'object' && + childrenRef.current?.removeEventListener('disappear', (e: CustomEvent) => onDisappear(e)); + }; + }, []); + + return cloneElement(Children.only(children), { ref: childrenRef }); +}); + +export default WeexAppear; From 21b85f1447be563ebf7eb557ebc882df11100cf6 Mon Sep 17 00:00:00 2001 From: linbudu599 Date: Tue, 18 Jul 2023 21:17:11 +0800 Subject: [PATCH 2/3] fix(appear): use copy of ref for valid ref value in useEffect cleanups --- packages/appear/src/typings.ts | 4 ++-- packages/appear/src/weex/index.tsx | 26 +++++++++++++------------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/packages/appear/src/typings.ts b/packages/appear/src/typings.ts index bddb24c7e2..d65714c961 100644 --- a/packages/appear/src/typings.ts +++ b/packages/appear/src/typings.ts @@ -3,13 +3,13 @@ import type * as React from 'react'; export interface AppearProps { children: React.ReactElement; /** - * 元素进入可视区域触发的事件 + * Triggered when the element enters the visible area. * @param {CustomEvent} e * @returns {void} */ onAppear?: (e: CustomEvent) => void; /** - * 元素离开可视区域触发的事件 + * Triggered when the element leaves the visible area. * @param {CustomEvent} e * @returns {void} */ diff --git a/packages/appear/src/weex/index.tsx b/packages/appear/src/weex/index.tsx index 6f70319bd7..76410d3fd8 100644 --- a/packages/appear/src/weex/index.tsx +++ b/packages/appear/src/weex/index.tsx @@ -1,30 +1,30 @@ +import type { ForwardedRef } from 'react'; import { useEffect, useRef, forwardRef, cloneElement, Children } from 'react'; import type { AppearProps } from '../typings'; const WeexAppear = forwardRef((props, ref) => { - const childrenRef = ref || useRef(null); + const childrenRef: ForwardedRef = ref ?? useRef(null); const { children, onAppear, onDisappear } = props; useEffect(() => { - onAppear && - typeof childrenRef === 'object' && - childrenRef.current?.addEventListener('appear', (e: CustomEvent) => onAppear(e)); + // Use copy of childrenRef to avoid ref value changed in cleanup phase. + const nodeRef = typeof childrenRef === 'object' ? childrenRef.current : null; + + // Return early if onAppear callback not specified. + onAppear && nodeRef?.addEventListener('appear', (e: CustomEvent) => onAppear(e)); + return () => { - onAppear && - typeof childrenRef === 'object' && - childrenRef.current?.removeEventListener('appear', (e: CustomEvent) => onAppear(e)); + onAppear && nodeRef?.removeEventListener('appear', (e: CustomEvent) => onAppear(e)); }; }, []); useEffect(() => { - onDisappear && - typeof childrenRef === 'object' && - childrenRef.current?.addEventListener('disappear', (e: CustomEvent) => onDisappear(e)); + const nodeRef = typeof childrenRef === 'object' ? childrenRef.current : null; + + onDisappear && nodeRef?.addEventListener('disappear', (e: CustomEvent) => onDisappear(e)); return () => { - onDisappear && - typeof childrenRef === 'object' && - childrenRef.current?.removeEventListener('disappear', (e: CustomEvent) => onDisappear(e)); + onDisappear && nodeRef?.removeEventListener('disappear', (e: CustomEvent) => onDisappear(e)); }; }, []); From 99ff43d2ce943f8ce098b4dd7f7a49415a72cecd Mon Sep 17 00:00:00 2001 From: linbudu599 Date: Wed, 26 Jul 2023 14:34:31 +0800 Subject: [PATCH 3/3] chore: fixup ref usage --- packages/appear/src/weex/index.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/appear/src/weex/index.tsx b/packages/appear/src/weex/index.tsx index 76410d3fd8..79af3c66f3 100644 --- a/packages/appear/src/weex/index.tsx +++ b/packages/appear/src/weex/index.tsx @@ -3,7 +3,9 @@ import { useEffect, useRef, forwardRef, cloneElement, Children } from 'react'; import type { AppearProps } from '../typings'; const WeexAppear = forwardRef((props, ref) => { - const childrenRef: ForwardedRef = ref ?? useRef(null); + const internalRef = useRef(null); + const childrenRef: ForwardedRef = ref ?? internalRef; + const { children, onAppear, onDisappear } = props; useEffect(() => { @@ -16,7 +18,7 @@ const WeexAppear = forwardRef((props, ref) => { return () => { onAppear && nodeRef?.removeEventListener('appear', (e: CustomEvent) => onAppear(e)); }; - }, []); + }, [childrenRef, onAppear]); useEffect(() => { const nodeRef = typeof childrenRef === 'object' ? childrenRef.current : null; @@ -26,7 +28,7 @@ const WeexAppear = forwardRef((props, ref) => { return () => { onDisappear && nodeRef?.removeEventListener('disappear', (e: CustomEvent) => onDisappear(e)); }; - }, []); + }, [childrenRef, onDisappear]); return cloneElement(Children.only(children), { ref: childrenRef }); });