tips
传递数组或者对象作为函数参数时,可以用 Readonly<> 包裹原来的类型声明,譬如 Readonly<Array<number>>
,TypeScript 编译器会通过拒绝编译来保护这个参数不被修改。如果的确需要一个可以被修改的数组,可以通过 spread 操作 [...array] 来复制这个数组
function sortNumbers(array: Readonly<Array<number>>) {
return [...array].sort((a, b) => a - b)
}
使用 unkown 而不是 any 来标注类型尚未明确的变量。Any 告诉 TypeScript 编译器,不需要检查类型;而 unkown 则是把检查留到使用变量的时候。使用时可以通过 typeof 运算符获取到变量的实际类型,通过 as 添加上类型后就可以正常使用了
使用 Records 来代替 Objects,因为可以限定键的范围。如
type AllowedKeys = 'name' | 'age'
// use a type here instead of interface
type Person = Record<AllowedKeys, unknown>
const Human: Person = {
name: 'Steve',
age: 42,
}
tsconfig
隐式推断
ts 有隐式类型推断。开启后隐式 any 的推断会被检查并报错。
// x被隐式推断成any,抛出错误
function getNum(x) {
return x
}
// problems: Parameter 'x' implicitly has an 'any' type.
// 主动声明x是any避免错误
function getNum(x: any) {
return x
}
// 这种情况不会报错,因为隐式推断成undefined
let y
console.log(y)
// 这种情况也不会报错,因为赋值的时候类型推断成number
let z
z = 123
console.log(z)
严格检查
默认情况 undefined 和 null 是其他类型的子类型,也就是其他类型变量可以被赋值 null 或 undefined。开启后会检查这种情况并报错。
let num: number = undefined
// problems:Type 'undefined' is not assignable to type 'number'.
错误不输出
ts 默认即使编译报错也会生成 Js 文件,这一项设置 true,可以在报错的时候,不生成 js
基础
标记类型
- boolean,number,string,null,undefined
- void(一般用于函数空返回值,void 类型的变量可以赋值 undefined,不能赋值 null)
- any(允许被赋值给任意类型,any 类型不会被类型检查)
- unkonwn(允许被赋值给任意类型)
类型推论
定义时未指定类型且未赋值会被推断成 any,若赋值则会推断成赋值的类型
let myFavoriteNumber = 'seven' //推断成string
联合类型
‘或’
let myFavoriteNumber: string | number
交叉类型
‘与’
interface A {
a: string
}
interface B {
b: string
}
let test: A & B = {
a: 'test a',
b: 'test b',
}
对象的类型
描述对象的形状(shape)
interface Person {
name: string
age: number
}
let tom: Person = {
name: 'Tom',
age: 25,
}
定义的变量比接口少一些属性或多一些属性都是不允许的
可选属性
interface Person {
name: string
age?: number
}
let tom: Person = {
name: 'Tom',
}
任意属性
interface Person {
name: string
age?: number
[propName: string]: any
}
let tom: Person = {
name: 'Tom',
gender: 'male',
}
需要注意的是,一旦定义了任意属性,那么确定属性和可选属性的类型都必须是它的类型的子集:
interface Person {
name: string
age?: number
[propName: string]: string
}
let tom: Person = {
name: 'Tom',
age: 25,
gender: 'male',
}
// index.ts(3,5): error TS2411: Property 'age' of type 'number' is not assignable to string index type 'string'.
// index.ts(7,5): error TS2322: Type '{ [x: string]: string | number; name: string; age: number; gender: string; }' is not assignable to type 'Person'.
// Index signatures are incompatible.
// Type 'string | number' is not assignable to type 'string'.
// Type 'number' is not assignable to type 'string'.
//上例中,任意属性的值允许是 string,但是可选属性 age 的值却是 number,number 不是 string 的子属性,所以报错了。
一个接口中只能定义一个任意属性。如果接口中有多个类型的属性,则可以在任意属性中使用联合类型:
interface Person {
name: string
age?: number
[propName: string]: string | number | undefined // 因为age不一定有,可能是undefined
}
let tom: Person = {
name: 'Tom',
age: 25,
gender: 'male',
}
只读属性
有时候我们希望对象中的一些字段只能在创建的时候被赋值,那么可以用 readonly 定义只读属性
简单例子
interface Person {
readonly id: number
name: string
age?: number
[propName: string]: any
}
let tom: Person = {
id: 89757,
name: 'Tom',
gender: 'male',
}
tom.id = 9527
// index.ts(14,5): error TS2540: Cannot assign to 'id' because it is a constant or a read-only property.
// 上例中,使用 readonly 定义的属性 id 初始化后,又被赋值了,所以报错了。
注意,只读的约束存在于第一次给对象赋值的时候,而不是第一次给只读属性赋值的时候:
interface Person {
readonly id: number
name: string
age?: number
[propName: string]: any
}
let tom: Person = {
name: 'Tom',
gender: 'male',
}
tom.id = 89757
// index.ts(8,5): error TS2322: Type '{ name: string; gender: string; }' is not assignable to type 'Person'.
// Property 'id' is missing in type '{ name: string; gender: string; }'.
// index.ts(13,5): error TS2540: Cannot assign to 'id' because it is a constant or a read-only property.
//例中,报错信息有两处,第一处是在对 tom 进行赋值的时候,没有给 id 赋值。第二处是在给 tom.id 赋值的时候,由于它是只读属性,所以报错了。
数组的类型
类型+方括号表示法
let fibonacci: number[] = [1, 2, 3, 4]
泛型表示法(常用)
let fibonacci: Array<number> = [1, 2, 3, 4]
接口表示法(不常见)
interface NumberArray {
[index: number]: number
}
let fibonacci: NumberArray = [1, 2, 3, 4]
类数组的表示
类数组不是数组,不能用数组表示,但是可以参照接口表示法来写
比如函数参数 arguments。但事实上常用的类数组都有自己的接口定义,如 IArguments, NodeList, HTMLCollection 等
function sum() {
let args: {
[index: number]: number
length: number
callee: Function
} = arguments
}
//上述等价于
function sum() {
let args: IArguments = arguments
}
//IArguments 是 TypeScript 中定义好了的类型,它实际上就是:
interface IArguments {
[index: number]: any
length: number
callee: Function
}
函数的类型
函数声明式(简单,推荐)
function sum(x: number, y: number): number {
return x + y
}
函数表达式
let mySum: (x: number, y: number) => number = (x: number, y: number): number => {
return x + y
}
// 第一个冒号到第一个等号之间的可以省略,相当于不写type
//ts会类型推断的,不然这么写
//就像写了两遍
//或者这么写,注意是Function不是function,其实也相当于少写了type
let newSum: Function = (x: number, y: number): number => {
return x + y
}
用接口定义函数,注意参数的括号
interface SearchFunc {
(source: string, subString: string): boolean
}
let mySearch: SearchFunc = (source: string, subString: string): boolean => {
return source.search(subString) !== -1
}
可选参数(可选参数要放在最后),参数默认值
function buildName(firstName?: string, lastName: string = 'Clark'): string {
if (firstName) {
return firstName + lastName
}
return lastName
}
剩余参数,...rest 访问剩余参数
function myPush(array: Array<unknown>, ...rest: Array<unknown>) {
rest.forEach((item) => {
array.push(item)
})
}
myPush([], 1, 2, 3)
重载
重载允许一个函数接受不同数量或类型的参数时,作出不同的处理
比如,我们需要实现一个函数 reverse,输入数字 123 的时候,输出反转的数字 321,输入字符串 'hello' 的时候,输出反转的字符串 'olleh'
function reverse(x: number | string): number | string | void {
if (typeof x === 'number') {
return Number(x.toString().split('').reverse().join(''))
} else if (typeof x === 'string') {
return x.split('').reverse().join('')
}
}
然而这样有一个缺点,就是不能够精确的表达,输入为数字的时候,输出也应该为数字,输入为字符串的时候,输出也应该为字符串
这时,我们可以使用重载定义多个 reverse 的函数类型:
function reverse(x: number): number
function reverse(x: string): string
function reverse(x: number | string): number | string | void {
if (typeof x === 'number') {
return Number(x.toString().split('').reverse().join(''))
} else if (typeof x === 'string') {
return x.split('').reverse().join('')
}
}
//上例中,我们重复定义了多次函数 reverse,前几次都是函数定义,最后一次是函数实现。在编辑器的代码提示中,可以正确的看到前两个提示。
//注意,TypeScript 会优先从最前面的函数定义开始匹配,所以多个函数定义如果有包含关系,需要优先把精确的定义写在前面。
类型断言
语法
- 值 as 类型(推荐)
<类型>
值 (不推荐,tsx 语法中<Foo>
表示 ReactNode;而在 ts 中泛型也是这种语法,容易混淆)- 作用:给一个类型断言,让 ts 编译器听你的,若使用不当会造成运行时错误。常用于 ts 的一些不好解决的报错
- 示例
interface Cat {
name: string
run(): void
}
interface Fihs {
name: string
swim(): void
}
function isFish(animal: Cat | Fish) {
if (typeof (animal as Fish).swim) {
return true
}
return false
}
;(window as any).foo = 1
function getCacheData(key: string): any {
return (window as any).cache[key]
}
interface Cat {
name: string
run(): void
}
const tom = getCacheData('tom') as Cat
tom.run()
//用泛型可以更好地实现上述代码
function getCacheData<T>(key: string): T {
return (window as any).cache[key]
}
interface Cat {
name: string
run(): void
}
const tom = getCacheData<Cat>('tom')
tom.run()