TS 入门+基础+技巧性技能

200 阅读10分钟

TS 定义,环境搭建,6大优势

  • 定义:融合了面向对象后端的思想的超级版的 javaScript 语言。
  • 环境搭建
npm init -y 

yarn  add typescript -D

tsc --init
  • 优势:
  1. 编译时静态类型检测:函数或方法传参或变量赋值不匹配时,会出现编译错误提示 ,规避了开发期间的大量低级错误,省时,省力。
  2. 能自动提示:变量类型、变量属性,不用来回切换文件或不小心写错导致的编码隐患。
  3. 引入了泛型:让大中项目,前端框架底层源码具备了高可扩展性这个巨大的优势,同时也有类型安全检查的优势。
  4. 强大的 d.ts 声明文件:声明文件像一个书的目录一样,清晰直观展示了依赖库文件的接口,type类型,类,函数,变量等声明。
  5. 轻松编译成 JS 文件:即使 TS 文件有错误,绝大多数情况也能编译出 JS 文件。
  6. 灵活性高: 尽管 TS 是一门 强类型检查语言,但也提供了 any 类型 和 as any 断言,这提供了 TS的灵活度。

tsconfig.json 常用 18 项配置选项详解

{
  "compilerOptions": {
    "target": "es2020", // 指定 TS 编译成 JS 后的js版本
    "module": "commonjs", // TS 编译成 JS 后采用的模块规范 commonjs amd cmd  es等         
    "lib": ["DOM","ES2020"], /*  指定 TS 编码期间可以使用的库文件版本 比如:ES5就不支持Set集合 */
    "outDir": "./dist", //     指定 TS 文件编译成 JS 后的输出目录                 /* Redirect output structure to the directory. */
    "rootDir": "./src", // 指定 TS 文件源码目录
    "strict": true, // 启用严格检查模式
    "strictNullChecks":false,// null 和 undefined即是值,也是类型, null 和 undefined 值 只能赋值给 any ,unknown和它们各自的类型
    "noImplicitAny": true, // 一般是指表达式或函数参数上有隐含的 any类型时报错
    "experimentalDecorators": true, /* 启用ES7装饰器实验开启选项 */
    "emitDecoratorMetadata": true, /* 启用装饰器元数据开启选项 */
    "declaration": true, // 指定 TS 文件编译后生成相应的.d.ts文件
    "removeComments": false, // TS 文件编译后删除所有的注释
    
    "baseUrl": "src", /* 工作根目录  解析非相对模块的基地址*/
    "paths": {
        "@/datatype/*": ["datatype/*"],
        "@/131/*": ["131/*"],
        "@/132/*": ["132/*"]
      },    
    // 有些依赖库底层 为了兼容CommonJs规范、AMD规范这二者的规范中相互兼容,
    // 使用了 export =,将二者规范统一。
    // "esModuleInterop":true表示允许依赖库中出现export = 这种兼容规范导出的格式,
    //  TS 可以用import from导入 
    "esModuleInterop": true,  
  },
  "include": [ // 需要编译的ts文件一个*表示文件匹配表示忽略文件的深度问题
    "./src/**/*.ts" // **匹配src下所有的ts文件
, "src/datatype/typepsenumts"  ],
   "exclude": [
    "./src/**/test",
    "./src/**/premit", 
  ]
}
    
   

类型注解和类型推断

  • 类型注解 ——ts 在编写代码期间就能确定变量的类型
// 类型注解
let price: number = 3
type StudentType = { name: string, age: number }
let stuObj: StudentType = { name: "wangwu", age: 23 }

// 类型推导
let count = 3;
let custObj = { name: "wangwu", age: 23 }

any 和 unknown 的两个区别

//any 和 unknown 的两个区别
let price: any = "abc"
// unknown不能赋值
let total: number = price

let stuObj: any = { username: "wangwu", age: 23 }
// stuObj.username any可以打点获得username
let stuName: unknown = { username: "wangwu", age: 23 }
// stuName.username unknown不能打点获得username

// let stuAge:number=stuName  不能将unkown分配给number

export { }

函数和函数类型,rest 参数

// 自动推导返回值类型
let info1 = function (name: string, age: number) {
    console.log("name:", name, " age:", age);
    return 3
}

info1("wangwu", 23)


// 类似箭头函数的约束
let info2: (name: string, age: number) => number =
    function (name, age) {
        console.log("name:", name, " age:", age);
        return 3
    }
info2("wangwu", 23)

// 用type约束
type TypInfoFun = (name: string, age: number) => number
let info3: TypInfoFun =
    function (name, age) {
        console.log("name:", name, " age:", age);
        return 3
    }

info3("wangwu", 23)

// rest参数

function info4(name: string, age: number, ...rest: any) {
    console.log("name:", name, " age:", age, " rest:", rest);
    return rest
}

