类型拓宽

类型拓宽(type widening)是理解TS类型推导机制的关键。

一般来说,TS在推导类型的时候会放宽要求,故意推导出一个更宽泛的类型,而不限定为每个具体的类型。

声明变量时如果运行以后修改变量的值(例如使用letvar声明),变量类型将拓宽,从字面值放大到包含该字面量的基础类型

let a = 'x';  // string
let b = 123;  // number
let c = true; //boolean

然而,使用const声明不可变的变量时,情况不同,会自动的把类型缩窄

const a = 'x'  // 'x'
const b = 123  // 123
const c = true // true

我们当然可以显示的标注类型防止类型拓宽

let a:'x' = 'x';   // 'x'
let b:123 = 123;   // 123
let c:true = true; // true

不过使用**const声明的对象,并不会缩窄推导的类型**

const obj = {
  b: 123  // b是number类型
}

因为Javascript对象是可变的,所以在Typescript看来,创建对象之后你可能会更新对象;

null与undefined

在JavaScript中,nullundefined都表示缺少什么,Typescript也支持这两个值,并且都有各自的类型,类型名称就是null与undefined。

这两个类型比较特殊,在TS中,undefined类型只有undefined一个值,null类型也只有null一个值。

我们在写JavaScript的时候,这两个在语义上有细微的差别,undefined一般表示尚未定义,而null表示缺少值。

nullundefined没有开启strictNullChecks检查的情况下(tsconfig.json中设置了strict:true默认开始,如果想关闭,可以设置strictNullChecks:false),会被视为其他类型的子类型,比如string类型会被认为包含了nullundefined

nullundefined也是单独的类型是带有Javascript思维,在遇到复杂结构的时候经常会思考遗漏的问题。最重要的就是忽略类型兼容性的问题。

const temp1:undefined = undefined;
const temp2: null = null;

const temp3: string = null; // 仅在关闭了strictNullChecks时才成立
const temp4: string = undefined; // 仅在关闭了strictNullChecks时才成立

let temp5 = undefined; // any
let temp6:string = null; // 仅在关闭了strictNullChecks时才成立

// 仅在关闭了strictNullChecks时才成立
function getStr(): string { 
  if(Math.random() > 0.5) {
    return null
  }
  return "hello";
}

type User = {
  name: string;
  age: number;
};

function getUser(): User {
  if (Math.random() > 0.5) { 
    return null;
  }
  return {
    name: "John",
    age: 30,
  }
}

void

在JavaScript中,void有特殊的用法,比如

<a href="javascript:void(0)">点击</a>

我们在界面经常这样写来表示阻止a标签的默认行为.

这里的 void(0) 等价于 void 0,即 void expression 的语法,我们可以使用它来执行一个立即执行函数(IIFE)

void function(){
  alert(111);
}();

在Typescript中,void也表示一种类型,用于描述一个内部没有 return 语句,或者没有显式 return 一个值的函数的返回值,如:

function fn1() {}
function fn2() {
  return;
}
function fn3() {
  return undefined;
}

let m1 = fn1();
let m2 = fn2();
let m3 = fn3();
console.log(m1, m2, m3);

fn1fn2 的返回值类型都会被隐式推导为 void,只有显式返回了 undefined 值的 fn3 其返回值类型才被推导为了 undefined

注:fn3只有在tsconfig.json中开启了strictNullChecks:true的情况下,其返回值类型才会被推导为undefined,如果没有开启strict模式,或者关闭了strictNullChecks,fn3函数的返回值类型会被默认推导为any

虽然 fn3 的返回值类型会被推导为 undefined,但仍然可以使用 void 类型进行标注

function fn3():void {
  return undefined;
}

undefined 能够被赋值给 void 类型的变量,就像在 JavaScript 中一个没有返回值的函数会默认返回一个 undefined ,其实主要还是为了兼容性。但是,在strict模式下,null 类型会报错,除非关闭strictNullChecks

function fn3():void {
  return undefined;
}
function fn4():void {
  return null; // error 不能将类型null分配给类型void,关闭strictNullChecks不报错
}

let v1: void = undefined;
let v2: void = null; // error 不能将类型null分配给类型void,关闭strictNullChecks不报错

对象字面量

按照我们之前基础类型的惯性思维,在Typescript使用类型描述对象应该是下面这个样子:

let a: object = {
  b:'hello'
}

但是访问b的时候就会发生错误

console.log(a.b); //error 类型object上不存在属性"b"

为什么把一个变量声明成object类型,却做不了任何操作呢?

其实object类型对值并不了解,就只能表示该值是一个JavaScript对象,仅此而已。因此,当我们输入

a. 

Typescript不会有任何提示。

如果我们不显示注解,直接让Typescript推导

let a = {
  b:'hello'
}
console.log(a.b);

在这里插入图片描述

这其实就是对象字面量的语法,当然除了让Typescript推导出对象的解构,我们可以自己进行明确的描述

const a: {b: string} = {
  b:'hello'
}
console.log(a.b);

const user: {
  name: string
  age: number
} = {
  name: 'jack',
  age: 18
}
console.log(user.name)

与前面讲的基本类型不同,使用const声明对象不会导致Typescript把推导的类型缩窄。这是因为JavaScript对象是可变的,所以在JavaScript看来,创建对象之后你可能会更新对象的字段

可选符号?

默认情况下,Typescript对对象的属性要求十分的严格,如果声明对象有个类型为string的属性name和类型为number的属性age,Typescript将预期对象有这么两个属性。而且有且仅有这两个属性,如果缺少name和age属性,或者多了其他属性,Typescript将报错

// 类型 "{ name: string; }" 中缺少属性 "age",但类型 "{ name: string; age: number; }" 中需要该属性
let user: {
  name: string
  age: number
} = {
  name: 'jack',
  // age: 18
}

// error "类型“{ name: string; age: number; }”上不存在属性“sex”
user.sex = "男";

我们可以通过可选符号修饰符?告诉Typescript某个属性是可选的

let user: {
  name: string
  age?: number
  sex?: string
} = {
  name: 'jack'
}

注意:如果标注为可选属性,那么这个属性的类型其实是:类型 | undefined,也就是说,age?:number,其实真正的应该是age?:number | undefined

readonly

除了修饰符可选符号(?)之外,还可以使用readonly修饰符把字段标记为只读

let user: {
  readonly name: string
  age: number
} = {
  name: 'jack',
  age: 18
}
user.age = 19;
user.name = 'tom'; //error 无法为 "name" 赋值,因为它是只读属性

readonly不仅仅可以修饰对象的属性,数组,元祖和类中都可以使用readonly

更多推荐