React:如何在插入(渲染)到第二个容器中后等待ref可用?

12

编辑:更好的解释

背景:

我从第三方服务器收到一些纯HTML代码,我想要:

  • 将其插入到我的React应用中
  • 修改它

使用原生JS的方法

  • 我可以使用正则表达式修改字符串,并添加任何具有id的HTML标签
  • 然后,我可以像平常一样通过getElementById修改这些元素

使用React的方法

  • 我不应该使用DOM
  • 我应该在字符串中插入一些具有React引用的组件
  • 相反(将一些React组件插入为纯HTML),可以使用ReactDOMServer.renderToString
  • 因此,当我使用ReactDOM.render()注入组件时,问题在于render方法需要时间,因此如果在下一行尝试使用已插入组件中存在的引用,则其还不存在

问题

  • 如何做到这一点?通常,我会将代码放在useEffect中,具有[]依赖项,但是在这里,当应用程序已经挂载时,我正在rendering组件
  • 一个快速的解决方法是只需异步等待500毫秒,然后我就可以访问ref,但肯定有更好的方法

此代码失败,因为当ref被渲染时,它仍不可用,因此ref.current为undefined

如何等待ref

codesandbox

编辑:我提供了使用直接DOM工作的代码,但我认为应避免使用

import React, { useRef, useEffect } from "react";
import ReactDOM from "react-dom";

export default function App() {
  const myref = useRef();

  useEffect(() => {
    const Com = () => <div ref={myref}>hello</div>;
    ReactDOM.render(<Com />, document.getElementById("container"));
    console.log(myref.current); // undefined
    document.getElementById('container').textContent = "direct DOM works"

   // the next line fails since the ref is not yet available
   // myref.current.textContent = "but this REF is not available"; // fails
  }, []);

  const plainhtml = '<div><div id="container"></div><div>some more content</div><div id="another">even more content</div></div>'; // this is some large HTML fetched from an external server

  return (
    <div>
      <h1>Hello CodeSandbox</h1>
      <div dangerouslySetInnerHTML={{ __html: plainhtml }} />
    </div>
  );
}

3
你为什么要在useEffect内部调用ReactDOM.render?你应该将其作为子组件渲染,这样就可以使用状态来设置文本内容了。 - alistair
你能解释一下你想要解决的问题吗?你想要更新 textContent 吗? - Dennis Vash
@aabbccsmith 当plainhtml可用(从外部服务器获取)时,useEffect将被调用,但代码示例不需要包括此内容。 - GWorking
@DennisVash 我从外部服务器接收到一个HTML字符串,我可以使用dangerouslySetInnerHTML显示它,但是我想在其中插入一些React组件,这样做的方法是通过.render(),但是当我想要使用refs时,它们不可用(如果我等待几毫秒,它们就会变得可用)。所以问题是如何访问它们(这就是代码示例展示的内容)。 - GWorking
@GWorking,我已经更新了答案,请查看。 - Dennis Vash
2个回答

7

useEffect 使用空依赖数组会在首次渲染执行,所以你会在回调函数中得到 DOM 引用:

const htmlString = '<div id="container">Hello</div>';

export default function App() {
  const myRef = useRef();

  useEffect(() => {
    if (myRef.current) {
      myRef.current.textContent = 'whats up';
    }
    console.log(myRef.current);
  }, []);

  return (
    <div>
      <div ref={myRef} dangerouslySetInnerHTML={{ __html: htmlString }} />
      <div dangerouslySetInnerHTML={{ __html: htmlString }} />
    </div>
  );
}

/* App renders:
whats up
Hello
*/

Edit nervous-glade-8rtxb


你需要安装组件才能获取ref,我会添加一个我认为你想要实现的示例。 - Dennis Vash
所以只需使用appendChild等方法。你得到了你所要求的引用。 - Dennis Vash
很抱歉,我可能没有解释清楚。您提供的参考文献不在“htmlString”内,而是一个普通的参考文献,与“htmlString”无关。我要求的是前者,我并不是在询问如何在React中获取引用(这是问题的理解吗?)。 - GWorking
你只是想要 dangerouslySetInnerHTML 渲染的 HTML 的引用吗?就像我的例子中的 <div id="container" ref={myRef}>Hello</div>,你想要这个 myRef 吗? - Dennis Vash
1
我想要修改一些在 dangerouslySetInnerHTML 渲染的 HTML 元素,而不是整个 HTML,只是其中的一些元素。 - GWorking
显示剩余3条评论

3
我需要在useCallback中使用回调引用,以确保它只在指定的依赖项(即[])更改时重新呈现,从而仅在组件更改时执行(如此处所述)。

CodeSandbox

import React, { useEffect, useCallback } from "react";
import ReactDOM from "react-dom";

export default function App() {
  const measuredRef = useCallback(node => {
    if (node !== null) {
      node.textContent = "useCallback DOM also works";
    }
  }, []);

  useEffect(() => {
    const Com = () => <div ref={measuredRef}>hello</div>;
    ReactDOM.render(<Com />, document.getElementById("container"));
    document.getElementById("container").textContent = "direct DOM works";
  }, []);

  const plainhtml = '<div id="container"></div>';

  return (
    <div>
      <h1>Hello CodeSandbox</h1>
      <div dangerouslySetInnerHTML={{ __html: plainhtml }} />
    </div>
  );
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接