开篇
使用过 React 技术栈的同学相信都使用过 ref 传递给 render 中的元素,而在使用 React 封装组件时,会有这样一个场景:
组件将 props.children 作为 render 内容;组件内部会创建 ref 绑定到 props.children 上。
我们知道,元素上只能绑定一个 ref
属性引用,但对于上面这个场景,props.children 上可能已经存在一个 ref
属性,而组件内部定义的 ref
也会绑定到 props.children 上。
我们要想一种方式,将两者的 ref 都可以生效于元素上。
思路
首先我们回顾一下 React 创建 ref 的方式:
- React.createRef():React 16.3 版本提供的 class 创建 ref 方式;
- React.useRef():React Hooks 提供的 函数组件 创建 ref 方式;
- 回调 Refs:传递一个函数作为元素的 ref 属性,此函数接收 React 组件实例或 HTML DOM 元素作为参数。
综合考虑,既然 回调 Refs
允许我们传递一个函数,并且接收元素实例作为这个函数的参数,那我们就可以定义一个这样的函数,在函数内编写我们的逻辑来处理 多个 ref
绑定元素实例的场景。(函数的灵活性)
实现
- 编写一个函数(闭包函数),接收
props.children.ref
和组件内 ref
作为参数; - 函数(闭包函数)需要
return
返回一个函数,这个函数将作为回调 Refs
去作用于元素; - 在
return
的这个函数中,将函数参数(元素引用)绑定到props.children.ref
和组件内 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 – useForkRef
在 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)
复制代码
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END