React 18升级后ItemsList组件TypeScript类型错误求助
问题场景
升级至React 18后,使用ItemsList组件时触发TypeScript类型错误,组件调用代码如下:
<ItemsList items={items} dataTestId='items_list_block'> {(item) => ( // 报错位置 <ItemView item={item} /> )} </ItemsList>
ItemsList组件定义:
type tItemsListProps<T extends tOptionListItem> = { children: (item: T) => React.ReactNode; items: T[]; dataTestId?: string; }; function ItemsList<T extends tOptionListItem>({ children, items, dataTestId, }: React.PropsWithChildren<tItemsListProps<T>>) { return ( <Box> <List> {items?.map((item) => ( <List.Item key={item.id} data-testid='list_item'> {children(item)} </List.Item> ))} </List> </Box> ); }
错误信息
TS2322: Type '(item: T) => Element' is not assignable to type '(((item: T) => ReactNode) & string) | (((item: T) => ReactNode) & number) | (((item: T) => ReactNode) & false) | (((item: T) => ReactNode) & true) | (((item: T) => ReactNode) & ReactElement<...>) | (((item: T) => ReactNode) & ReactFragment) | (((item: T) => ReactNode) & ReactPortal)'.
Type '(item: T) => Element' is not assignable to type '((item: T) => ReactNode) & string'.
Type '(item: T) => Element' is not assignable to type 'string'.
ItemsList.tsx(7, 3): The expected type comes from property 'children' which is declared here on type 'IntrinsicAttributes & tItemsListProps & { children?: ReactNode; }'
问题原因
React 18中React.PropsWithChildren会自动为props添加children?: ReactNode属性,而你自定义的children: (item: T) => React.ReactNode与这个默认属性类型冲突——默认的ReactNode包含字符串、数字、布尔值等多种类型,导致TypeScript无法正确推导回调函数的类型。
解决方法
方法1:移除React.PropsWithChildren直接使用自定义Props
既然已经在tItemsListProps中明确定义了children的类型,无需再用PropsWithChildren包裹,避免额外添加冲突的children类型:
function ItemsList<T extends tOptionListItem>({ children, items, dataTestId, }: tItemsListProps<T>) { // 移除PropsWithChildren return ( <Box> <List> {items?.map((item) => ( <List.Item key={item.id} data-testid='list_item'> {children(item)} </List.Item> ))} </List> </Box> ); }
方法2:重命名回调类型的children属性
如果需要保留PropsWithChildren(比如组件还需支持其他类型的children),可以将作为渲染回调的children重命名为renderItem这类明确名称:
type tItemsListProps<T extends tOptionListItem> = { renderItem: (item: T) => React.ReactNode; // 重命名属性 items: T[]; dataTestId?: string; }; function ItemsList<T extends tOptionListItem>({ renderItem, items, dataTestId, children, // 保留默认children用于其他内容渲染 }: React.PropsWithChildren<tItemsListProps<T>>) { return ( <Box> <List> {items?.map((item) => ( <List.Item key={item.id} data-testid='list_item'> {renderItem(item)} </List.Item> ))} </List> {children} {/* 可选:渲染其他子内容 */} </Box> ); }
调用方式修改为:
<ItemsList items={items} dataTestId='items_list_block' renderItem={(item) => <ItemView item={item} />} />
方法3:覆盖PropsWithChildren的默认children类型
通过交叉类型明确覆盖默认的children类型,确保TypeScript识别正确的回调类型:
function ItemsList<T extends tOptionListItem>({ children, items, dataTestId, }: tItemsListProps<T> & { children?: never }) { // 覆盖默认children为never return ( <Box> <List> {items?.map((item) => ( <List.Item key={item.id} data-testid='list_item'> {children(item)} </List.Item> ))} </List> </Box> ); }
内容的提问来源于stack exchange,提问作者Vitalii Shevtsov

