当 ref 指向 DOM 元素时,使用 ref.current 作为 useEffect 的依赖是否安全?

新手上路,请多包涵

我知道 ref 是一个可变容器,因此它不应该列在 useEffect 的依赖项中,但是 ref.current 可能是一个变化的值。

当 ref 用于存储 <div ref={ref}> 类的 DOM 元素时,并且当我开发一个依赖于该元素的自定义钩子时,假设 ref.current 如果组件有条件返回,则会随时间变化喜欢:

 const Foo = ({inline}) => {
  const ref = useRef(null);
  return inline ? <span ref={ref} /> : <div ref={ref} />;
};

我的自定义效果接收 ref 对象并使用 ref.current 作为依赖项是否安全?

 const useFoo = ref => {
  useEffect(
    () => {
      const element = ref.current;
      // Maybe observe the resize of element
    },
    [ref.current]
  );
};

我读过 这条评论 说 ref 应该在 useEffect 中使用,但我无法弄清楚 ref.current 改变但不会触发效果的任何情况。

正如那个问题所建议的,我应该使用回调引用,但是作为参数的引用对于集成多个钩子非常友好:

 const ref = useRef(null);
useFoo(ref);
useBar(ref);

虽然回调 ref 更难使用,因为用户被强制编写它们:

 const fooRef = useFoo();
const barRef = useBar();
const ref = element => {
  fooRef(element);
  barRef(element);
};

<div ref={ref} />

这就是为什么我要问在 useEffect ref.current 是否安全。

原文由 otakustay 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 1k
1 个回答

这是不安全的,因为改变引用 不会触发 render ,因此 不会触发 useEffect

React Hook useEffect 有一个不必要的依赖:’ref.current’。排除它或删除依赖项数组。像 ‘ref.current’ 这样的可变值不是有效的依赖项,因为改变它们不会重新渲染组件。 (反应挂钩/详尽的依赖)

反模式示例:

 const Foo = () => {
  const [, render] = useReducer(p => !p, false);
  const ref = useRef(0);

  const onClickRender = () => {
    ref.current += 1;
    render();
  };

  const onClickNoRender = () => {
    ref.current += 1;
  };

  useEffect(() => {
    console.log('ref changed');
  }, [ref.current]);

  return (
    <>
      <button onClick={onClickRender}>Render</button>
      <button onClick={onClickNoRender}>No Render</button>
    </>
  );
};

编辑 xenodochial-snowflake-hhgr6


与此模式相关的一个 现实生活用例 是当我们想要有一个持久引用时, 即使元素卸载了

检查下一个示例,其中卸载时我们无法坚持元素大小调整。我们将尝试使用 useRefuseEffect 上面的组合, 但它不起作用

 // BAD EXAMPLE, SEE SOLUTION BELOW
const Component = () => {
  const ref = useRef();

  const [isMounted, toggle] = useReducer((p) => !p, true);
  const [elementRect, setElementRect] = useState();

  useEffect(() => {
    console.log(ref.current);
    setElementRect(ref.current?.getBoundingClientRect());
  }, [ref.current]);

  return (
    <>
      {isMounted && <div ref={ref}>Example</div>}
      <button onClick={toggle}>Toggle</button>
      <pre>{JSON.stringify(elementRect, null, 2)}</pre>
    </>
  );
};

编辑 Bad-Example,Ref 不处理卸载


令人惊讶的是,要修复它,我们需要直接处理 node ,同时使用 useCallback 函数:

 // GOOD EXAMPLE
const Component = () => {
  const [isMounted, toggle] = useReducer((p) => !p, true);
  const [elementRect, setElementRect] = useState();

  const handleRect = useCallback((node) => {
    setElementRect(node?.getBoundingClientRect());
  }, []);

  return (
    <>
      {isMounted && <div ref={handleRect}>Example</div>}
      <button onClick={toggle}>Toggle</button>
      <pre>{JSON.stringify(elementRect, null, 2)}</pre>
    </>
  );
};

编辑好例子,直接处理节点

原文由 Dennis Vash 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
logo
Stack Overflow 翻译
子站问答
访问
宣传栏