info4("wangwu", 23, "1111", "beijing", 23,)

函数类型升级

type TypStuobj = { username: string, age: number, phone: string }

function info(stuObj: TypStuobj) {
  console.log("name:", stuObj.username, " age:", stuObj.age);
  return 3
}

let stuObj: TypStuobj = { username: "wangwu", age: 23, phone: "111" }
info(stuObj)

// 函数解构
function subInfo({ username, phone }: TypStuobj) {
  console.log("name:", username, " phone:", phone);
  return 3
}

subInfo({ username: "lisi", age: 33, phone: "222" })


export { }

BigInt

  1. number 的极限值运算
// 获取最大的整数值
let max = Number.MAX_SAFE_INTEGER;

 let max1 = max + 5 
 let max2 = max + 15

 console.log(max1 === max2) // 结果相同——true 
  1. 使用 BigInt
方法1:
第一步:
修改 tsconfig.json 选项——"lib": ["DOM","ES2020"] 

第二步:
let max = BigInt(Number.MAX_SAFE_INTEGER);

const max1 = max + BigInt(15)
const max2 = max + BigInt(5)

console.log("max1:", max1)// 9007199254740992n
console.log("max2:", max2)
console.log(max1 === max2) // false

方法2:
第一步:
修改 tsconfig.json 选项——"lib": ["DOM","ES2020"] + "target": "es2020"

第二步:
let max = BigInt(Number.MAX_SAFE_INTEGER);

const max1 = max + 15n
const max2 = max + 5n

console.log("max1:", max1)// 9007199254740992n
console.log("max2:", max2)
console.log(max1 === max2) // false

看似简单的取值为何总抛错?

let obj = { username: "wangwu", age: 23 }

// 如果不加string约束,就要用const而非let
const username = "username"

obj[username]


// let obj: object = { username: "wangwu", age: 23 }

// const username = "username"

// let result = (obj as any)[username]

什么场景 never 能被直接推导出来而不用定义?

// dataFlowAnalysisWithNever 方法穷尽了 DataFlow 的所有可能类型。 
// 通过这个示例,我们可以得出一个结论:
// 使用 never 避免出现未来扩展新的类没有对应类型的实现,
// 目的就是写出类型绝对安全的代码。
type DataFlow = string | number
function dataFlowAnalysisWithNever(dataFlow: DataFlow) {
  if (typeof dataFlow === 'string') {
    console.log(dataFlow)
  } else if (typeof dataFlow === 'number') {

  } else {
    // dataFlow 在这里是 never 
    let nothings = dataFlow;//never
  }
}
dataFlowAnalysisWithNever("免税店")


export { }

枚举好处——为什么要用枚举?

  • 解决多次 if /switch 判断中值的语义化的问题
  1. 常量解决
const Status = {
  MANAGER_ADUIT_FAIL: -1,
  NO_ADUIT: 0,
  MANAGER_ADUIT_SUCCESS: 1,
  FINAL_ADUIT_SUCCESS: 2
}
// 审核类
class MyAduit {

  getAduitStatus(status: number): void {

    if (status === Status.NO_ADUIT) {
      console.log("没有审核");
    } else if (status === Status.MANAGER_ADUIT_SUCCESS) {
      console.log("经理审核通过");
    } else if (status === Status.FINAL_ADUIT_SUCCESS) {
      console.log("财务审核通过");
    }
  }
}

const aduit = new MyAduit();
aduit.getAduitStatus(Status.MANAGER_ADUIT_FAIL);
export { }
  1. 常量解决带来的局限性:方法参数不能定义为具体类型,只能初级使用 number,string 基本类型替代,降低了代码的可读性和可维护性。

深入枚举,枚举分类,枚举底层,枚举好处

  1. 枚举的定义: 用来存放一组固定的常量的序列
  2. 枚举分类
// 字符串枚举
enum EnumAuditStatus {
  MANAGER_ADUIT_FAIL = "项目经理审核失败"
  NO_ADUIT = "没有审核"
  MANAGER_ADUIT_SUCCESS = "项目经理审核成功"
  FINAL_ADUIT_SUCCESS = "财务审核成功"
}

//  字符串枚举
enum WeekEnd {
  Monday = "Monday",
  Tuesday = "Tuesday",
  Wensday = "Wensday",
  ThirsDay = "ThirsDay",
  Friday = "Friday",
  Sarturday = "Sarturday",
  Sunday = "Sunday"
}

// 数字枚举
enum EnumAuditStatus {
  MANAGER_ADUIT_FAIL = -1,//第一个常量值设置为-1
  NO_ADUIT, // 第二个常量值自动递增1 就为0
  MANAGER_ADUIT_SUCCESS,// // // 第二个常量值自动递增2 就为1
  FINAL_ADUIT_SUCCESS // // // 第二个常量值自动递增3 就为2
}

