我有一个组件,它接受一个已知具有ID的项目列表,以及一个过滤这些项目的函数。
带 ID 的类型是所有项都将具有的泛型项目类型。
但更具体的项目将包括其他道具。
type GenericItem = {
id: string;
}
type SpecificItem = {
id: string;
someOtherProp: boolean;
}
我还有一个函数类型,它使用泛型类型进行操作。
type GenericItemFunction = (item: GenericItem) => boolean;
然后,我有了这个组件,它在其 props 中使用了 GenericItem 和 GenericItemFunction。
type CompProps = {
fn: GenericItemFunction;
items: GenericItem[];
}
const Comp: React.FC<CompProps> = ({ fn, items }) => <></>;
当我尝试将此组件与特定类型一起使用时,我收到错误,说我无法使用 GenericItemFunction 的实现,因为项目的类型不兼容。
const App = () => {
const items: SpecificItem[] = [];
const filter = (item: SpecificItem) => item.someOtherProp;
return (
<Comp
fn={filter} // error on `fn` prop
items={items}
/>
)
}
我收到的打字稿错误是:
Type '(item: SpecificItem) => boolean' is not assignable to type 'GenericItemFunction'.
Types of parameters 'item' and 'item' are incompatible.
Property 'someOtherProp' is missing in type 'GenericItem' but required in type 'SpecificItem'.
我想我有两个问题;
首先,当两种类型都期望id: string
属性时,为什么会出现冲突?
其次,有没有更理智的方法来做这样的事情?
我的第一个问题是GenericItemFunction
上的item
类型可以从 App 组件中提供给items
道具的值推断出来。
但说实话,我不确定那会是什么样子......
我的另一个想法是让Comp
成为泛型,但不显示使用使用泛型的反应组件......似乎 jsx/tsx 并不真正支持这种语法。
我希望这样的事情会引发各种错误。
const Comp = <T extends GenericItem,>({ fn, items }) => <></>;
const App = () => {
return <Comp<SpecificType> />;
}
最后,我确实尝试了这个,没有任何错误。但缺点是推断items
的类型是任何类型。
type GenericItem = {
id: string;
}
type SpecificItem = {
id: string;
someOtherProp: boolean;
}
type GenericItemFunction <T> = (item: T) => boolean;
type CompProps <T extends GenericItem = any> = {
fn: GenericItemFunction<T>;
items: T[];
}
const Comp: React.FC<CompProps> = ({ fn, items }) => <></>;
const App = () => {
const items: SpecificItem[] = [];
const filter = (item: SpecificItem) => item.someOtherProp;
return (
<Comp
fn={filter}
items={items}
/>
)
}
这是我一直使用的游乐场的链接。
https://www.typescriptlang.org/play?#code/C4TwDgpgBA4hB2EBOBLAxgSWBAtlAvFAN4BQAkCgCYBcUAzsKvAOYDcJAviSaJFAMqQ0KAGbosuAsRJRZUKrQZM2MuXQD2OCAHlgAC2QAFJOrC0ARuvUAbCAEN47Lj3DQ4iVJmw4AYgFd4NGAUdXgoAB4AFQA+KQAKFG9aSIBKAljLG3tHbhc+AGFNMGNTOgjIqAgAD2x4SjL3ZHFvKQcQWMJSMhF4WkbPCV8AoJD4KOj2OXlvOmSAbQBdJxI0UIYoQpwzKAAleyCAOh988M3ikzA6Dqg4oigegBpp3DKONPxY8OjwgHoJ7lW8HWAEEwGB4u9YqQpoD1okXrRBBBhGIvLhFlJFpM5LDgPcUNZsEh4vCcIihKJmrhIc8cAcNFpdAYkCUwOxVLIkBBgH4kGE4hyphEzoKhXIevgiGJCcguGKxaS6JLFXL5X9BSlOEA
更新:
为什么使用任何作为通用项类型的默认值?如果没有这个,我相信它应该从GenericItemFunction正确地推断Genericitem。– 鼓室
删除CompProps
typedef 的= any
会导致Comp
声明中出现错误...
type CompProps <T extends GenericItem> = {
fn: GenericItemFunction<T>;
items: T[];
}
const Comp: React.FC<CompProps> = ({ fn, items }) => <></>; // this line has the error
Generic type 'CompProps' requires 1 type argument(s).
意思是,我仍然必须在某处声明类型。这意味着在使用组件之前,我需要知道GenericItem
类型的变体。
SpecificItem
只是恰好与GenericItem
typedef 重叠的类型表示。
在大多数情况下,Comp
不会知道实际使用哪种类型,并且不会向作者提供任何有用的信息。
我希望像这样的东西...
type CompProps <T extends GenericItem> = {
items: <T extends GenericItem>[];
fn: <infer from T>[];
}
但我不确定这是否存在,或者类似的东西。
:face_palm:
使用<T extends GenericType = any>
CompProps
是执行此操作的正确方法。
type CompProps <T extends GenericItem = any> = {
items: T[];
filter: (item: T) => boolean;
}
const Comp: React.FC<CompProps> = (props) => <></>;
魔术就在这里发生:
const App = () => {
...
const filter = (item: SpecificItem) => item.someOtherProp;
...
}
const Comp: React.FC<CompProps>
不在乎泛型类型是什么,因此= any
.只要它至少具有与GenericType
相同的形状.
但是在App
组件中,我们正在定义过滤器 prop 的方法,我们在参数中声明SpecificItem
typedef。只有该方法使用SpecificItem
typedef。CompProps
只关心它是否符合所需的GenericItem
形状。
希望对某人有所帮助。
在大多数情况下,
Comp
不会知道实际使用哪种类型,并且任何类型都不会向作者提供任何有用的信息。
对我来说,这意味着Comp
是通用的。所以我会删除CompProps
上的= any
:
type CompProps<T extends GenericItem> = {
fn: GenericItemFunction<T>;
items: T[];
};
。,然后将Comp
定义为泛型。可能有一种方法可以用React.FC
做到这一点,但我个人不使用React.FC
所以我不知道它是什么。:-)React.FC
对你没有多大作用,正如一些人指出的那样,除其他外,它说你的组件接受children
无论它是否接受。当我的组件接受children
时,我更喜欢明确说明它。
所以我会这样定义Comp
:
const Comp = <T extends GenericItem>({ fn, items }: CompProps<T>) => {
// Just to show that `fn` and `items` play together:
const example = items.map(fn);
return <>{example}</>;
};
如果您希望它有孩子,您可以轻松添加它们。如果您只想支持文本,请使用string
,如果您只想允许任何内容,请使用React.Node
:
// Does allow children:
type Comp2Props<T extends GenericItem> = {
fn: GenericItemFunction<T>;
items: T[];
children: React.ReactNode;
};
const Comp2 = <T extends GenericItem>({ fn, items, children }: Comp2Props<T>) => {
// Just to show that `fn` and `items` play together:
const example = items.map(fn);
return <>{example}</>;
};
无论哪种方式,它都能很好地适用于你的方案:
const App = () => {
const items: SpecificItem[] = [];
const filter = (item: SpecificItem) => item.someOtherProp;
return <>
<Comp
fn={filter}
items={items}
/>
</>;
};
这些类型意味着它在您可能不希望它的地方不起作用:
const App2 = () => {
const genericItems: GenericItem[] = [];
const filterGeneric = (item: GenericItem) => item.id === "example"; // Silly definition, but...
const specificItems: SpecificItem[] = [];
const filterSpecific = (item: SpecificItem) => item.someOtherProp;
return <>
{/* Desirable error, `fn` can't handle `GenericItem`s, only `SpecificItem`s */}
<Comp
fn={filterSpecific}
items={genericItems}
/>
{/* Works, `fn` handles the supertype of the items, which is fine */}
<Comp
fn={filterGeneric}
items={specificItems}
/>
{/* Desirable error because `Comp` doesn't [now] support `children` */}
<Comp
fn={filterSpecific}
items={specificItems}
>
children here
</Comp>
{/* Works because `Comp2` does support `children`*/}
<Comp2
fn={filterSpecific}
items={specificItems}
>
children here
</Comp2>
</>;
};
游乐场链接