ts官方文档

小满主页

小满ts

remake

很久没登CSDN了,没想到当时随便记录的一篇居然挺多人看的。更新一波把坑填了,顺便也回顾一下ts的相关内容
推荐一篇很nice的ts入门博客:typescript史上最强学习入门文章(2w字)

至少我看完这篇之后项目里基本随便用了,只不会做一些很难的类型体操(个人目前认为这完全是没有必要的,当然会是更好,而且也有面试会考相关的知识点)

小满zs的ts到目前,集成了一些es6特性和前端工程化相关的内容,实操性上更友好,比较适合作为新手上路的实践教程。不过我也是后面二刷了部分才理解到讲的一些点。

然后分享一些这么久以来使用ts的感受吧。一开始确实是恨的,毕竟本来唰唰两下写完的js,现在还要花额外的时间去标注各种各样的类型,一些复杂的类型写起来也很让人烦躁。

然而到现在习惯ts之后,拿到一个js的文件反而会有些晕了,而且项目中基本都会默认的先去思考类型再动手实现,并且尽量做到完整的类型标注。

个人向的优点:

代码即文档 & 智能提示。

这点真的非常难能可贵,尤其是针对具有完备类型提示的第三方库而言,在使用第三方函数的时候都是一个黑盒,然而我们可以通过vscode F12直接跳转到其类型定义,这样就能大概的清楚这个函数的作用以及如何使用(传参)。对于其提供的对象,. 一下就能看到他有的属性,这样也省去了记忆的时间。

可读性上,有的人认为优秀的代码是“自注释”的,我非常不认同这点。团队中每个人的技术栈、编码水平、编码风格都是不固定的,好的代码可读性会更好,但也不能因此否认注释的作用,毕竟每个人眼中的“好代码”也不尽相同(当然坏代码是加再多注释都没用的)。而ts就相当于提供了一个统一的规范,通过其类型检查,再加上eslint、lint-staged、husky等配置,就可以一定程度上减少协作开发时遇到💩代码的可能性。

1(由于小满zs视频更新,顺序已不对应)

安装ts

npm i typescript -g

tsc -v 查看版本号

调试

tsc --init 初始化ts,生成tsconfig.json

tsc -w 编译出一个js文件、

node index.js node调试js文件

其他

NaN、Infinity也算number类型
还有一些进制也算 比如hex

void类型是null/undefined,一般用于函数
严格模式void不能被赋为null

严格模式在cfg里面"strict"里改
非严格模式下,undefined和null可以穿插赋值

2

安装库

npm i ts-node -g

配置镜像库:
npm i xmzs -g
其他命令
mmp ls
mmp -h
mmp use

生成package.json
npm init -y

生成配置文件node_modules
npm i @types/node -D

然后就可以直接使用 ts-node index.ts来看到输出了

类型

顺序
  1. any unknown
  2. Object
  3. Number String Boolean
  4. number string boolean
  5. 1 “qwq” false
  6. never
其他

unknown 只能赋值给自身或者any
unknown 不能用.读到属性

3 Object、object、{}

Object

和any差不多

object

引用数据类型

{}

相当于new Object
但是赋值后无法修改

4 接口

特点

  1. 接口和类型必须一模一样
  2. 接口重名,重合
  3. 索引签名
    [propName:string]:any
    让其他属性取消强校验
  4. ?标志属性非必填
  5. readonly 限制只读,常用于函数和id
  6. 继承 extend
  7. 函数接口,写对参数和返回值的约束
    注意限制的位置
interface Fn {
  (name: string): number[];
}

const a: Fn = function fn(name: string) {
  return [1];
};

5 数组

number[] = Array<number>
number[][] = Array<Array<number>>

对象数组用interface

interface x {
  name: string;
}

const arr: x[] = [{ name: "qwq" }];

6 函数

  1. 函数定义类型和返回值
    返回值在()后定义
  2. 默认值和可选参不可一起用
  3. 参数是对象,interface对参数约束就好
  4. ts可以定义this的类型
    js不能用
interface Obj {
  user: number[];
  add(this: Obj, num: number): void;
}

const obj: Obj = {
  user: [1, 2, 3],
  add(num) {
    this.user.push(num);
  },
};
obj.add(4)
console.log(obj);

  1. 函数重载
