You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何在以state为依赖的useEffect中ReactDOM.render()返回null?

React 17.0.2中ReactDOM.render()返回null的原因分析

问题场景

在React 17.0.2版本中,两种监听键盘事件的实现方式,导致ReactDOM.render()的返回值出现差异:

  • 方式一:通过keyboard状态作为依赖的useEffect嵌套处理,ReactDOM.render()返回null,但Test组件能正常渲染到页面
  • 方式二:直接在useEffect内绑定事件监听并处理,ReactDOM.render()能正常返回Test组件的实例

相关代码

index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <meta name="theme-color" content="#000000" />
    <meta
      name="description"
      content="Web site created using create-react-app"
    />
    <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
    <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
    <title>React App</title>
  </head>
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>
    <div id="home"></div>
  </body>
</html>

App.js

function App() {
  const [element, setElement] = useState(null);

  // 出现问题的代码
  const [keyboard, setKeyboard] = useState(null);
  useEffect(() => {
    if (keyboard === "KeyS") {
      const elementDiv = createElement(Test);
      const test = ReactDOM.render(elementDiv, document.getElementById("home"));
      console.log(test); // 输出null
      setElement(test);
    }
  }, [keyboard]);

  useEffect(() => {
    const createKeyEvent = (e) => {
      setKeyboard(e.code);
    };
    const removeKeyEvent = () => {
      setKeyboard(null);
    };

    document.addEventListener("keydown", createKeyEvent);
    document.addEventListener("keyup", removeKeyEvent);

    return () => {
      document.removeEventListener("keydown", createKeyEvent);
      document.removeEventListener("keyup", removeKeyEvent);
    };
  }, []);

  // 正常工作的代码
  useEffect(() => {
    const createKeyEvent = (e) => {
      if (e.code === "KeyS") {
        const elementDiv = createElement(Test);
        const test = ReactDOM.render(
          elementDiv,
          document.getElementById("home")
        );
        console.log(test); // 正常输出组件实例
        setElement(test);
      }
    };

    document.addEventListener("keydown", createKeyEvent);

    return () => {
      document.removeEventListener("keydown", createKeyEvent);
    };
  }, []);

  return <div className="App">hi</div>;
}

export default App;

Test.js

export default class Test extends Component {
  render() {
    return <div>hello</div>;
  }
}

原因分析

核心差异在于调用ReactDOM.render()时的执行上下文:

  1. 方式一的执行流程:

    • 按下键盘触发keydown事件,调用setKeyboard更新状态
    • 状态更新触发App组件重新渲染,进而执行依赖keyboard的useEffect
    • 此时的useEffect运行在React自身的异步更新周期内,React 17对这种场景下的ReactDOM.render()做了调整:不会同步返回组件实例,而是返回null,但组件仍会正常渲染到DOM中。
  2. 方式二的执行流程:

    • 按下键盘触发keydown事件,直接在原生DOM事件的同步回调中调用ReactDOM.render()
    • 这个执行上下文不属于React的更新周期,所以ReactDOM.render()能同步返回组件实例。

另外补充:React 17之后官方更推荐使用createRoot替代ReactDOM.render,createRoot的API设计中不再返回组件实例,这也是React逐步弱化组件实例获取的一个方向。


内容的提问来源于stack exchange,提问作者김종현

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.27 23:05:49