免费获取学习方案
ARTICLE DETAIL

资讯详情

深耕编程基础知识与建站技术分享的一线实战洞察。

Flow 泛型(Generics)完全指南:编写类型安全的可复用函数、类与类型别名

Flow 泛型(Generics)完全指南:编写类型安全的可复用函数、类与类型别名 Flow 泛型Generics完全指南编写类型安全的可复用函数、类与类型别名【免费下载链接】flowAdds static typing to JavaScript to improve developer productivity and code quality.项目地址: https://gitcode.com/gh_mirrors/flow30/flow泛型又称多态类型是 Flow 静态类型系统的核心能力之一它允许你编写能够作用于任意类型的函数、类与类型别名同时在输入与输出之间保留类型关系。本指南围绕 website/docs/types/generics.md 展开完整讲解 Flow 泛型的语法位置、行为语义、参数化泛型、类型参数默认值与变型variance关键字并结合当前仓库的源码与测试验证底层行为。读完本文你将掌握用泛型写出可复用、类型安全的代码并理解 Flow 与 TypeScript 在泛型上的关键差异。function identityT(value: T): T { return value; }:::info TypeScript 对比 Flow 与 TypeScript 在泛型上有两处关键分歧全默认类型参数不可省略尖括号当所有类型参数都有默认值时仍然必须写Foo不能写Foo详见 Flow 与 TypeScript 对比。类型参数默认不变型invariant把Cnumber拓宽为Cnumber | string会被拒绝需要通过out T/in T显式选择协变或逆变。而 TypeScript 会从使用位置推断变型并保留兼容性例外这会让读写字段的静态保证更弱详见 泛型变型对比。 :::何时使用泛型当函数、类或类型别名需要跨多种类型工作且需要保留输入与输出之间的类型关系时就应使用泛型如果类型本身无关紧要也不需要追踪它使用unknown更合适如果只有固定数量的具体类型用联合类型 表达可能更清晰。警告Flow 不会推断泛型。如果你希望某个东西具有泛型类型必须显式标注它。否则Flow 可能推断出一个比预期更少多态性的具体类型。泛型的语法位置Flow 中泛型可以出现在多种语法位置下面逐一说明。带泛型的函数函数通过在参数列表前添加类型参数列表T来创建泛型。泛型可以出现在函数中任何可以放置类型的地方参数类型或返回类型function methodT(param: T): T { return param; } const f functionT(param: T): T { return param; }带泛型的函数类型函数类型与普通函数一样通过在函数类型参数列表前添加T来创建泛型可用于参数类型或返回类型T(param: T) T它可以直接作为类型使用function method(func: T(param: T) T) { // ... }带泛型的类类通过在类体前放置类型参数列表来创建泛型class ItemT { // ... }泛型可以出现在类中任何可以放置类型的位置属性类型以及方法参数/返回类型class ItemT { prop: T; constructor(param: T) { this.prop param; } method(): T { return this.prop; } }带泛型的类型别名type ItemT { foo: T, bar: T, };带泛型的接口interface ItemT { foo: T, bar: T, }向可调用对象显式提供类型参数你可以在调用处直接为可调用对象的泛型提供类型参数function doSomethingT(param: T): T { // ... return param; } doSomethingnumber(3);也可以在new表达式中为泛型类直接提供类型参数class GenericClassT {} const c new GenericClassnumber();如果只想指定部分类型参数可以使用_让 Flow 为你推断一个类型class GenericClassT, Ustring, Vnumber{} const c new GenericClassboolean, _, string();警告出于性能考虑我们始终建议在能够显式写出具体类型参数时进行标注。_并非不安全但它比显式指定类型参数更慢。泛型的行为泛型像变量一样工作泛型类型与变量或函数参数很像只不过它们用于类型。只要在作用域内你就可以使用它们function constantT(value: T): () T { return function(): T { return value; }; }按需创建任意多个泛型类型参数列表中可以按需放置任意数量的泛型并随意命名function identityOne, Two, Three(one: One, two: Two, three: Three) { // ... }泛型会追踪值当使用泛型类型标注一个值时Flow 会追踪该值确保你不会把它替换成别的东西function identityT(value: T): T { return foo; // Error! }function identityT(value: T): T { value foo; // Error! return value; // Error! }Flow 会追踪通过泛型传入的值的具体类型以便之后继续使用function identityT(value: T): T { return value; } let one: 1 identity(1); let two: 2 identity(2); let three: 3 identity(42); // Error为泛型添加类型约束bounds与unknown类似泛型有一个未知类型面你不能把泛型当作某个具体类型来使用function logFooT(obj: T): T { console.log(obj.foo); // Error! return obj; }你可以先做细化refine但泛型仍然允许任何类型被传入function logFooT(obj: T): T { if (obj obj.foo) { console.log(obj.foo); // Works. } return obj; } logFoo({ foo: foo, bar: bar }); // Works. logFoo({ bar: bar }); // Works. :(正确做法是使用extends关键字给泛型添加约束bound在保留泛型行为的同时只允许特定类型function logFooT extends {foo: string, ...}(obj: T): T { console.log(obj.foo); // Works! return obj; } logFoo({ foo: foo, bar: bar }); // Works! logFoo({ bar: bar }); // Error!约束同样可以用于字面量类型的保持function identityT extends number(value: T): T { return value; } let one: 1 identity(1); let two: 2 identity(2); let three: three identity(three); // Error!⚠ 旧式约束语法提醒extends关键字作为泛型约束自 Flow 0.302 起默认启用。旧代码可能使用遗留的冒号语法例如T: string而非T extends string。迁移细节见 现代 Flow 语法迁移指南。泛型类型起到边界bound的作用在 Flow 中大多数时候当你把一个类型传入另一个更宽泛的类型时会丢失原始类型——Flow 会忘记它曾经更具体过function identity(val: string): string { return val; } let foo: foo foo; // Works! let bar: bar identity(bar); // Error!泛型允许你在添加约束的同时保住更具体的类型。因此泛型上的类型起到边界作用function identityT extends string(val: T): T { return val; } let foo: foo foo; // Works! let bar: bar identity(bar); // Works!注意当你持有一个带约束的泛型值时不能把它当作更具体的类型使用function identityT extends string(val: T): T { let str: string val; // Works! let bar: bar val; // Error! return val; } identity(bar);这一边界语义在仓库的测试中有直接体现tests/generics/generics.js 通过class CX、class DT、泛型方法mS(z:S,u:T):S以及泛型继承class EX extends CX、class GY extends FArrayY等用例验证了泛型在类、方法与继承链中的类型传递与关系保持。参数化泛型parametric polymorphism泛型有时允许你把类型像函数参数一样传进去这被称为参数化泛型或参数多态。例如带泛型的类型别名就是参数化的。使用它时必须提供类型参数type ItemT { prop: T, } let item: Itemstring { prop: value };你可以把它想象成向函数传参只不过返回值是一个可供使用的类型。类作为类型使用时、类型别名和接口都要求你传入类型参数函数和函数类型没有参数化泛型。类class ItemT { prop: T; constructor(param: T) { this.prop param; } } let item1: Itemnumber new Item(42); // Works! let item2: Item new Item(42); // Error!类型别名type ItemT { prop: T, }; let item1: Itemnumber { prop: 42 }; // Works! let item2: Item { prop: 42 }; // Error!接口interface HasPropT { prop: T, } class Item { prop: string; } Item.prototype as HasPropstring; // Works! Item.prototype as HasProp; // Error!为参数化泛型添加默认值与函数参数一样你也可以为参数化泛型提供默认值type ItemT extends number 1 { prop: T, }; let foo: Item { prop: 1 }; let bar: Item2 { prop: 2 };使用该类型时必须带上尖括号就像函数调用必须带括号一样。裸写Item不带即使所有参数都有默认值也会报[missing-type-arg]错误。裸名保留给未应用的类型构造器——应用它时始终需要显式类型参数或者用空回退到默认值。与 TypeScript 的差异TypeScript 允许在全部参数都有默认值时省略整个类型参数列表Foo与Foo等价。Flow 则要求始终写见 全默认泛型参数省略的对比。变型关键字Variance Keywords你可以通过变型关键字指定泛型的子类型行为。默认情况下泛型是不变型invariant的但可以在声明前加out关键字使其协变covariant或加in关键字使其逆变contravariant。完整介绍见 Flow 的变型文档。变型关键字让你更精确地表达使用泛型的意图赋予 Flow 做更精确类型检查的能力。例如你可能希望下面的关系成立type GenericBoxout T T; const x: GenericBoxnumber 3; x as GenericBoxnumber| string;没有out变型关键字时上面的例子无法成立type GenericBoxErrorT T; const x: GenericBoxErrornumber 3; x as GenericBoxErrornumber| string; // Error: number | string is not compatible with number.注意一旦用变型关键字标注泛型Flow 会检查这些类型只出现在与该变型匹配的位置。例如你不能声明一个协变的类型参数却在逆变位置使用它type NotActuallyCovariantout T (T) void; // Error源码与文档层面的佐证变型的底层模型在 website/docs/lang/variance.md 中有完整推导——out对应输出位置返回类型、只读属性、getter 结果in对应输入位置函数参数、只写属性、setter 参数不写关键字则是同时出现在输入输出位置的不变型。违反声明的变型会报[incompatible-variance]错误例如type Boxout T { get(): T; set(val: T): void; }。此外变型也是 Flow 与 TypeScript 的核心分歧之一Flow 的泛型参数默认不变型对比表见 flow-vs-typescript.md而 TypeScript 从使用位置推断变型。⚠ 旧式变型符号提醒out与in变型关键字自 Flow 0.315 起默认启用。旧代码可能使用遗留的/-符号例如T而非out T-T而非in T它们仍会被识别但已弃用。迁移细节见 现代 Flow 语法迁移指南。结合其他类型特性使用泛型泛型不是孤立存在的它与 Flow 的其他类型特性组合使用能发挥最大价值条件类型泛型参数是条件类型conditional types的输入。例如可以基于泛型T编写类型层面的typeof运算符并在传入联合类型时对联合成员做分发distribution——该分发行为仅在检查类型是泛型时启用见 条件类型。映射类型映射类型依赖泛型参数来转换对象类型见 映射类型。类型别名类型别名泛型是参数化的使用时必须为每个泛型传参见 类型别名。联合类型当判别属性来自泛型参数时Flow 无法在联合类型上完成细化因为泛型尚未解析为具体字面量需要将判别属性重构为具体字面量类型见 带泛型的可区分联合。小结特性关键规则错误/注意点泛型定义在函数、函数类型、类、类型别名、接口的参数列表前加TFlow 不推断泛型必须显式标注显式类型参数调用处fnumber(x)、new Cnumber()_可让 Flow 推断部分参数但性能较慢泛型追踪Flow 追踪通过泛型传入值的具体类型赋值其他类型或返回其他类型会报错类型约束T extends Bound约束泛型不能当作更具体类型使用参数化泛型类/别名/接口使用时必须提供类型参数裸名报[missing-type-arg]需写Foo默认值T extends number 1即使全默认也必须写与 TS 不同变型默认不变型out T协变、in T逆变违反位置约束报[incompatible-variance]参见类型别名 — 定义具名类型常与泛型组合使用函数 — 在函数签名中使用泛型变型 — 协变与逆变如何与泛型交互条件类型 — 由泛型类型参数计算出的类型映射类型 — 使用泛型转换对象类型现代 Flow 语法迁移 — 将遗留T: Bound约束与T/-T变型符号迁移到现代关键字形式【免费下载链接】flowAdds static typing to JavaScript to improve developer productivity and code quality.项目地址: https://gitcode.com/gh_mirrors/flow30/flow创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表