如何在非受控@radix-ui Dialog中使用@headlessui/react Transition组件
Radix UI Dialog 非受控模式下结合 Transition 动画解决方案
核心问题分析
非受控模式下,Dialog.Root 内部管理状态,但 Transition.Root 需要感知状态变化才能触发离开动画。你当前的代码中 show={true} 固定为真,导致Dialog关闭时Portal直接卸载,Transition无法执行离开动画。
可行方案
方案1:利用Radix UI内部状态上下文
Radix UI的Dialog提供了内部状态上下文,可直接获取当前open状态,无需外部维护useState,完美适配非受控模式:
import { Transition } from '@headlessui/react'; import * as DialogPrimitive from '@radix-ui/react-dialog'; import { Fragment } from 'react'; function Uncontrolled() { // 获取Dialog内部管理的open状态 const { open } = DialogPrimitive.useDialogContext(); return ( <DialogPrimitive.Root> <DialogPrimitive.Trigger>Button Trigger</DialogPrimitive.Trigger> <DialogPrimitive.Portal forceMount> <Transition.Root show={open} appear={true}> <Transition.Child as={Fragment}> <DialogPrimitive.Overlay /> </Transition.Child> <Transition.Child as={Fragment}> <DialogPrimitive.Content> Content </DialogPrimitive.Content> </Transition.Child> </Transition.Root> </DialogPrimitive.Portal> </DialogPrimitive.Root> ); }
关键细节:
- 必须给
DialogPrimitive.Portal添加forceMount,确保Transition组件始终存在,完整执行进入/离开动画 - 移除Overlay的
forceMount,由Transition控制其显示隐藏逻辑
方案2:自定义上下文封装(优化你的备选方案)
如果需要更灵活的状态扩展,可封装自定义上下文,保持外部使用的非受控体验:
import { Transition } from '@headlessui/react'; import * as DialogPrimitive from '@radix-ui/react-dialog'; import { Fragment, createContext, useContext, useState } from 'react'; const DialogContext = createContext(); function DialogProvider({ children }) { const [open, setOpen] = useState(false); return ( <DialogContext.Provider value={{ open, setOpen }}> {children} </DialogContext.Provider> ); } function UncontrolledDialog() { const { open, setOpen } = useContext(DialogContext); return ( <DialogPrimitive.Root open={open} onOpenChange={setOpen}> <DialogPrimitive.Trigger>Button Trigger</DialogPrimitive.Trigger> <DialogPrimitive.Portal forceMount> <Transition.Root show={open} appear={true}> <Transition.Child as={Fragment}> <DialogPrimitive.Overlay /> </Transition.Child> <Transition.Child as={Fragment}> <DialogPrimitive.Content> Content </DialogPrimitive.Content> </Transition.Child> </Transition.Root> </DialogPrimitive.Portal> </DialogPrimitive.Root> ); } // 外部使用示例 function App() { return ( <DialogProvider> <UncontrolledDialog /> </DialogProvider> ); }
关于提取data-state属性的说明
直接提取data-state作为JS变量不可行,因为DOM属性是渲染后的结果,无法在React渲染阶段同步获取并传递给Transition.Root的show属性。通过上下文获取状态是同步的,能保证动画与状态变化完全对齐。
内容的提问来源于stack exchange,提问作者wongx
相关产品推荐
相关产品推荐

