React结合Material UI如何创建子组件ref存入Context并传递给后代组件
实现方案
你提到的两种方案都可以实现,这里分别给出可直接运行的实现,不需要你之前考虑的useEffect+useState的繁琐逻辑:
方案1:Context传递(适合嵌套层级深的场景)
不需要层层透传props,直接在任意深层子组件取引用即可:
- 首先在
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;
- 在深层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:
- 在
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> ) }
- 给
FormContainer加上对应prop类型,逐层往下传递到Popper组件即可使用。
相关说明
- 不需要用
useState存储ref.current:ref本身是可变引用对象,传递整个ref对象下去,子组件随时访问.current都是最新的DOM节点,不需要触发组件重渲染,逻辑更简洁。 - 不需要自己写
forwardRef:Material UI的所有原生组件都已经内置了forwardRef处理,直接给DialogContent传ref就能拿到底层的DOM元素,TS类型直接指定为React.RefObject<HTMLDivElement>即可,没有类型兼容问题。
内容的提问来源于stack exchange,提问作者user7247147
相关产品推荐
相关产品推荐

