Skip to content

细节补缺

类型别名 type

类型别名可以给现有类型起一个新的名称,可以简单将它视作为JS的变量标识符,主要用于定义一些复杂的类型:比如联合类型、交叉类型等。

typescript
type ID = number

type Person = {
  name: string
  walk: () => void
}

function createPerson(name:string):Person{
  let p: Person = {
    name,
    walk(){
      console.log("直立行走")
    }
  }

  return p
}

类型操作

TS提供多种运算符,从现有类型中构建新的类型。

联合类型

是由两个或多个类型组成的类型,用|连接,表示或的意思。

typescript
let foo: number | string

foo = "abc" // OK
foo = 123 // OK

在使用联合类型时,只能访问联合类型所有成员共同的成员属性,当联合类型被确定类型后,该变量只能使用确定类型的属性和方法

typescript
/* 编译报错 */
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

如果要使用其中一个成员类型的属性和方法,要对联合类型缩小判断

typescript
let foo: number | string

if(typeof foo === "string"){
  foo.length // OK
}

交叉类型

在多个成员类型中取交集合并成一个类型,用&连接,表示且的关系。

animal的类型必须要同时满足BirdFish的类型约束

typescript
type Bird = {
  fly: () => void
}
type Fish = {
  swim: () => void
}

const animal: Bird & Fish = {
  fly() {},
  swim() {},
}

可选属性

指对象的某些属性可以不声明也可以是undefined,它跟显式定义undefined有些不同。

typescript
type Person = {
  name: string
  gender: string | undefined
  age?: number
}

let person: Person = {
  name: "张三",
  gender: undefined, //gender只能是string或undefined,不能缺省,而age属性可以 
}

只读属性

某些属性只能在创建时赋值,之后不可修改。

typescript
type Person = {
  name: string
  age?: number
  readonly id: number
}

let person: Person = {
  id: Math.floor(Math.random() * 1000),
  name: "张三"
}

person.id = "123"; // Error 无法为id赋值,因为它是只读属性

索引签名

处理动态属性名,描述对象中未知数量但符合特定键值类型规则的属性。

一旦确定了索引签名属性的值的类型,其余的确定属性和可选属性的类型都必须为任意类型属性的子集(包括已声明的属性)。

typescript
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关键字来定义对象的类型。接口是一种用于描述对象形状的方式,它定义了一个对象需要具备的属性和方法。

typescript
interface Person {
  name: string
  age: number
}

const person: Person = {
  name: 'maomao',
  age: 18,
}

继承

接口继承可以在现有类型上扩展新的类型,与JS class的继承不一样,接口是可以多继承的

typescript
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会将多次声明的类型进行合并

typescript
interface Postion{
  x: number
  y: number
}

interface Postion{
  y: number
}

// => 最终类型
// interface Postion{
//   x: number
//   y: number
//   y: number
// }

约束函数

interface不止可以约束对象类型,还能用来约束函数类型

typescript
interface MathOperation {
  (a: number, b: number): number; // 约束参数类型和返回值类型
}

// 符合接口的函数
const add: MathOperation = (x, y) => x + y;
const multiply: MathOperation = (x, y) => x * y;

type 的区别

typeinterface非常相似都可以描述一个对象或者函数,大多时候,可以选择任一种使用,但是也存在区别之处:

  • 定义范围type的定义范围比interface大,interface仅仅用于描述对象的结构,无法约束基本类型、元组、联合类型等
  • 扩展type通过交叉类型&进行扩展,interface通过继承扩展
  • 重复定义interface支持声明合并,type不支持声明合并,重复声明会报错

优先使用 interface约束对象类型,在面对复杂类型时,再选择使用type来定义类型。

类型安全

TS提供类型安全机制,协助编辑器在自动推导时推断更加精准的类型范围,将宽泛的类型收窄为更为安全的类型。

类型保护

使用代码逻辑判断用于确保该类型在一定范围内

  • typeof
  • in
  • instanceof
  • is

类型断言

通过类型断言告诉编辑器“你很确定某个值的类型”。

类型断言好比其他语言中的类型转换,但是只进行运行时的类型转换,TS会假设你已经进行了必须的类型检查。

类型断言有两种方式定义

typescript
let someValue: any = 'this is a string'

let strLength: number = (someValue as string).length
let strLength: number = (<string>someValue).length

两种方式都是被允许的,但是在tsx文件中,只允许as语法。

非空断言

当一个对象属性为可选属性时,在访问时可用可选链访问属性

typescript
interface IPerson {
  name: string;
  age: number;
  hobbies?: string[];
}

const p: IPerson = {
  name: "张三",
  age: 20,
  hobbies: ["唱", "跳", "rap", "篮球"],
};

// 使用可选链访问可选属性
console.log(p.hobbies?.[0]);

但给可选属性赋值时,就会报错:可选链只能用于访问属性,不能用于赋值

typescript
p.hobbies?.[0] = "music"; // Error

可以使用非空断言操作符来解决这个场景

typescript
p.hobbies![0] = "music";

非空断言操作很危险,在不 100% 确定该值的类型时,尽量不要使用它。

函数

参数

  • 可选参数
  • 默认参数
  • 剩余参数
typescript
// 普通参数
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);
}

返回值

显式声明函数的返回值

typescript
// 返回值
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的组件添加额外的属性。

tsx
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

在开发中一般使用函数表达式的方式约束函数类型,如果函数需要添加额外的属性的话,可以使用接口的方式,约束函数的同时添加额外的属性。

构造函数

描述一个函数是构造函数,在工厂函数中很常见

typescript
class Person {/* ... */}

interface IPerson {
  new (name: string): Person;
}

function create(ctor: IPerson) {
  return new ctor("张三");
}

const p = create(Person)

函数重载

JS中是没有函数重载的概念,在TS中,只要下面的条件成立,即可视为函数重载:

  • 函数名一致
  • 函数参数数量不同
  • 函数参数类型不同
  • 函数返回值不同

TS中做函数重载需要对类型收窄

typescript
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

如有转载或 CV 的请标注本站原文地址