【问题标题】:Is it safe to use a useState "setter" function as a callback ref?使用 useState “setter” 函数作为回调 ref 是否安全?
【发布时间】:2020-04-02 06:26:17
【问题描述】:

使用useState 钩子的setter 函数作为回调ref 函数是否安全?这会对 Suspense 或其他即将到来的 React 更改造成麻烦吗?如果“是的,这没关系”,那就太酷了!如果“不”为什么不呢?如果“也许”,那么什么时候可以,什么时候不行?

我问是因为我的一个组件需要安装三个 ref 才能调用 DOM API。其中两个必需的引用是通过 JSX ref 属性在同一组件中分配的“普通”引用。稍后将通过 React 上下文将另一个 ref 分配到深度嵌套的组件中。我需要一种方法来在所有三个 ref 都安装后强制重新渲染父组件,并在卸载任何 ref 时强制执行 useEffect 清理。

最初我编写了自己的回调引用处理程序,它调用了我存储在上下文提供程序中的 useState 设置器。但后来我意识到useState setter 做了我自己的回调 ref 所做的一切。仅使用 setter 而不是编写我自己的回调 ref 函数是否安全?还是有更好和/或更安全的方法来做我想做的事情?

我尝试用谷歌搜索 "useState" "callback ref"(和其他类似的关键字变体),但结果没有帮助,除了 @theKashey 的优秀 use-callback-ref 包,我肯定会在其他地方使用(例如,当我需要通过回调 ref 到需要 RefObject 的组件,或者当我既需要回调又需要在本地使用 ref 时)但在这种情况下,所有回调需要做的就是在 ref 更改时设置一个状态变量,所以 Anton 的包似乎有点矫枉过正这里。

下面是一个简化的示例,位于https://codesandbox.io/s/dreamy-shockley-5dc74

import * as React from 'react';
import { useState, forwardRef, useEffect, createContext, useContext, useMemo } from 'react';
import { render } from 'react-dom';

const Child = forwardRef((props, ref) => {
  return <div ref={ref}>This is a regular child component</div>;
});

const refContext = createContext();
const ContextUsingChild = props => {
  const { setValue } = useContext(refContext);
  return <div ref={setValue}>This child uses context</div>;
};

const Parent = () => {
  const [child1, setChild1] = useState(null);
  const [child2, setChild2] = useState(null);
  const [child3, setChild3] = useState(null);

  useEffect(() => {
    if (child1 && child2) {
      console.log(`Child 1 text: ${child1.innerText}`);
      console.log(`Child 2 text: ${child2.innerText}`);
      console.log(`Child 3 text: ${child3.innerText}`);
    } else {
      console.log(`Child 1: ${child1 ? '' : 'not '}mounted`);
      console.log(`Child 2: ${child2 ? '' : 'not '}mounted`);
      console.log(`Child 3: ${child3 ? '' : 'not '}mounted`);
      console.log(`In a real app, would run a cleanup function here`);
    }
  }, [child1, child2, child3]);

  const value = useMemo(() => ({ setValue: setChild3 }), []);

  return (
    <refContext.Provider value={value}>
      <div className="App">
        This is text in the parent component
        <Child ref={setChild1} />
        <Child ref={setChild2} />
        <ContextUsingChild />
      </div>
    </refContext.Provider>
  );
};

const rootElement = document.getElementById('root');
render(<Parent />, rootElement);

【问题讨论】:

  • 不确定您是否已经得到答案。我目前也在想同样的事情。像 react-popper 这样的库也使用 useState setter 作为回调引用...popper.js.org/react-popper/v2/#example
  • 我注意到我的应用程序中ref={setElement}ref={element =&gt; setElement(element} 之间存在差异...前者有时会错过更新...我不知道为什么

标签: reactjs react-hooks react-context react-suspense


【解决方案1】:

useState "setter" 函数在渲染周期中保持相同的引用,因此它们应该可以安全使用。甚至mentioned 可以在依赖数组中省略它们:

(setCount 函数的身份保证是稳定的,所以可以安全地省略。)

你可以直接传递setter函数:

<refContext.Provider value={setChild3}>

...然后阅读:

const ContextUsingChild = props => {
  const setChild3 = useContext(refContext);
  return <div ref={setChild3}>This child uses context</div>;
};

【讨论】:

    【解决方案2】:

    每次出现目标元素时,您都会有额外的重新渲染(因为它可能会被有条件地渲染,它可能会在一生中发生不止一次)。

    否则它应该可以正常工作。除了,它可能会让未来的代码阅读者感到困惑。

    我宁愿使用额外的功能 + useRef 来减少认知负担(又名“混乱”)。甚至更多:我们可能不需要回调引用,因为我们有useEffect

    const childRef = useRef();
    
    useEffect(() => {
     ... do something
    }, [childRef.current]);
    
    <div ref={childRef} ...
    

    但是可以肯定的是,在非常普遍的情况下,这可能不适合我们,因为在每个目标元素的生命周期中,回调 ref 仅被调用两次,而如果列表中有更多依赖项,则可能会更频繁地触发 useEffect。但还是这样。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-05-10
      • 2015-10-02
      • 2018-08-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-10-31
      • 1970-01-01
      相关资源
      最近更新 更多