let user: number[] = [1, 2, 3];
//传number类型数组 添加
function findNum(add: number[]): number[];
//传id,单个查询
function findNum(id: number): number[];
//没有传,查全部
function findNum(): number[];
//实现
function findNum(ids?: number | number[]): number[] {
  //判断类型是否为数组用Array的方法
  if (Array.isArray(ids)) {
    user.push(...ids);
    return user;
  }
  //判断是否为number这类用typeof
  else if (typeof ids == "number") {
    return user.filter((v) => v == ids);
  }
  //最后也要有返回值
  else {
    return user;
  }
}
findNum([4]);
console.log("查全部", findNum());
console.log("查单个", findNum(1));

6

| 联合
!!强转
& 交叉(将两个类型合起来)
类型断言:
(type as A)或者(<A>type)
注意:断言只能欺骗一下ts编译器,实际传数据传的啥类型还是啥类型

7 内置对象

第7节的代码雨还是很有意思的
https://www.bilibili.com/video/BV1wR4y1377K?p=8&vd_source=133a4c6b8765759be3947374e6336df7

ecma

Number Date RegExp Error XMLHttprequest

let num: Number = new Number(1);

dom

bom

自行查阅吧^^

8 class

和java/c++里学的类实现差不多

教程用例比较复杂,可以理解为在手写vue源码的一部分,虚拟dom渲染为真实dom

  1. 基本用法
    extends:继承
    implements:使用implements来实现一些类共有方法属性的提取,interface提取,implements实现

  2. 修饰符
    readonly
    public
    private 只能内部
    protected 给子类和内部
    在外面都是不能用.访问到的

  3. super调父类方法
    实质为父类的prototype.constructor.call

  4. static
    静态方法只能被类本身调用,实例无法调用
    在static方法里的this也只能访问带static的东西。
    原因:
    static属性加载在其他属性之前,static初始化的时候别的属性还不存在,调用不了

  5. getter setter
    用get和set关键字
    存取器要求你将编译器设置为输出ECMAScript 5或更高。 不支持降级到ECMAScript 3。

9 class

抽象类abstract
猫狗动物的例子挺好的hh

特点:

抽象方法只能描述不能实现
抽象类不能被实例化

类可以当作接口使用
类定义会创建两个东西:类的实例类型和一个构造函数。 因为类可以创建出类型,所以你能够在允许使用接口的地方使用类。

自己写的一个很屑的案例

abstract class Animal {
  type: string;
  constructor(type: string) {
    this.type = type;
  }
  abstract speak(): void;
}

class Cat extends Animal {
  constructor(name: string) {
    super("Cat");
    this.name = name;
  }
  name: string;

  speak(): void {
    console.log(`${this.type} + ${this.name}`);
  }
}

let c = new Cat("qwq");
c.speak();

不要整出例如这样的代码就可以了

//不能实例化
let d = new Animal()
//不能实现
abstract speak(): void{
    console.log(this.type);
  }


10 元组

const arr: [x: string, y: number] = ["123", 123];

越界后会推断为联合类型

arr.push(1234) 对
arr.push(true)

可用readonly修饰(const不能让元组只读)
可用?

使用typeof获取元组的类型

type first = typeof arr[0]

略显奇怪的用法

type first = typeof arr["length"]

可以得到元组的大小为2

实例:用于描述excel数据

11 枚举

enum关键字
可用可不用,相当于是一个对象

数字枚举

enum Color {
  //默认从0开始
  red = 1,
  //为2
  blue,
  white = 6,
}

console.log(Color.blue);

字符串枚举

必须都初始化一下

异构枚举

混着写是可以的

接口枚举

在接口中写枚举,更优雅一些(有一条规范是不要出现任何常量的直接赋值)

enum Color {
  red = "qwq",
  blue = 1,
}

interface A {
  red: Color.red;
}

let obj: A = {
  red: Color.red,
};

const枚举

没啥应用场景

直接编译为常量

const enum Color {
  red,
  blue,
}

let code: number = 0;
if (code === Color.red) {
  console.log("yes");
}

编译为js

var code = 0;
if (code === 0 /* Color.red */) {
    console.log("yes");
}

不加const的编译:对象形式

var Color;
(function (Color) {
    Color[Color["red"] = 0] = "red";
    Color[Color["blue"] = 1] = "blue";
})(Color || (Color = {}));
var code = 0;
if (code === Color.red) {
    console.log("yes");
}

反向映射

字符串不可以用这玩意
可以互相取挺方便的,value拿key就用这招

enum Color {
  red,
  blue,
}

