TypeScript

1/1/2021 TypeScript

概述 typeScript 是一门基于 javaScript 之上的一门编程语言,解决了 javascript 自有的语言类型系统的不足。TypeScript 大大提升了系统的安全性。

# 语言类型

区分编程语言的两个维度:

1.类型安全:强类型与弱类型

2.类型检查:静态类型与动态类型

# 类型安全

强类型 VS 弱类型:

1.强类型,从语言层面限制函数的实参类型必须与形参类型相同,而弱类型没有。

2.强类型有更强的语言类型的约束,而弱类型几乎没有什么约束。

3.强类型没有默认的隐式转换,而弱类型有任意数据类型的隐式转换

# 类型检查

静态类型:一个变量在声明的时候就已经是确定的,而且在声明后不允许修改。

动态类型:需要在运行的时候才能确定变量的类型,而且变量的类型也可以随时发生变化。动态类型中变量是没有类型的,而变量中存放的值是有类型的

总之,从类型安全维度来看,强弱类型之间的区别就是是否允许数据类型的隐式转换。从类型安全的维度来看,静态和动态的区别就是声明后的变量是否能够被修改变量类型

# javascript 类型系统特征

是一门弱类型动态语言,本身没有语言类型特征,类型比较【任性】,却丢失了类型系统的可靠性,【不靠谱】 那为什么它不是一门强类型静态语言呢?

原因如下:

1.刚开始其应用比较简单,不需要做类型系统

  1. 它是一门脚本语言,不需要编译

# 弱类型的问题

  1. 运行时才会发现异常,代码不立即执行的情况下,就会留下隐患
//1. 运行时才会发现异常
const obj = {};
obj.foo();
//代码不立即执行的情况下,就会留下隐患
setTimeout(() => {
  obj.foo();
}, 10000);

复制代码;

运行结果:

img

2.数据类型不一样的情况下,运行结果与实际功能不一致

//2.运行结果与实际功能不一致的情况
function add(a, b) {
  return a + b;
}
console.log(add(100, 101));
console.log(add(100, "101"));
//结果
// 201
// 100101 该结果与实际功能不符
复制代码;

3.使用任意的数据类型当做对象的属性名,会让人产生困惑

//3.使用任意的数据类型当做对象的属性名,会让人产生困惑
const obj = {};
obj[true] = 0;
console.log(obj[true]);

复制代码;

在代码量小的情况下可以通过约定来解决这些问题,但现在的 JavaScript 越来越丰富多样,君子约定是有隐患的,强制要求才有保障

# 强类型的优势

1.错误更早的暴露

2.更加智能,更加准确

3.重构更加牢固

4.减少不必要的类型判断

function sum(a, b) {
  //类型判断
  if (typeof a !== "number" || typeof b !== "number") {
    throw new TypeError("arguments is not number");
  }
  return a + b;
}
复制代码;

# Flow

javascript 类型检查工具

快速上手

  1. yarn init --yes ,生成 package.json
  2. yarn add flow-bin --dev ,安装 flow

由于使用 yarn 的方式会超时,解决办法:配置一下源

yarn config set registry registry.npm.taobao.org (opens new window) -g

yarn config set sass_binary_site cdn.npm.taobao.org/dist/node-s… (opens new window) -g 3.yarn flow init ,初始化 flow 4.yarn flow 运行 flow 运行结果:

img

# flow 编译移除

在 js 代码中加入了 flow 的类型之后,执行代码会报错如下图所示:

img

这是由于 javascript 本身对 flow 的类型指定是无法识别的,因此我们在运行阶段要将 flow 的类型指定的代码移除掉 方法有两种

  1. 安装 flow-remove-types 模块, yarn add flow-remove-types --dev 安装完成后使用命令 yarn flow-remove-types 【当前文件目录】 -d dist ,其中-d dist 表示创建一个转换后存放文件的目录,结果如下

img

  1. 使用 babel 插件
  • yarn add @babel/core @babel/cli @bale/preset-flow --dev ,@babel/cli 使我们能在命令行工具使用 babel 的命令,@bale/preset-flow 是一个移除 flow 类型的插件
  • 创建一个 .babelrc 文件
{
    "presets":["@babel/preset-flow"]
}
复制代码
  • yarn babel src -d dist

# flow 开发工具插件

在 vscode 中打开 查看-》 扩展 ,在搜索框中输入 flow language support 安装即可,安装完之后写完代码保存后就可以帮你运行监测出问题

img

# 类型推断

