ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Radix Primitives react-context-menu 2.3.x 演进全解析:版本变更、受控状态与源码实现深度剖析

Radix Primitives react-context-menu 2.3.x 演进全解析:版本变更、受控状态与源码实现深度剖析 前端UI组件【免费下载链接】primitivesRadix Primitives is an open-source UI component library for building high-quality, accessible design systems and web apps. Maintained by workos.项目地址https://gitcode.com/gh_mirrors/pr/primitives点击查看免费下载本文以radix-ui/react-context-menu的 CHANGELOG 为核心脉络结合仓库内 组件实现、单元测试、Storybook 示例、SSR 测试页面 与 Playwright 端到端测试系统梳理该组件从 2.2.8 到 2.3.7 的版本演进、核心 API尤其是受控open的正确用法与底层原理。读完你将掌握如何安全使用受控open、如何理解并规避树摇优化/条件导出带来的行为差异、以及这些变更背后对应的源码级证据。版本路线图从 2.2.8 到 2.3.7 我们得到了什么CHANGELOG 记录了两个版本区间2.2.8至2.2.16小版本迭代主要聚焦依赖升级与基础修复与2.3.0至2.3.7功能增强与工程化改进。当前仓库中 package.json 声明版本号为2.3.7依赖了五个工作区包依赖2.3.7 中的版本radix-ui/primitive1.1.7radix-ui/react-context1.2.2radix-ui/react-menu2.1.24radix-ui/react-primitive2.1.10radix-ui/react-use-controllable-state1.2.62.3.x 的三个关键节点2.3.0引入受控open最重要的功能变更为ContextMenu.Root新增受控openprop官方用途是读取打开状态与编程式关闭菜单明确不建议用open编程式打开菜单因为菜单定位依赖用户交互右键/长按产生的位置同步修复长按触屏场景下子菜单在重新打开后仍保持展开的问题并为所有 package.json 增加repository.directory。2.3.3两个行为修复修复菜单已打开时再次触发例如在另一位置右键ContextMenu未重新锚定到最新指针位置的问题修复菜单项、Tab 触发器、工具栏链接、Select 项拦截来自可聚焦后代的Space/Enter键的问题。2.3.4树摇tree-shaking优化组件部分标记/* __PURE__ */并使用命名渲染函数替代Component.displayName ...赋值使打包器能够删除未使用的组件通过条件导出conditional exports将开发期警告从生产构建中剔除。2.3.5 / 2.3.6 / 2.3.7发布链路与兼容性收尾2.3.5 重新经 CI 发布以附加 provenance软件来源证明签名2.3.7 回退了一些会与 React Server Components 产生兼容性问题的破坏性变更reverted breaking changes that caused compatibility issues with RSC属于一次安全网补丁。受控 open读取与关闭的正确姿势源码中的定位逻辑在 context-menu.tsx 中ContextMenu组件通过useControllableState管理开关状态const [open, setOpen] useControllableState({ prop: openProp, defaultProp: false, onChange: onOpenChange, caller: CONTEXT_MENU_NAME, });注意ContextMenu没有defaultOpen组件接口只声明open/onOpenChange/dir/modal因此open{false}是唯一合法的受控默认值。触发器的打开逻辑在 ContextMenuTrigger右键或长按时记录指针坐标并调用context.onOpenChange(true)同时用React.useMemo构建一个虚拟锚点getBoundingClientRect返回以指针位置为中心的零尺寸矩形供 popper 定位。const handleOpen (event: React.MouseEvent | React.PointerEvent) { context.hasInteractedRef.current true; setPoint({ x: event.clientX, y: event.clientY }); context.onOpenChange(true); };开发期警告为什么不能编程式打开源码在开发环境下监听openPropif (openProp true !hasInteractedRef.current !hasWarnedRef.current) { console.warn( ContextMenu: The open prop has been set to true before the user has interacted with the trigger, so its position is indeterminate. This is likely unintended and will result in the menu being anchored to the top-left corner of the viewport., ); }含义很明确受控状态只保证你能读取状态与关闭菜单若在用户交互之前把open设为true菜单会锚定到视口左上角坐标 0,0因为从未记录过指针位置。对应测试见 context-menu.test.tsx其中open{true}的用例会触发一次警告而未交互的用户触发打开则不会警告。正确用法示例读取状态并编程式关闭对应 Storybook Controlled 故事function ControlledMenu() { const [open, setOpen] React.useState(false); return ( pThe menu is currently {open ? open : closed}./p ContextMenu.Root open{open} onOpenChange{setOpen} ContextMenu.TriggerRight click here/ContextMenu.Trigger ContextMenu.Portal ContextMenu.Content button typebutton onClick{() setOpen(false)}Close/button ContextMenu.Item onSelect{() console.log(undo)}Undo/ContextMenu.Item /ContextMenu.Content /ContextMenu.Portal /ContextMenu.Root / ); }注意在 Storybook 的Controlled故事中菜单内容内嵌了一个Close按钮点击后通过setOpen(false)编程式关闭——这正是官方推荐的受控用途。受控状态下右键仍可打开单元测试 opens on right click and reflects the open state 验证了即使传入open{false}与onOpenChange右键依然会调用onOpenChange(true)而 respects a controlled open{false} 则验证若父组件不回写open菜单保持关闭trigger 的data-state仍为closed。换句话说受控打开是单向否决——你可以阻止打开但不能脱离交互凭空打开。触发机制右键、长按与虚拟锚点重定位事件绑定全貌ContextMenuTrigger在 源码 中绑定了四类指针事件全部通过composeEventHandlers与用户传入的事件处理合并onContextMenu非禁用时调用handleOpen并event.preventDefault()屏蔽浏览器原生菜单onPointerDown仅对pointerType ! mouse触摸/笔的指针生效whenTouchOrPen辅助函数过滤若菜单已打开则先关闭再启动 700ms 长按定时器触发handleOpenonPointerMove/onPointerCancel/onPointerUp长按期间移动指针、取消或抬起都会清除长按定时器。此外disabled时所有事件处理原样透传给用户且保留原生右键菜单并渲染data-disabled属性触发元素同时设置WebkitTouchCallout: none以阻止 iOS 长按弹出系统菜单。700ms 长按阈值是源码中可查证的实现细节longPressTimerRef.current window.setTimeout(() handleOpen(event), 700)。2.3.3 的重锚定修复2.3.3 修复了已打开时再次触发不更新指针位置。源码 virtualRef 的 useMemo 依赖point说明每当指针位置更新虚拟锚点会被重建。对应回归测试 re-anchors the content when re-triggered in a new location while open 验证在 (10,10) 右键打开菜单后再到 (200,150) 右键[data-radix-popper-content-wrapper]的transform会发生变化。Playwright 端到端测试 context-menu.spec.ts 也从真实浏览器层面验证了重新右键时关闭已展开的子菜单并重开根菜单。定位与外观默认参数与 CSS 自定义属性ContextMenuContent直接基于MenuPrimitive.Content源码 固定了三个默认值参数默认值说明sideright菜单出现在指针右侧sideOffset2距锚点 2pxalignstart沿对齐轴起点对齐alignOffset示例中常用-5可从MenuPrimitive.Content透传。Content会屏蔽从 Menu 层继承的side/sideOffset/align/onEntryFocus通过Omit类型声明见 ContextMenuContentProps防止用户改变这三个定位语义。此外源码 将 popper 的内部 CSS 变量重命名 为--radix-context-menu-*命名空间供样式中使用--radix-context-menu-content-transform-origin; --radix-context-menu-content-available-width; --radix-context-menu-content-available-height; --radix-context-menu-trigger-width; --radix-context-menu-trigger-height;ContextMenuSubContent同样应用了这套变量重命名源码。Storybook 的动画样式见 context-menu.stories.module.css。组件 API 全览入口文件 标记use client并以短别名Root、Trigger…导出全部 18 个部件。基于 context-menu.tsx 的 export 区块 整理如下部件职责ContextMenu.Root根容器open/onOpenChange/dirltr、rtl/modal默认trueContextMenu.Trigger右键/长按触发内部渲染span与虚拟锚点ContextMenu.Portal将内容传送到 body 末尾ContextMenu.Content菜单内容默认siderightContextMenu.Group/ContextMenu.Label分组与组标签rolegroupContextMenu.Item普通菜单项rolemenuitemonSelect后默认关闭ContextMenu.CheckboxItem复选菜单项rolemenuitemcheckboxContextMenu.RadioGroup/ContextMenu.RadioItem单选组rolemenuitemradioContextMenu.ItemIndicator选中态指示符配合checked/value显隐ContextMenu.Separator分隔线ContextMenu.Arrow指向触发点的箭头ContextMenu.Sub/SubTrigger/SubContent子菜单键盘可打开关于modal默认true时菜单打开期间document.body被设置为pointer-events: none测试 disables pointer events 验证保证焦点被限制在菜单内modal{false}时则不设置对应测试。如需在打开菜单的同时允许用户操作页面其他元素可显式关闭模态。子菜单键盘交互与 RTL子菜单由Sub/SubTrigger/SubContent组成。单元测试验证了以下键盘行为焦点在SubTrigger上按ArrowRightLTR/ArrowLeftRTL打开子菜单并聚焦第一项测试用例打开时SubTrigger通过aria-controls关联子菜单内容仅在打开期间存在测试用例禁用的SubTrigger不会被键盘打开测试用例。e2e 测试 context-menu.spec.ts 进一步确认Enter、Space、ArrowRight均可打开子菜单并聚焦第一项ArrowLeft只关闭当前聚焦的子菜单根菜单保持打开见 L182-L191typeahead输入首字母跳转行为被限定在活动菜单内L217-L230。RTL 场景下方向键语义反转L274-L295。2.3.3 的另一修复菜单项拦截来自可聚焦后代的Space/Enter也与子菜单键盘路径相关即菜单项内部若包含可聚焦元素其按键不应被菜单项的选中逻辑抢走。无头、可组合与工程细节部件组合与 asChild所有部件在 context-menu.tsx 中都是对radix-ui/react-menu对应部件的薄封装例如ContextMenuItem仅透传MenuPrimitive.Item因此继承了 Menu 的全部能力焦点管理、类型匹配、方向键导航、Escape 关闭、交互外关闭等。每个部件都支持asChild——测试 prop spreading 系统验证了各部件会将未消费的 props 透传到渲染元素并在asChild时把role/data-state/aria-*等语义属性合并到子元素上例如Trigger asChild会把data-state和WebkitTouchCallout样式合入自定义元素见 L575-L604。RSC 兼容与 SSR组件源文件入口标注use clientindex.ts 第一行保证在 Server Components 边界下安全使用SSR 测试应用 提供了一个最小可运行的 Server 组件示例仅引入ContextMenu与基础菜单项2.3.7 明确回退破坏性变更以保持 RSC 兼容见 CHANGELOG 首条。2.3.4 的树摇优化如何生效在源码中可见ContextMenuTrigger、ContextMenuContent等部件以/* __PURE__ */ React.forwardRef(...)包裹例如 L112、L234且渲染函数均为命名函数而非匿名箭头函数加displayName赋值。这让 Rollup/Vite/Webpack 等打包器能识别纯函数调用并删除未引用部件——即使index.ts导出了全部部件只要应用只 import 需要的部件未使用的部件代码即可被树摇掉。配合 2.3.4 的条件导出开发期console.warn如受控 open 警告只保留在 development 产物中。升级注意事项与可验证事实清单open只读不写不要用open编程式打开菜单会导致定位失效并触发开发警告只用于读取/关闭。modal默认truemodal 菜单会屏蔽 body 指针事件并限制焦点false时保留页面交互。触发方式鼠标右键、触摸/笔长按 700ms 触发disabled时透传原生右键菜单。定位默认值sideright、sideOffset{2}、alignstart不可从Content覆盖alignOffset可用。2.3.4 之后注意打包差异/* __PURE__ */与条件导出是构建时行为升级后若自定义构建请重新验证产物大小与 dev 警告。2.3.7 的 RSC 回退如果升级 2.3.7 时遇到与 Server Components 相关的问题请确认是否停留在更早版本的特性上——本次补丁正是为消除这类兼容性问题而发布。以上所有结论均可在 CHANGELOG、组件实现、单元测试 与 e2e 测试 中直接复核。赞分享前端UI组件【免费下载链接】primitivesRadix Primitives is an open-source UI component library for building high-quality, accessible design systems and web apps. Maintained by workos.项目地址https://gitcode.com/gh_mirrors/pr/primitives点击查看免费下载相关推荐深入解析 radix-ui/react-context从版本演进看 Radix Primitives 的上下文创建体系深入解析 radix ui/react context从版本演进看 Radix Primitives 的上下文创建体系 radix ui/react co前端UI组件Radix Primitives HoverCard 组件演进实录从 1.1.8 到 1.1.23 的变更解读与源码剖析Radix Primitives HoverCard 组件演进实录从 1.1.8 到 1.1.23 的变更解读与源码剖析 HoverCard悬停卡片是 R前端UI组件radix-ui/react-compose-refs 深入解析Radix Primitives 中 ref 组合工具的实现原理与版本演进radix ui/react compose refs 深入解析Radix Primitives 中 ref 组合工具的实现原理与版本演进 导读 radi前端UI组件上一篇SwiftTweaks 项目常见问题解决方案下一篇RTranslator模型下载慢手动部署10个ONNX文件10分钟跑通1.2GB离线翻译模型创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表