TypeScript 从入门到精通

一、什么是 TypeScript

先看一下官网上对 TypeScript 的介绍。

图1.png

关键词:超集
言外之意 JavaScript 的所有用法 TypeScript 都支持。

关键词:Type
TypeScript 强调也是其优点之意是【类型】

为什么需要 Ts 呢?举个小例子:
一个杯子可以装水,也可以当笔筒装杂物。但是,拿装杂物的杯子些许显得不太干净。物有所用,各司其职
Js 是弱类型可以定义 number 类型,可以定义 string 类型,这就并未各司其职。
所以,这就诞生了 Ts

【前端经常遇见的类型问题]

image.png

二、TypeScript 的安装及运行

安装:npm install -g typescript 或者 yarn add global typescript
确认是否安装成功: tsc -v

在官网中(中文网 -- 练习)可以将 Ts 转换为 Js

图2.png

浏览器会识别 html / css / js。识别不了 ts,所以 ts 编译转变为为 js。
https://jkchao.github.io/typescript-book-chinese/compiler/overview.html#%E6%96%87%E4%BB%B6%EF%BC%9Autilities

打开编辑器(确保在node/npm/typescript都成功的前提下)
第一步:新建文件夹 TS-APP
第二步:TS-APP下面新建一个index.html文件。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
  <script src="./app.js"></script>
</body>
</html>

在<script src="./app.js"></script>若src="./app.ts"会报错

图3.png

第三步:在TS-APP下面新建一个app.ts文件

class Greeter {
  greeting: string;
  constructor(message: string) {
      this.greeting = message;
  }
  greet() {
      return "Hello, " + this.greeting;
  }
}

let greeter = new Greeter("world");

let button = document.createElement('button');
button.textContent = "Say Hello";
button.onclick = function() {
  alert(greeter.greet());
}

document.body.appendChild(button);

要想在浏览器运行起来,需要将 app.ts 编译转换为 app.js。此时就要进行此命令: tsc app.ts
✅运行完此命令之后,会自动生成一个 app.js 文件。此时浏览器打开,效果如下:

图4.png

问答区:如果想同时运行多个 ts 文件该如何操作。
此时需要进行此命令 tsc --init
命令执行成功之后,会自动生成一个 tsConfig.json 文件,此文件则会将帮助所有的 ts 文件编译转换为相应的 js 文件。
✅运行所有 ts 文件,tsc 即可,则会全部转换

图6.png

在此处,插播推荐两个插件:
Live Server(能够保存的时候,自动更改)
TypeScript Auto Compiler(能够保存之后,不需要执行 tsc 命令,自动会生成相应的 js 文件)

三、TypeScript 入门

(一)TypeScript -- 基本数据类型和报错解析

为了让程序有价值,我们需要能够处理最简单的数据单元:数字,字符串,结构体,布尔值等。 TypeScript支持与JavaScript几乎相同的数据类型,此外还提供了实用的枚举类型方便我们使用。

// 基本数据类型
let num = 25;
let float = 25.5;
let hex = 0xf000;  // 16进制
let binary = 0b1001; // 2进制
let octal = 0o733;  // 8进制

// 重新赋值
num = '25';  // 会报错,可以在终端编写 tsc 进行查看错误❌,如下图7.

// ts原型
let num = 25;
// 等同于
// let num: number = 25;
图7.png
其他数据类型:string 、boolean 、 any

⚠️注意:在如下例子中:anything 赋予任何值都不会报错,是因为只是给 anything 开辟了一个空间,但是并没有定义其类型。在项目中,尽量避免需用 any,不太利于后期维护。

//  boolean
// ts原型
let isLogin = false;
// 等同于
// let isLogin: string = false;

// **----------------------** // 

// string
let str: string = 'hello Tc';

// **----------------------** // 

// any
let anything; // 等同于 let anything: any
// 没报错的原因,只是开辟了一个空间,但是没有定义其类型
anything = 25;
anything = 'hello'

(二)TypeScript -- 数组 元组 枚举

// 数组 元组 枚举
let names: Array<string> = ['hello', 'word'];
// console.log(names[0]); // hello
// names[0] = 100; // 报错,不能将类型“100”分配给类型“string”。
// names[0] = 'yes';

let number: number[] = [1, 2, 3];
let anyArray: any[] = [1, 'hello', true];

// **----------------------** // 

// 元组
let colors: [string, number] = ['hello', 99];
// let colors: [string, number] = [99, 'hello']; // 会报错

