TypeScript快速入门
https://blog.csdn.net/weixin_51457459/article/details/115283944
语言类型的分类
强类型与弱类型
编程语言的划分从安全的维度划分
强类型:变量类型定义之后不会任意进行隐式类型转换 (JAVA)
弱类型:会进行隐式转换 (JS)
强类型优势
- 错误更早暴露。如:编译时就知道代码的错误了
- 代码更智能,编码更准确。如:编辑器知道变量类型,会实时进行提示
- 减少不必要的类型判断。
静态类型与动态类型
编程语言从类型检查的维度划分
静态类型:变量在声明的时候其类型就是明确的,声明过后他的类型是不允许进行随意的修改的
动态类型:变量是没有类型的,变量的类型取决于代码运行时值的类型
JS自有类型系统的问题
JAVASCRIPT 属于弱类型&动态类型语言
弱类型&动态类型语言的缺点
- “任意”
- “不靠谱”
- 由于JS是属于脚本语言,不需要进行编译,其执行依赖运行环境。代码错误往往都是运行时才能够发现错误,而不能够在编译的时候就知道错误的所在。
// 弱类型的问题
// 进行隐式转换,使函数功能发生改变了
// 这样的结果不是我们期待的结果,我们期待的时求两个数的和
function add(num1, num2) {
return num1 + num2
}
add(100, '200') // 100200
JS 为什么会设计成弱类型&静态类型语言哪?
这是历史遗留问题,早期的web应用都是非常简单的应用,不需要太多代码就可以实现一个应用,如果在有类型检查会显得复杂且多余。但现在随着web应用越来越复杂庞大,之前的语言设计就显得力不从心。
JavaScript 类型系统问题解决方案
- Flow FACEBOOK团队推出的JS类型检查器来进行类型检查 Flow的文章
- 使用 TypeScript
TypeScript
TypeScript的认识
- 根据前面的点我们知道JavaScript语言设计的不足在于其是弱类型&动态类型的语言,不会进行类型检查,不适合于开发大型应用
- TypeScript是为了解决JavaScript自有类型系统的问题而诞生的,TypeScript是JavaScript的超集
- TypeScript中也可以写ES6+的语法,在编译的时候会转换为目标,相当于会有个babel的功能
快速上手
快速上手查看官方文档:TypeScript中文官方手册
TypeScript 的语法
TS 六种原始类型
// TypeScript 原始数据类型
// Number
const num: number = 1234
const str: string = '1234'
const _undefined: void = undefined
const _boolean: boolean = true
const _null: null = null
// const _symbol: symbol = Symbol()
编译后:
// TypeScript 原始数据类型
// Number
var num = 1234;
var str = '1234';
var _undefined = undefined;
var _boolean = true;
var _null = null;
// const _symbol: symbol = Symbol()
需要注意的点是:
- 非严格模式下 变量可以为null || undefined || 所定义的类型,严格模式下 定义的变量只能够是所定义的类型
- tsconfig配置文件默认编译代码是ES5,所使用的标准库也是ES5的,ES5的ts标准库没有对ES6内置对象进行定义,所以使用定义symbol类型会出现错误。解决办法修改tsconfig中lib配置为['ES2015', 'DOM']。DOM标准库包含BOM DOM 所谓的标准库就是TS对JS内置对象,api的定义,可以理解为代码检查规则配置文件。
让TS以中文提示错误消息
- 运行npx tsc --locale zh-CN
- vs code 中 修改设置里面找到 typescript locale配置
TS中的Object 类型(除了原始类型以外的其他类型)
object类型指原始类型一外的其他类型
const a: object = function () { }
const b: object = / /
对象类型限制
object 对象类型的限制可以使用字面量的形式进行限制
const obj: { name: string, age: number } = { name: 'reborn', age: 18 }
数组类型
// 表示定义的数组必须都是数字类型
const arr: number[] = [1, 2, 3]
// 泛型定义数组, 这个与上面是相同的意思
const arr1: Array<number> = [1, 2, 3]
元组类型的定义
元组含义是明确元素数量以及明确元素类型的数组*
如下:定义个三个元素,第一个为number类型,第二个为string类型,第三个为boolean类型
const tuple: [number, string, boolean] = [1, '1', false,]
枚举类型
通过enum关键字可以定义枚举数据结构,枚举数据结构一般都是定义一些常量的,如下面定义文章的状态类型。
const enum postStatus {
draft = 0,
unpublished = 1,
published = 2
}
console.log(postStatus['draft'])
上面代码会被编译成如下代码:
var postStatus;
(function (postStatus) {
postStatus[postStatus["draft"] = 0] = "draft";
postStatus[postStatus["unpublished"] = 1] = "unpublished";
postStatus[postStatus["published"] = 2] = "published";
})(postStatus || (postStatus = {}));
console.log(postStatus['draft']);
另外一点可以直接定义常量枚举,只需要在 enum关键字前面加上 const,这样编译出来的代码直接是枚举值了
const enum postStatus {
draft = 0,
unpublished = 1,
published = 2
}
console.log(postStatus['draft'])
console.log(0 /* 'draft' */);
注意的点:
- 枚举数据可以分为 字符串枚举与 数字枚举
- 数字枚举可以不用定义值,默认会从 0 开始,如上面postStatus {draft,unpublished,published}, 会得到
- 数字枚举定义第一个值,后面的值会默认补全。如上面postStatus {draft = 8,unpublished,published}, 会得到
- 字符串枚举必须要定义相应的值
函数类型
函数字面量声明
fn1 函数的形参必须是两个number 类型,该函数的返回值必须是number类型
function fn1(num1: number, num2: number): number {
return num1 + num2
}
函数表达式声明
函数表达式会有一个变量fn2接受一个函数,当前变量也可以进行类型定义,表面接受什么样的函数
如fn2表示,它只能够接受一个为字符串的参数,且没有返回值的函数
const fn2: (greeting: string) => void = (greeting: string): void => {
console.log(`${greeting} Reborn~`)
}
编译完为
var fn2 = function (greeting) {
console.log(greeting + " Reborn~");
};
Date类型
const date: Date = new Date()
正则表达式
const reg: RegExp = /^/
TS 中定义任意类型
Any类型表示,hello函数可以接受任意类型参数。
any 类型不会有TS类型检查,一般是用于兼容老的代码。
function hello(greeting: any): void { }
TS 的隐式类型推断机制
TS 具有隐式类型推断机制。下图我们并没有给变量 a 添加上类型注解,但给 a 变量赋值了number类型的2,此时 TS 就会推断改数据类型为number类型,当我们再次给a赋值字符串’3423’的时候,VsCode就会有报错提示。
虽然说隐式类型推断机制能够让我们不用去写类型注解为我们开发提供方便,但是不建议采用次机制.
TS 类型断言
TS 类型断言的应用场景
// 1. 假设arr是来自 后台接口 返回的数据
const arr = [1, 2, 3, 4]
// 2. 我们需要查找 为 2 的数据
const target = arr.find(item => item === 2)
// 4. 这时候可以采用类型断言,告诉 TS 我们确认改类型为 number
// - 采用 as 关键字
const result = target as number
// - 采用泛型也可以进行类型断言 , 不过再写 jsx 的时候泛型的尖括号会与html标签进行冲突
// const result = <number> target
// 3. 此时就不能够进行 * 运算,因为target 类型有可能为 number 有可能为 undefined
const res = result * result
TS 接口
一句话用来约束对象的结构(成员的类型、成员个数),在实际的编译时并不会编译为JS代码
// TS 接口
// 用来约束对象成员
// 1. 通过interface关键字约定接口
interface Post {
title: string
content: string
subTitle?: string
// 2. 可选成员, 只读成员, 动态成员
// - 通过 ? 定义 可选成员
// - 通过 readonly 关键字定义只读成员
// - 通过 对象的 key 的计算属性定义 动态成员
readonly summary: string
}
function printPost(post: Post): void {
console.log(post.title)
console.log(post.content)
console.log(post.summary)
}
printPost({ title: 'reborn', content: 'vergood', summary: '哈哈哈哈' })
// - 通过 对象的 key 的计算属性定义 动态成员
// 设置动态成员,就可以为对象添加任意成员,
// 下面接口表示可以为对象添加任意成员, 类型约束为了 key 为str, value str
interface dongtai {
[dd: string]: string
}
const obj: dongtai = {
hello: 'hello',
age: '18'
}
编译后的代码长这样,可以看到并没有接口
// TS 接口
// 用来约束对象成员
function printPost(post) {
console.log(post.title);
console.log(post.content);
console.log(post.summary);
}
printPost({ title: 'reborn', content: 'vergood', summary: '哈哈哈哈' });
var obj = {
hello: 'hello',
age: '18'
};
TS 中的类
传统的JavaScript程序使用函数和基于原型的继承来创建可重用的组件,但对于熟悉使用面向对象方式的程序员来讲就有些棘手,因为他们用的是基于类的继承并且对象是由类构建出来的。 从ECMAScript 2015,也就是ECMAScript 6开始,JavaScript程序员将能够使用基于类的面向对象的方式。 使用TypeScript,我们允许开发者现在就使用这些特性,并且编译后的JavaScript可以在所有主流浏览器和平台上运行,而不需要等到下个JavaScript版本。
const { log } = console
// TS 中类的使用
// 1. 为实例添加属性的时候,需要先对属性进行声明
// 3. 可以给类的成员定义访问修饰符如 private 定制私有属性,public定义 公有属性(默认) protected 受保护的(只允许在子类访问该成员)
class Person {
// 2. ES2016 可以直接在对象中通过 变量名= 值 的形式为 实例赋值属性
public name: string
// name?:string = undefined
// 6. private 关键字定义的变量仅适用于当前 类的内部使用
private age: number
protected gender: boolean
// 7. 通过添加 readonly 关键字 标注对象是只读属性
public readonly weight: number = 180
// 4. 给构造函数定义为公有类型,可以在外部通过new 关键字创建实例,如果为 private 只能够在构造函数内部创建实例
public constructor(name: string, age: number, gender: boolean) {
this.name = name
this.age = age
this.gender = gender
}
public sayHi(): void {
log(this.age)
}
}
class Student extends Person {
public constructor(name: string, age: number) {
super(name, age, false)
// 5. protected 关键字定义的属性仅适用于子类继承访问
log(this.gender, 'protected')
}
}
编译后
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
if (typeof b !== "function" && b !== null)
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
var log = console.log;
// TS 中类的使用
// 1. 为实例添加属性的时候,需要先对属性进行声明
// 3. 可以给类的成员定义访问修饰符如 private 定制私有属性,public定义 公有属性(默认) protected 受保护的(只允许在子类访问该成员)
var Person = /** @class */ (function () {
// 4. 给构造函数定义为公有类型,可以在外部通过new 关键字创建实例,如果为 private 只能够在构造函数内部创建实例
function Person(name, age, gender) {
// 7. 通过添加 readonly 关键字 标注对象是只读属性
this.weight = 180;
this.name = name;
this.age = age;
this.gender = gender;
}
Person.prototype.sayHi = function () {
log(this.age);
};
return Person;
}());
var Student = /** @class */ (function (_super) {
__extends(Student, _super);
function Student(name, age) {
var _this = _super.call(this, name, age, false) || this;
// 5. protected 关键字定义的属性仅适用于子类继承访问
log(_this.gender, 'protected');
return _this;
}
return Student;
}(Person));
类中的接口
实现类的结构
需求:
现在需要定义两个类 Person、Animal 类,都有相同行为 吃 与 行走等行为,此时我们可以通过接口进行抽象公共方法
定义类的接口,表示该数据类型要有 eatFood 和 walk 的行为(方法)
const { log } = console
interface EatAndWalk {
eatFood(food: string): void
walk(where: string): void
}
class Person implements EatAndWalk {
public eatFood(food: string): void {
log(`person eat ${food}`)
}
public walk(where: string): void {
log(`person ${where} on street`)
}
}
class Animal implements EatAndWalk {
public eatFood(food: string): void {
log(`animal eat ${food}`)
}
public walk(where: string): void {
log(`animal ${where} on street`)
}
}
编译后
var log = console.log;
var Person = /** @class */ (function () {
function Person() {
}
Person.prototype.eatFood = function (food) {
log("person eat " + food);
};
Person.prototype.walk = function (where) {
log("person " + where + " on street");
};
return Person;
}());
var Animal = /** @class */ (function () {
function Animal() {
}
Animal.prototype.eatFood = function (food) {
log("animal eat " + food);
};
Animal.prototype.walk = function (where) {
log("animal " + where + " on street");
};
return Animal;
}());
抽象类
abstract class Animal {
eat(food: string): void {
console.log(`eat ${food}`)
}
// 也可以定义抽象方法,当父类有抽象方法的时候,其子类也必须要有所定义的方法。
abstract run(distance: number): void
}
class Cat extends Animal {
constructor(food: string) {
super()
this.eat(food)
}
run(distance: number): void {
console.log(`run ${distance} meters`)
}
}
js代码
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
if (typeof b !== "function" && b !== null)
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
var Animal = /** @class */ (function () {
function Animal() {
}
Animal.prototype.eat = function (food) {
console.log("eat " + food);
};
return Animal;
}());
var Cat = /** @class */ (function (_super) {
__extends(Cat, _super);
function Cat(food) {
var _this = _super.call(this) || this;
_this.eat(food);
return _this;
}
Cat.prototype.run = function (distance) {
console.log("run " + distance + " meters");
};
return Cat;
}(Animal));
泛型
软件工程中,我们不仅要创建一致的定义良好的API,同时也要考虑可重用性。 组件不仅能够支持当前的数据类型,同时也能支持未来的数据类型,这在创建大型系统时为你提供了十分灵活的功能。
在像C#和Java这样的语言中,可以使用泛型
来创建可重用的组件,一个组件可以支持多种类型的数据。 这样用户就可以以自己的数据类型来使用组件。
function createArra<T>(length: number, value: T): T[] {
const arr = Array<T>(length).fill(value)
return arr
}
const resNumberArr = createArra<number>(3, 100)
console.log(resNumberArr)
const resStringArr = createArra<string>(3, '333')
console.log(resStringArr)
编译后:
function createArra(length, value) {
var arr = Array(length).fill(value);
return arr;
}
var resNumberArr = createArra(3, 100);
console.log(resNumberArr);
var resStringArr = createArra(3, '333');
console.log(resStringArr);
TS 类型声明
import { upperCase } from 'lodash'
// TS 类型声明
// 如果引入的第三方模块不是 ts模块的的话,类型系统会失效,必须要自己手动声明方法类型
// 1. 声明 upperCase方法
declare function upperCase(str: string): string
// 2. 此时调用upperCase就会进行类型检查
const res = upperCase('hello')