根据代码中的变量使用情况,自动推断出变量类型。建议还是添加类型注解,让我们的代码有更好的可读性

/**
 * 类型推断
 * 根据代码中的变量使用情况,推断中变量类型
 * @flow
 */

function squre(n) {
  return n * n;
}
squre("100");
复制代码;

错误提示:

img

# 类型注解

/**
 * 类型注解
 *
 * @flow
 */
// 对函数的返回值
function squre(n: number): number {
  //return n*n;
  return "";
}
squre(100);
//对变量进行标注
let i: number = "100";

复制代码;

检测效果:

img

# FLOW 支持的原始类型

/**
 * FLOW 支持的原始数据类型
 *
 * @flow
 */
const s: string = "55";
const num: number = Infinity; //NaN//100;
const bol: boolean = false; //true
const em: null = null;
const p: void = undefined;
const sym: symbol = Symbol();

复制代码;

# 数组类型

/**
 * 数组数据类型
 *
 * @flow
 */
//使用泛型
const arry: Array<number> = [1, 2, 3];
//---------------------
const arry2: number[] = [1, 2, "ff"];
//元组
const arry3: [string, number] = ["foo", "7"];
复制代码;

# 对象类型

/**
 * 对象类型
 *
 * @flow
 */
//
const obj1: { foo: string; age: number } = { foo: "value", age: 18 };
//属性可缺省
const obj2: { foo?: string; age: number } = { age: 18 };
// 表示当前的对象允许添加任意个数的键,且键值都是字符串
const obj3: { [string]: string } = {};
obj3.key1 = "nu";
obj3.key2 = 90;
复制代码;

# 函数类型

/**
 * 函数类型
 *
 * @flow
 */
//指定回调函数的
function foo(callback: (string, number) => void) {
  callback("strig", 100);
}
foo((str, num) => {
  console.log(str, num);
  // return 1;
});
复制代码;

# 特殊类型

/**
 * 特殊类型
 *
 * @flow
 */

//字面量类型,限制某个变量必须为某个值
const a: "foo" = "foo"; //123;
//配合联合类型
const type: "success" | "warn" | "danger" = "danger";
//或类型
const b: string | number = 100;

//使用type声明一个联合类型
type stringOrNumber = string | number;

const c: stringOrNumber = "null";

//maybe 类型
const d: ?number = null;
//相当于
const d: number | null | undefined = null;
复制代码;

##TypeScript 概述 是 javascript 的超集,关系如下图:

手机截屏

TypeScript 的兼容性特别好,任何一种 JavaScript 运行环境都支持。相比于 Flow ,TS 的功能更加强大,生态也更健全、更完善。vscode 对 TS 的支持更好。TS 成为了前段领域中的第二语言。 缺点:

1.语言本身多了很多的概念,TS 属于渐进性

2.项目初期,TS 会增加一些成本

# TypeScript 快速上手

  1. 在项目中生成 package.json ,yarn init -yes
  2. 安装 TS , yarn add typescript --dev
  3. 创建一个 01-getting-started.ts ,写入代码如下
const hello = (name) => {
  console.log(`hello,${name}`);
};
hello("linhuan");
复制代码;
  1. yarn tsc .\01-getting-started.ts 对 ts 文件进行编译
  2. 编译完成后生成一个 01-getting-started.js 文件
//最终结果是一个ES3 的语法文件
var hello = function (name) {
  console.log("hello\uFF0C" + name);
};
hello("linhuan");

复制代码;
  1. 添加类型注解
const hello = (name: string) => {
  console.log(`hello,${name}`);
};
hello(123);
//编译结果
//error TS2345: Argument of type '123' is not assignable to parameter of type 'string'.

//4 hello(123);
复制代码;

编译后结果出错了。

# TypeScript 配置文件

在终端中运行 yarn tsc --init ,生成一个 tsconfig.json 的配置文件

# TypeScript 原始数据类型

const a: string = "123";
const b: number = 100; //NaN //Infinity
const c: boolean = true; //false//null;//严格模式下布尔值不支持为空,非严格模式下支持
const d: void = undefined; //null;//严格模式下布尔值不支持为空,非严格模式下支持
const e: symbol = Symbol(); //由于该类型是es2015的标准,配置文件中的target 使用的是es5,解决办法 1.将target修改为es2015,2. 配置文件中的lib修改为"lib":["ES2015","DOM"]
复制代码;

# 标准库声明

内置对象所对应的声明

# TypeScript 显示中文消息