// **----------------------** // 
// 枚举
enum Color{
  Black,
  Yellow,
  Red,
}
// let myColor: Color = Color.Black; // 输出为 0
// let myColor: Color = Color.Yellow; // 输出为 1
// 若Yellow = 100, 则Red为101

此枚举类型,转换为 js 的时候是函数形式

var Color;
(function (Color) {
    Color[Color["Black"] = 0] = "Black";
    Color[Color["Yellow"] = 1] = "Yellow";
    Color[Color["Red"] = 2] = "Red";
})(Color || (Color = {}));
// let myColor: Color = Color.Black; // 输出为 0
// let myColor: Color = Color.Yellow; // 输出为 1
// 若Yellow = 100, 则Red为101

控制台输出:{0: "Black", 1: "Yellow", 2: "Red", Black: 0, Yellow: 1, Red: 2}

(三)函数相关类型

// 函数的相关类型
function returnValue() {
  return 'hello';
}
// console.log(returnValue()); // hello

// 规范写法
function returnNum(): number { // 定义其返回值类型
  return 520;
}

// **----------------------** // 

// 若函数返回值 -- 空
function sayHello(): void {
  console.log('hello @@@@@');
}
sayHello();


// **----------------------** // 

// 参数类型
// 不标准写法,已经知道value1和value2的类型。
// 报错内容:参数“value1”隐式具有“any”类型。
// function sumVal(value1, value2) {
//   return value1 + value2;
// }

function sumVal1(value1: number, value2: number): number {
  return value1 + value2;
  // return value1 * value2; // 如果两个参数中有一个不是数值 那么返回的是NAN。但是若为0或者'',返回0。
}
console.log(sumVal1(1, 2)); // 3
// console.log(sumVal1(1, ''));

function sumVal2(value1: number, value2: string): string {
  return value1 + value2;
}
console.log(sumVal2(1, 'hello')); // 1hello

// **----------------------** // 

// 函数类型
let myFunc: (a: number, b: number) => number;
// 若函数为定义类型,如下则都可以赋值不同类型。
// myFunc = sayHello;
// myFunc();
myFunc = sumVal1; // 将函数 sumVal1 赋予给 myFunc
console.log(myFunc(5, 5));   // 10

⚠️注意:在最开始入 ts 坑时,除开会写 any 之外。印象最深刻的是 void。fuc(): void或者fuc: () => void。类似这样的写法都没少写。但是,碰到 .then。一报错就改成 any
void 其实是代表函数返回值为时才使用的。所以,不言而喻,.then 为什么会报错了。

⚠️any,有人开玩笑说:把 TS 用成 AnyScript 的人开除。如果项目中经常使用any,则失去了TS本身最大的意义

(四)对象 object & type

// 对象 object & type
let listObj = {
  name: 'Danile',
  age: 31
};

// 不正确写法
//  报错内容:需要去包含 name 和 age 两个属性
// listObj = {}; 

// 报错内容:不能包含别的属性
// listObj =  {
//   n: 'hello',
//   a: 12,
// }

// 最规范写法
// let listObj(name: string, age: number) = {
//   name: 'Danile',
//   age: 31
// };

// **----------------------** // 

// 稍微复杂对象类型
let complex: { data: number[], myFunc: (item: number) => number[] } = {
  data: [1, 2, 3],
  myFunc: function(item: number): number[] {
    this.data.push(item);
    return this.data;
  }
}
// console.log(complex.myFunc(520));

// **----------------------** // 

// type 生成类型
// type MyType = { data: number[], myFunc: (item: number) => number[] };
interface MyType { data: number[], myFunc: (item: number) => number[] };

let complex2: MyType = {
  data: [1, 2, 3],
  myFunc: function(item: number): number[] {
    this.data.push(item);
    return this.data;
  }
}
console.log(complex2.myFunc(520));

❓type 和 interface 的区别?

  • 其中 interface 可以如下合并多个,而 type 只能使用 & 类进行连接。
interface A {
a: number;
}
interface A {
 b: number;
}
const a: A = {
 a: 3,
 b: 4
}

interface 可以继承 , type 不可以继承(type已经可以继承、实现了。是2.X版本改的)

(五)union type(联合类型) 检查类型 null undefined never

// union type 检查类型 null undefined never
// union type
let unionType: number | string | boolean = 12;
unionType = '12';
unionType = true;


