细节补缺
类型别名 type
类型别名可以给现有类型起一个新的名称,可以简单将它视作为JS
的变量标识符,主要用于定义一些复杂的类型:比如联合类型、交叉类型等。
type ID = number
type Person = {
name: string
walk: () => void
}
function createPerson(name:string):Person{
let p: Person = {
name,
walk(){
console.log("直立行走")
}
}
return p
}
类型操作
TS
提供多种运算符,从现有类型中构建新的类型。
联合类型
是由两个或多个类型组成的类型,用|
连接,表示或的意思。
let foo: number | string
foo = "abc" // OK
foo = 123 // OK
在使用联合类型时,只能访问联合类型所有成员共同的成员属性,当联合类型被确定类型后,该变量只能使用确定类型的属性和方法
/* 编译报错 */
function getLength(value: string | number): number {
// Error: 类型 “string | number” 上不存在属性 “length”(类型 “number”上 不存在属性 “length”)
return value.length
}
/* 编译通过 */
function valueToStr(value: string | number): string {
return value.toString()
}
foo = "abc"
foo.length // OK
foo = 123
foo.length // Error
如果要使用其中一个成员类型的属性和方法,要对联合类型缩小判断
let foo: number | string
if(typeof foo === "string"){
foo.length // OK
}
交叉类型
在多个成员类型中取交集合并成一个类型,用&
连接,表示且的关系。
animal
的类型必须要同时满足Bird
和Fish
的类型约束
type Bird = {
fly: () => void
}
type Fish = {
swim: () => void
}
const animal: Bird & Fish = {
fly() {},
swim() {},
}
可选属性
指对象的某些属性可以不声明也可以是undefined
,它跟显式定义undefined
有些不同。
type Person = {
name: string
gender: string | undefined
age?: number
}
let person: Person = {
name: "张三",
gender: undefined, //gender只能是string或undefined,不能缺省,而age属性可以
}
只读属性
某些属性只能在创建时赋值,之后不可修改。
type Person = {
name: string
age?: number
readonly id: number
}
let person: Person = {
id: Math.floor(Math.random() * 1000),
name: "张三"
}
person.id = "123"; // Error 无法为id赋值,因为它是只读属性
索引签名
处理动态属性名,描述对象中未知数量但符合特定键值类型规则的属性。
一旦确定了索引签名属性的值的类型,其余的确定属性和可选属性的类型都必须为任意类型属性的子集(包括已声明的属性)。
interface UserData {
id: number;
[key: string]: number | string; // 约束所有属性值类型
}
const data: UserData = {
id: 1, // ✅ 符合(number)
name: "Alice", // ✅ 符合(string)
age: 30 // ✅ 符合(number)
// isAdmin: true // ❌ 错误:boolean 不匹配 string | number
};
接口 interface
使用接口interface
关键字来定义对象的类型。接口是一种用于描述对象形状的方式,它定义了一个对象需要具备的属性和方法。
interface Person {
name: string
age: number
}
const person: Person = {
name: 'maomao',
age: 18,
}
继承
接口继承可以在现有类型上扩展新的类型,与JS class
的继承不一样,接口是可以多继承的
interface IBaseInfo{
hp: number
mp: number
}
interface Behavior{
walk: ()=> void
}
interface Hero extends IBaseInfo,Behavior{
name: string
title: string
}
// => 继承完的类型为:
// interface Hero{
// name: string
// title: string
// hp: number
// mp: number
// walk(){/*...*/}
// }
声明合并
同一个接口可以多次声明,TS
会将多次声明的类型进行合并
interface Postion{
x: number
y: number
}
interface Postion{
y: number
}
// => 最终类型
// interface Postion{
// x: number
// y: number
// y: number
// }
约束函数
interface
不止可以约束对象类型,还能用来约束函数类型
interface MathOperation {
(a: number, b: number): number; // 约束参数类型和返回值类型
}
// 符合接口的函数
const add: MathOperation = (x, y) => x + y;
const multiply: MathOperation = (x, y) => x * y;
与 type
的区别
type
与interface
非常相似都可以描述一个对象或者函数,大多时候,可以选择任一种使用,但是也存在区别之处:
- 定义范围:
type
的定义范围比interface
大,interface
仅仅用于描述对象的结构,无法约束基本类型、元组、联合类型等 - 扩展:
type
通过交叉类型&
进行扩展,interface
通过继承扩展 - 重复定义:
interface
支持声明合并,type
不支持声明合并,重复声明会报错
优先使用 interface
约束对象类型,在面对复杂类型时,再选择使用type
来定义类型。
类型安全
TS
提供类型安全机制,协助编辑器在自动推导时推断更加精准的类型范围,将宽泛的类型收窄为更为安全的类型。
类型保护
使用代码逻辑判断用于确保该类型在一定范围内
- typeof
- in
- instanceof
- is
类型断言
通过类型断言告诉编辑器“你很确定某个值的类型”。
类型断言好比其他语言中的类型转换,但是只进行运行时的类型转换,TS
会假设你已经进行了必须的类型检查。
类型断言有两种方式定义
let someValue: any = 'this is a string'
let strLength: number = (someValue as string).length
let strLength: number = (<string>someValue).length
两种方式都是被允许的,但是在tsx
文件中,只允许as
语法。
非空断言
当一个对象属性为可选属性时,在访问时可用可选链访问属性
interface IPerson {
name: string;
age: number;
hobbies?: string[];
}
const p: IPerson = {
name: "张三",
age: 20,
hobbies: ["唱", "跳", "rap", "篮球"],
};
// 使用可选链访问可选属性
console.log(p.hobbies?.[0]);
但给可选属性赋值时,就会报错:可选链只能用于访问属性,不能用于赋值
p.hobbies?.[0] = "music"; // Error
可以使用非空断言操作符来解决这个场景
p.hobbies![0] = "music";
非空断言操作很危险,在不 100% 确定该值的类型时,尽量不要使用它。
函数
参数
- 可选参数
- 默认参数
- 剩余参数
// 普通参数
function sum1(a: number, b: number) {
return a + b;
}
// 可选参数
// 1. 可选参数必须在必传参数后面
// 2. 可选参数的类型是 [type] | undefined
const sum2 = function (a: number, b?: number) {
return a + (b || 0);
};
// 默认参数
// 1. 设置了默认参数,类型注解可以省略
// 2. 设置了默认参数后,不再受可选参数必须在最后一位的限制
const sum3 = (a?: number, b = 0) => {
return (a || 0) + b; // 参数a变成了可选,可能为未定义
};
// 剩余参数
// 1. 剩余参数必须在最后一位
// 2. 剩余参数的类型是一个array
function sum4(a: number, ...rest: number[]) {
return a + rest.reduce((pre, cur) => pre + cur, 0);
}
返回值
显式声明函数的返回值
// 返回值
function add1(a: number, b: number): number {
return a + b;
}
const add2: (a: number, b: number) => number = function (a, b) {
return a + b;
};
const add3 = (a: number, b: number): number => a + b;
function add4(a:number, b:number):number{
return `${a+b}` // Error 不能将string类型分配给number类型
}
函数调用签名
函数是个对象,能对其添加其他属性。比如React
的组件添加额外的属性。
interface FormItem {
(props:any): React.ReactNode
}
interface Form{
(props:any): React.ReactNode
FormItem: FormItem
}
function Form: Form(props){}
function FormItem: FormItem(props){}
Form.FormItem = FormItem
在开发中一般使用函数表达式的方式约束函数类型,如果函数需要添加额外的属性的话,可以使用接口的方式,约束函数的同时添加额外的属性。
构造函数
描述一个函数是构造函数,在工厂函数中很常见
class Person {/* ... */}
interface IPerson {
new (name: string): Person;
}
function create(ctor: IPerson) {
return new ctor("张三");
}
const p = create(Person)
函数重载
JS
中是没有函数重载的概念,在TS
中,只要下面的条件成立,即可视为函数重载:
- 函数名一致
- 函数参数数量不同
- 函数参数类型不同
- 函数返回值不同
在TS
中做函数重载需要对类型收窄
function reverse(x: number): number;
function reverse(x: string): string;
function reverse(x: number | string): number | string {
if (typeof x === 'number') {
return Number(x.toString().split('').reverse().join(''));
} else {
return x.split('').reverse().join('');
}
}
console.log(reverse('abc')) // 'cba'
console.log(reverse(123)) // 321