带你走进TyprScript | 青训营笔记

93 阅读7分钟

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

1、什么是TypeScript

TypeScript 是 JavaScript 的超集,扩展了 JavaScript 的语法,因此现有的 JavaScript 代码可与 TypeScript 一起工作无需任何修改,TypeScript 通过类型注解提供编译时的静态类型检查。TypeScript 可处理已有的 JavaScript 代码,并只对其中的 TypeScript 代码进行编译。

1.1 TypeScript发展史

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

2、TypeScript的优点

  • 代码的可读性和可维护性:举个🌰看后端某个接口返回值,一般需要去network看or去看接口文档,才知道返回数据结构,而正确用了ts后,编辑器会提醒接口返回值的类型,这点相当实用。

  • 编译阶段就发现大部分错误,避免了很多线上bug

  • 增强了编辑器和 IDE 的功能,包括代码补全接口提示跳转到定义重构

2.1 TypeScript的缺点

  • 有一定的学习成本,需要理解接口(Interfaces)、泛型(Generics)、类(Classes)、枚举类型(Enums)等前端工程师可能不是很熟悉的概念

  • 会增加一些开发成本,当然这是前期的,后期维护更简单了

  • 一些JavaScript库需要兼容,提供声明文件,像vue2,底层对ts的兼容就不是很好。

  • ts编译是需要时间的,这就意味着项目大了以后,开发环境启动和生产环境打包的速度就成了考验

  • 可以看看Deno 内部代码将停用 TypeScript,并公布五项具体理由

3、TypeScript的语法

3.1 基础数据类型

image.png

// 可以直接使用字面量进行声明,就好像是一个常量,声明之后的值是不可改变的
let a: 10;
a = 12;

// | 表示 或 ,也就是说 b 的值 可以是 men 也可以是 women
let b: 'man' | 'women'
b = 'menwomen'

// 既可以是字符串也可以是布尔值
let c: string | boolean
c = 'hello'
c = true

// any 表示任意类型
let d: any
d = true

// unknow 表示未知类型
let e: unknown
e = 'hello'

let s: string

// any 可以赋值给任意对象 ,这时的s就会被any盖掉
s = d
// 而使用 unknow 进行赋值就会报错
// s = e
// 而我们需要将e的值赋给s,我们需要进行类型判断
if (typeof e === "string") {
	s = e
}

// 或者使用类型断言,将e强行表示为string类型
s = e as string
s = <string>e

// void 表示函数没有返回值 可以不return,但是可以return null underfind
function fn(num): void {
	console.log("void")
}

// 而使用 nerve 表示没有返回值 返回为null和underfind也会报错
function fun(): never {
	return null
}

// 对象
let f: Object
f = {}

// 而使用 {} 就可以对对象的属性进行限制,并且对象的属性也必须相同,而我们在对属性名后面加上一个? 表示属性可选
let g: { name: string, age?: number }
g = { name: "a" }
g = { sex: "men" }
g = { name: "b", age: 18 }

// 通过【abb:string】:any 表示我们的后续属性可以有任意属性
let h: { name: string, [abb: string]: any }
h = { name: 'yueyue', age: 18, sex: "man" }

let i: (a: number, b: number) => number

i = function (n1, n2): number {
	return n1 + n2
}

// arr 数组 字符串数组
let j: string[]
j = ['a', 'b', 'c']
let k: Array<number>
k = [1, 2, 4, 5]

// 元组表示一个已知元素数量和类型的数组,各元素的类型不必相同。
let l:[string, number]
l = ['12', 5]

// 枚举
enum sex {
	men = 1,
	women = 0,
}

let m: { name: string, sex: sex }

m = { name: 'yueyue', sex: sex.women }
console.log(m)

3.2 对象类型

简单理解interface 和 type 的区别:type 更强大,interface 可以进行声明合并,type 不行;

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 = 12345;
/*成功:任意属性标注下可以添加任意属性*/
bytedancer.platfor = 'data';
/*报错:缺少属性"name",hobby可缺省*/
const bytedancer2: IBytedancer = {
    jobId: 89757,
    sex: 'man',
    age: 18,
}

type Hero = { 
    name: string, 
    age: number, 
    skill: string, 
    skinNum?: number, 
};

3.3 函数类型

function add(x,y){
    return x+y;
}
const mult = (x,y) => x*y;

interface IMult{
    (x: number,y: number): number;
}
const mult: IMult = (x,y) => x*y

function add(x: number,y: number): number {
    return x+y;
}
const mult: (x: number,y: number) => number = (x,y) => x*y;

3.4 函数重载

/*对getDate函数进行重载,timestamp为可缺省参数*/
function getDate(type: 'string',timestamp?: string): string;
function getDate(type: 'date',timestamp?: string): Date;
function getDate(type: 'string' | 'date',timestamp?: string): Date | string{
    const date = new Date(timestamp);
    return type === 'string' ? date.toLocaleString(): date;
}
const x = getDate('date');
const y = getDate('string','2018-01-10'); 

