[官方文档](TypeScript: JavaScript With Syntax For Types.)
> 以下是精简版本,如果不想看全篇看下面的内容就够了。
ts内置类型8个
- Number
- String
- BigInt
- Boolean
- Symbol
- Null
- Undefined
- Object
更多详情可以参考 [MDN](JavaScript 数据类型和数据结构 - JavaScript | MDN),
需要注意:原始类型都可以使用typeof进行测试,除了null,typeof null 返回'object',需要使用===null 来测试
其他重要的ts类型
- unKnown
- never
- object literal eg{property:Type}
- void for functions with no documented return value
- T[] Array[T]
- [T,T] tuples,
- (t:T) => U functions
Notes:
1、方法的语法包含参数的名字,使用起来很难
let fast:(a:any,b:any)=>any=(a,b)=>a;
//更准确的
let fast:<T,U>(a:T,b:U)=>T=(a,b)=>a;
2、对象类型表明对象值的语法
let o:{n:number;xs:object[]} = {n:1,xs:[]};
Boxed types(盒装类型)
Javascript具有基本类型的盒装等价物,其中包含程序员与这些类似相关的方法。Typescript反映了这一点,例如,原始类型number和装箱类型Number之间的差异。很少需要盒装类型,因为他们的方法返回原始方法
(1).toExponential();
// equivalent to
Number.prototype.toExponential.call(1);
Gradual typing(渐进类型)
Typescript在无法确定表达式的类型时使用类型any.与Dynamic相比,将any称为类型就是言过其实了。它只是在出现的任何地方关闭类型检查器,使用noImplicitAny配置tsconfig.json
// tsconfig.json
{
"compilerOptions": {
"noImplicitAny": true
}
}
Structural typing(结构类型)
结构类型是大多数函数是程序员比较熟悉的概念。
// @strict: false
let o = { x: "hi", extra: 1 }; // ok
let o2: { x: string } = o; // ok
下面可以使用类型起名,接口,以及类定义接口(但是,在递归定义和类型参数方面,类型别名的行为不同于接口)
type One = { p: string };
interface Two {
p: string;
}
class Three {
p = "Hello";
}
let x: One = { p: "hi" };
let two: Two = x;
two = new Three();
联合类型(|)
联合类型由两个及以上的成员构成,使用的时候值需是满足其中一种即可。
以下是内置的判断联合类型的条件
- 字符串 typeof s === "string"
- 数字 typeof n ==="number"
- 大整数 typeof m === "bigint"
- 布尔值 typeof b === "boolean"
- 符号 typeof g === "symbol"
- undefined typeof undefined === "undefined"
- 函数 typeof f === "function"
- 数组 Array.isArray(a)
- 对象 typeof o === "object"
type LockStates = "locked" | "unlocked"
let state:LockStates = 'locked'
//注意以下内容的使用
let s = "right";
pad("hi",10,s) //此时s报错 error:'string' is not assignable to '"left"' | '"right"'
//更改使用 正确用法
let s:"left" | "right" = "right";
pad("hi",10,s)
交叉类型(&)
交叉类型可以将多个类型合并为一个类型,合并后的类型拥有所有类型的属性和方法。
type Combined = { a: number } & { b: string };
type Conflicting = { a: number } & { a: string };
注意:Conflicting a有两种类型一种是number一种是string,本没有这种类型的所以被解析出来是never类型。
Contextual typing 上下文类
Typescript有一些明显的地方可以类型推断。
declare function map<T, U>(f: (t: T) => U, ts: T[]): U[];
let sns = map((n) => n.toString(), [1, 2, 3]);
此处,此示例中的 n: number 也是如此,尽管在调用之前尚未推断出 T 和 U。 实际上,在使用 [1,2,3] 推断 T=number 之后,n => n.toString() 的返回类型被用来推断 U=string,导致 sns 的类型为 string[]。
declare function run<T>(thunk: (t: T) => void): T;
let i: { inference: string } = run((o) => {
o.inference = "INSERT STATE HERE";
});
o 的类型被确定为{inference:string}因为:
- 声明初始值设定项根据声明的类型进行上下文类型化: { inference: string }。
- 调用的返回类型使用上下文类型进行推断,因此编译器推断 T={ inference: string }。
- 箭头函数使用上下文类型来键入它们的参数,因此编译器给出了 o: { inference: string }。
类型别名
类别名称只是别名,会给类型起一个新的名字
- 基础案例
type Size = [number,number]
let x:Size = [122,125]
- 通过&创建新的类型
type FString = string & { __compileTimeOnly: any };
FString就像普通的字符串,只是编译器认为他有一个名为__compileTimeOnly的属性,但实际上并不存在,这意味着FString仍然可以分配给string,但反之则不行。
接口interface和类型的区别
注意:类型别名和接口interface非常相似,很多情况下你可以在他们之间自由选择。interface的几乎所有功能type中可以使用,主要区别在于无法重新打开类型添加新属性,而接口始终可以可以扩展。
- 类别名称不能参与声明合并,接口可以
两者如何实现扩展
- 接口只能用于声明对象的类型,不能重命名基本类型
建议:在多数情况下,可以根据个人喜好进行选择,一般情况下都用interface,如果不满足可使用type
判别联合
与data最接近的等效项是具有判别属性的类型联合
type Shape =
| { kind: "circle"; radius: number }
| { kind: "square"; x: number }
| { kind: "triangle"; x: number; y: number };
标记或判别只是每个对象类型中的属性,每个变体都具有相同属性和不同的单元类型,这仍然是一个普通的联合类型;前导 | 是联合类型语法的可选部分。可以通过javascript代码来区分联合体的成员
type Shape =
| { kind: "circle"; radius: number }
| { kind: "square"; x: number }
| { kind: "triangle"; x: number; y: number };
function area(s: Shape) {
if (s.kind === "circle") {
return Math.PI * s.radius * s.radius;
} else if (s.kind === "square") {
return s.x * s.x;
} else {
return (s.x * s.y) / 2;
}
}
上面的代码area的返回类型被推断为number,默认为完整代码,如果没有涉及某些结果可以将返回类型修改为 number|undefined
类型参数
函数的参数也可以需要声明类型的。没有大小写要求,但类型通常是单个大写字母。类型参数也可以被约束为一个类型。有点像类型约束。
function firstish<T extends { length: number }>(t1: T, t2: T): T {
return t1.length > t2.length ? t1 : t2;
}
因为Typescript是结构化的,所以它不像名义系统那样需要类型参数。具体来说,不需要他们来时函数多态。类型参数应该只用于传播类型信息,例如约束参数为同一类型
//T不是必须的;因为它只被引用了一次,所以它没有被用来约束返回值或其他参数类型。
function length<T extends ArrayLike<unknown>>(t: T): number {}
function length(t: ArrayLike<unknown>): number {}
模块系统
通过import导入export导出
//导入
import { value, Type } from "npm-package";
import * as prefix from "../lib/third-package";
import f = require("single-function-package"); //通过commonjs模块 使用node.js模块
readonly和const
- const的应用
在Javascript中,尽管它允许带有const的变量声明,声明引用时不可变的,引用仍是可以变得
const a = [1, 2, 3];
a.push(102); // ):
a[0] = 101; // D:
针对对象或者数组使用const进行断言。
//数组断言
let a = [1, 2, 3] as const;
a.push(102); // error
a[0] = 101; // error
//对象断言(优点:在使用的时候可以清楚的看到属性内容)
const test = {
name:'124',
age:13
} as const
- readonly的应用
//属性的使用
interface Rx{
readoly x:number
}
let rx: Rx = { x: 1 };
rx.x = 12; // error
//接口的使用
interface X {
x: number;
}
let rx: Readonly<X> = { x: 1 };
rx.x = 12; // error
//数组的使用
let a: ReadonlyArray<number> = [1, 2, 3];
let b: readonly number[] = [1, 2, 3];
a.push(102); // error
b[0] = 101; // error
非空断言运算符(后缀!)
Typescript还具有一种特殊的语法,可以在不进行热河显示检查的情况下从类型中删除null和undefined.在热河表达时候写!实际上是一个类型断言,该值不是null或undefined
function liveDangerously(x?: number | null) {
// No error
console.log(x!.toFixed());
}
//当我们知道x不为null或者undefined 使用!就很重要
书写规范
- 有些情况,typescript可以为我们推断出类型,建议不要自己手动在填上类型。
- 使用noImplicitAny来控制any
- 使用strictNullChecks 来控制undefined或者null
常见问题
- 类型断言
declare function handleRequest(url: string, method: "GET" | "POST"): void;
const req = { url: "https://example.com", method: "GET" };
handleRequest(req.url, req.method);
//Argument of type 'string' is not assignable to parameter of type '"GET" | "POST"'.
解决方法一:
//明确类型为GET
handleRequest(req.url, req.method as "GET");
解决方法二
//as const 后缀的作用类似用从上图,但用于类型系统,确保所有书信分配字面量类型。而不是通用版本,如string或number
const req = { url: "https://example.com", method: "GET" } as const;
handleRequest(req.url, req.method);