《你不知道的React》-useRef

155 阅读2分钟

小而美的系列教程,周末定时更新,后续会推出基于 React 从 0~1 开发的三方插件,如果有收获,记得给个点赞。

有过 React 开发经验的同学应该都用过 useRef ,你一般在什么场景使用?

说几个比较常用的,欢迎补充。

  1. 获取元素
  2. 获取子组件元素
  3. 防止闭包问题

获取元素

这应该是比较常见的了,例如 input 自动聚焦,当页面加载完成时,input 元素可自动聚焦。

const App = () => {
  const ref = useRef<HTMLInputElement>(null);
  // returns: { current: null }

  /** 获取元素 */
  useEffect(() => {
    ref.current?.focus();
  }, []);

  return (
    <>
      <input type="text" ref={ref} />
    </>
  );
};

export default App;

当然 ref 也支持回调

const callback = (ele: HTMLInputElement) => ele.focus();

<input type="text" ref={callback} />

获取子组件元素

和前者比较类似,由于 ref 在 React 里面属于 key code,默认情况下是不能直接作为 props 传递的,需要借助 forwardRef。

const Counter = forwardRef((props, ref: React.ForwardedRef<any>) => {
  return <input type="text" ref={ref} />;
});

/** 获取子组件元素 */
const App = () => {
  const ref = useRef<HTMLInputElement>(null);
  const handleOnClick = () => ref.current?.focus();

  return (
    <>
      <Counter ref={ref} />
      <button onClick={handleOnClick}>focus</button>
    </>
  );
};

export default App;

这样就可以获取到对应的 input 元素;我们可以更进一步,如果想获取子组件对应的方法或属性呢?

在不用 props callback 的情况下,可以借助 useImperativeHandle 实现:

const Counter = forwardRef((props, ref: React.ForwardedRef<any>) => {
  const componentName = "Counter";
  useImperativeHandle(ref, () => ({
    componentName,
    xx: () => 'xx',
  }));
  
  return <div ref={ref}>children</div>;
});

上层通过 ref.current 即可获取到对应返回值

image.png

防止闭包问题

我们在使用三方组件时,由于不清楚内部实现,有时会出现意想不到的结果。 例如下面这个示例,不论我们点击多少次 change data , 当我们点击 log 时, 打印的结果永远是 []

const SomeComponent = ({ onReady }: { onReady: () => void }) => {
  const onClick = useCallback(onReady, []);
  return <button onClick={() => onClick()}>log</button>;
};

const App = () => {
  const [data, setData] = useState<number[]>([]);

  const onReady = () => {
    console.log(data);
  };

  return (
    <>
      <p>{data.length}</p>
      <SomeComponent onReady={onReady} />
      <button onClick={()=> setData(data.concat([2]))}>change data</button>
    </>
  );
};

export default App;

对于这类问题,我们可以通过 ref 来获取最新数据,原因在于 useRef 返回的是 { current: null },将对应数据赋值给 current,在声明之后,引用地址是不变的。

const App = () => {
  const [data, setData] = useState<number[]>([]);
  const refData = useRef<number[]>(null);
  
  refData.current = data;
  const onReady = () => {
    console.log(refData.current);
  };
};

如果你意犹未尽:beta.reactjs.org/learn/manip…