解决TS2339错误:DropdownContextTypeProps|null无DropdownToggle属性
解决React下拉菜单的TypeScript TS2339错误
问题原因
你定义的DropdownMenuContext类型是DropdownContextTypeProps | null,使用useContext获取上下文时,TypeScript无法确定返回值一定不为null,因此解构DropdownToggle时会触发TS2339错误——尽管实际运行时所有子组件都在DropdownMenuContext.Provider内部,上下文不会为null。
解决方案
推荐使用自定义Hook的方式,既解决TS类型问题,又能避免组件在Provider外部使用的潜在错误,具体如下:
1. 编写自定义Hook
在代码中添加一个专门获取下拉菜单上下文的Hook,确保上下文不为空:
// 在createContext之后添加 const useDropdownMenuContext = () => { const context = useContext(DropdownMenuContext); if (!context) { throw new Error('DropdownMenu.Button 必须在 DropdownMenu 组件内部使用'); } return context; };
2. 修改DropdownMenuButton组件
用自定义Hook替换原有的useContext调用:
const DropdownMenuButton = ({ children, icon }: DropdownMenuButtonProps) => { const { DropdownToggle } = useDropdownMenuContext(); const DropdownButtonClick = () => DropdownToggle() return ( <div className="dropdown-menu__button" onClick={DropdownButtonClick}> <span className="dropdown-menu__button-icon">{icon}</span> <span>{children}</span> <span className="dropdown-menu__button-toggle-icon"> <Icon icon="down" /> </span> </div> ) }
其他可选方案(不推荐)
- 非空断言:直接在解构时添加
!强制断言上下文不为null,但如果组件意外在Provider外部使用,会导致运行时错误:const { DropdownToggle } = useContext(DropdownMenuContext)!; - 设置上下文默认值:给
createContext传入符合类型的默认值,去掉| null类型,但默认的空函数在Provider外部使用时无实际作用:const DropdownMenuContext = createContext<DropdownContextTypeProps>({ dropdownOpen: false, DropdownToggle: () => {} });
修改后的完整代码
import './DropdownMenu.scss' import { NavLink } from 'react-router-dom' import { createContext, ReactNode, useContext, useState } from 'react' import { Icon } from '@/Components/Ui/Icon' type DropdownMenuProps = { children: ReactNode } type DropdownMenuButtonProps = { children: ReactNode icon: ReactNode } type DropdownMenuItemsProps = { children: ReactNode } type DropdownMenuItemProps = { children: ReactNode url: string } type DropdownContextTypeProps = { dropdownOpen: boolean DropdownToggle: () => void } const DropdownMenuContext = createContext<DropdownContextTypeProps | null>(null) // 自定义Hook确保上下文不为空 const useDropdownMenuContext = () => { const context = useContext(DropdownMenuContext); if (!context) { throw new Error('DropdownMenu.Button 必须在 DropdownMenu 组件内部使用'); } return context; }; export const DropdownMenu = ({ children }: DropdownMenuProps) => { const [dropdownOpen, setDropdownOpen] = useState<boolean>(false) const DropdownToggle = () => setDropdownOpen(!dropdownOpen) const DropdownContextData = { dropdownOpen, DropdownToggle, } return ( <DropdownMenuContext.Provider value={DropdownContextData}> <div className={`dropdown-menu ${dropdownOpen ? 'open' : ''}`}> {children} </div> </DropdownMenuContext.Provider> ) } const DropdownMenuButton = ({ children, icon }: DropdownMenuButtonProps) => { const { DropdownToggle } = useDropdownMenuContext(); const DropdownButtonClick = () => DropdownToggle() return ( <div className="dropdown-menu__button" onClick={DropdownButtonClick}> <span className="dropdown-menu__button-icon">{icon}</span> <span>{children}</span> <span className="dropdown-menu__button-toggle-icon"> <Icon icon="down" /> </span> </div> ) } const DropdownMenuItems = ({ children }: DropdownMenuItemsProps) => { return ( <div className="dropdown-menu__items"> <div className="dropdown-menu__items-content">{children}</div> </div> ) } const DropdownMenuItem = ({ children, url }: DropdownMenuItemProps) => { return ( <NavLink to={url} className="dropdown-menu__item"> <span className="dropdown-menu__item-icon"> <Icon icon="circle" /> </span> <span>{children}</span> </NavLink> ) } DropdownMenu.Button = DropdownMenuButton DropdownMenu.Items = DropdownMenuItems DropdownMenu.Item = DropdownMenuItem
内容的提问来源于stack exchange,提问作者Selçuk
相关产品推荐
相关产品推荐