终端中错误显示中文的配置: 终端中输入 yarn tsc --locale zh-CN 。 VScode : 文件-首选项-设置 在设置页面的搜索框中输入 typescript local ,然后修改选项。如下图

img

# TypeScript 作用域

问题:在新建的文件中使用了和其他文件中同名的变量名

img

原因是,TypeScript 在编译的时候会把变量默认是在全局作用域中的,那如何解决这个问题呢,添加 代码 export {} 即可,这里不是表示 export 空对象,而是设置当前代码声明的变量是局部作用域

# Object 类型

TypeScript 中的 Object 类型泛指非原始对象类型,如对象,数组,函数

const object: object = {}; //function(){}//[];
//对象可以使用字面量的方式来限制对象中必须包含和注解类型一致的属性名和类型一致的属性值
const obj: { foo: string; age: number } = { foo: "f", age: 19 };
复制代码;

# 数组类型

const arry: Array<number> = [1, 2, 3];
const arr: number[] = [4, 5, 6];
复制代码;

# 元素类型

明确数组的元素数量和元素类型的,类型不一定要都一样

# 枚举类型

枚举的好处: 给一组数值,起更好理解的名称 一个枚举有组固定的值,并不会超出这个固定值得可能性 特征: 1.枚举属性不配置默认值的情况下,默认 从 0 开始累加,若是上一个属性值配置了一个数字,则下一个默认是加 1 后的值 ts 代码:

// enum Postatus{
//     Draft=0,
//     Unpublish=1,
//     Published=2
// }

// 枚举属性不配置默认值的情况下,默认 从 0 开始累加,若是上一个属性值配置了一个数字,则下一个默认是加1 后的值
enum Postatus {
  Draft,
  Unpublish,
  Published,
}
//console.log(Postatus[0]);
console.log(Postatus.Published);
复制代码;

编译后的 js 代码:

var Postatus;
(function (Postatus) {
  Postatus[(Postatus["Draft"] = 0)] = "Draft";
  Postatus[(Postatus["Unpublish"] = 1)] = "Unpublish";
  Postatus[(Postatus["Published"] = 2)] = "Published";
})(Postatus || (Postatus = {}));
//console.log(Postatus[0]);
console.log(Postatus.Published);
//# sourceMappingURL=04-enum-type.js.map
复制代码;

2.枚举类型的 ts 代码编译后不同于其他情况,相关代码都被移除,若是想不出现这种情况,给枚举添加一个 const ts:

const enum Postatus {
  Draft,
  Unpublish,
  Published,
}
//console.log(Postatus[0]);
console.log(Postatus.Published);
复制代码;

js:

console.log(2 /* Published */);
复制代码;

# 函数类型

function func1(a: number, b: number = 10, ...rest: number[]): string {
  return "func1";
}

func1(100, 200);

func1(100);

func1(100, 200, 300);

// -----------------------------------------

const func2: (a: number, b: number) => string = function (
  a: number,
  b: number
): string {
  return "func2";
};
复制代码;

# 任意类型 any

function stringify(value: any) {
  return JSON.stringify(value);
}

stringify("string");

stringify(100);

stringify(true);

let foo: any = "string";
//在运行中也可以修改成任意类型的值
foo = 100;
//在语法层面上不会报错
foo.bar();

//  any 类型是不安全的,不要轻易使用,但是有时候为了兼容老的代码的时候也会用到any类型
复制代码;

# 隐式类型判断 Type inference

let age = 18; // number

// age = 'string'

let foo;

foo = 100;

foo = "string";

// 建议为每个变量添加明确的类型标注
复制代码;

# 类型断言

// 假定这个 nums 来自一个明确的接口
const nums = [110, 120, 119, 112];

const res = nums.find((i) => i > 0);

// const square = res * res

const num1 = res as number;

const num2 = <number>res; // JSX 下不能使用
复制代码;

需要注意类型断言并不是类型转换,类型断言是在编译中体现的,但编译完成后就不存在了,而类型转换是在运行中体现的

# 接口

抽象的概念,用来约定对象的概念,使用一个接口就必须去遵循这个接口的全部的约定

interface Post {
  title: string;
  content: string;
}

function printPost(post: Post) {
  console.log(post.title);
  console.log(post.content);
}

printPost({
  title: "Hello TypeScript",
  content: "A javascript superset",
});
复制代码;

接口的可选成员,只读成员,动态属性

interface Post {
  title: string;
  content: string;
  subtitle?: string; //可选成员
  readonly summary: string; //只读成员
}

