如何在Lexical的TypeaheadMenuPlugin中自动聚焦ShadCN Command组件
问题原因分析
- Ref传递失效:ShadCN的Command组件如果未通过
forwardRef将ref转发到底层DOM元素,直接使用useRef会拿到组件实例而非可聚焦的DOM节点,调用focus()无效。 - 聚焦时机过早:原代码的空依赖useEffect在组件挂载时执行,但此时Lexical的菜单还未触发渲染(
anchorElementRef.current可能仍为空),Portal中的Command组件尚未创建,导致commandRef.current为null。
解决方案
方案一:修正Ref传递与聚焦时机
确保Command组件的ref正确指向底层DOM,并延迟到DOM渲染完成后再执行聚焦:
import React, { useEffect, useRef } from "react"; import ReactDOM from "react-dom"; import { LexicalTypeaheadMenuPlugin } from "@lexical/react/LexicalTypeaheadMenuPlugin"; import { Command, CommandGroup, CommandItem, CommandList } from "@/components/ui/command"; const ComponentPickerMenuPlugin: React.FC = () => { // 指定ref类型为HTMLDivElement,匹配Command组件的根DOM节点 const commandRef = useRef<HTMLDivElement>(null); useEffect(() => { if (commandRef.current) { // 使用requestAnimationFrame确保DOM完全渲染后再聚焦 requestAnimationFrame(() => { commandRef.current?.focus(); }); } }, [commandRef.current]); // 监听ref的变化,确保菜单渲染后触发 return ( <LexicalTypeaheadMenuPlugin menuRenderFn={(anchorElementRef) => anchorElementRef.current ? ReactDOM.createPortal( <Command ref={commandRef} tabIndex={0} className="rounded-lg"> <CommandList> <CommandGroup heading="Suggestions"> <CommandItem>Option 1</CommandItem> <CommandItem>Option 2</CommandItem> </CommandGroup> </CommandList> </Command>, anchorElementRef.current ) : null } /> ); }; export default ComponentPickerMenuPlugin;
方案二:聚焦第一个可选项(更符合菜单交互逻辑)
通常菜单的合理交互是直接聚焦到第一个选项,方便用户用上下键切换,无需聚焦容器本身:
import React, { useEffect, useRef } from "react"; import ReactDOM from "react-dom"; import { LexicalTypeaheadMenuPlugin } from "@lexical/react/LexicalTypeaheadMenuPlugin"; import { Command, CommandGroup, CommandItem, CommandList } from "@/components/ui/command"; const ComponentPickerMenuPlugin: React.FC = () => { const firstItemRef = useRef<HTMLDivElement>(null); useEffect(() => { if (firstItemRef.current) { requestAnimationFrame(() => { firstItemRef.current?.focus(); }); } }, [firstItemRef.current]); return ( <LexicalTypeaheadMenuPlugin menuRenderFn={(anchorElementRef) => anchorElementRef.current ? ReactDOM.createPortal( <Command className="rounded-lg"> <CommandList> <CommandGroup heading="Suggestions"> <CommandItem ref={firstItemRef}>Option 1</CommandItem> <CommandItem>Option 2</CommandItem> </CommandGroup> </CommandList> </Command>, anchorElementRef.current ) : null } /> ); }; export default ComponentPickerMenuPlugin;
方案三:自定义封装Command组件(解决Ref未转发问题)
如果ShadCN的Command组件未默认转发ref,可自定义封装组件内部处理聚焦:
import React, { useEffect, useRef, forwardRef } from "react"; import { Command as ShadCommand, CommandGroup, CommandItem, CommandList } from "@/components/ui/command"; import { LexicalTypeaheadMenuPlugin } from "@lexical/react/LexicalTypeaheadMenuPlugin"; import ReactDOM from "react-dom"; // 自定义带自动聚焦的Command组件,转发ref到底层DOM const CommandWithAutoFocus = forwardRef<HTMLDivElement, React.ComponentProps<typeof ShadCommand>>( (props, ref) => { const innerRef = useRef<HTMLDivElement>(null); useEffect(() => { if (innerRef.current) { innerRef.current.focus(); } }, []); return <ShadCommand ref={innerRef} tabIndex={0} {...props} />; } ); const ComponentPickerMenuPlugin: React.FC = () => { return ( <LexicalTypeaheadMenuPlugin menuRenderFn={(anchorElementRef) => anchorElementRef.current ? ReactDOM.createPortal( <CommandWithAutoFocus className="rounded-lg"> <CommandList> <CommandGroup heading="Suggestions"> <CommandItem>Option 1</CommandItem> <CommandItem>Option 2</CommandItem> </CommandGroup> </CommandList> </CommandWithAutoFocus>, anchorElementRef.current ) : null } /> ); }; export default ComponentPickerMenuPlugin;
内容的提问来源于stack exchange,提问作者Umamaheswaran
相关产品推荐
相关产品推荐

