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

如何在非受控@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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.01 23:50:23