当你使用简单的泛型时,泛型常用 T、U、V 表示。如果在你的参数里,不止拥有一个泛型,你应该使用一个更语义化名称,如 TKey 和 TValue (通常情况下,以 T 做为泛型前缀也在如 C++ 的其他语言里做为模版。)
泛型函数
function identity(arg: T): T {
return arg;
}
// 可选:把类型作为参数,从而指定该函数的参数和返回值都必须是该类型。
function identity<T>(arg: T): T {
return arg;
}
// 传入string作为类型,从而指定该函数的参数和返回值都必须是字符串。
let output = identity<string>('myString');
泛型 interface
interface GenericIdentityFn {
<T>(arg: T): T;
}
// 把类型参数提到interface层面可以当参数传入
interface GenericIdentityFn<T> {
(arg: T): T;
}
function identity<T>(arg: T): T {
return arg;
}
let myIdentity: GenericIdentityFn<number> = identity;
let foo = 123
let bar = 'Hello'
foo = bar // Error: cannot assign `string` to a `number`
返回值推断
// 返回值推断
function add(a: number, b: number) {
return a + b
}
let foo = add(1, 2) // foo: number
条件推断
function getString(a :string | number) :string {
if (typeof a === 'number') {
return String(a) // a: number
}
return a
}
类型断言:
let pet = getSmallPet();
// 当不能确定一个变量的类型,需要对变量类型进行断言,这样ts才不会抛出错误
if ((<Fish>pet).swim) {
(<Fish>pet).swim();
} else {
(<Bird>pet).fly();
}
然而,当你在 JSX 中使用 的断言语法时,这会与 JSX 的语法存在歧义:
因此可以使用(pet as Fly).fly()的 as 语法来断言。
类型守护:
function isFish(pet: Fish | Bird): pet is Fish {
return (<Fish>pet).swim !== undefined;
}
function padLeft(value: string, padding: string | number) {
if (isNumber(padding)) {
return Array(padding + 1).join(' ') + value;
}
if (isString(padding)) {
return padding + value;
}
throw new Error(`Expected string or number, got '${padding}'.`);
}
类型别名
type Name = string;
type NameResolver = () => string;
type NameOrResolver = Name | NameResolver;
function getName(n: NameOrResolver): Name {
if (typeof n === 'string') {
return n;
} else {
return n();
}
}
类型操作符和类型工具:
readonly, ?
extends 和条件类型
T extends U ? X : Y
如果 T 类型可以认为是继承于U类型(要么 T 和 U 是同一种基础类型,要么 T 类型的代表范围 小于 U类型,也就是 T 是 U 的子集,U 是 T 的超集),则取 X, 否则取 Y;
typeof: 从变量中读出其类型(通常由 ts 推断得出)
这允许你告诉编译器,一个变量的类型与其他类型相同
let foo = 123;
let bar: typeof foo; // 'bar' 类型与 'foo' 类型相同(在这里是: 'number')
bar = 456; // ok
bar = '789'; // Error: 'string' 不能分配给 'number' 类型
export as namespace myLib;
// d3.d.ts
/*~ If this module has methods, declare them as functions like so.
*/
export function myMethod(a: string): string;
export function myOtherMethod(a: number): number;
/*~ You can declare types that are available via importing the module */
export interface someType {
name: string;
length: number;
extras?: string[];
}
/*~ You can declare properties of the module using const, let, or var */
export const myField: number;
/*~ If there are types, properties, or methods inside dotted names
*~ of the module, declare them inside a 'namespace'.
*/
export namespace subProp {
/*~ For example, given this definition, someone could write:
*~ import { subProp } from 'yourModule';
*~ subProp.foo();
*~ or
*~ import * as yourMod from 'yourModule';
*~ yourMod.subProp.foo();
*/
export function foo(): void;
}
快速忽略第三方库的类型声明:
// my-typings.ts
declare module "react"; // each of its imports are `any`
type AppProps = {
message: string;
count: number;
disabled: boolean;
/** array of a type! */
names: string[];
/** string literals to specify exact string values, with a union type to join them together */
status: "waiting" | "success";
/** any object as long as you dont use its properties (not common) */
obj: object;
obj2: {}; // almost the same as `object`, exactly the same as `Object`
/** an object with defined properties (preferred) */
obj3: {
id: string;
title: string;
};
/** array of objects! (common) */
objArr: {
id: string;
title: string;
}[];
/** any function as long as you don't invoke it (not recommended) */
onSomething: Function;
/** function that doesn't take or return anything (VERY COMMON) */
onClick: () => void;
/** function with named prop (VERY COMMON) */
onChange: (id: number) => void;
/** alternative function type syntax that takes an event (VERY COMMON) */
onClick(event: React.MouseEvent<HTMLButtonElement>): void;
/** an optional prop (VERY COMMON!) */
optional?: OptionalType;
};
React.FC<Props> | React.FunctionComponent<Props>
const MyComponent: React.FC<Props> = ...
React.Component<Props, State>
class MyComponent extends React.Component<Props, State> { ...
TypeScript
类型系统(type system)
当比较两种数据的时候, 如果比较的是他们的类型结构, 则是结构类型, 也就是说即使两个参数的类型不同,但是如果他们的类型结构是一样,我们就认为它们的类型是兼容的
比如 Java, C++, Swift则主要是以命名类型为主:
比如 ts, Haskell, Elm则主要是以结构类型为主:
基础类型:
Boolean
Number
String
symbol
Array
Object: 表示所有的非原始类型,也就是引用类型。
Null
Undefined
默认Null和undefined可以复制给任意的其他类型。
可以通过
strictNullChecks
来限制这种行为,让null和undefined只能复制给他们自身和void类型。Tuple: 可以知道数组内不同元素的类型的数组.
Enum: 定义一组数值
Any: 任意类型,主要用于兼容第三方的代码,
可以使用
noImplitAny
来限制any类型的使用。Void: 没有任何类型,例如函数没有返回值
Never: 表示永远不存在的类型,是任意类型的子类型,也可以赋值给任意类型。但是其他所有的类型都不能赋值给never类型。
Interfaces
接口定义了一些类型的成员和对应的类型,它是只在编译阶段存在,不能包含成员的具体实现细节。
所有的相同的 Interface 的声明都会最终合并在一起,因此如果你想在一个已经声明了的 Interface 上继续添加属性的话,可以继续声明该 Interface 即可。
x?: Number;
const
readyonly x: Number;
只读数组,任何调用数组上可变方法(push, pop)等方法都会导致编译错误。
let ro: ReadonlyArray<number> = a;
表示一个可被调用的类型注解
当声明一个class的同时,也相当于声明了一个拥有类的成员的同名的一个类类型。
与C#或Java里接口的基本作用一样,TypeScript也能够用它来明确的强制一个类去符合某种契约。
除了类可以互相继承之外,接口也可以相互继承。 这让我们能够从一个接口里复制成员到另一个接口里,可以更灵活地将接口分割到可重用的模块里。
Namespaces and Modules
namespaces是一种过时的ts模块化方案。
但namespaces可以用于ts的代码组织,例如一些顶层API比如react, 但是有大量的类型和方法,可以用namespace将其包含在一个顶级的命名空间中,而不是大量的export,常常用于一些顶级变量例如jquery, react, angular等.d.ts声明文件的编写。
相同名字的namespace会互相合并,并且namespace是全局作用域的。
例如:
modules 也就是es2015的模块方案,使用export 和 import 来导入导出模块。
declare 用于声明已经实现了的函数或者模块的类型。
模块解析
遵循NodeJs的模块解析策略
三斜线指令
/// <reference path="..." />
指令是三斜线指令中最常见的一种。 它用于声明文件间的依赖。 告诉编译器在编译过程中要引入的额外的文件。大多数常用的第三方模块都可以在
DefinitelyTyped
这个库中找到对应的声明文件。默认在编译时会将所有的node_modules中的@types文件下的声明文件引入进来。 可以通过
typeRoots
和types
配置项来改变默认行为。自动引入只在你使用了全局的声明(相反于模块)时是重要的。 如果你使用 import "foo"语句,TypeScript仍然会查找node_modules和node_modules/@types文件夹来获取foo包。
Generics
泛型是在定义一个类型或者接口的时候,我们不知道其中某些参数的具体类型,但是又想对其进行某种约束,这时候就可以用泛型(常常是一个大写字母)来代替表示该类型。
当你使用简单的泛型时,泛型常用 T、U、V 表示。如果在你的参数里,不止拥有一个泛型,你应该使用一个更语义化名称,如 TKey 和 TValue (通常情况下,以 T 做为泛型前缀也在如 C++ 的其他语言里做为模版。)
有时在使用泛型参数的时候,也可以省略该参数,让ts为我们自动推导出T的类型。
泛型参数同样可以有默认值。
泛型约束:也可以规定该泛型必须具有某些类型, 这里可以规定T必须是number或者string类型。
泛型类和泛型 interface 类似:
为了让泛型可以工作在一些特殊的情况,引入泛型约束。
类型进阶
类型推断
一些编程语言要求变量声明的时候必须指定它的类型, 比如C和java, 一些编程语言可以在变量声明的时候自动推断变量的类型, 例如haskell, typescript.
类型断言:
然而,当你在 JSX 中使用 的断言语法时,这会与 JSX 的语法存在歧义:
因此可以使用
(pet as Fly).fly()
的 as 语法来断言。类型守护:
类型别名
类型操作符和类型工具:
readonly, ?
extends 和条件类型
如果 T 类型可以认为是继承于U类型(要么 T 和 U 是同一种基础类型,要么 T 类型的代表范围 小于 U类型,也就是 T 是 U 的子集,U 是 T 的超集),则取 X, 否则取 Y;
这允许你告诉编译器,一个变量的类型与其他类型相同
keyof 操作符能让你捕获一个类型的键。例如,你可以使用它来捕获变量的键名称,在通过使用 typeof 来获取类型之后:
更多工具类型参考:utility-types
.d.ts
一个典型的module类型库的声明文件模板
快速忽略第三方库的类型声明:
扩展第三方的类型声明:
React + TS
@types/react, @types/react-dom
基本的Props类型模板:
使用标准的Ts注释方式:
/** comment */
, 方便使用者清楚的知道组件的属性含义.React.FC<Props>
|React.FunctionComponent<Props>
React.Component<Props, State>
React.ComponentType<Props>
React.ComponentProps<typeof XXX>
|React.ComponentProps<htmlComponent>
React.ReactElement
|JSX.Element
React.ReactNode
React.CSSProperties
React.HTMLProps<HTMLXXXElement>
React.ReactEventHandler<HTMLXXXElement>
React.XXXEvent<HTMLXXXElement>
更加细粒度的事件类型, 比如
ChangeEvent, FormEvent, FocusEvent, KeyboardEvent, MouseEvent, DragEvent, PointerEvent, WheelEvent, TouchEvent
.Refrence
React & Redux in TypeScript - Static Typing Guide