TypeScript 入门 | 青训营笔记

87 阅读7分钟

TypeScript 入门 | 青训营笔记

这是我参与「第五届青训营 」笔记创作活动的第 4 天

详细知识点介绍:

TypeScript发展历史

  • 2012-10:微软发布了 TypeScript 第一个版本(0.8)
  • 2014-10:Angular 发布了基于 TypeScript 的 2.0 版本
  • 2015-04:微软发布了 Visual Studio Code
  • 2016-05:@types/react 发布,TypeScript 可开发React
  • 2020-09:Vue 发布了 3.0 版本,官方支持TypeScript
  • 2021-11:v4.5 版本发布

对比

  • JavaScript

    • 动态类型
    • 弱类型语言
  • TypeScript

    • 静态语言
    • 弱类型语言

静态类型

  • 可读性增强:基于语法解析TSDoc,ide增强
  • 可维护性增强:在编译阶段暴露大部分错误(多人合作的大型项目中,获得更好的稳定性和开发效率)

JS 的超集

  • 包含于兼容所有JS特性,支持共存
  • 支持渐进式引入与升级

基础数据类型

/* 字符串 */
const q: string = 'string'
/* 数字 */
const w: number = 1
/* 布尔值 */
const e: boolean = true
/* null */
const r: null = null
/* undefined */
const t: undefined = undefined

对象类型

const bytedancer: IBytedancer = {
  jobId: 9303245,
  name: 'Lin',
  sex: 'man',
  age: 28,
  hobby: 'swimming',
}

interface IBytedancer {
  /* 只读属性:约束属性不可在对象初始化外赋值 */
  readonly jobId: number;
  name: string;
  sex: 'man' | 'woman' | 'other';
  age: number;
  /* 可选属性:定义改属性可以不存在 */
  hobby?: string;
  /* 任意属性:约束所有对象属性都必须是该属性的子类型 */
  [key: string]: any;
}

/* 报错:无法分配到 "jobId" , 因为它是只读属性 */
bytedancer.jobId = 123456;
/* 成功:任意属性标注下可以添加任意属性 */
bytedancer.plateform = 'data';
/* 报错:缺少属性 "name", hobby可缺省 */
const bytedancer2: IBytedancer = {
  jobId: 89757,
  sex: 'woman',
  age: 18,
}
  • 声明一个对象都是要自定义类型
  • 用 interface 进行声明,一般用大写 i 命名
  • 在对对象进行取值的时候,key如果是一个变量,用中括号方式定义
  • any 是 TypeScript 中特有的类型

函数类型

function add(x: number, y: number): number {
  return x + y;
}

/* 方式一 */
const mult: (x: number, y: number) => number = (x, y) => x * Y;

/* 方式二 */
interface IMult {
  (x: number, y: number): number;
}
const numlt: IMult = (x, y) => x * y;

函数重载

/* 对getDate函数进行重载,timestamp为可缺省参数 */
function getDate(type: 'string', timestamp?: string): string;
interface IGetDate {
  (type: 'string', timestamp?: string): string;
  (type: 'date ', timestamp?: string): Date;
  (type: 'string' | 'date', timestamp?: string): Date | string;
}
/*  不能将类型“(type: any, timestamp: any) => string | Date”分配给类型"IGetDate"。
  不能将类型“string | Date"分配给类型“string"。
    不能将类型“Date"分配给类型“string"。ts (2322)*/
const getDate2: IGetDate = (type,timestamp) => {
  const date = new Date(timestamp);
  return type == 'string' ? date.toLocaleString() : date;
}

数组类型

/* 「类型 + 方括号」 表示 */
type IArr1 = number[];
/*泛型表示*/
type IArr2 = Array<string | number | Record<string, number>>;
/*元祖表示*/
type IArr3 = [number, number, string,string];
/*接口表示*/
interface IArr4 {
  [key: number]: any;
}