let code: number = Color.red;

let key = Color[code];

console.log(code);//0
console.log(key);//red

11 类型推论 类型别名

类型推论

不写:的情况下,会自动推断变量的类型,默认any

别名

type关键字

type-interface
type不能用extends,可以使用&,|来定义交叉、联合类型
type重名不会合并

高级用法

type num = 1 extends never ? 1: 0

num为0,extends:包含
左边的值会作为右边类型的子类型

12 never类型

type a = number & string

a为never类型

然后对于函数,死函数/运行报错函数设置为never类型

function a():never{
  while(true){

  }
}

never处于类型的最底级,联合没用

一般用于switch语句中default的部分,通过never类型的错误找到逻辑错误

13 symbol类型

基础

唯一标识,在不同的内存空间

let a1: symbol = Symbol(1);
let a2: symbol = Symbol(1);

console.log(a1 == a2);//false

Symbol.for
for全局symbol是否注册过这个key,有就用,无才创建。但是需要传字符串值

let a1: symbol = Symbol.for("1");
let a2: symbol = Symbol.for("1");

console.log(a1 == a2);//true

用处:解决属性的key取重复的问题
因为对symbol而言,就算里面的值是一样的,也算不同的

let a1: symbol = Symbol(1);
let a2: symbol = Symbol(1);

let obj = {
  [a1]: 123,
  [a2]: 234,
  //name: "qwq",
  //name: "sayori",
  //报错:对象文本不能具有多个名称相同的属性,在js中会下面的覆盖上面的

};

console.log(obj);
//{ [Symbol(1)]: 123, [Symbol(1)]: 234 }

问题:
for in 、Object.keys 、Object.getOwnPropertyNames都不能读到symbol
需要使用
Object.getOwnPropertySymbols,但是这个只能单独取symbol

使用Reflect.ownKeys才可以都取到

生成器 generator函数

function* gen() {
  //同步/异步都可以
  yield Promise.resolve("qwq");
  yield "123";
  yield "sayoriqwq";
}

const A = gen();
console.log(A.next());
console.log(A.next());
console.log(A.next());
console.log(A.next());

输出:

{ value: Promise { 'qwq' }, done: false }
{ value: '123', done: false }
{ value: 'sayoriqwq', done: false }
{ value: undefined, done: true }

done为true说明迭代完成了

迭代器

和生成器其实差不多,也是一个函数,用法也是一样的

let arr = [1, 2, 3];

console.log(arr[Symbol.iterator]().next().value);//1

set map

set自带去重

let set: Set<number> = new Set([1, 1, 2, 2]);
console.log(set);//Set(2) { 1, 2 }

map相关方法

let map: Map<any, any> = new Map();

let arr = [1, 2, 3];
map.set(arr, "qwq");
console.log(map.get(arr));//qwq

然后讲了一些伪数组
arguments,querySelectorAll

迭代器应用场景:支持遍历所有这些乱七八糟的数据类型

语法糖 for of

二者等效,也可以用来遍历map、array等实现了iterator接口的对象

let set: Set<number> = new Set([1, 1, 2, 3]);

const each = (value: any) => {
  let It: any = value[Symbol.iterator]();
  let next: any = { done: false };
  while (!next.done) {
    next = It.next();
    if (!next.done) {
      console.log(next.value);
    }
  }
};

for(let value of set){
  console.log(value);
}

注意:for of对象不能用,对象身上没有iterator
原生object对象是默认没有部署Iterator接口,即object不是一个可迭代对象。但不是有{...obj}的解构语法吗?

数组解构:通过模式匹配来完成。按照位置来分配值。(基于索引)

对象解构:通过属性名来匹配。会尝试寻找同名的属性,并将其值赋给对应的变量。(基于属性名)

使对象支持for of:手动实现iterator方法

let obj = {
  max: 5,
  current: 0,
  //配置迭代器
  [Symbol.iterator]() {
    return {
      //迭代到下一层之前把属性速记
      max: this.max,
      current: this.current,
      next() {
        //迭代完成
        if (this.current == this.max) {
          return {
            value: undefined,
            done: true,
          };
        }
        //继续迭代 
        else {
          return {
            value: this.current++,
            done: false,
          };
        }
      },
    };
  },
};

for (let value of obj) {
  console.log(value);
}

console.log([...obj]);

14 泛型

动态类型,ts中的核心概念,可以说学会了泛型就学会了入门级的ts

