使用Render Prop时如何让复合组件自动传递泛型类型?
复合组件中Render Prop的泛型自动推断方案
我正在开发一个Compound Component模式的List组件,希望实现Render Prop中参数类型的自动推断,无需手动指定泛型。当前组件代码如下:
interface ListContextValue<T> { rows: T[]; } const createListContext = once(<T>() => createContext<ListContextValue<T> | undefined>(undefined) ); const useListContext = <T>() => { const context = useContext(createListContext<T>()); if (!context) { throw new Error( "useListContext must be used within an ListContext.Provider" ); } return context; }; interface ListProps<T> { rows: T[]; children?: React.ReactNode; } export const List = <T,>({ rows, children }: ListProps<T>) => { const ListContext = createListContext<T>(); return ( <ListContext.Provider value={{ rows }}> {children} </ListContext.Provider> ); }; export interface ListContainerProps<T> extends Omit<SlotProps, "children"> { children?: (props: { row: T }) => React.ReactNode; } export const ListContainer = <T,>({ children }: ListContainerProps<T>) => { const { rows } = useListContext<T>(); return ( <div> {rows.map((row) => { return children ? children({ row }) : null; })} </div> ); };
使用组件时,Render Prop函数里的row类型无法自动推断,默认是unknown:
export default function UserList() { return ( <List rows={rows}> <ListContainer> {({ row }) => ( <div> {/* row is unknown type */} {row} </div> )} </ListContainer> </List> ); }
手动指定泛型虽然可行,但不够优雅:
export default function UserList() { return ( <List<User[]> rows={rows}> <ListContainer<User>> {({ row }) => ( <div> {row} </div> )} </ListContainer> </List> ); }
解决方案
核心问题在于List组件的children类型是React.ReactNode,无法携带泛型信息传递给子组件ListContainer。我们需要让List的children成为一个接收泛型参数的函数,让TypeScript能够自动推断并传递类型。
方案一:通过函数子组件透传上下文类型
修改List的children为函数类型,让TypeScript从rows的类型推断出泛型,并传递给子组件:
import React, { createContext, useContext } from 'react'; import { once } from 'your-utils-package'; interface ListContextValue<T> { rows: T[]; } const createListContext = once(<T>() => createContext<ListContextValue<T> | undefined>(undefined) ); const useListContext = <T>() => { const context = useContext(createListContext<T>()); if (!context) { throw new Error("useListContext must be used within an ListContext.Provider"); } return context; }; // 修改ListProps,children为无参函数 interface ListProps<T> { rows: T[]; children: () => React.ReactNode; } export const List = <T,>({ rows, children }: ListProps<T>) => { const ListContext = createListContext<T>(); return ( <ListContext.Provider value={{ rows }}> {children()} </ListContext.Provider> ); }; // ListContainer不再手动指定泛型,从useListContext推断 export interface ListContainerProps<T> extends Omit<SlotProps, "children"> { children?: (props: { row: T }) => React.ReactNode; } export const ListContainer = <T,>({ children }: ListContainerProps<T>) => { const { rows } = useListContext<T>(); return ( <div> {rows.map((row, index) => ( <React.Fragment key={index}> {children?.(row)} </React.Fragment> ))} </div> ); };
使用示例
此时row类型会自动推断,无需手动指定泛型:
export default function UserList() { const rows: User[] = [{ id: 1, name: "张三" }]; return ( <List rows={rows}> {() => ( <ListContainer> {({ row }) => ( <div> {/* row自动推断为User类型 */} {row.name} </div> )} </ListContainer> )} </List> ); }
方案二:通过类型标记传递泛型
另一种更直观的方式是给List的children传递一个类型标记参数,明确告知子组件泛型类型:
// 调整ListProps,让children接收类型标记参数 interface ListProps<T> { rows: T[]; children: (props: { __type: T }) => React.ReactNode; } export const List = <T,>({ rows, children }: ListProps<T>) => { const ListContext = createListContext<T>(); return ( <ListContext.Provider value={{ rows }}> {/* 传递类型标记,仅用于类型推断,实际值无意义 */} {children({ __type: {} as T })} </ListContext.Provider> ); };
使用示例
export default function UserList() { const rows: User[] = [{ id: 1, name: "张三" }]; return ( <List rows={rows}> {({ __type }) => ( <ListContainer> {/* 通过typeof __type自动推断row类型 */} {({ row }: { row: typeof __type }) => ( <div>{row.name}</div> )} </ListContainer> )} </List> ); }
核心原理
两种方案都是通过将List的children从静态的ReactNode改为动态的泛型函数,让TypeScript能够在组件树中追踪泛型类型信息,从而实现row类型的自动推断,避免手动指定泛型的繁琐。
内容的提问来源于stack exchange,提问作者hackid
相关产品推荐
相关产品推荐