// 检查类型
let checkType = 10;
if (typeof checkType == 'number') {
  console.log('number')
}

// null 和 undefined
// let myNull = 12;
// myNull = null; // 如果"strict": false的时候则不会有问题

let myNull = null;
myNull = undefined;  

// never
// never类型是任何类型的子类型,也可以赋值给任何类型;然而,没有类型是never的子类型或可以赋值给never
// 类型(除了never本身之外)。及时any也不可以赋值给never。通常表现为抛出异常或无法执行到终点(例如无限循环)

let x: never;
// x = 123; // 报错:不能将类型“123”分配给类型“never”。

// never的应用场景 抛出异常
function error(message: string): never {
  throw new Error(message);
}

// 死循环
function loop(): never {
  while (true) {}
}

let y: number;
y = ( () => {
  throw new Error('message');
})();

字面量类型:字面量也就是 JavaScript 基元类型具体的值。而在 TypeScript 中,我们可以将字面量作为一种自定义的类型,这种类型被称为字面量类型

type China = 'China';
let country: China = 'China';  // ok
country = 'America';  // error: Type '"America"' is not assignable to type '"China"'.
null 和 undefined

当你指定了--strictNullChecks标记,null和undefined只能赋值给void和它们各自。 这能避免 很多常见的问题(防止项目aa.bb 取不到值等情况)。

(六)class 类(属性,方法)| 继承

✅区分 public protected private 的区别
public: 公共的
private: 当成员被标记成 private时,它就不能在声明它的类的外部访问。
protected: protected修饰符与 private修饰符的行为很相似,但有一点不同, protected成员在派生类中仍然可以访问

// class 类(属性,方法)
class Person {
  public name: string;
  protected gender: string;
  private age: number = 27;
  // public username: string; 


  constructor(name: string, gender: string, public username: string) {
    this.name = name;
    this.username = username;
    this.gender = gender;
  }

  printAge(age: number) {
    this.age = age;
    console.log(this.age);
  }

  setGender(gender: string) {
    this.gender = gender;
    console.log(this.gender);
  }
}

const person = new Person('Danile', '女', 'Ts');
console.log(person.name, person.username);

person.printAge(30);
person.setGender('男');


// **----------------------** // 
// 继承
// 子类可以获得父类所有公开的
class Studen extends Person {
  studentId: number;
  constructor(name: string, username: string, studentId: number) {
    super(name, username);
    this.studentId = studentId;
  }
}

const student = new Studen('Kris', 'Ts', 23);
console.log(student.name, student.username, student.studentId);
console.log(student)
把类当做接口使用

类定义会创建两个东西:类的实例类型和一个构造函数。 因为类可以创建出类型,所以你能够在允许使用接口的地方使用类。

   x: number;
   y: number;
}
interface Point3d extends Point {
   z: number;
}
let point3d: Point3d = {x: 1, y: 2, z: 3};

(七) class set get修饰词 用于隔离私有属性和可公开属性

// 1.class set get修饰词 用于隔离私有属性 和 可公开属性
// 2.class 静态属性和方法

class Person1 {
  private _name: string = 'Danile_getName';

  // 私用属性赋值
  set setName(value: string) {
    this._name = value;
  }

  // 私有属性取值
  get getName() {
    return this._name;
  }
}

let person1 = new Person1();
console.log(person1.getName);   //  Danile_getName
person1.setName = 'Danile_setName';
console.log(person1.getName);  //  Danile_setNames

对于 set 和 get 还可以应用在校验等场景。看如下图:

图8

(八)namespace 命名空间

// namespace 命名空间
namespace myMath {
  export const PI = 3.14;

  export function sumValue(num1: number, num2: number): number {
    return num1 + num2;
  }
  
  export function calcCircle(value: number) {
    return value * PI
  }
}

const PI = 2.88;

console.log(myMath.sumValue(5, 10));  // 10
console.log(myMath.PI);  // 3.14

console.log(PI); //  2.88

命名空间最大的好处是:隔离环境变量的污染

若将命名空间放不到不同的文件夹,该如何?

// sumValue.ts
namespace myMath {
  export function sumValue(num1: number, num2: number): number {
    return num1 + num2;
  }
}

// calcCircle.ts
namespace myMath {
  export const PI = 3.14;
  export function calcCircle(value: number) {
    return value * PI
  }
}

// nameSpace.ts
console.log(myMath.sumValue);  // 会报错的,找不到myMath的文件
console.log(myMath.calcCircle);

