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

React结合Material UI如何创建子组件ref存入Context并传递给后代组件

实现方案

你提到的两种方案都可以实现,这里分别给出可直接运行的实现,不需要你之前考虑的useEffect+useState的繁琐逻辑:

方案1:Context传递(适合嵌套层级深的场景)

不需要层层透传props,直接在任意深层子组件取引用即可:

  1. 首先在FormDialog组件内创建Context并绑定ref到DialogContent:
import React, { createContext, useContext, useRef } from 'react';
import { Dialog, DialogContent, DialogTitle, Typography } from '@material-ui/core';

// 创建Context存储ref对象
const DialogContentRefContext = createContext<React.RefObject<HTMLDivElement> | null>(null);
// 导出快捷调用hook,方便子组件取引用
export const useDialogContentRef = () => useContext(DialogContentRefContext);

type Props = {
  formId: string;
  onClose: () => void;
  onSubmit: () => void;
}

const FormDialog = ({ formId, onClose, onSubmit }: Props) => {
  // 创建ref直接绑定DialogContent,MUI官方组件已内置forwardRef支持,直接传即可
  const dialogContentRef = useRef<HTMLDivElement>(null);

  return (
    <Dialog open disableBackdropClick>
      <DialogTitle>
        Create New Form
        <br />
        <Typography
          color="textSecondary"
          variant="caption"
        >
          Start a new form by filling in some starter info.
        </Typography>
      </DialogTitle>
      <DialogContent dividers ref={dialogContentRef}>
        {/* 用Provider传递ref对象,ref本身是稳定引用,不会触发不必要的重渲染 */}
        <DialogContentRefContext.Provider value={dialogContentRef}>
          <FormContainer
            formId={formId}
            onSubmit={onSubmit}
            onClose={onClose}
          />
        </DialogContentRefContext.Provider>
      </DialogContent>
    </Dialog>
  );
};

export default FormDialog;
  1. 在深层Popper组件中直接取引用使用:
import { useDialogContentRef } from './FormDialog';

const YourNestedPopper = () => {
  const dialogContentRef = useDialogContentRef();
  const [popperOpen, setPopperOpen] = useState(false);

  useEffect(() => {
    if (!popperOpen || !dialogContentRef?.current) return;
    const contentEl = dialogContentRef.current;
    // 这里写你原本的滚动锁定逻辑,比如阻止wheel事件或者设置overflow
    const preventScroll = (e: WheelEvent) => e.stopPropagation();
    contentEl.addEventListener('wheel', preventScroll);
    return () => contentEl.removeEventListener('wheel', preventScroll);
  }, [popperOpen, dialogContentRef]);

  // 剩余Popper组件逻辑
  return <>{/* Popper内容 */}</>
}

方案2:Props透传(适合嵌套层级少的场景)

如果FormContainer到Popper的层级不超过3层,直接透传props更简单,不需要创建Context:

  1. 在FormDialog中把ref作为props传给FormContainer:
const FormDialog = ({ formId, onClose, onSubmit }: Props) => {
  const dialogContentRef = useRef<HTMLDivElement>(null);
  return (
    <Dialog open disableBackdropClick>
      {/* 省略其他相同代码 */}
      <DialogContent dividers ref={dialogContentRef}>
        <FormContainer
          formId={formId}
          onSubmit={onSubmit}
          onClose={onClose}
          dialogContentRef={dialogContentRef}
        />
      </DialogContent>
    </Dialog>
  )
}
  1. 给FormContainer加上对应prop类型,逐层往下传递到Popper组件即可使用。

相关说明

  1. 不需要用useState存储ref.current:ref本身是可变引用对象,传递整个ref对象下去,子组件随时访问.current都是最新的DOM节点,不需要触发组件重渲染,逻辑更简洁。
  2. 不需要自己写forwardRef:Material UI的所有原生组件都已经内置了forwardRef处理,直接给DialogContent传ref就能拿到底层的DOM元素,TS类型直接指定为React.RefObject<HTMLDivElement>即可,没有类型兼容问题。

内容的提问来源于stack exchange,提问作者user7247147

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.05 08:57:03