// 数字枚举
enum Week {
  Monday = 1,
  Tuesday,
  Wensday,
  ThirsDay,
  Friday,
  Sarturday,
  Sunday
}
  1. 枚举取值方式
export enum EnumAuditStatus {
  MANAGER_ADUIT_FAIL = -1,//第一个常量值设置为-1
  NO_ADUIT, // 第二个常量值自动递增1 就为0
  MANAGER_ADUIT_SUCCESS,// // // 第二个常量值自动递增2 就为1
  FINAL_ADUIT_SUCCESS // // // 第二个常量值自动递增3 就为2
}
// 取值方式1:枚举反向取值 根据枚举中常量值来取出常量名
console.log("EnumAuditStatus[0]", EnumAuditStatus[0]);
console.log("EnumAuditStatus[1]", EnumAuditStatus[1]);
// 取值方式2:枚举取值 根据枚举中常量名来取出常量值
console.log("EnumAuditStatus.FINAL_ADUIT_SUCCESS",
  EnumAuditStatus.FINAL_ADUIT_SUCCESS);	
  1. 枚举底层
    • 数字类型枚举底层
    var Week;
    (function (Week) {
        // 相当于 Week[1] = "Monday";
        Week[Week["Monday"] = 1] = "Monday";
        Week[Week["Tuesday"] = 2] = "Tuesday";
        Week[Week["Wensday"] = 3] = "Wensday";
        Week[Week["ThirsDay"] = 4] = "ThirsDay";
        Week[Week["Friday"] = 5] = "Friday";
        Week[Week["Sarturday"] = 6] = "Sarturday";
        Week[Week["Sunday"] = 7] = "Sunday";
    })(Week || (Week = {}));
    
    • 字符串枚举底层
    var WeekEnd;
    (function (WeekEnd) {
        WeekEnd["Monday"] = "Monday";
        WeekEnd["Tuesday"] = "Tuesday";
        WeekEnd["Wensday"] = "Wensday";
        WeekEnd["ThirsDay"] = "ThirsDay";
        WeekEnd["Friday"] = "Friday";
        WeekEnd["Sarturday"] = "Sarturday";
        WeekEnd["Sunday"] = "Sunday";
    })(WeekEnd || (WeekEnd = {}));
    
  2. 枚举好处
    • 有默认值和可以自增值,节省编码时间
    • 语义更清晰,可读性增强,
  3. 枚举应用
export enum EnumAuditStatus {
  MANAGER_ADUIT_FAIL = -1,//第一个常量值设置为-1
  NO_ADUIT, // 第二个常量值自动递增1 就为0
  MANAGER_ADUIT_SUCCESS,// // // 第二个常量值自动递增2 就为1
  FINAL_ADUIT_SUCCESS // // // 第二个常量值自动递增3 就为2
}


interface Expense {
  id: number,
  events: string,
  time: Date,
  enumAuditStatus: EnumAuditStatus
}

class ExpenseService {
  addExpense(expense: Expense) { }
}
let expenseService = new ExpenseService();

// 审核类
class MyAduit {
  getAduitStatus(status: EnumAuditStatus): void {
    let mystatus: EnumAuditStatus = 10;//定义枚举类型的变量
    let mystatus2: EnumAuditStatus = mystatus;
    mystatus2 = mystatus2 + 1;
    console.log("mystatus:", mystatus);//10
    console.log("mystatus2", mystatus2);//11

    if (status === EnumAuditStatus.NO_ADUIT) {//NO_ADUIT=0
      console.log("没有审核");
    } else if (status === EnumAuditStatus.MANAGER_ADUIT_SUCCESS) {
      console.log("经理审核通过");
      let expense: Expense = {
        id: 1,
        events: "飞机票报销",
        time: new Date(),
        enumAuditStatus: status
      }
      expenseService.addExpense(expense)
    } else if (status === EnumAuditStatus.FINAL_ADUIT_SUCCESS) {
      console.log("财务审核通过");
    } else {
      console.log("审核失败");
    }
  }
}

const aduit = new MyAduit();
aduit.getAduitStatus(EnumAuditStatus.FINAL_ADUIT_SUCCESS);
export { }

接口 ( interface )定义,实现,应用场景

  • 定义:是为一系列同类对象或同类别的类,提供属性定义和方法声明,但没有任何赋值和实现的数据类型。
  • 接口实现
  • 应用场景
    1. 提供方法的对象类型的参数时使用
    2. 为多个同类别的类提供统一的方法和属性声明

接口可选属性,可索引类型,函数类型

interface Product {
  id: number;
  name: string;
  price: number;
  count: number;
  // 可选属性
  mark?: string;
  //可索引类型
  [key: string]: any;
  // 函数类型
  transfer: () => void
}

