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

如何在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.25 20:54:06