近日,我在 react 中封装一个"划词翻译功能"时,遇到一个使用 ref 的问题。解决了很长时间。
我们平常在使用 useRef 获取Dom元素,并且直接在 useEffect 中,立马就可以使用
function Ap() {
const ref = React.useRef();
React.useEffect(() => {
// 通常这里是可以直接获取到的
console.log(ref.current); // input Dom
}, []);
return (
<label>
<input type="text" ref={ref} />
</label>
);
}
但是,我遇到的情况是这样的(手撸代码,不必运行,好好看)
function App() {
const [state, setState] = useState(false);
useEffect(() => {
// 异步加载数据后
setState(true);
}, [])
const ref = useRef();
useEffect(() => {
// 拿不到了吧
console.log(ref.current); // undefined
}, []);
// jsx中条件未满足,所以没有createElement,所以拿不到ref
return (
<label>
{state && <input type="text" ref={ref} />}
</label>
);
}
那么,我们在封装功能的时候,需要用 ref (一开始就要拿,且保证只拿一次) 怎么办呢?
-
使用
callbackRef用回调函数的方式获取 ref,进而保存使用因为
callbackRef会在真实Dom生成时执行
官网提示:
- React will call the ref callback with the DOM element when the component mounts, and call it with null when it unmounts.
function App() {
const [state, setState] = useState(false);
useEffect(() => {
// 异步加载数据后
setState(true);
}, [])
const domRef = useRef();
const onMouseUp = async (e) => {
console.log(e);
};
// 通过 callbackRef,在组件挂载后,操作该DOM
// 注意:还要用useCallback来包装函数,不是为了性能优化,
// 而是为了不生成新的回调函数让diff对比时发现差异,再次执行回调
const callbackRef = useCallback((ref) => {
// 并且卸载组件时,会再次传入null调用该函数,会引发报错
// 所以:遇到null阻止运行
if (!ref) return;
// 给dom绑定事件
ref.addEventListener("mouseup", onMouseUp);
// 保留ref,便于组件卸载时清除副作用
domRef.current = ref;
}, []);
useEffect(() => {
return () => {
// 清除事件
domRef.current.removeEventListener("mouseup", onMouseUp);
};
}, []);
// jsx中条件未满足,所以没有createElement,所以拿不到ref
return (
<label>
{state && <input type="text" ref={callbackRef} />}
</label>
);
}
-
注意:
- 回调函数要用
useCallback缓存,避免生成新的ref让Diff对比时,以为发现新的差异 - 卸载组件时,会再次传入null调用
callbackRef,要注意规避报错
- 回调函数要用
总结:
callbackRef相较于 直接给值 的方式还是有一些特殊用处的。