Typescript学习笔记

67 阅读10分钟

void和never的区别

void一般应用于函数的返回值上。它的意思是返回undefined、null。(我们平时不写return,其实就是return undefined)

never也是应用于函数的返回值上。它的意思是不允许返回。注意是不允许return,而不是return undefined。一个函数只要写了return,那返回值类型必然不能是never。

对象作为函数参数时

当一个函数的参数是对象时,ts的校验会有区别:

function person(obj: {firstName: string, lastName: string}): void {
  console.log(`${obj.firstName} ${obj.lastName}`);
}

// 传参时构建对象:传入匹配的对象的属性,一个也不多一个也不少,可通过ts类型检测
person({firstName: 'Felix', lastName: 'Luo'}); // test pass

// 传参时构建对象:传入的对象的属性多了或者少了,不可通过ts类型检测
person({firstName: 'Felix', lastName: 'Luo', age: 18}); // test fail

//单独声明一个obj时,传入函数中,如果传入属性多了,可通过检测;如果传入属性少了,不能通过检测
const obj = {
  firstName: 'Felix',
  lastName: 'Luo',
  age: 18,
};
person(obj); // test pass


const obj2 = {
  firstName: 'Felix',
}
person(obj2); // test fail

readonly

typescript用来规范一个object中某个属性不允许被覆盖。注意这只是typescript的检验,而不是JavaScript的要求。typescript声明了readonly,是ts的检测,但不是JavaScript中这个对象的这个属性是readonly的。

type User = {
  readonly id: number;
  username: string;
  readonly hobbies: [string];
}

const user: User  = {
  id: 12345,
  username: 'Felix',
  hobbies: ['basketball'],
}
console.log(user.id);  // correct
// user.id = 12345;  // alert
user.hobbies[0] = 'football'; // don't alert

注意:如果声明了一个对象/数组是readonly,那么修改对象/数组里面的值是没问题的,因为对象和数组都是引用类型,只要不修改他们的引用值是不会alert的。

type类型之间的组合

假设我们声明了多个type,但此时我们需要新建一个type,同时包含了前面的几种type。此时我们可以使用type的组合。

type Cat = {
  liveNum: number;
}

type Dog = {
  breed: string;
}

type CatDog = Cat & Dog & {   // combine 
  color: string;
}

const newAnimal: CatDog = {
  liveNum: 9,
  breed: 'husky',
  color: 'yellow',
}

关于union

string | number 作为参数

在函数内部可以使用if 判断,ts会智能识别if的 typeof 判断!

声明数组时,想要数组内多个类型的值,也可以使用union

type age: (number | string )[] = [1, 2 ,'']

区分:

type age: number[] | string[] = [1, 2 ,'']

literal type

指定值。但单单指定一个数值没什么用,可以配合union一起使用。

type zero: 0 = 0

type isTrue: "true" I "false" I "unknown "

tuples

这个类型是针对于数组的。它让数组的长度、顺序、每个元素的类型都固定。

type HTTPResponse = [number, string]; // 数组的第一位只能是number,第二位只能是string
// 顺序不能变长度也不能变

const goodRes: HTTPResponse = [200, 'ok'];  // pass check
const goodRes: HTTPResponse = ['ok', 200]; // fail
const goodRes: HTTPResponse = ['ok', 200, 123]; // fail

// 我们不能更改数组的值为其他类型的值。

goodRes[0] = 300; // pass check
goodRes[1] = 500; // fail

还有一种比较特殊的情况,使用数组的一些方法不会经过ts检测。如

type HTTPResponse = [number, string]; // 数组的第一位只能是number,第二位只能是string
// 顺序不能变长度也不能变

const goodRes: HTTPResponse = [200, 'ok'];  // pass check

goodRes.push(666);  // pass check
goodRes.pop(); // pass check
goodRes.pop(); // pass check
goodRes.pop(); // pass check

相对来说,tuple会用得比较少。我们其实可以直接使用对象的形式,更加清晰。

enums

我们平时声明常量,就是直接声明,它只是给我们的0,“pending”一个解释的词语。

const PENDING = 0;
const SHIPPED = 1;
const DELIVERED = 2;

if(statusCode === PENDING) {
  dosth...
} else if(statusCode === SHIPPED) {
	dosth else....
}

那我们解释一下enums是什么。首先这个enums只存在于typescript中。它可以帮助我们声明一串常量,而不需要像上面JS那样定义常量并给其赋值。我们可以把enums就当做一个对象使用。下面举一个简单例子。

// 可供我们使用的常量
enum OrderStatus {
  PENDING,
  SHIPPED,
  DELIVERED,
  RETURNED
}

// 当做OrderStatus.PENDING是一个具体的值就好
const myStatus = OrderStatus.PENDING;