const arr1: IArr1 = [123456];
const arr2: IArr2 = [1, 2'3', '4', { a: 1 }];
const arr3: IArr3 = [1, 2'3''4'];
const arr4: IArr4 = [ 'string' ,() => null,{},[]];

TypeScript 补充类型

/* 空类型,表示无赋值 */
type IEmptyFunction = () => void;
/* 任意类型,是所有类型的子类型 */
type IAnyType = any;
/* 枚举类型:支持枚举值到枚举名的正、反向映射 */
enum EnumExample {
  add = '+',
  mult = '*',
}
EnumExample['add'] === '+';
EnumExample['+'] === 'add';

enum ECorlor { Mon, Tue, Wed, Thu, Fri, Sat, Sun };
ECorlor['Mon'] === 0;
ECorlor[0] === 'Mon';
/* 泛型 */
type INumArr = Array<number>;

TypeScript 泛型

function getRepeatArr(target) {
  return new Array(100).fill(target);
}
type IGetRepeatArr = (target: any) => any[];
/* 不预先指定具体的类型,而在使用的时候再指定类型的一种特性 */
type IGetRepeatArrR = <T>(target: T)=> T[];
在输入不确定的类型,用any类型进行接受,然后返回的类型也是any,会造成数据类型的丢失。
然后使用泛用类型,先不指定类型,使用的时候根据输入的类型指定类型。
指定的类型也用于输出的类型
/* 泛型接口 & 多泛型 */
interface IX<T,U> {
  key: T;
  val: U;
}
/* 泛型类 */
class IMan<T> {
  instance: T;
}
/* 泛型别名 */
type ITypeArr<T> = Array<T>;
  • 泛型约束
/*泛型约束:限制泛型必须符合字符串*/
type IGetRepeatStringArr = <T extends string>(target: T) => T[];
const getStrArr: IGetRepeatstringAIr = target => new Array(100).fill(target);
/* 报错: 类型"number"的参数不能赋给类型"string”的参数 */
getStrArr(123);
  • 泛型参数默认类型
/* 泛型参数默认类型 */
type IGetRepeatArr<T = number> = (target: T) => T[];
const getRepeatArr: IGetRepeatArr = target => new Array(100).fill(target);
/* 报错: 类型“string"的参数不能赋给类型“number”的参数 */
getRepeatArr('123');

类型别名 & 类型断言

/* 通过type关键字定义了I0bjArr的别名类型 */
type IObjArr = Array<{
  key: string;
  [objKey: string]: any;
}
function keyBy<T extends IObjArr>(objArr: Array<T>) {
  /* 未指定类型时,result类型为 {} */
  const result = objArr.reduce((res, val, key) => {
    res[key] = val;
    return res;
  }, {});
  /* 通过as关键字,断言result类型为正确类型 */
  return result as Record<string, T>;
}

字符串/数字 字面量

/* 允许指定字符串/数字必须的固定值 */

/* IDomTag必须为html、body、div、span中的其一 */
type IDomTag = 'html' | ' body' | 'div' | 'span ';
/* IOddNumber必须为1、3、5、7、9中的其 */
type IOddNumber = 1 | 3 | 5 | 7 | 9;

联合/交叉类型

const bookList = [{
  author: 'xiaoming',
  type: 'history',
  range: '2001-2021',
}, {
  author: 'xiaoli',
  type: 'story',
  theme: 'love',
}]

转化为ts

interface IHistoryBook {
  author: string;
  type: string;
  range: string;
}
interface IStoryBook {
  author: string;
  type: string;
  theme: string;
}
type IBookList = Array<IHistoryBook | IStoryBook>;

先声明两个类型,然后让数组类型的值的类型两个都可以选择,但是代码量很多。

改进后

type IBookList = Array<{
  author: string;
} & ({
  type: 'history';
  range: string;
} | {
  type: 'story';
  theme: string;
})>
  • 联合类型(改进前):IA | IB;联合类型表示一个值可以是几种类型之一
  • 交叉类型(改进后):IA & IB;多种类型叠加到一起成为一种类型,它包含了所需的所有类型的特性

类型保护与类型守卫

