时间:2023-01-23 08:25:30 | 栏目: | 点击:次
使用过 React 技术栈的同学相信都使用过 ref 传递给 render 中的元素,而在使用 React 封装组件时,会有这样一个场景:
组件将 props.children 作为 render 内容;组件内部会创建 ref 绑定到 props.children 上。
我们知道,元素上只能绑定一个 ref 属性引用,但对于上面这个场景,props.children 上可能已经存在一个 ref 属性,而组件内部定义的 ref 也会绑定到 props.children 上。
我们要想一种方式,将两者的 ref 都可以生效于元素上。
首先我们回顾一下 React 创建 ref 的方式:
综合考虑,既然 回调 Refs 允许我们传递一个函数,并且接收元素实例作为这个函数的参数,那我们就可以定义一个这样的函数,在函数内编写我们的逻辑来处理 多个 ref 绑定元素实例的场景。(函数的灵活性)
上代码:
function forkRef(refA, refB) { return refValue => { setRef(refA, refValue); setRef(refB, refValue); }; } function setRef(ref, value) { if (typeof ref === 'function') { ref(value); } else if (ref) { ref.current = value; } }
在 setRef 中会针对创建 ref 的方式做不同处理,比如:React.createRef 和 React.useRef 创建的 ref 是一个具有 current 属性的对象。
使用:
const nodeRef = React.useRef(null); // 组件内部的 ref const handleRef = forkRef(props.children.ref, nodeRef); const childrenProps = { ref: handleRef }; return React.cloneElement(children, childrenProps);
在 Hook 函数组件中,我们可以借助于 React.memo() 优化一下 forkRef() 的逻辑,避免每次组件更新时都创建一个新的闭包函数。
下面我们使用 TS 编写一个 useForkRef:
import * as React from 'react'; interface MutableRefObject<T> { current: T; } type Ref<T> = ((instance: T | null) => void) | MutableRefObject<T> | null; export function setRef(ref: Ref<unknown>, value: unknown) { if (typeof ref === 'function') { ref(value); } else if (ref) { ref.current = value; } } export default function useForkRef(refA: Ref<unknown>, refB: Ref<unknown>) { return React.useMemo(() => { if (refA == null && refB == null) { return null; } return (refValue: unknown) => { setRef(refA, refValue); setRef(refB, refValue); }; }, [refA, refB]); }
使用:
const nodeRef = React.useRef<HTMLElement>(null); // 组件内部的 ref const handleRef = useForkRef(children.ref, nodeRef); const childrenProps: any = { ref: handleRef }; React.cloneElement(children, childrenProps)