如何在React中编写返回JSX元素的useComponent自定义Hook
问题根因
- 第一个示例代码存在拼写错误:输入框的
onChange事件被写为onChane,导致输入回调无法触发。 - 核心逻辑问题:直接将预渲染完成的
JSX.Element存入全局上下文时,该JSX是当前渲染周期的固定快照,后续状态更新后,已存入上下文的旧JSX的props不会自动同步更新,因此会出现输入无响应、状态不同步的问题。
解决方案
方案1:适配现有上下文逻辑的修复方案
核心思路是不预渲染JSX,改为在Dialog组件渲染时动态生成最新的组件节点,同时通过useCallback保证渲染函数能访问到最新状态:
import { createContext, useContext, useState, useCallback } from "react"; const Test = ({ value, setValue }) => { return ( <> <p>Component</p> <input value={value} onChange={(e) => setValue(e.target.value)} /> </> ); }; const DialogCTX = createContext({}); export function DialogProvider(props) { // 存储渲染函数,而非固定JSX const [renderComp, setRenderComp] = useState<(() => JSX.Element) | null>(null); const ctx = { renderComp, setRenderComp }; return ( <DialogCTX.Provider value={ctx}> {props.children} </DialogCTX.Provider> ); } export const useDialog = () => useContext(DialogCTX); const Dialog = () => { const { renderComp } = useDialog(); return <div> <p>Dialog</p> {/* 渲染时才调用函数生成最新的JSX */} {renderComp && renderComp()} </div> } const Setter = () => { const [value, setValue] = useState(""); const { setRenderComp } = useDialog(); // 用useCallback包裹,依赖更新时自动生成新的渲染函数 const renderTest = useCallback(() => { return <Test value={value} setValue={setValue} /> }, [value, setValue]) return <div> <p>Setter component</p> <p>{value}</p> <button onClick={() => setRenderComp(renderTest)}>打开弹窗</button> </div> } export default function App() { return <div className="App"> <DialogProvider> <Setter /> <Dialog /> </DialogProvider> </div> }
方案2:使用Portal的最佳实践方案
全局弹窗更推荐用React Portal实现,不需要额外维护上下文存储组件,状态天然和调用方同步:
import { useState, createPortal } from "react"; // 全局弹窗挂载节点,提前在HTML中添加<div id="dialog-root"></div> const DialogRoot = document.getElementById("dialog-root"); const Test = ({ value, setValue }) => { return ( <> <p>Component</p> <input value={value} onChange={(e) => setValue(e.target.value)} /> </> ); }; // 全局弹窗组件 const Dialog = ({ children, visible }) => { if (!visible || !DialogRoot) return null; return createPortal(<div className="dialog"> <p>Dialog</p> {children} </div>, DialogRoot) } const Setter = () => { const [value, setValue] = useState(""); const [dialogVisible, setDialogVisible] = useState(false); return <div> <p>Setter component</p> <p>{value}</p> <button onClick={() => setDialogVisible(true)}>打开弹窗</button> {/* 直接在当前组件渲染弹窗,状态完全同步 */} <Dialog visible={dialogVisible}> <Test value={value} setValue={setValue} /> </Dialog> </div> } export default function App() { return <div className="App"> <Setter /> </div> }
内容的提问来源于stack exchange,提问作者entropyfeverone
相关产品推荐
相关产品推荐