interface IA { a: 1, a1: 2 }
interface IB { b: 1b1: 2 }
function log(arg: IA | IB) {
  /* 报错:类型“IA|IB”上不存在属性“a"。类型“IB”上不存在属性“a”。 */
  /* 结论:访问联合类型时,处于程序安全,仅能访问联合类型中的交集部分 */
  if (arg.a) {
    console.log(arg.a1)
  } else {
    console.log(arg.b1);
  }
}

在ts中访问联合类型时,所访问的那个属性必须是在两个类型中都有的公共的属性才可以进行访问。这个也叫做类型保护。

interface IA { a: 1, a1: 2 }
interface IB { b: 1, b1: 2 }
/* 类型守卫: 定义一个函数,它的返回值是一个类型谓词,生效范围为子作用域 */
function getIsIA(arg: IA | IB): arg is IA {
  return !!(arg as IA).a;
}
function log2(arg: IA | IB) {
  if (getIsIA(arg)) {
    console.log(arg.a1)
  } else {
    console.log(arg.b1);
  }
}

写一个函数用 as 进行断言是不是 IA 最后返回一个类型谓词,再将类型谓词转化为布尔值,最终返回一个布尔值进行判断。

高级类型

/**
*实现merge函数类型
*要求sourceObj必须为targetObj的子集
*/
function merge1(source0bj, target0bj) {
  const result = { ...source0bj };
  for(let key in target0bj) {
    const itemVal = sourceObj[key];
    itemVal && (result[key] = itemVal);
  }
  return result;
}
function merge2(source0bj, target0bj) {
  return { ...source0bj, ...target0bj };
}

ts

interface ISource0bj {
  x?: string;
  y?: string;
}
interface ITarget0bj {
  x: string;
  y: string;
}
type IMerge = (source0bj: ISource0bj, target0bj: ITarget0bj) => ITarget0bj;
/**
*类型实现繁琐:若obj类型较为复杂,则声明source和target便需要大量重复2通
*容易出错:若target增加/减少key,则需要source联动去除
*/
interface IMerge {
  <Textends Record<string,any>>(source0bj: Partial<T>, target0bj: T): T;
}
type IPartial<T extends Record<string,any>> = {
  [P in keyof T]?: T[P];
}
// 索引类型:关键字【keyof】,其相当于取值对象中的所有key组成的字符串字面量,如
type IKeys = keyof i a: string; b: number }; // => type IKeys = "a" | "b"
// 关键字【in】,其相当于取值字符串字面量中的一种可能,配合泛型P, 即表示每个key
// 关键字【?】,通过设定对象可选选项,即可自动推导出子集类型

函数返回值类型

// 实现函数delayCall的类型声明
// delayCall接受一个函数作为入参, 其实现延迟1s运行函数
// 其返回promise, 结果为入参函数的返回结果
function delayCall(func) {
  return new Promise(resolve => {
    setTimeout(() => {
      const result = func();
      resolve(result);
    }, 1000);
  });
}
type IDelayCall = <T extends () => any>(func: T) => ReturnType<T>;

type IReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R: any

//关键字【extends】跟随泛型出现时,表示类型推断,其表达可类比三元表达式
//如T===判断类型﹖类型A:类型B

//关键字【infer】出现在类型推荐中,表示定义类型变量,可以用于指代类型
//如该场景下,将函数的返回值类型作为变量,使用新泛型R表示,使用在类型推荐命中的结果中

工程应用

浏览器 Web

webpack

  1. 配置 webpack loader 相关配置
  2. 配置 tsconfig.js 文件
  3. 运行 webpack 启动/打包
  4. loader 处理 ts 文件时,会进行编译与类型检查

相关的 loader

  1. awesome-typescript-loader
  2. babel-loader

NodeJs

image.png

使用 TSC 编译

  1. 安装 Node 与 npm
  2. 配置 tsconfig.js 文件
  3. 使用 npm 安装 tsc
  4. 使用 tsc 运行编译得到 js 文件