const hello: Post = {
  title: "Hello TypeScript",
  content: "A javascript superset",
  summary: "A javascript",
};

// hello.summary = 'other'

// 动态属性----------------------------------

interface Cache {
  [prop: string]: string;
}

const cache: Cache = {};

cache.foo = "value1";
cache.bar = "value2";
复制代码;

# 类的基本使用

描述一类事物的抽象特征

class Person {
  name: string; // = 'init name'
  age: number; //类的属性必须要有初始值或者构造函数里面要赋值

  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }

  sayHi(msg: string): void {
    console.log(`I am ${this.name}, ${msg}`);
  }
}
复制代码;

# 访问修饰符

控制属性可访问的级别

// 默认public 外部都可以访问
// 私有属性只能在类的内部访问
//protected 不能在类的外部被访问,相比于private 它是可以在继承中使用
class Person {
  public name: string; // = 'init name'
  private age: number;
  protected gender: boolean;

  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
    this.gender = true;
  }

  sayHi(msg: string): void {
    console.log(`I am ${this.name}, ${msg}`);
    console.log(this.age);
  }
}

class Student extends Person {
  private constructor(name: string, age: number) {
    super(name, age);
    console.log(this.gender);
  }

  static create(name: string, age: number) {
    return new Student(name, age);
  }
}

const tom = new Person("tom", 18);
console.log(tom.name);
// console.log(tom.age)
// console.log(tom.gender)

const jack = Student.create("jack", 18);
复制代码;

# 只读属性 readonly

只读属性只允许在声明的时候初始化或者在构造函数中初始化,一旦初始化会不允许修改

class Person {
  public name: string; // = 'init name'
  private age: number;
  // 只读成员
  protected readonly gender: boolean;

  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
    this.gender = true;
  }

  sayHi(msg: string): void {
    console.log(`I am ${this.name}, ${msg}`);
    console.log(this.age);
  }
}

const tom = new Person("tom", 18);
console.log(tom.name);
// tom.gender = false
复制代码;

# 类和接口

类描述的是一类对象所具有的属性和行为,接口则是抽象出了不同类别中的相同的行为。例如 人和动物都能吃和睡觉,具有相同的行为,但实现起来却大不相同。

interface Eat {
  eat(food: string): void;
}

interface Run {
  run(distance: number): void;
}

class Person implements Eat, Run {
  eat(food: string): void {
    console.log(`优雅的进餐: ${food}`);
  }

  run(distance: number) {
    console.log(`直立行走: ${distance}`);
  }
}

class Animal implements Eat, Run {
  eat(food: string): void {
    console.log(`呼噜呼噜的吃: ${food}`);
  }

  run(distance: number) {
    console.log(`爬行: ${distance}`);
  }
}
复制代码;

# 抽象类

类似于接口,抽象出公有的属性和方法,这个方法可以是具体的实现,抽象类中也可以包含抽象方法。

abstract class Animal {
  eat(food: string): void {
    console.log(`呼噜呼噜的吃: ${food}`);
  }

  abstract run(distance: number): void;
}

class Dog extends Animal {
  run(distance: number): void {
    console.log("四脚爬行", distance);
  }
}

const d = new Dog();
d.eat("嗯西马");
d.run(100);
复制代码;

# 泛型

我们在定义接口,函数,类的时候,没有指定具体的类型,而在使用的时候才去指定类型的一种特征,极大程度的复用代码

function createNumberArray(length: number, value: number): number[] {
  const arr = Array<number>(length).fill(value);
  return arr;
}

function createStringArray(length: number, value: string): string[] {
  const arr = Array<string>(length).fill(value);
  return arr;
}
//泛型的使用
function createArray<T>(length: number, value: T): T[] {
  const arr = Array<T>(length).fill(value);
  return arr;
}

// const res = createNumberArray(3, 100)
// res => [100, 100, 100]

const res = createArray<string>(3, "foo");
复制代码;

# 类型声明

我们有时候引用 npm 的第三方模块,但它不是使用 TypeScript 编写的。例如我们引入 lodash 模块,调用 camelCase ,如下可以看到这个方法没有类似限制的相关提示

img

此时,我们可以通过类型声明来解决该问题

declare function camelCase(input: string): string;
复制代码;

结果可以看到已经可以正常提示了

img

类型声明就是为了解决一些没有使用 TypeScript 进行类型声明的模块,兼容一些普通的 JS 模块

原文链接: TypeScript 学习笔记 (opens new window)

Last Updated: 6/13/2023, 3:58:54 PM