Typescript 使用日志

最近这两年,有很多人都在讨论 Typescript,无论是社区还是各种文章都能看出来,整体来说正面的信息是大于负面的,这篇文章就来整理一下我所了解的 Typescript。

本文主要分为 3 个部分:

•Typescript 基本概念•Typescript 高级用法•Typescript 总结

Typescript 基本概念

至于官网的定义,这里就不多做解释了,大家可以去官网查看。Typescript 设计目标[1]

我理解的定义:赋予 Javascript 类型的概念,让代码可以在运行前就能发现问题。

Typescript 都有哪些类型

1、Typescript 基本类型,也就是可以被直接使用的单一类型。

•数字•字符串•布尔类型•null•undefined•any•unknown•void•object•枚举•never

2、复合类型,包含多个单一类型的类型。

•数组类型•元组类型•字面量类型•接口类型

3、如果一个类型不能满足要求怎么办?

•可空类型,默认任何类型都可以被赋值成 null 或 undefined。•联合类型,不确定类型是哪个,但能提供几种选择,如:type1 | type2。•交叉类型,必须满足多个类型的组合,如:type1 & type2。

类型都在哪里使用

在 Typescript 中,类型通常在以下几种情况下使用。

•变量中使用•类中使用•接口中使用•函数中使用

类型在变量中使用

在变量中使用时,直接在变量后面加上类型即可。

let a: number;let b: string;let c: null;let d: undefined;let e: boolean;let obj: Ixxx = {  a: 1,  b: 2,};let fun: Iyyy = () => {};

类型在类中使用

在类中使用方式和在变量中类似,只是提供了一些专门为类设计的静态属性、静态方法、成员属性、构造函数中的类型等。