function calToal(product: Product) {
  console.log("product总价:", product.price * product.count)
  product.transfer();
}

calToal({
  id: 100, name: "电脑", price: 5000, count: 10,
  mark: "注意轻纺", place: "", quatity: "二手",
  transfer() {
    console.log(this.name, "运输");
  }
})

// 函数的可索引类型,随便加几个函数,只要满足(state: any) => void就行
interface Getter {
  [key: string]: (state: any) => void
}

let getter: Getter = {
  getProductInfo(state: string) {

  },
  getOneProduct(state: string) {

  }
}

export { }

接口继承的使用场景:

新的接口只是在原来接口继承之上增加了一些属性或方法,这时就用接口继承

// 例子1:
// 开始定义了一个接口
interface  Point{
    x:number;
    y:number;
}

// 需求发生了变化,但是是在原来 Point 接口的基础之上增加了一个新的 z:number 属性。
interface  Point3d extends Point{
    z:number;
}


// 例子2:Vue3源码中 稍复杂一点的接口继承
interface Error {
  name: string;
  message: string
}

interface CompilerError extends Error {
  code: number
}

const enum ErrorCodes {
  // parse errors
  ABRUPT_CLOSING_OF_EMPTY_COMMENT,
  CDATA_IN_HTML_CONTENT,
  DUPLICATE_ATTRIBUTE,
  END_TAG_WITH_ATTRIBUTES,
  END_TAG_WITH_TRAILING_SOLIDUS,
  EOF_BEFORE_TAG_NAME,
  EOF_IN_CDATA,
  EOF_IN_COMMENT,
  EOF_IN_SCRIPT_HTML_COMMENT_LIKE_TEXT,
  EOF_IN_TAG,
  INCORRECTLY_CLOSED_COMMENT
   ......
}
 
 interface CoreCompilerError extends CompilerError {
  code: ErrorCodes
}

//  其他应用比较少的场景:
//  1 接口也可以继承多个接口  2 接口可以继承类  3 类可以继承一个或多个接口
//  同学们可以

联合类型

// 指多个类型的合并类型

// 1. 基本数据类型的联合类型
function add(previous: string | number, current: string | number) {
  
}
// 2. 引用类型的联合类型
interface Car {
  brand: string;//  品牌
  No: number;// 车牌号
  price: number;
  placeOrigin: string;//产地
  load(): void
}

interface Plane {
  category: string;// 飞机类别
  price: number;// 价格
  placeOrigin: string;// 产地
  airline: string;// 所属航空公司
  load(): void
}

function carry(vechile: Car | Plane) {// 运载
  vechile.load();
}

type 和 interface 区别

  1. 定义类型范围不同。interface 只能定义对象类型或接口当名字的函数类型。type 可以定义任何类型,包括基础类型、联合类型 ,交叉类型,元组。
// type 定义基础类型
type num=number 

//  type 定义联合类型例子1:
type baseType=string |number | symbol

//  type 定义联合类型例子2:
interface Car { brandNo: string}
interface Plane { No: string; brandNo: string}
type TypVechile = Car| Plane 

//  元组
interface Car { brandNo: string}
interface Plane { No: string; brandNo: string}
type TypVechile = [Car, Plane]
  1. 接口可以extends 一个或者多个 接口或类, 也可以继承type,但type 类型没有继承功能。但一般 接口继承 类 和 type 的应用场景很少见。
  2. 用 type 交叉类型 & 可让类型中的成员合并成一个新的 type 类型,但接口不能交叉合并
type Group = { groupName: string, memberNum: number }
type GroupInfoLog = { info: string, happen: string }
type GroupMemeber = Group & GroupInfoLog// type 交叉类型合并

let data: GroupMemeber = {
  groupName: "001", memberNum: 10,
  info: "集体爬山", happen: "中途有组员差点滑落,有惊无险",
}

export { }
  1. 接口可以合并声明。定义两个相同名称的接口会合并声明,定义两个同名的type会出现编译错误。
interface Error {
  name: string;
}

interface Error {
  message: string;
  stack?: string;
}
// 接口合并
let error: Error = {
  message: "空指针",
  name: "NullPointException"
}

为什么要用声明文件?

  • 如果文件使用 TS 编写,在编译时可以自动生成声明文件,并在发布的时候将 .d.ts 文件一起发布,我们无需编写声明文件。
  • 当我们在 TS 文件中引入使用第三方库的类型或使用 集成库 时,比如:@types/jquery 库,ES6 库的 Map 类型 ,这些库用 JS 开发,不能获取 TS 一样的 类型提示,需要一个声明文件来帮助库的使用者来获取库的类型提示。
  • 注意:声明文件中只对类型定义,不能进行 赋值 和 实现。