概念

function qwq<T>(a: T, b: T): T[] {
  return [a, b];
}
//一样的,一般让ts自己推断类型
console.log(qwq(1, 2));
console.log(qwq<number>(1, 2));

类型泛型:

type A<T> = string | number | T;

let a: A<null> = null;

接口也可以用

相比于any,泛型可以推断类型,而any只能是any

泛型也可以支持多个

function add<T, K>(a: T, b: K): (T | K)[] {
  return [a, b];
}

add(1,false)

可以自己写一下代码,就可以看到在写的时候ts对类型的推断了,不传的时候是unknown,传的时候推断为我们传的值的类型

实战

调接口的时候用
https://www.bilibili.com/video/BV1wR4y1377K/?p=17&spm_id_from=pageDriver&vd_source=133a4c6b8765759be3947374e6336df7

泛型约束

控制类型的范围

interface Len {
  length: number;
}

function fn<T extends Len>(a: T) {
  a: length;
}

fn(111);//错,number类型没有length属性
fn("1q213");
fn([1, 2, 3]);

keyof 约束对象中的key

let obj = {
  name: "sayori",
  age: 123,
};

//T限制为传引用类型,K限制为传T的key值
function fn<T extends object, K extends keyof T>(obj: T, key: K) {
  return obj[key];
}

console.log(fn(obj, "name"));//sayori
console.log(fn(obj, "qwe"));//报错,qwe不是obj里的一个key

而且在写代码的时候也会有一个很智能的提示

keyof 高级(手写类型工具partial)

interface Data {
  age: number;
  name: string;
  sex: string;
}

type Options<T extends object> = {
  readonly [key in keyof T]: T[key];
};

type A = Options<Data>

A来说,就已经实现了Data里每一项都为只读

当然也可以用?修饰使得Data项目都可选

15 cfg配置文件

tsc --init生成
配置文件里有注释,也可以去翻官方文档

16 namespace

实质是包了一层function
可以嵌套
在最新版本中,推荐使用ES Modules作为模块化的解决方案,而不是过度使用namespace

抽离命名空间
以文件形式export,然后import

简化命名空间
import newName = A.B.C.D的形式为命名空间取别名

ts-node现在认识它了

重名算到一起(和interface一样)

17 声明文件

对第三方包想要提供/获取ts类型提示:
declare 关键字声明模块
.d.ts文件
有的包自带,有的包是后面布丁上的,装包的时候可以尝试用@types/{包名}来装一下对应的类型,没有就是未提供。

案例:对express进行了类型注解

18 类型守卫

类型收缩:宽类型(any等)在内部如果进行了类型判断(typeof instanceof等),推导时则会收窄其类型。

const isArr = (arr: any) => arr instanceof Array

类型谓词:is 关键字实现。给上述返回布尔值的条件函数赋予类型守卫的能力

const isArr = (arr: any): arr is any[] => arr instanceof Array

19 协变、逆变(鸭子类型)

值协变,函数逆变
核心:可以多,但不可以少

直接上例子

interface A {
  name: string
  age: number
}

interface B {
  name: string
  age: number
  sex: string
}

let a: A = {
  name: 'a',
  age: 1,
}

let b: B = {
  name: 'b',
  age: 2,
  sex: 'man',
}

a = b (发生协变)
// b = a(不允许)
let fna = (param: A) => {}
fna(b) (发生逆变)

// let fnb = (param: B) => {}
// fnb(a) //错误

fnb = fna
// fna = fnb //错误

双向协变:要这个特性去ts配置文件将
strictFunctionTypes 置为 false

ts的基础部分到此也就差不多结束了,掌握以上内容即可在项目中实现基本的类型标注,并且享受ts带来的好处了。

如果想要进一步提高ts水平,建议是首先尝试改造自己原有的项目(逻辑会比较熟悉),尽可能多的为其带上类型,不懂的地方善用搜索引擎和gpt。期间可能就会接触到一些ts高级特性,然后选学即可。然后可以阅读一些框架的源码(例如Vue3 watch部分的类型,真的是非常的复杂),或者自己尝试写一个mini版本标注一下。

查看方式:在你的项目中对第三方库的函数直接F12跳转,就可以看到对应的声明文件啦

如果想要成为ts高手,那么类型体操是必不可少的一环。可以自行查找资料,进行相关习题的针对训练。

Logo

前往低代码交流专区

更多推荐