【发布时间】:2019-07-04 15:47:52
【问题描述】:
我想将 React 的 memo 用于具有 generic 参数的函数。不幸的是,泛型参数默认为泛型,并且所有花哨的泛型推导逻辑都丢失了(TypeScript v3.5.2)。在下面的示例中,WithMemo(使用 React.memo)失败:
Property 'length' does not exist on type 'string | number'.
Property 'length' does not exist on type 'number'.
而WithoutMemo 的工作方式与预期一样。
interface TProps<T extends string | number> {
arg: T;
output: (o: T) => string;
}
const Test = <T extends string | number>(props: TProps<T>) => {
const { arg, output } = props;
return <div>{output(arg)} </div>;
};
const myArg = 'a string';
const WithoutMemo = <Test arg={myArg} output={o => `${o}: ${o.length}`} />;
const MemoTest = React.memo(Test);
const WithMemo = <MemoTest arg={myArg} output={o => `${o}: ${o.length}`} />;
我查看过this question,但我认为这与我的问题无关。
可能的解决方案
我找到了一个使用通用接口的可能解决方案,但它似乎有点粗糙:
const myArgStr = 'a string';
const myArgNo: number = 2;
const WithoutMemo = (
<>
<Test arg={myArgStr} output={o => `${o}: ${o.length}`} />
<Test arg={myArgNo} output={o => `${o * 2}`} />
</>
);
interface MemoHelperFn {
<T extends string | number>(arg: TProps<T>): JSX.Element;
}
const MemoTest: MemoHelperFn = React.memo(Test);
const WithMemo = (
<>
<MemoTest arg={myArgStr} output={o => `${o}: ${o.length}`} />
<MemoTest arg={myArgNo} output={o => `${o * 2}`} />
</>
);
// Below fail as expected
const FailsWithoutMemo = (
<>
<Test arg={myArgNo} output={o => `${o}: ${o.length}`} />
<Test arg={myArgStr} output={o => `${o * 2}`} />
</>
);
const FailsWithMemo = (
<>
<MemoTest arg={myArgNo} output={o => `${o}: ${o.length}`} />
<MemoTest arg={myArgStr} output={o => `${o * 2}`} />
</>
);
有没有更优雅的想法来解决这个问题?
【问题讨论】:
-
好吧,数字没有长度属性,所以编译器是对的。您的代码没有泛型工作,因为它根本不被调用或只用字符串调用,而不是用数字调用。要修复它,您需要仅在传递字符串时添加类型和调用长度检查。
-
@RadosławCybulski - 我不同意。泛型的重点是确保编译器可以将
arg与output类型匹配。添加typeof arg === 'string'完全消除了泛型类型的优雅。该示例是对我的 typeahead 包中的内容的简化。在那里你可以有大量依赖于泛型决定的依赖,在每个函数中检查输入会太痛苦。 -
查看您的代码
const WithMemo = <MemoTest arg={myArg} output={o =>${o}:${o.length}} />;。output中的这段代码需要o具有length属性,编译器告诉您该数字没有。编译器不会猜到,您总是将字符串传递给这个Test实例(否则 lambda 将不起作用)。泛型的整个想法不是通过键入更少的行来使编码更容易,而是通过更早地捕获错误(例如您所做的错误)来使编码更容易。您输入的代码适用于字符串,但不适用于数字,然后您尝试将其推送到数字,编译器会检测到。 -
@RadosławCybulski - 我想这是一个固执己见的问题。我想要弄清楚在调用
React.memo后是否可以拥有通用功能。否则,我可以跳过整个通用逻辑并使用自定义type MyType = string | number,然后按照您的建议到处检查。同样,这个问题不是关于这是否是一个好主意,而是如果可能。 -
output: (o: any) => string;inTProps定义应该可以工作。您也可以尝试限制 lambda 本身的类型(例如output={(o: string) => "${o}: ${o.length}"}和output={(o: number) => "${o * 2}"})。
标签: reactjs typescript