class Greeter {    static name:string = 'Greeter'    static log(){console.log(‘log')}    greeting: string;    constructor(message: string) {        this.greeting = message;    }    greet() {        return "Hello, " + this.greeting;    }}let greeter = new Greeter("world");

类型在接口中使用

在接口中使用也比较简单,可以理解为组合多个单一类型。

interface IData {  name: string;  age: number;  func: (s: string) => void;}

类型在函数中使用

在函数中使用类型时,主要用于处理函数参数、函数返回值。

// 函数参数function a(all: string) {}// 函数返回值function a(a: string): string {}// 可选参数function a(a: number, b?: number) {}

Typescript 高级用法

Typescript 中的基本用法非常简单,有 js 基础的同学很快就能上手,接下来我们分析一下 Typescript 中更高级的用法,以完成更精密的类型检查。

类中的高级用法

在类中的高级用法主要有以下几点:

•继承•存储器 get set•readonly 修饰符•公有,私有,受保护的修饰符•抽象类 abstract

继承和存储器和 ES6 里的功能是一致的,这里就不多说了,主要说一下类的修饰符和抽象类。

类中的修饰符是体现面向对象封装性的主要手段,类中的属性和方法在被不同修饰符修饰之后,就有了不同权限的划分,例如:

•public 表示在当前类、子类、实例中都能访问。•protected 表示只能在当前类、子类中访问。•private 表示只能在当前类访问。

class Animal {  // 公有,私有,受保护的修饰符  protected AnimalName: string;  readonly age: number;  static type: string;  private _age: number;  // 属性存储器  get age(): number {    return this._age;  }  set age(age: number) {    this._age = age;  }  run() {    console.log("run", this.AnimalName, this.age);  }  constructor(theName: string) {    this.AnimalName = theName;  }}Animal.type = "2"; // 静态属性const dog = new Animal("dog");dog.age = 2; // 给 readonly 属性赋值会报错dog.AnimalName; // 实例中访问 protected 报错dog.run; // 正常

在类中的继承也十分简单,和 ES6 的语法是一样的。

class Cat extends Animal {  dump() {    console.log(this.AnimalName);  }}let cat = new Cat("catname");cat.AnimalName; // 受保护的对象,报错cat.run; // 正常cat.age = 2; // 正常

在面向对象中,有一个比较重要的概念就是抽象类,抽象类用于类的抽象,可以定义一些类的公共属性、公共方法,让继承的子类去实现,也可以自己实现。

抽象类有以下两个特点。

•抽象类不能直接实例化•抽象类中的抽象属性和方法,必须被子类实现

tip 经典问题:抽象类的接口的区别

•抽象类要被子类继承,接口要被类实现。•在 ts 中使用 extends 去继承一个抽象类。•在 ts 中使用 implements 去实现一个接口。•接口只能做方法声明,抽象类中可以作方法声明,也可以做方法实现。•抽象类是有规律的,抽离的是一个类别的公共部分,而接口只是对相同属性和方法的抽象,属性和方法可以无任何关联。

抽象类的用法如下。

abstract class Animal {  abstract makeSound(): void;  // 直接定义方法实例  move(): void {    console.log("roaming the earch...");  }}class Cat extends Animal {  makeSound() {} // 必须实现的抽象方法  move() {    console.log('move');  }}new Cat3();

接口中的高级用法

接口中的高级用法主要有以下几点:

•继承•可选属性•只读属性•索引类型:字符串和数字•函数类型接口•给类添加类型,构造函数类型

接口中除了可以定义常规属性之外,还可以定义可选属性、索引类型等。

interface Ia {  a: string;  b?: string; // 可选属性  readonly c: number; // 只读属性  [key: number]: string; // 索引类型}// 接口继承interface Ib extends Ia {  age: number;}let test1: Ia = {  a: "",  c: 2,  age: 1,};test1.c = 2; // 报错,只读属性const item0 = test1[0]; // 索引类型

接口中同时也支持定义函数类型、构造函数类型。

// 接口定义函数类型interface SearchFunc {  (source: string, subString: string): boolean;}let mySearch: SearchFunc = function (x: string, y: string) {  return false;};// 接口中编写类的构造函数类型检查interface IClass {  new (hour: number, minute: number);}let test2: IClass = class {  constructor(x: number, y: number) {}};

函数中的高级用法

函数中的高级用法主要有以下几点:

•函数重载•this 类型

函数重载

函数重载指的是一个函数可以根据不同的入参匹配对应的类型。

例如:案例中的 doSomeThing 在传一个参数的时候被提示为 number 类型,传两个参数的话,第一个参数就必须是 string 类型。

// 函数重载function doSomeThing(x: string, y: number): string;function doSomeThing(x: number): string;function doSomeThing(x): any {}let result = doSomeThing(0);let result1 = doSomeThing("", 2);

This 类型

我们都知道,Javascript 中的 this 只有在运行的时候,才能够判断,所以对于 Typescript 来说是很难做静态判断的,对此 Typescript 给我们提供了手动绑定 this 类型,让我们能够在明确 this 的情况下,给到静态的类型提示。

其实在 Javascript 中的 this,就只有这五种情况:

•对象调用,指向调用的对象•全局函数调用,指向 window 对象•call apply 调用,指向绑定的对象•dom.addEventListener 调用,指向 dom•箭头函数中的 this ,指向绑定时的上下文

// 全局函数调用 - windowfunction doSomeThing() {  return this;}const result2 = doSomeThing();// 对象调用 - 对象interface IObj {  age: number;  // 手动指定 this 类型  doSomeThing(this: IObj): IObj;  doSomeThing2(): Function;}const obj: IObj = {  age: 12,  doSomeThing: function () {    return this;  },  doSomeThing2: () => {    console.log(this);  },};const result3 = obj.doSomeThing();let globalDoSomeThing = obj.doSomeThing;globalDoSomeThing(); // 这样会报错,因为我们只允许在对象中调用// call apply 绑定对应的对象function fn() {  console.log(this);}fn.bind(document)();// dom.addEventListenerdocument.body.addEventListener("click", function () {  console.log(this); // body});

泛型

泛型表示的是一个类型在定义时并不确定,需要在调用的时候才能确定的类型,主要包含以下几个知识点:

•泛型函数•泛型类•泛型约束 T extends XXX

我们试想一下,如果一个函数,把传入的参数直接输出,我们怎么去给它编写类型?传入的参数可以是任何类型,难道我们需要把每个类型都写一遍?

•使用函数重载,得把每个类型都写一遍,不适合。•泛型,用一个类型占位 T 去代替,在使用时指定对应的类型即可。

// 使用泛型function doSomeThing(param: T): T {  return param;}let y = doSomeThing(1);// 泛型类class MyClass {  log(msg: T) {    return msg;  }}let my = new MyClass();my.log("");// 泛型约束,可以规定最终执行时,只能是哪些类型function d2(param: T): T {  return param;}let z = d2(true);

其实泛型本来很简单,但许多初学 Typescript 的同学觉得泛型很难,其实是因为泛型可以结合索引查询符 keyof、索引访问符 T[k] 等写出难以阅读的代码,我们来看一下。

// 以下四种方法,表达的含义是一致的,都是把对象中的某一个属性的 value 取出来,组成一个数组function showKey1(items: K[], obj: T): T[K][] {  return items.map((item) => obj[item]);}function showKey2(items: K[], obj: T): Array {  return items.map((item) => obj[item]);}function showKey3(  items: K[],  obj: { [K in keyof T]: any }): T[K][] {  return items.map((item) => obj[item]);}function showKey4(  items: K[],  obj: { [K in keyof T]: any }): Array {  return items.map((item) => obj[item]);}let obj22 = showKey4(["age"], {  name: "yhl",  age: 12,});

类型兼容性

类型兼容性是我认为 Typescript 中最难理解的一个部分,我们来分析一下。

•对象中的兼容•函数返回值兼容•函数参数列表兼容•函数参数结构兼容•类中的兼容•泛型中的兼容

在 Typescript 中是通过结构体来判断兼容性的,如果两个的结构体一致,就直接兼容了,但如果不一致,Typescript 给我们提供了一下两种兼容方式:

以 A = B 这个表达式为例:

•协变,表示 B 的结构体必须包含 A 中的所有结构,即:B 中的属性可以比 A 多,但不能少。•逆变,和协变相反,即:B 中的所有属性都在 A 中能找到,可以比 A 的少。•双向协变,即没有规则,B 中的属性可以比 A 多,也可以比 A 少。

对象中的兼容

对象中的兼容,采用的是协变。

let obj1 = {  a: 1,  b: "b",  c: true,};let obj2 = {  a: 1,};obj2 = obj1;obj1 = obj2; // 报错,因为 obj2 属性不够

函数返回值兼容

函数返回值中的兼容,采用的是协变。

let fun1 = function (): { a: number; b: string } {  return { a: 1, b: "" };};let fun2 = function (): { a: number } {  return { a: 1 };};fun1 = fun2; // 报错,fun2 中没有 b 参数fun2 = fun1;

函数参数个数兼容

函数参数个数的兼容,采用的是逆变。

// 如果函数中的所有参数,都可以在赋值目标中找到,就能赋值let fun1 = function (a: number, b: string) {};let fun2 = function (a: number) {};fun1 = fun2;fun2 = fun1; // 报错, fun1 中的 b 参数不能再 fun2 中找到

函数参数兼容

函数参数兼容,采用的是双向协变。

let fn1 = (a: { name: string; age: number }) => {  console.log("使用 name 和 age");};let fn2 = (a: { name: string }) => {  console.log("使用 name");};fn2 = fn1; // 正常fn1 = fn2; // 正常

理解函数参数双向协变

1、我们思考一下,一个函数 dog => dog,它的子函数是什么?

注意:原函数如果被修改成了另一个函数,但他的类型是不会改变的,ts 还是会按照原函数的类型去做类型检查!

grayDog => grayDog

•不对,如果传了其他类型的 dog,没有 grayDog 的方法,会报错。

grayDog => animal

•同上。

animal => animal

•返回值不对,返回值始终是协变的,必须多传。

animal => grayDog

•正确。

所以,函数参数类型应该是逆变的。

2、为什么 Typescript 中的函数参数也是协变呢?

enum EventType { Mouse, Keyboard }interface Event { timestamp: number; }interface MouseEvent extends Event { x: number; y: number }function listenEvent(eventType: EventType, handler: (n: Event) => void) {  /* ... */}listenEvent(EventType.Mouse, (e: MouseEvent) => console.log(e.x + "," + e.y));

上面代码中,我们在调用时传的是 mouse 类型,所以在回调函数中,我们是知道返回的参数一定是一个 MouseEvent 类型,这样是符合逻辑的,但由于 MouseEvent 类型的属性是多于 Event 类型的,所以说 Typescript 的参数类型也是支持协变的。

类中的兼容

类中的兼容,是在比较两个实例中的结构体,是一种协变。

class Student1 {  name: string;  // private weight:number}class Student2 {  // extends Student1  name: string;  age: number;}let student1 = new Student1();let student2 = new Student2();student1 = student2;student2 = student1; // 报错,student1 没有 age 参数

需要注意的是,实例中的属性和方法会受到类中修饰符的影响,如果是 private 修饰符,那么必须保证两者之间的 private 修饰的属性来自同一对象。如上文中如果把 private 注释放开的话,只能通过继承去实现兼容。

泛型中的兼容

泛型中的兼容,如果没有用到 T,则两个泛型也是兼容的。

interface Empty {}let x1: Empty;let y1: Empty;x1 = y1;y1 = x1;

高级类型

Typescript 中的高级类型包括:交叉类型、联合类型、字面量类型、索引类型、映射类型等,这里我们主要讨论一下

•联合类型•映射类型

联合类型

联合类型是指一个对象可能是多个类型中的一个,如:let a :number | string 表示 a 要么是 number 类型,要么是 string 类型。

那么问题来了,我们怎么去确定运行时到底是什么类型?

答:类型保护。类型保护是针对于联合类型,让我们能够通过逻辑判断,确定最终的类型,是来自联合类型中的哪个类型。

判断联合类型的方法很多:

•typeof•instanceof•in•字面量保护,===!=====!=•自定义类型保护,通过判断是否有某个属性等

// 自定义类型保护function isFish(pet: Fish | Bird): pet is Fish {  return (pet).swim !== undefined;}if (isFish(pet)) {  pet.swim();} else {  pet.fly();}

映射类型

映射类型表示可以对某一个类型进行操作,产生出另一个符合我们要求的类型:

ReadOnly,将 T 中的类型都变为只读。•Partial,将 T 中的类型都变为可选。•Exclude,从 T 中剔除可以赋值给 U 的类型。•Extract,提取 T 中可以赋值给 U 的类型。•NonNullable,从 T 中剔除 null 和 undefined。•ReturnType,获取函数返回值类型。•InstanceType,获取构造函数类型的实例类型。

我们也可以编写自定义的映射类型。

//定义toPromise映射type ToPromise = { [K in keyof T]: Promise };type NumberList = [number, number];type PromiseCoordinate = ToPromise;// [Promise, Promise]

Typescript 总结

写了这么多,接下来说说我对 Typescript 的一些看法。

Typescript 优点

1、静态类型检查,提早发现问题。

2、类型即文档,便于理解,协作。

3、类型推导,自动补全,提升开发效率。

4、出错时,可以大概率排除类型问题,缩短 bug 解决时间。

实战中的优点:

1、发现 es 规范中弃用的方法,如:Date.toGMTString。

2、避免了一些不友好的开发代码,如:动态给 obj 添加属性。

3、vue 使用变量,如果没有在 data 定义,会直接抛出问题。

Typescript 缺点

1、短期增加开发成本。

2、部分库还没有写 types 文件。

3、不是完全的超集。

实战中的问题:

1、还有一些坑不好解决,axios 编写了拦截器之后,typescript 反映不到 response 中去。

参考资料

•Typescript 官网[2]•深入理解 Typescript[3]

References

[1] Typescript 设计目标: https://github.com/Microsoft/TypeScript/wiki/TypeScript-Design-Goals[2] Typescript 官网: https://www.tslang.cn/[3] 深入理解 Typescript: https://jkchao.github.io/typescript-book-chinese/

◆ ◆ ◆  ◆ 

你的在看我当成喜欢

typescript parseint不能传number_Typescript 使用日志相关推荐

  1. qq空间 上传html,qq空间怎么上传本地视频两种上传方式(说说/日志)

    首先打开QQ空间,我们有两种上传本地视频到QQ空间的方式, 第一种是说说上传,第二种是日志上传. 先来说第一种, 以上是我们打开qq空间以后,里面说说界面,后面有一组类似冒号的东西,把鼠标移至那里,然 ...

  2. 记录并通过邮件上传App崩溃日志

    1.引子 最近在做一个社交app的过程中,用户总是反映app在跳转到分享页面的时候App无故退出. 我在我的手机上实验了几下,都能成功,神奇的安卓啊,最后想到了一个办法, 记录用户app的崩溃日志来解 ...

  3. Taro+NutUI+vue3+TypeScript - 图片上传

    Taro+Vue3+TypeScript实现手机相册图片上传功能 <template><view><nut-uploader:url="uploadUrl&qu ...

  4. syslog传到服务器日志文件,将supervisor产生的日志通过syslog上传到服务端

    Ubuntu rsyslog的配置 服务端: 修改/etc/rsyslog.conf,将UDP或者TCP发送的注释取消. 修改/etc/default/rsyslog,将RSYSLOGD_OPTION ...

  5. 【转载】柳传志-管理日志 团队管理

    http://www.iteye.com/topic/695487 复制是走向成功的捷径 管理三要素:建班子,定战略,带队伍 建班子一把手做决策,但是事后必须总结,以解决分歧. 班子议事要建立规则,要 ...

  6. 搜索客户端传参监控,request日志监控

    设计思路 README.md 客户端传参request监控客户端: 1.客户端入手:客户端添加接口请求日志,打印request  和responses,把日志单独存放,然后进行日志分析监控 2.服务端 ...

  7. 巧用shell+rsync服务实现日志自动过滤处理压缩并上传日志服务器,自动分类

    分享一个自己很早以前先写的日志存储方案,简单实用,业务端上传完日志后自己清除已经上传过的日志,连清空间的工作都省了.(未完) 找出所有部署目录下的日志文件夹,压缩指定文件与文件夹,不改变日志压缩路径( ...

  8. 打印日志文件并将其上传存到HDFS中

    在本地打印日志 , 对日志文件进行分析 , 把需要上传的日志文件移动到待上传目录 , 将待上传的日志文件逐个上传到HDFS , 并移动到备份目录 , 定期清理过期备份日志 . 1.首先生产日志文件: ...

  9. 【游戏开发进阶】新发带你玩转Unity日志打印技巧(彩色日志 | 日志存储与上传 | 日志开关 | 日志双击溯源)

    文章目录 一.前言 二.常规日志打印 1.打印Hello World 2.打印任意类型的数据 3.context参数干嘛的 4.格式化输出 三.彩色日志打印 四.日志存储与上传 1.打印日志事件 2. ...

最新文章

  1. R语言绘制堆叠的环状条形图
  2. ClickHouse系列教程一:Debian/Ubuntu 下ClickHouse的安装和使用
  3. 第十一章:集合(一)
  4. 根据录入的计算公式计算_工业铝型材承重计算
  5. count相加 sqlserver_mssql sqlserver sum(1)、sum(2)、count(1)、count(8)、count(*)、统计函数应用区别简介...
  6. windows7 docker mysql_DOCKER windows 7 详细安装教程
  7. linux安装openssh 密码错误,Linux安装openssh的后门补丁
  8. 猪和兔子的玻璃体给人用(仅仅是个人想法)
  9. 把mysql某一列求和_Laravel 对某一列进行筛选然后求和sum()的例子
  10. git介绍和常用操作
  11. 用scikit-learn学习主成分分析(PCA)
  12. matlab 找到数组中第一个不连续点_超全Matlab绘图方法整理
  13. 小觅双目摄像头标准版视觉惯性 SLAM DEMO
  14. 删除的win10应用商店怎么恢复
  15. 量子计算机向世诗词,诗云(刘慈欣向李白致敬的好文!)
  16. 外贸邮箱能群发吗?用哪个外贸邮箱发开发信回复率高?
  17. android九宫格切图,拼图九宫格切图app
  18. CSS单行、多行文本溢出隐藏
  19. 班级管理系统c语言学号姓名,c++学生成绩管理系统1、问题描述 学生信息包括:学号、姓名、性别、年龄、班级等信息。 小学生...
  20. C++中使用placement new

热门文章

  1. Fail - Fast机制
  2. amazon中文文档
  3. P1144 最短路计数
  4. 基本排序算法[python实现]
  5. BZOJ 4310 二分+SA+RMQ
  6. 翻译:理解TCP/IP网络栈编写网络应用(上)
  7. 拖动布局之保存布局页面
  8. Windows 系统防火墙
  9. DataGridView
  10. [Usaco2009 Feb]Bullcow 牡牛和牝牛