组件道具中不兼容的函数参数



我有一个组件,它接受一个已知具有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。– 鼓室

删除CompPropstypedef 的= 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只是恰好与GenericItemtypedef 重叠的类型表示。

在大多数情况下,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 的方法,我们在参数中声明SpecificItemtypedef。只有该方法使用SpecificItemtypedef。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>
</>;
};

游乐场链接

最新更新