一、什么是 TypeScript
先看一下官网上对 TypeScript 的介绍。
关键词:超集
言外之意 JavaScript 的所有用法 TypeScript 都支持。
关键词:Type
TypeScript 强调也是其优点之意是【类型】
为什么需要 Ts 呢?举个小例子:
一个杯子可以装水,也可以当笔筒装杂物。但是,拿装杂物的杯子些许显得不太干净。物有所用,各司其职
。
Js 是弱类型可以定义 number 类型,可以定义 string 类型,这就并未各司其职。
所以,这就诞生了 Ts
【前端经常遇见的类型问题]
二、TypeScript 的安装及运行
安装:npm install -g typescript 或者 yarn add global typescript
确认是否安装成功: tsc -v
在官网中(中文网 -- 练习)可以将 Ts 转换为 Js
浏览器会识别 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"会报错
第三步:在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 文件。此时浏览器打开,效果如下:
问答区:如果想同时运行多个 ts 文件该如何操作。
此时需要进行此命令tsc --init
命令执行成功之后,会自动生成一个tsConfig.json
文件,此文件则会将帮助所有的 ts 文件编译转换为相应的 js 文件。
✅运行所有 ts 文件,tsc
即可,则会全部转换
在此处,插播推荐两个插件:
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;
其他数据类型: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 还可以应用在校验等场景。看如下图:
(八)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
- 运行时的错误;
- 快速编译;
- 全局作用域;
- 难以分析;
- 难以扩展;
- _references;
- 代码重用;
- 多目标;
- 单独编译;
相关链接:https://jkchao.github.io/typescript-book-chinese/tips/outFileCaution.html#%E8%BF%90%E8%A1%8C%E6%97%B6%E7%9A%84%E9%94%99%E8%AF%AF
多重命名空间
// 多重命名空间
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)。这样为了可复用性更高,则可以考虑用范型