// 使用了OrderStatus类型,那我们穿参数的时候就必须要传OrderStatus类型了
function isDelivered(status: OrderStatus) {
  return status === OrderStatus.DELIVERED
}

isDelivered(OrderStatus.DELIVERED);

不太好理解的话,我们就可以直接理解为PENDING就是1,相当于 const PENDING = 1;

另外,enums是可以被赋值的。

enum OrderStatus {
  PENDING = 666,
  SHIPPED = 66,
  DELIVERED = "felix",
  RETURNED = "666",
}

NOTE: 赋值为string的时候,需要注意后面的也需要被赋值,而不能是默认的值。(默认值为0,1,2,3,4)。

目前感觉就不用赋值,把它当做一个常量来使用就可以了!只是更换了定义常量的方式而已。有个好处是我们确保常量不会被更改。

那么enums到底是啥呢?我们可以看看它被编译成JS是啥样的。

首先编译之后的JS是什么我们先不去理解,但是很明显,编译之后它给JS注入了很多代码。所以很多人不喜 欢用enums。

我们也还可以在enums前面加上const声明,我们看看效果是咋样。

可以看到enums的代码已经不见了,当我们需要使用enums的值是,就会赋一个默认值在上面。

Anyway,enums有些人喜欢使用,有些人不太用,完全取决于自己。enums的好处是定义常量比较简单清晰,同时它能确保常量值不被修改。(但其实我们用const声明基本类型的常量也是不能改的)

Interfaces

Interfaces和正常使用type声明一个类型,基本上是一样的。至于他们两个的区别,后面咱们再解释。interfaces只用来声明object的类型。

  • readonly和option的操作符都是和type声明是一样的。
  • 对象里面声明方法的方式也是一样的,必须遵守参数,返回值的完全一致。
// readonly和option的操作符都是和type声明是一样的。
type Point1 = {
  x: number;
  y: number;
}

const p1: Point1 = {
  x: 12,
  y: 34,
}

interface Point2 {
  readonly x: number;
  y?: number;
}

const p2: Point2 = {
  x: 56,
  y: 78,
}


// 对象里面声明方法的方式也是一样的,必须遵守参数,返回值的完全一致。
type Person1 = {
  firstName: string;
  lastName: string;
  nickName?: string;
  sayHi: () => string;
}

interface Person2 {
  firstName: string;
  lastName: string;
  nickName?: string;
  sayHi: (message: string) => string;
}

const felix: Person1 = {
  firstName: 'felix',
  lastName: 'luo',
  nickName: 'fei',
  sayHi: (msg: string) => {   // msg不要求和message完全一致,但需要类型一致
    return msg;
  }
}

const felix2: Person2 = {
  firstName: 'felix',
  lastName: 'luo',
  nickName: 'fei',
  sayHi: () => {
    return 'hello'!
  }
}

interfaces VS types

interface可以往里添加内容,reopen

具体可以看下面的代码,具体使用场景还不晓得。

interface Dog {
    name: string;
    age: number;
}

// 重新使用关键字,声明同一个interface,那么上下两个Dog他们之间是会合并在一起。
interface Dog {
    breed: string;
    bark(): string;
}
// 这里的never需要满足上述两个Dog声明的4个属性
const never: Dog = {
    name: 'never',
    age: 3,
    breed: '雪纳瑞',
    bark() {
        return 'woof!';
    }
}

type Cat = {
    name: string;
    age: number;
}
// 这里再次声明Cat,直接报错
type Cat = {
    
}

interface可以继承

没错,就是我们所熟知的继承。一个interface可以继承多个interface。

interface Dog {
    name: string;
    age: number;
}

interface Dog {
    breed: string;
    bark(): string;
}

interface Animal {
  	type: stirng;
}

// 这里继承了Dog之后,会把Dog所有的属性继承下来
interface SportDog extends Dog, Animal {
    sport: "basketball" | "football" | "baseball";
}

// 这里需要包含所有的属性才不会报错
const never: SportDog = {
    name: 'never',
    age: 3,
    breed: '雪纳瑞',
    bark() {
        return 'woof!';
    },
    sport: "baseball",
    type: 'Dog',
}

Interfaces 和 types的区别

主要区别:

  • Interface只能描述对象,而type可以描述所有类型
  • Interface可以reopen,type不行。
  • Interface可以继承,type可以使用&符号实现类似功能

juejin.cn/post/684490…

Typescript中重要的一些设置。

www.typescriptlang.org/tsconfig

初始化ts文件

tsc --init  //生成Typescript的配置文件

监控ts文件的变化,及时生成js文件

tsc --watch fileName or not
tsc -w fileName or not

设置需要进行编译的ts文件

files

