React+TS:如何同时使用点标记组件与泛型?
问题描述
我有一个名为List的组件,它通过点标记声明了子组件Title,初始实现代码如下:
type Item = { name: string; value: number; }; type ListProps = { title: string; data: Item[]; }; export const List: React.FC<ListProps> & { Title: React.FC<{ title: string }> } = ({ title, data }) => { return ( <div> <List.Title title={title} /> <ul> {data.map((item) => ( <li> {item.name}: {item.value} </li> ))} </ul> </div> ); }; List.Title = ({title}) => <div>{title}</div>
使用时一切正常:
<List title="Stats" data={[ { name: "strength", value: 30 }, { name: "dexterity", value: 50 }, { name: "intellect", value: 100 }, { name: "vitality", value: 40 } ]} />
但当我想给组件添加泛型参数,让item.name可以是特定类型并从父组件传入时,遇到了类型问题。比如期望这样使用:
<List<"strength" | "dexterity" | "intellect" | "vitality"> title="Stats" data={[ { name: "strength", value: 30 }, { name: "dexterity", value: 50 }, { name: "intellect", value: 100 }, { name: "vitality", value: 40 } ]} />
我尝试了泛型写法,但无法给List.Title正确指定类型:
type Item<ItemName extends string = string> = { name: ItemName; value: number; }; type ListProps<ItemName extends string = string> = { title: string; data: Item<ItemName>[]; }; export const List = <ItemName extends string = string,>({ title, data }: ListProps<ItemName>): JSX.Element => { return ( <div> <ul> {data.map((item) => ( <li> {item.name}: {item.value} </li> ))} </ul> </div> ); }; // 此处无法正常工作,因为List没有Title属性 // List.Title = ({ title }) => <div>{title}</div>;
请问如何将泛型与点标记子组件这两种技术结合使用?
解决方案
可以通过显式定义泛型组件的类型结构,把Title子组件的类型包含进去,同时保留主组件的泛型能力。具体实现如下:
import React from 'react'; // 定义泛型Item类型 type Item<ItemName extends string = string> = { name: ItemName; value: number; }; // 定义泛型ListProps类型 type ListProps<ItemName extends string = string> = { title: string; data: Item<ItemName>[]; }; // 定义完整的List组件类型:泛型FC + Title子组件类型 type ListComponent = { <ItemName extends string = string>(props: ListProps<ItemName>): JSX.Element; Title: React.FC<{ title: string }>; }; // 实现主组件 const ListBase = <ItemName extends string = string>({ title, data }: ListProps<ItemName>) => { return ( <div> <List.Title title={title} /> <ul> {data.map((item, index) => ( <li key={index}> {item.name}: {item.value} </li> ))} </ul> </div> ); }; // 组装完整的List组件,挂载Title子组件 export const List: ListComponent = ListBase as ListComponent; List.Title = ({ title }) => <div>{title}</div>;
这样就可以正常使用泛型版本的List组件,同时保留List.Title的点标记调用:
<List<"strength" | "dexterity" | "intellect" | "vitality"> title="Stats" data={[ { name: "strength", value: 30 }, { name: "dexterity", value: 50 }, { name: "intellect", value: 100 }, { name: "vitality", value: 40 } ]} />
关键说明
- 单独定义
ListComponent类型,它包含一个泛型函数(对应主组件)和Title子组件的类型声明。 - 先实现主组件
ListBase,再将其断言为ListComponent类型,最后挂载Title子组件。 - 这种方式既保留了泛型的类型约束能力,又维持了点标记子组件的调用方式。
内容的提问来源于stack exchange,提问作者Max Green
相关产品推荐
相关产品推荐

