近日,我在 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 targetRef = 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={ref} />}
  </label>
  );
}
  • 注意:

    • 回调函数要用useCallback缓存,防止生成新的 ref 让Diff比照时,认为发现新的差异
    • 卸载组件时,会再次传入null调用callbackRef,要注意规避报错

总结:

  • callbackRef 相较于 直接给值 的方法还是有一些特殊用途的。