Specifies an allowlist of files to include in the program. An error occurs if any of the files can’t be found.

{
  "compilerOptions": {},
  "files": [
    "core.ts",
    "sys.ts",
    "types.ts",
    "scanner.ts",
    "parser.ts",
    "utilities.ts",
    "binder.ts",
    "checker.ts",
    "tsc.ts"
  ]
}

include 和 exclude

需要注意的是,如果files设置了值,那么include就会失效,默认就是[]。如果files没有设置值,include的默认值是**/*。

LINK: www.typescriptlang.org/tsconfig#in…

exclude是跳过include里面包含的文件。默认值node_modules, bower_components, jspm_packages, outDir

LINK: www.typescriptlang.org/tsconfig#ex…

outDir

这个就是编译成js文件时输出的目录。

LINK: www.typescriptlang.org/tsconfig#ou…

target

target是Typescript编译成JavaScript时,允许使用的JavaScript规范。比如说默认值是es5,那么我们在ts中写了箭头函数,编译成js的时候,就会帮你改为function函数。 如果把这个值改为es6或es2015,那么从ts编译成js的时候,箭头函数就不会被改为function函数,因为es6是允许使用箭头函数的

LINK: www.typescriptlang.org/tsconfig#ta…

strict

严格模式。默认是true。如果设置为false,可以单独打开一些设置,比如不允许String类型的值为undefined或null。一般情况下不建议设置为false,我们使用ts最大的目的就是type check。

image.png

LINK: www.typescriptlang.org/tsconfig#st…

Working with DOM

built-in type

在Typescript中,DOM对象跟JavaScript一样也是内置的。与JavaScript不同的是,ts中使用DOM对象时,它们的类型早已声明好了。(built-in)

这个类型文件在我们安装Typescript的时候就带上了。我们可以通过配置tsconfig文件来控制是否使用要使用这些内置类型。

配置路径:"compilerOptions" -> "lib" 默认这个配置是被注释了,也就是所有的内置类型文件都是可以用的。在某些特殊情况下,你可能不需要用到所有的内置类型文件,如在node项目中就不需要dom的类型。此时可以通过这个配置来配置你所需要的内置类型文件即可。

LINK: www.typescriptlang.org/tsconfig#li…

Non-null的处理

假设我们此时使用getElementById获取一个元素,Typescript返回的值的类型为HTMLElement | null。因为它不知道这个元素是否存在,所以可能会有null的情况。

image.png

我们常用的处理情况应该是使用btn之前,使用链式操作符来判断这个值是否是true(其实跟判空差不多,具体的可以参阅MDN)。

const btn = document.getElementById('btn');
btn?.addEventListener('click', () => {
  alert('click me!');
})

另外Typescript也提供了Non-null的符号。(仅仅存在于Typescript中) 如果你能确认返回的值一定不会是null,那么你可以在这个方法执行后面加上叹号(!)

const btn = document.getElementById('btn')!;
btn.addEventListener('click', () => {
  alert('click me!');
})

可见此时返回的btn的类型仅仅是HTMLElement,而不会是null。

image.png

第二种方式仅仅在你确确实实能保证返回值不会是null时才可以使用。

type assertions

类型断言,是你能确定某个时候某个值的类型是你能确认的,可以让Typescript暂时认为你说的这个类型是正确的,不会出现类型判断错误。用一句话来说 I know you more than you. 具体看例子即可明白。

image.png

如图所示,something被我们设置为unknown类型。此时我想获取something的长度,因为我能确保它是一个字符串。所以我们可以直接使用as关键字,将something假设成string,此时在去获取字符串的长度,就不会报错了。如果直接使用something.length,则会报错,因为unknown没有length属性。

在dom的操作中,assertions中会有实际的用处,具体我们看例子。 HTML页面中有一个button和一个input,我们点击button的时候输出input的值。

const btn = document.getElementById('btn')!;
const input = document.getElementById('todoinput')!;

btn.addEventListener('click', () => {
  alert(input.value)
;})

但此时会报错。

image.png

因为我们获取元素的时候,typescript并不知道我们获取的元素是哪种类型,比如HTMLButtonElement,HTMLInputElement...所以返回的元素的类型默认是HTMLELement | null。 但是HTMLElement上是没有Value这个属性的。所以会报错。

我们自己能够确定获取到的是HTMLInputElement,所以我们此时就可以使用assertions,来获取这个value。

const btn = document.getElementById('btn')! as HTMLButtonElement;
const input = document.getElementById('todoinput') as HTMLInputElement;

btn.addEventListener('click', () => {
  alert(input.value)
;})

此时就不会报错了。

其实我们可以理解assertions为帮助Typescript来识别我们的类型,有时候他并不够智能,我们需要提供帮助,因为我们有时候确实比它更了解它。