【问题标题】:Is using a ref in a custom hook feasible or advised for mutating the DOM?在自定义挂钩中使用 ref 是否可行或建议更改 DOM?
【发布时间】:2020-02-13 20:47:41
【问题描述】:

我正在努力将第 3 方库整合到 React 应用程序中,而 hooks 使这一切变得非常容易。 但是,我遇到了一些问题,我希望能够弄清楚“幕后”发生的事情。

为简单起见,假设这是我直接改变 DOM 的第 3 方代码:

const renderStuff = (div, txt) => {
  if(div) div.innerHTML = txt;
}

而我的组件是这样的:

export const EffectRender = () => {
  const divRef = useRef();
  useRenderer(divRef, "Hello, world");
  return <div ref={divRef}></div>;
}

这是建议的自定义挂钩:

const useRenderer = (ref, txt) => {
  const div = ref.current;
  useEffect(() => {
    renderStuff(div, txt);
  },[div, txt])
};

如果其中一个参数(在本例中为 txt)更新较晚,例如由于异步加载,则此方法有效。 但是useEffect 永远不会识别ref.current 值何时发生变化。 因此,如果在设置 ref.current 之前设置了 txt(在这种情况下),则组件永远不会呈现。

我知道我可以通过在自定义挂钩中使用 setState 来解决这个问题,就像在 this example 中一样。 但这开始感觉很麻烦。

我也明白我可以将renderStuff 调用放在主要组件上的useEffect 挂钩中,这样可以保证设置了 ref.current。 所以:useEffect(() =&gt; { renderStuff(divRef.current, txt); },[txt]); 很好。

我的问题确实是在自定义钩子中使用 ref 的整个方法是否是一个好主意。 是否有一种更简单的方法可以让钩子识别 ref 何时更改? 或者这是自定义钩子不适合该任务的情况?

【问题讨论】:

    标签: reactjs react-hooks


    【解决方案1】:

    问题在于useRenderer 中的const div = ref.current; 是在钩子之外声明的。在循环的这个时刻,ref 仍然没有被分配,所以它的值是null

    如果我正确理解了这个问题,那么解决方案就是简单地将 ref 移动到 useEffect 回调中。这是您的建议之一,我相信它是正确的方法:

    const useRenderer = (ref, txt) => {
        useEffect(() => {
            const div = ref.current;
            renderStuff(div, txt);
        }, [txt]);
    };
    

    useEffect 依赖项不会在 ref 更改时触发。这在您的情况下是可以的,因为当 useEffect 运行时 ref 已经有了它的分配值。但是,如果 ref 发生了变化并且您需要跟踪变化,那么可以使用 useState

    【讨论】:

    • 感谢您的回答。效果很好!仅供参考:在您提到的提案中,我假设 useEffect 必须在原始组件中。我开始看到我的想法出了什么问题——假设useEffect 必须检测ref.current 何时被设置。但事实并非如此。
    • 我很高兴它成功了!在钩子中使用useEffect,相当于直接在组件中使用它,但是以更方便/可共享的方式。
    【解决方案2】:

    感谢Alvaro 澄清问题。我想在这里解释一下我的想法哪里出了问题,假设我不是唯一犯这个错误的人。

    我的逻辑不好

    我们使用effect 钩子是因为代码正在改变DOM,而useEffect 的存在正是为了处理这种副作用。

    我们使用ref 挂钩连接到给定的 DOM 元素。 ref 实例就像一个单例,因为该实例在应用程序的生命周期内不会更改。只有ref.current 属性发生变化。

    并且直接在hooks reference docs的相关部分下我读到了这个:

    请记住,useRef 不会在其内容更改时通知您。 改变 .current 属性不会导致重新渲染。

    由此,我了解到需要将相关依赖项传递给useEffect 调用。并且要更新的元素(在ref.current 中)是这些依赖项之一。由于更改 ref.current 属性不会触发重新渲染,因此它可能也不会触发 useEffect 调用。

    顺便说一句:es-lint 强化了这种想法,要求我将 ref.current(在我的情况下为 div = ref.current)添加到依赖项列表中:React Hook useEffect has a missing dependency: 'div'. Either include it or remove the dependency array. 当然,我始终相信 linter。

    我的结论:没有简单的方法可以使用effect 挂钩来渲染到ref 实例。我需要以某种方式将ref.current 放入useState 设置器中。丑!

    我的核心假设

    我认为useEffect 处理副作用的方式无关紧要。 effect 钩子是埋在typescript 源代码内部的一个黑盒子,它以神秘的方式移动。

    但唯一“埋没”的就是use-effect docs中的这句话:

    传递给 useEffect 的函数将在渲染完成后运行 提交到屏幕上。

    渲染之后”。当然。这就是如何useEffect 处理副作用,保证在 DOM 准备好之前不运行。

    解决方案

    正如 Alvaro 的回答所暗示的,请务必阅读 ref.current 属性 inside useEffect 钩子。 ref 永远不会改变,ref.current 保证已经填充了 DOM 元素。

    像往常一样,事后看来是显而易见的。再次感谢阿尔瓦罗。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-12-03
      • 1970-01-01
      • 1970-01-01
      • 2022-01-27
      • 2012-12-08
      相关资源
      最近更新 更多