interface IGetDate{
    (type: 'string',timestamp?: string): string;
    (type: 'date',timestamp?: string): date;
    (type: 'string' | 'date',timestamp?: string): Date | string;
}
/*不能将类型“(type: 'string' | 'date',timestamp?: string): Date | string;”分配给类型“IGetDate”*/
/*不能将类型“string | Date;”分配给类型“string”*/
/*不能将类型“Date”分配给类型“string”*/
const getDate2: IGetDate = (type,timestamp) => {
    const date = new Date(timestamp);
    return type === 'string' ? date.toLocaleString() : date;
}

3.5 数组类型

/*【类型 + 方括号】表示*/
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 = [1,2,3,4,5,6];
const arr2:IArr2 = [1,2,'3','4',{a: 1}];
const arr3:IArr3 = [1,2,'3','4'];
const arr4:IArr4 = ['string',() => null,{},[]];

3.6 补充类型

3.6.1 枚举类型

提高代码可维护性,统一维护某些枚举值,避免 JiShi === 1这种魔法数字。JiShi === JiShiEnum.BLUEJ这样写,老板一眼就知道我想找谁。

// 初始值默认为 0 
enum JiShiEnum { 
    REDJ, 
    BLUEJ, 
    GREENJ,
 } 
// 设置初始值 
enum JiShiEnum { 
    REDJ = 8, 
    BLUEJ, 
    GREENJ, 
} 
const jishi: JiShiEnum = JiShiENUM.BLUE 
console.log(jishi) // 9 
// 字符串枚举,每个都需要声明 
enum JiShiEnum { 
    REDJ = "8号", 
    BLUEJ = "9号", 
    GREENJ = "10号", 
}

3.6.2 泛型

 // T 自定义名称 
 function myFun<T>(params: T[]) { 
     return params; 
 } 
 myFun <string> (["123", "456"]); 
 // 定义多个泛型 
 function join<T, P>(first: T, second: P) { 
     return `${first}${second}`; 
 } 
 join <number, string> (1, "2");   
 
 function getRepeatArr(target){
     return new Array(100).fill(target);
 }
 type IGetRepeatArr = (target: any) => any[];
 //不预先指定具体类型,而在使用的时候在指定类型的一种特性
 type IGetRepeatArrR = <T>(target: T) =>[T];
 
 //泛型接口 & 多泛型
 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: IGetRepeateStringArr = 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')

3.6.3 类型别名 & 类型断言

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

3.7 高级类型

3.7.1 联合/交叉类型

  • 联合类型:IA | IB,表示一个值可以是几种类型之一
  • 交叉类型:IA & IB,多种类型叠加到一起成为一种类型,它包含了所需的所有类型的特性
const booklist = [{
    author: 'xiaoming',
    type: 'history',
    range: '2001-2021',
},{
    author: 'xioaming',
    type: 'story',
    theme: 'love',
}]

type IBookList = Array<{
    author: string;
} & ({
    type: 'history';
    range: string;
} | {
    type: 'story';
    theme: string;
})>

3.7.2 类型保护 & 类型守卫

类型保护是可执行运行时检查的一种表达式,用于确保该类型在一定的范围内。换句话说,类型保护可以保证一个字符串是一个字符串,尽管它的值也可以是一个数值。类型保护与特性检测并不是完全不同,其主要思想是尝试检测属性、方法或原型,以确定如何处理值。目前主要有四种的方式来实现类型保护:

**in 关键字**
interface Admin {
  name: string;
  privileges: string[];
}

interface Employee {
  name: string;
  startDate: Date;
}

type UnknownEmployee = Employee | Admin;

function printEmployeeInformation(emp: UnknownEmployee) {
  console.log("Name: " + emp.name);
  if ("privileges" in emp) {
    console.log("Privileges: " + emp.privileges);
  }
  if ("startDate" in emp) {
    console.log("Start Date: " + emp.startDate);
  }
}

**typeof关键字**
function padLeft(value: string, padding: string | number) {
  if (typeof padding === "number") {
      return Array(padding + 1).join(" ") + value;
  }
  if (typeof padding === "string") {
      return padding + value;
  }
  throw new Error(`Expected string or number, got '${padding}'.`);
}

**instanceof关键字**
interface Padder {
  getPaddingString(): string;
}

class SpaceRepeatingPadder implements Padder {
  constructor(private numSpaces: number) {}
  getPaddingString() {
    return Array(this.numSpaces + 1).join(" ");
  }
}

class StringPadder implements Padder {
  constructor(private value: string) {}
  getPaddingString() {
    return this.value;
  }
}

let padder: Padder = new SpaceRepeatingPadder(6);

if (padder instanceof SpaceRepeatingPadder) {
  // padder的类型收窄为 'SpaceRepeatingPadder'
}

**自定义类型保护的类型谓词**
function isNumber(x: any): x is number {
  return typeof x === "number";
}

function isString(x: any): x is string {
  return typeof x === "string";
}

更多内容可以点击这里深入学习哦!