TypeScript基础 | 青训营笔记

74 阅读11分钟

TypeScript

官网:www.typescriptlang.org/

中文网:www.tslang.cn/ 个人翻译

JS 开发中的问题

(其实就目前来看,我所理解的 JS,就以下遇到的问题,我觉得这是一个语言严谨性的体现,不过只是我的看法,目前我感觉是这样)

  • 使用了错误的或者不存在的变量、函数或者成员(比如函数名称、变量书写等错误)
  • 把一个不确定的类型当作一个确定的类型
  • 在使用 nullundefined(比如,can not read "" of undefined

JS 最不舒服的地方

  • js 语言本身的特性,决定了他不适合大型的复杂项目
  • 弱类型语言,某个变量的类型可以随意更换
  • 解释性,代码错误发生的时间,在代码运行的时候

TS

TSJS的超集,是一个可选的、静态的类型系统

  • 超集 整数、正整数、负整数中,整数就是正整数和负整数的超集
  • 类型系统 对代码中的所有的标识符(变量、函数、参数、返回值)进行类型检查
  • 可选的 不是说必须要用 TS,用 JS 也是 ok 的
  • 静态的 无论是浏览器环境还是 node 环境,无法直接识别 TS 代码 静态的涵义:静态的类型检查发生的时间在编译的时候,而不是在运行的时候

tsc:ts 编辑器

tsc: ts -> js,转换器 www.typescriptlang.org/在线编译

额外减轻开发压力

有了类型检查,增强了面向对象的开发

js 中也有类和对象,js 也支持面向对象开发,但是实际上,我们很少去写一个类因为 js 没有类型检查,很多面向对象的场景实行有问题

使用TS可以编写出完善的面向对象代码

基本类型约束

TS 是一个可选的静态的类型系统(不是必须的)

如何进行类型约束

仅需要在 变量函数的参数函数的返回值位置加上:类型

ts 在很多场景中可以完成类型推导

any: 表示任意类型,对该类型,ts 不进行类型检查

小技巧,如何区分数字字符串和数字,关键看怎么读? 如果按照数字的方式朗读,则为数字;否则,为字符串。

源代码和编译结果的差异

编译结果中没有类型约束信息,有问题的话会在源代码中很容易的看出啦,所以编译结果是没有任何问题的JS代码

基本类型

  • number:数字
  • string:字符串
  • boolean:布尔
  • 数组 (必须要约束,数组的每一项是说明类型);
    • 例如:let num:number[]=[1,2,3](语法糖);
    • let num:Array<number>=[1,2,3]
    • 当然你不手动写约束,它也可以推导出来
  • object: 对象
  • nullundefined

nullundefined 是所有其他类型的子类型,它们可以赋值给其他类型

    • 在这个是时候会发生这样的情况:let num :string = null,这不是我们希望的

解决

通过在配置文件中添加strictNullChecks:true,可以获得更严格的空类型检查,nullundefined 只能赋值给自身。

其他常用类型

  • 联合类型:多种类型任选其一
// 在严格模式下,下面的代码会报错
let num: number = undefined;
// 联合模式,不会报错
let num: number | undefined = undefined;

类型保护

配合类型保护进行判断

类型保护:当对某个变量进行类型判断之后,在判断的语句块中便可以确定它的确切类型typeof 可以触发类型保护

  • void 类型:通常用于约束函数的返回值,表示该函数没有任何返回

  • never 类型:通常用于约束函数的返回值,表示该函数永远不可能结束

  • 字面量类型:使用一个值进行约束,表示被约束的变量,只能被赋予约束的值

    • let arr: []; //表示 arr 只能是空数组
      
  • 元祖类型(Tuple): 一个固定长度的数组,并且数组中每一项的类型确定

    • let arr: [string, number];
      
  • any 类型: any 类型可以绕过类型检查,因此,any 类型的数据可以赋值给任意类型

    • let res: any = "字符串";
      let num: number = res; //所以存在隐患
      

类型别名

对已知的一些类型定义名称

type 类型名 = ...

type user = {
  name: string,
  age: number,
  gander: "male" | "female",
};
//约束函数的返回值是,每一项是 user 类型的数组
function getUsers(): user[] {
  return [];
}

函数的相关约束

函数重载:在函数实现之前,对函数调用的多种情况进行声明 (我他妈直呼卧槽)

可选参数:可以在某些参数名后加上问号,表示该参数可以不用传递(参数非必须)。可选参数必须在参数列表的末尾

注意:默认参数也被认为是可选参数

扩展类型-枚举

扩展类型:类型别名、枚举、接口、类

枚举通常用于约束某个变量的取值范围。

字面量和联合类型配合使用,也可以达到同样的目标。

字面量类型的问题

  • 类型约束位置,会产生重复代码。可以使用类型别名解决该问题。

  • 逻辑含义和真实的值产生了混淆,会导致当修改真实值的时候,产生大量的修改。

    • type gender = "男" | "女";
      let person = {
        gen: gender,
      };
      <!-- 那么对于person.gen的赋值只能是"男" | "女",
      假如需要更改成"帅哥"|"美女",那么需要修改的地方太多了 -->
      
  • 字面量类型不会进入到编译结果。

枚举

如何定义一个枚举:

enum 枚举名{
    枚举字段1 = 值1,
    枚举字段2 = 值2,
    ...
}
enum Gender {
  male = "帅哥", //把逻辑含义和真实的值清晰的分开
  female = "美女",
}

let gender: Gender;

gender = Gender.male; //逻辑含义的值

枚举会出现在编译结果中,编译结果中表现为对象。

枚举的规则:

  • 枚举的字段值可以是字符串数字
  • 数字枚举的值会自动自增
  • 数字枚举约束的变量,可以直接赋值为数字
  • 数字枚举的编译结果 和 字符串枚举有差异

最佳实践:

  • 尽量不要在一个枚举中既出现字符串字段,又出现数字字段
  • 使用枚举时,尽量使用枚举字段的名称,而不使用真实的值

枚举拓展:位枚举(枚举的位运算)

针对数字枚举

位运算:两个数字换算成二进制后进行的运算

enum Permission {
    Read = 1,   // 0001
    Write = 2,  // 0010
    Create = 4, // 0100
    Delete = 8  // 1000
}

//1. 如何组合权限
//使用或运算
//0001
//或
//0010
//0011
let p: Permission = Permission.Read | Permission.Write;

//2. 如何判断是否拥有某个权限
//0011
//且
//0010
//0010
function hasPermission(target: Permission, per: Permission) {
    return (target & per) === per;
}
//判断变量p是否拥有可读权限

//3. 如何删除某个权限
//0011
//异或:相同取0,不同取1
//0010
//0001
p = p ^ Permission.Write;
console.log(hasPermission(p, Permission.Write));

模块化(相关配置文件的配置)

相关配置:

配置名称含义
module设置编译结果中使用的模块化标准
moduleResolution设置解析模块的模式 (一般设置为 node)
noImplicitUseStrict编译结果中不包含"use strict"
removeComments编译结果移除注释
noEmitOnError错误时不生成编译结果
esModuleInterop启用 es 模块化交互非 es 模块导出

前端领域中的模块化标准:ES6、commonjs、amd、umd、system、esnext

TS 中如何书写模块化语句 编译结果??

TS 中如何书写模块化语句

TS 中,导入和导出模块,统一使用 ES6 的模块化标准

编译结果中的模块化

可配置

TS 中的模块化在编译结果中:

  • 如果编译结果的模块化标准是 ES6: 没有区别
  • 如果编译结果的模块化标准是 commonjs:导出的声明会变成 exports 的属性,默认的导出会变成 exportsdefault 属性;
    • 总结一下,好理解一些。在 TS文件中支持的是ES6模块化标准,但是对于编译的JS结果可能是Common JS或者ES6(这个我们可以在配置文件中配置)

启用 es 模块化交互非 es 模块导出

// import fs from "fs"; //module.exports={},这里算是默认导入,有问题,当然在配置文件中开启ts与CommonJS交互就可以了
// import * as fs from "fs"; //这样就可以
// import {readFile} from "fs"//这样也可以,不过只导入具体的函数

// fs.readFileSync("./")

// import myModule from "./myModule"

// import myModule = require("./myModule")

如何在 TS 中书写 commonjs 模块化代码

TS 作为 JS 的超集,当然可以使用 JS 中的方式,但是这样你不会获得类型检查

需要使用以下语法

导出:export = xxx

导入:import xxx = require("xxx")

模块解析

模块解析:应该从什么位置寻找模块(就是找路径)

TS 中,有两种模块解析策略

  • classic:经典
  • node:node 解析策略(唯一的变化,是将 js 替换为 ts)
    • 相对路径require("./xxx")
    • 非相对模块require("xxx")

接口和类型兼容性

扩展类型-接口

接口:inteface

扩展类型类型别名枚举接口

TypeScript 的接口:用于约束类、对象、函数契约(标准)

契约(标准)的形式:

  • API 文档,弱标准
  • 代码约束,强标准

和类型别名一样,接口,不出现在编译结果中

  1. 接口约束对象

    // // 定义接口
    // interface User {
    //   name: string;
    //   age: number;
    // }
    // // 接口约束对象
    // let people: User = {
    //   name: "John",
    //   age: 36,
    // };
    
    // // 类型别名
    // type User = {
    //   name: string;
    //   age: number;
    // };
    // // 类型别名约束对象
    // let people: User = {
    //   name: "John",
    //   age: 36,
    // };
    
  2. 接口约束函数

    • 函数为对象中的成员
    // 定义接口
    interface User {
      name: string;
      age: number;
      //不出现在编译结果中(函数无法执行),
      //在接口中只能对方法做约束,不能实现
      sayHellow: () => void;
      // sayHellow(): void;//效果一样
    }
    // 接口约束对象
    let people: User = {
      name: "John",
      age: 36,
      sayHellow: () => {
        console.log("你好");
      },
    };
    
    • 接口直接约束函数
    // 接口约束函数
    interface Condition {
     <!-- 这里的大括号不表示对象了,表示定界符 -->
      (n: number): boolean;
    }
    function sum(numbers: number[], callBack: Condition):number {
      let res = 0;
      numbers.forEach((n) => {
        if (callBack(n)) {
          // 条件真
          res += n;
        }
      });
      return res;
    }
    console.log(sum([1, 2, 3, 4], (n) => n % 2 == 0));
    
  3. 类型别名约束函数

// 类型别名约束函数
type Condition = (n: number) => boolean;
// type Condition = {  //效果一样
//   (n: number): boolean,
// };

function sum(numbers: number[], callBack: Condition): number {
  let res = 0;
  numbers.forEach((n) => {
    if (callBack(n)) {
      // 条件真
      res += n;
    }
  });
  return res;
}

console.log(sum([1, 2, 3, 4], (n) => n % 2 == 0)); //6

接口可以继承

可以通过接口之间的继承,实现多种接口的组合

// 接口的继承;
interface A {
  obj1: number;
}

interface B extends A {
  obj2: string;
}

let n: B = {
  obj1: 10,
  obj2: "hello",
};

使用类型别名可以实现类似的组合效果,需要通过&,它叫做交叉类型

type A = {
  obj1: number,
};
type B = {
  obj2: string,
};
type C = {
  obj3: boolean,
} & A &
  B;

let n: C = {
  obj1: 10,
  obj2: "hello",
  obj3: true,
};

它们的区别:

  • 子接口不能覆盖父接口的成员
  • 交叉类型会把相同成员的类型进行交叉(就是相同成员就会具有交叉的约束)

readonly

只读修饰符,修饰的目标是只读

只读修饰符不在编译结果中

interface User {
  // 只读修饰符,限定只能最开始的时候给id赋值
  readonly id: number;
  name: string;
  age: number;
}

let u: User = {
  id: 1,
  name: "john",
  age: 20,
};

类型兼容性

B->A(B 赋值给 A,B 需要满足 A 的特征),如果能完成赋值,则 B 和 A 类型兼容

鸭子辨型法(子结构辨型法):目标类型( B )需要某一些特征,赋值的类型( A )只要能满足该特征即可

  • 基本类型:完全匹配

  • 对象类型:鸭子辨型法

    • 类型断言 当直接使用对象字面量赋值的时候,会进行更加严格的判断

      <!-- 还可以在我们要断言的变量前面这样断言 <断言的类型> -->
      interface Duck {
        sound: "嘎嘎嘎"
        swin(): void
      }
      let person = {
        name: "伪装成鸭子的人",
        age: 11,
        sound: "嘎嘎嘎" as "嘎嘎嘎",//类型断言,string->字面量"嘎嘎嘎"
        swin() {
          console.log(this.name + "正在游泳,并发出了" + this.sound + "的声音");
        }
      }
      let isDuck: Duck = person; //可以赋值
      let isDuck2: Duck = { //不可以,直接把对象字面量赋值,会更加严格的判断
        name: "伪装成鸭子的人",
        age: 11,
        sound: "嘎嘎嘎" as "嘎嘎嘎",//类型断言,string->字面量"嘎嘎嘎"
        swin() {
          console.log(this.name + "正在游泳,并发出了" + this.sound + "的声音");
        }
      }
      let duck: Duck = {
        sound: "嘎嘎嘎" as "嘎嘎嘎",//类型断言,string->字面量"嘎嘎嘎"
        swin() {
          console.log(this.name + "正在游泳,并发出了" + this.sound + "的声音");
        }
      };
      
  • 函数类型

    • 参数:传递给目标函数的参数可以少,但不可以多,对函数参数没有严格要求你
    • 返回值:要求返回必须返回;不要求返回,你随意;
    interface Condition {
      (n: number, i: number): boolean;
    }
    function sum(numbers: number[], callBack: Condition) {
      let s = 0;
      for (let i = 0; i < numbers.length; i++) {
        const n = numbers[i];
        if (callBack(n, i)) {
          s += n;
        }
      }
      return s;
    }
    const result = sum([3, 4, 5, 7, 11], (n) => n % 2 !== 0);
    console.log(result);