为何在以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()时的执行上下文:
方式一的执行流程:
- 按下键盘触发
keydown事件,调用setKeyboard更新状态 - 状态更新触发App组件重新渲染,进而执行依赖
keyboard的useEffect - 此时的
useEffect运行在React自身的异步更新周期内,React 17对这种场景下的ReactDOM.render()做了调整:不会同步返回组件实例,而是返回null,但组件仍会正常渲染到DOM中。
- 按下键盘触发
方式二的执行流程:
- 按下键盘触发
keydown事件,直接在原生DOM事件的同步回调中调用ReactDOM.render() - 这个执行上下文不属于React的更新周期,所以
ReactDOM.render()能同步返回组件实例。
- 按下键盘触发
另外补充:React 17之后官方更推荐使用createRoot替代ReactDOM.render,createRoot的API设计中不再返回组件实例,这也是React逐步弱化组件实例获取的一个方向。
内容的提问来源于stack exchange,提问作者김종현
相关产品推荐
相关产品推荐