// 解决方案1: 在app.ts(最外层入口)加,<script src="sumValue.js"></script>
// 解决方案2: tsc --outfile app.js sumValue.ts calcCircle.ts app.ts
// app.js 是输入文件
// sumValue.ts calcCircle.ts app.ts 合并为app.js文件

谨慎使用 --outFile

多重命名空间

// 多重命名空间 
namespace myMath {
  export namespace Circle {
    export const PI = 3.14;

    export function sumValue(num1: number, num2: number): number {
      return num1 + num2;
    }
    
    export function calcCircle(value: number) {
      return value * PI
    }
  }
}

// 若 namespace Circle 不进行export。则myMath.Circle取不到。
console.log(myMath.Circle.sumValue(5, 10));

reference -- 引入ts文件,写法: ///

// 引入文件
// 它是一种注释,告诉typescript编译器,当前文件使用了哪些声明文件,以帮助编辑器提示信息,
// 及编译器检查类型。这种注释很重要,如果后面的路径不对,则编译会失败。

/// <reference path="text_1.ts" />

// **----------------------** // 
// 多个文件打包
tsc --outFile app.ts

(九)interface

// interface 接口
interface PersonInterface {
  name: string,
  ages: number, // :号,是必传
  sex?: string, // ?:,是非必传
  readonly salary: number, // 只读不能修改,  personInfo.salary = 1000(会报错)
  [propName: string]: any, // 给任何名字
  greet(): void,
}

// type Person2 = { name: string, ages: number }

let personInfo: PersonInterface = {
   name: 'Danile',
   ages: 24,
   ids: [1, 2, 3] ,
    greet() {
      console.log('greet')
  }
};

// let personInfo: Person2 = {
//   name: 'Danile',
//   ages: 24,
// };

// console.log(personInfo);

interface 继承和类的实现

// interface 继承
interface PersonInterface {
  name: string,
  ages: number, // :号,是必传
  sex?: string, // ?:,是非必传
  readonly salary: number, // 只读不能修改,  personInfo.salary = 1000(会报错)
  [propName: string]: any, // 给任何名字
  greet(): void,
}

interface StudentInterface {
  id: number,
  course: string,
}
class People implements PersonInterface, StudentInterface{
  name: string = 'Danile';
  ages: number = 22;
  salary: number = 8000;
  id: number = 404;
  course: string = 'ts is good';
  greet() {
    console.log('hello world');
    
  }
}

// **----------------------** // 
// interface 的继承(接口继承接口)
interface Employee extends PersonInterface {
  work: string,
}

const employee: Employee = {
  name: 'Danile_001',
  ages: 10,
  salary: 10000,
  work: '前端研发',
  greet() {
    console.log('hello kris');
  }
}

console.log(employee);

(十)范型的使用,以及场景

// TypeScript 泛型(Generic)

// 在函数中使用泛型
// function identify<T>(arg: T): T {
//   return arg;
// }

// 可以明确指定类型
console.log(identify<string>('string'));
// console.log(identify<string>(25)); // 会报错

// **----------------------** // 

// 交给ts推断类型
console.log(identify(25));

// 在接口中使用泛型
interface GenericIdentify{
  <T>(arg: T): T,
}
function identify<T>(arg: T): T {
  return arg;
}

let myIdentify: GenericIdentify = identify;

// 可以明确制定类型
console.log(myIdentify<string>('my-identify'));

// 交给ts推断类型
console.log(myIdentify(20));

// **----------------------** // 
// 为泛型添加约束
function getLength<T extends { length: number }>(obj:T):any {
  return obj.length;
}

// 这是给泛型添加约束
// function getLength<T extends number>(obj:T):any {
//   return obj.length;
// }

const obj = {
  name: 'Danile',
  ages: 18,
  length: 10,
}

// const obj = 2000000;
console.log(getLength(obj));

// **----------------------** // 
// 泛型的应用 -- class
class CountNumber<T extends number> {
  number1: T;
  number2: T;

  constructor(num1: T, num2: T) {
    this.number1 = num1;
    this.number2 = num2;
  }

  calcalate(): number {
    // 前面加上 + 号是ts去运算
    return +this.number1 * +this.number2;
  }
}

const countNumber = new CountNumber<number>(10, 20);
console.log(countNumber.calcalate());

对于范型可能用到的项目中场景

  • 登录用户名,可能是手机号(number),可能是名字(string)。这样为了可复用性更高,则可以考虑用范型
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容