浅尝 TS
这两天我根据 B 站的某某教学视频速刷了一遍 TypeScript,看完的同时做了以下的知识点总结。虽然花的时间不多,但是收货也还是有的~
初识
TypeScript 是什么?
- 以 JavaScript 为基础构建的强类型语言
- 是 JavaScript 的超集,拓展了 JavaScript,并添加了类型
- TS 不能被 JS 解析器直接执行,需要先进行编译,转换成 JS
TypeScript 相对 JS 新增了什么?
- 类型 (作为强类型语言,它毫无疑问增加了类型的声明)
- 添加 ES 不具备的新特性(元组、泛型、接口等)
- 丰富的配置选项(可以被编译成不同版本的 js)
- 强大的开发工具(作为 vscode 的底层开发语言,实现在开发中一些提示等)
TypeScript 开发环境搭建
下载安装 node.js
使用 npm 全局安装 TypeScript
1 2
| // 可查看typescript编译器信息 tsc
|
创建一个 ts 文件
使用 tsc 对 ts 文件进行编译
类型
基本类型
类型声明
- 类型声明是 TS 非常重要的一个特点
- 通过类型声明可以指定 TS 中变量(包含参数、返回值)的类型
- 指定类型后,当为变量赋值时,TS 编译器会自动检查值是否符合类型声明,符合则赋值,否则报错
- 简言之,类型声明给变量设置了类型,使得变量只能存储某种类型的值
- 基本语法:
1 2 3 4 5 6 7 8 9 10 11 12 13
| let 变量: 类型 let 变量: 类型 = 值 function fn(形参: 类型, ...) :类型 {} let fn:(形参:类型, 形参: 类型 ...) => 返回值类型 let obj: { a: string, b: number} let obj: { a: string, [others: string]:any} let arr: string[] let arr: Array<string> let tuple1: [string, string] enum grender{ male: 1, female: 0; }
|
- 联合声明:利用 | ,限制某个变量的类型在可供选择的类型之间
1 2
| let a: boolean | number;
|
自动类型判断
TS 拥有自动的类型判断机制
当对变量的声明和赋值是同时进行的,TS 编译器会自动判断变量的类型
当对变量的声明和赋值是同时进行的,可以省略掉手动设置类型声明
举例:
变量 hh 的类型会被默认成 boolean 类型,后续如果对它进行另外类型的赋值则会报错
常见类型
类型 |
例子 |
描述 |
number |
1,-1,1.1 |
数字 |
string |
‘h’, ‘hello’ |
字符串 |
boolean |
true/false |
布尔值 |
object |
{name: ‘ywc’ } |
对象 |
array |
[1,2,3] |
数组 |
tuple |
[1,2,3] |
元组,固定长度的数组 |
enum |
enum{A,B} |
枚举,TS 中新增类型 |
any |
* |
任意类型(相当于关闭了 TS 的类型检测建议不用) |
unknown |
* |
类型安全的 any |
void |
空值(undefined) |
没有值/undefiend |
never |
没有值 |
不能是任何值 |
字面值 |
值本身类型 |
限制变量的类型就是值本身的类型 |
为啥说不建议使用 any?
因为 any 类型的变量 A 赋值给其他类型的变量 B 时,会导致 B 不会对 A 的具体类型进行类型校验,同时变量 A 本身可以被赋值成任何类型的值。那就失去了类型限制的意义。
1 2 3 4
| let A: any; A = "any but string"; let B: number; B = A;
|
为啥说 unknown 是安全的 any?
因为 unknown 类型的变量不能直接赋值给其他量
1 2 3 4
| let a: unknown; a = "unknown but string"; let b: string = "111"; b = a;
|
正确做法 1– 增加条件语句:
1 2 3
| if (typeof a === "string") { b = a; }
|
正确做法 2 – 类型断言:
1 2 3
| b = a as string;
b = <string>a;
|
选项编译
创建配置文件
在项目文件夹的根目录中创建tsconfig.json
文件,ts 编译器会根据该文件中的配置信息进行编译。
基本配置项
include
- 指定需要被编译的 ts 文件
- 默认值 [“**/*”]
- 示例:
exclude
- 指定不需要被编译的 ts 文件
- 默认值 [“node_moudles”, “bower_components”, “json_packags”]
- 示例:
1 2
| "exclude": ["src/css/*"]
|
extends
1 2
| "extends": ["./config/base"]
|
files
- 指定被编译文件的文件列表。只在需要被编译的文件少的情况下才用到
- 示例:
1 2 3 4 5
| "files":[ "app.ts", "core.ts" ]
|
复杂配置项
compilerOptions
target
指定 ECMAScript 目标版本
moudle
指定生成哪个模块系统代码
lib
编译过程中需要引入的库文件的列表
outdir
指定编译后的文件目录
outfile
将编译后的代码合并成一个文件
allowJs
是否对 js 文件进行编译
checkJs
是否检查 js 代码是否符合语法规范
removeComments
删除所有注释,除了以 /!*开头的版权信息
noEmit
不生成输出文件
noEmitOnError
报错时不生成输出文件
strict
严格模式的总开关
alwaysStrict
以严格模式解析并为每个源文件生成 “use strict”语句
noImplicitAny
在表达式和声明上有隐含的 any 类型时报错
noImplicitThis
当 this 表达式的值为 any 类型的时候,生成一个错误。
strictNullChecks
严格检查空值
使用 webapck 打包 ts 代码
- 初始化 npm 包管理器
- 下载 webapck 相关依赖
1
| npm i -D webpack webpack-cli typescript ts-loader
|
- 配置 webpack.config.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| const path = require("path"); module.exports = { mode: "development", entry: "./src/index.ts", output: { path: path.resolve(__dirname, "dist"), filename: "bundle.js", }, module: { rules: [ { test: /\.ts$/, use: "ts-loader", exclude: /node_modules/, }, ], }, };
|
- 配置 tsconfig.json
1 2 3 4 5 6 7 8 9 10
| { "compilerOptions": { "target": "ES2015", "module": "ES2015", "strict": true }, "exclude":[ "node_modules" ] }
|
- 配置打包指令
- 增加页面展示
- 在 src 目录中创建
index.html
- 下载插件
html-webpack-plugin
1
| npm i -D html-webpack-plugin
|
1 2 3 4 5 6 7 8 9 10
| // webpack.config.js const HtmlWebpackPlugin = require("html-webpack-plugin"); module.exports = { ..., plugins: [ new HtmlWebpackPlugin({ template: "./src/index.html", }), ] }
|
- 增加热更新
1
| npm i -D webpack-dev-server
|
启动热更新服务其实是在本地打开了一个服务端口,用来访问我们站点
1 2 3 4 5 6 7 8
| // package.json { ..., "scripts": { "start": "webpack serve --open" } }
|
- 删除旧的打包文件
- 下载插件
clean-webpack-plugin
1
| npm i -D clean-webpack-plugin
|
1 2 3 4 5 6 7 8 9 10 11
| // webpack.config.js
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
module.exports = { ..., plugins: [ ..., new CleanWebpackPlugin() ] }
|
- 配置引入文件
1 2 3 4
| // 用来设置引入文件 resolve: { extensions: [".ts", ".js"], },
|
- 兼容更多浏览器
1
| npm i -D @babel/core @babel/preset-env babel-loader core-js
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| module.exports = { ..., output: { ..., environment: { arrowFunction: false, }, }, module: { rules: [ { test: /\.ts$/, use: [ { loader: "babel-loader", options: { presets: [ [ "@babel/preset-env", { targets: { chrome: "80", ie: "11", }, corejs: "3", useBuiltIns: "usage", }, ], ], }, }, "ts-loader", ], exclude: /node_modules/, }, ], }, }
|
类
简介
- 实例属性:通过实例对象进行访问,可修改
- 只读属性:通过实例对象进行访问,不可修改
- 类属性(静态属性): 通过类进行访问,可修改
- 实例方法:通过实例对象进行调用
- 类方法(类方法):通过类进行调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| class Person { name: string = "ywc"; age: number = 25; readonly collage: string = "南开大学"; static type: string = "animal"; static readonly hh: string = "haha";
sayHello() { console.log("hello"); } static walk() { console.log("walking"); } }
|
构造函数
- 构造函数在实例对象创建时会被调用
- 构造函数内部的 this 就是当前创建的实例对象
- 可以通过 this 向新创建的对象中添加属性
- 举例如下:
1 2 3 4 5 6 7 8 9 10 11
| class Dog { name: string; age: number; constructor(name: string, age: number) { this.name = name; this.age = age; console.log(this); } } const dog = new Dog("小黑", 3);
|
继承– extends
- 如果多个类存在公共属性和方法可以将公共部分抽离成一个公共类
- 子类可以通过继承的方式获取到父类的属性和方法,从而减少重复代码
- 如果子类中存在和父类相同的方法将会覆盖父类的方法
- 举例如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| class Animal { constructor(name, age) { this.name = name; this.age = age; } bark() { alert("the Animal is barking!"); } }
class Dog extends Animal { bark() { alert(`${this.name} is barking`); } }
class Cat extends Animal { bark() { alert(`${this.name} is barking`); } } const dog = new Dog("小白", 1); const cat = new Cat("小黑", 2); console.log(dog); console.log(cat); dog.bark(); cat.bark();
|
super
- 派生类的构造函数必须包含
super
调用,否则报错
- 派生类(子类)中如果
super
后跟着方法,则表示 super
代表父类;如果跟着参数则 super
代表父类构造函数
- 举例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| class Animal { name: string; constructor(name: string) { this.name = name; } bark() { alert("the Animal is barking!"); } } class Dog extends Animal { age: number; constructor(name: string, age: number) { super(name); this.age = age; } dogBark() { super.bark(); } } const dog = new Dog("小白", 1); console.log(dog); dog.bark(); dog.dogBark();
|
抽象类 abstract
- 和普通类区别不大,只是不能用来创建对象
- 抽象类专门为了当父类
- 提供抽象方法,子类必须对该方法进行改写,否则无法继承
- 举例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| abstract class Animal { name: string; constructor(name: string) { this.name = name; } abstract bark(): void; } class Dog extends Animal { bark() { alert("dog is barking!"); } } const dog = new Dog("小白"); console.log(dog); dog.bark();
|
接口 interface
- 接口在定义类或对象时会限制其的结构
- 接口中的所有属性不能有实际值
- 接口中的方法只能是抽象方法
- 举例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| type myType = { name: string; age: number; }; const obj: myType = { name: "ywc", age: 12, }; console.log(obj);
interface myInterface { name: string; age: number; } const obj2: myInterface = { name: "小鱼", age: 25, }; console.log(obj2);
interface myInterf { name: string; sayHello(): void; } class Person implements myInterf { name: string; constructor(name: string) { this.name = name; } sayHello(): void { console.log("hello"); } } const man1 = new Person("ywc"); console.log(man1); man1.sayHello();
|
属性封装
- 属性的三种修饰符:public(默认)、private(私有属性,类外部无法访问)、protected(保护属性)
- 私有属性只能在当前类进行访问。如果子类和实例对象需要进行读写操作,可以通过 setter 方式添加相应的校验设置私有属性;通过 getter 获取私有属性
- 保护属性只能在当前类和子类中进行访问
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
| class Dog { public _name: string; private _age: number; constructor(_name: string, _age: number) { this._name = _name; if (_age > 0) { this._age = _age; } else { this._age = 0; } } getAge() { return this._age; } setAge(age: number) { if (age > 0) { this._age = age; } } get name() { return this._name; } set name(val: string) { this._name = val; } get age() { return this._age; } set age(val: number) { if (val > 0) { this._age = val; } } } const dog = new Dog("小白", 1); console.log(dog.getAge()); dog.setAge(8); console.log(dog); dog._name = "小呵";
dog.age = 10; console.log(dog);
class A { protected age: number; constructor(age: number) { this.age = age; } } class B extends A { tellAge() { console.log(this.age); } } const smallB = new B(12); console.log(smallB);
smallB.tellAge();
|
泛型
- 定义函数或者类时,如果类型不明确那么就可以使用泛型
- 可以用一个或多个变量来表示不明确的属性类型
- 举例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| function fn<T>(a: T): T { console.log(a); return a; } fn(12); fn<string>("1299999");
function fn2<T, K>(a: T, b: K): T { console.log(b); let res: T = a; return res; } fn2("ywc", "yyy"); console.log(fn2("ywc", "yyy"));
interface Inter { length: number; } function getLength<T extends Inter>(a: T): number { return a.length; } const target = { length: 11, name: "hh", }; console.log(getLength(target));
class Person<T> { name: T; constructor(name: T) { this.name = name; } } const person1 = new Person("ywc"); const person2 = new Person<string>("yyy"); console.log(person1); console.log(person2);
|
这是我目前对 typescript 的一些认知吧。后期在 vue3 中使用 ts,加深对 ts 的认识后,还会再写一篇关于 TypeScript 的知识分享。
源码地址: https://github.com/heywc/tsdemo.git
