TS进阶知识
一:TypeScript的配置文件:
https://blog.csdn.net/weixin_46221198/article/details/125589781
√ 标记为常用配置
常用配置
"include": ["./src/*"],//src目录下的编译为js,,支持支持 glob 模式匹配√
//"files":["a.ts"],//指定具体的文件(只能是文件),需编译的文件较少时使用√
"exclude":["./data/*"]//data目录下的不不便宜为js√
"compilerOptions": {
"incremental": true, // TS编译器在第一次编译之后会生成一个存储编译信息的文件,第二次编译会在第一次的基础上进行增量编译,可以提高编译的速度 √(增量编译)
"tsBuildInfoFile": "./buildFile", // 增量编译文件的存储位置
"diagnostics": true, // 打印诊断信息
"target": "ES5", // 目标语言的版本
"module": "CommonJS", // 生成代码的模板标准
"outFile": "./app.js", // 将多个相互依赖的文件生成一个文件,可以用在AMD模块中,即开启时应设置"module": "AMD",
"lib": ["DOM", "ES2015", "ScriptHost", "ES2019.Array"], // TS需要引用的库,即声明文件,es5 默认引用dom、es5、scripthost,如需要使用es的高级版本特性,通常都需要配置,如es8的数组新特性需要引入"ES2019.Array",
"allowJS": true, // 允许编译器编译JS,JSX文件 √
"checkJs": true, // 允许在JS文件中报错,通常与allowJS一起使用
"outDir": "./dist", // 指定输出目录 √
"rootDir": "./", // 指定输出文件目录(用于输出),用于控制输出目录结构 √
"declaration": true, // 生成声明文件,开启后会自动生成声明文件
"declarationDir": "./file", // 指定生成声明文件存放目录
"emitDeclarationOnly": true, // 只生成声明文件,而不会生成js文件
"sourceMap": true, // 生成目标文件的sourceMap文件
"inlineSourceMap": true, // 生成目标文件的inline SourceMap,inline SourceMap会包含在生成的js文件中
"declarationMap": true, // 为声明文件生成sourceMap
"typeRoots": [], // 声明文件目录,默认时node_modules/@types
"types": [], // 加载的声明文件包
"removeComments":true, // 删除注释 √
"noEmit": true, // 不输出文件,即编译后不会生成任何js文件
"noEmitOnError": true, // 发送错误时不输出任何文件
"noEmitHelpers": true, // 不生成helper函数,减小体积,需要额外安装,常配合importHelpers一起使用
"importHelpers": true, // 通过tslib引入helper函数,文件必须是模块
"downlevelIteration": true, // 降级遍历器实现,如果目标源是es3/5,那么遍历器会有降级的实现
"strict": true, // 开启所有严格的类型检查 √
"alwaysStrict": true, // 在代码中注入'use strict' √
"noImplicitAny": true, // 不允许隐式的any类型 √
"strictNullChecks": true, // 不允许把null、undefined赋值给其他类型的变量
"strictFunctionTypes": true, // 不允许函数参数双向协变
"strictPropertyInitialization": true, // 类的实例属性必须初始化
"strictBindCallApply": true, // 严格的bind/call/apply检查
"noImplicitThis": true, // 不允许this有隐式的any类型
"noUnusedLocals": true, // 检查只声明、未使用的局部变量(只提示不报错)
"noUnusedParameters": true, // 检查未使用的函数参数(只提示不报错)
"noFallthroughCasesInSwitch": true, // 防止switch语句贯穿(即如果没有break语句后面不会执行)
"noImplicitReturns": true, //每个分支都会有返回值
"esModuleInterop": true, // 允许export=导出,由import from 导入
"allowUmdGlobalAccess": true, // 允许在模块中全局变量的方式访问umd模块
"moduleResolution": "node", // 模块解析策略,ts默认用node的解析策略,即相对的方式导入
"baseUrl": "./", // 解析非相对模块的基地址,默认是当前目录
"paths": { // 路径映射,相对于baseUrl
// 如使用jq时不想使用默认版本,而需要手动指定版本,可进行如下配置
"jquery": ["node_modules/jquery/dist/jquery.min.js"]
},
"rootDirs": ["src","out"], // 将多个目录放在一个虚拟目录下,用于运行时,即编译后引入文件的位置可能发生变化,这也设置可以虚拟src和out在同一个目录下,不用再去改变路径也不会报错
"listEmittedFiles": true, // 打印输出文件
"listFiles": true// 打印编译的文件(包括引用的声明文件)
}
二:联合类型和类型保护(类型断言)
类型保护:就是一些表达式或者关键字在编译时候就能确定在某个作用域内变量的类型.
类型断言:通过类型断言这种方式可以告诉编译器,“相信我,我知道自己在干什么”。 类型断言好比其它语言里的类型转换,但是不进行特殊的数据检查和解构。 它没有运行时的影响,只是在编译阶段起作用。 TypeScript 会假设你,程序员,已经进行了必须的检查。
//联合类型和类型保护
interface Bird {
fly: boolean;
sing: () => {};
}
interface Dog {
fly: boolean;
bark: () => {};
}
function trainAnimal(animal: Bird | Dog) {//联合类型
animal.sing();//报错
}
类型联合报错.png
这里不能确认animal上是否有sing方法,这里我们需要进行类型保护
//方法一:类型断言
function trainAnimal(animal: Bird | Dog) {
//联合类型
if (animal.fly) {
(animal as Bird).sing();
} else {
(animal as Dog).bark();
}
}
//方法二 in语法判断的方式
function trainAnimalSecond(animal: Bird | Dog) {
if ("sing" in animal) {
animal.sing();
} else {
animal.bark();
}
}
//instanceof 语法判断
class Num {
count: number;
constructor(num: number) {
this.count = num;
}
}
//instanceof 语法判断
function addSecond(first: object | Num, second: object | Num) {
if (first instanceof Num && second instanceof Num) {
return first.count + second.count;
}
return 0;
}
三:枚举
枚举:允许开发者定义一组命名的常量。使用枚举可以使其更容易记录意图,或创建一组不同的情况。TypeScript提供了基于数字和字符串的枚举。
1 数值型枚举
我们首先从数字枚举开始,如果你来自其他语言,可能会更熟悉它。一个枚举可以用 enum 关键字来定义。
enum Direction {
Up = 1,
Down,
Left,
Right,
}
上面,我们有一个数字枚举,其中 Up 被初始化为 1 ,所有下面的成员从这一点开始自动递增。换句话说,Direction.Up的值是 1 ,Down 是 2,Left是3,Right是4。
如果我们愿意,我们可以完全不使用初始化器:
enum Direction {
Up,
Down,
Left,
Right,
}
这里,Up的值是0,Down是1,依次类推。这种自动递增的行为对于我们可能不关心成员值本身,但关心每个值与同一枚举中的其他值不同的情况很有用。
使用枚举很简单:只需将任何成员作为枚举本身的一个属性来访问,并使用枚举的名称来声明类型:
enum UserResponse {
No = 0,
Yes = 1,
}
function respond(recipient: string, message: UserResponse): void {
// ...
}
respond("Princess Caroline", UserResponse.Yes);
数字枚举可以混合在计算和常量成员中(见下文)。简而言之,没有初始化器的枚举要么需要放在第一位,要么必须放在用数字常量或其他常量枚举成员初始化的数字枚举之后。换句话说,下面的情况是不允许的:
enum E {
A = getSomeValue(),
B,
// Ⓧ Enum成员必须有初始化器。
}
2 字符串枚举
字符串枚举是一个类似的概念,但有一些细微的运行时差异,如下文所述。在一个字符串枚举中,每个成员都必须用一个字符串字头或另一个字符串枚举成员进行常量初始化。
enum Direction {
Up = "UP",
Down = "DOWN",
Left = "LEFT",
Right = "RIGHT",
}
虽然字符串枚举没有自动递增的行为,但字符串枚举有一个好处,那就是它们可以很好地 "序列化"。换句话说,如果你在调试时不得不读取一个数字枚举的运行时值,这个值往往是不透明的--它本身并不传达任何有用的意义(尽管 反向映射往往可以帮助你),字符串枚举允许你在代码运行时给出一个有意义的、可读的值,与枚举成员本身的名称无关。
四.泛型:generic泛指的类型,指的是在定义函数/接口/类型时,不预先指定具体的类型,而是在使用的时候在指定类型限制的一种特性。
//函数中的泛型:限制入参,返回值
function connect<Common>(first: Common, second: Common) {
return `${first}${second}`;
}
connect<string>("1", "2");
connect<number>(1, 2);
function join<Common>(first: Common, second: Common):T {
return first;
}
//------------------------------------------------------
//多个泛型
function map<T, P>(first: T, second: P) {
return `${first}${second}`;
}
map<string, number>("1", 2);
map(1,2); //默认类型推断--<number,number>
类中的泛型:
// class DataManager {
// constructor(
// private item1: string[] | number[],
// private item2: string[] | number[]
// ) {} //类型联合
// getItem(index: number) {
// return this.item1[index];
// }
// }
//当有大量的参数需要联合类型时,我们可以使用泛型来进行代码优化
class DataManager<T, K> {
constructor(private item1: T[], private item2: K[]) {}
getItem(index: number): string {
return `${this.item1[index]}${this.item2[index]}`;
}
}
const dataManager = new DataManager<string, number>(["1"], [2]);
interface Test {
name: string;
}
class Manager<T extends Test> {
constructor(private item: T[]) {}
getItem(index: number): string {
return this.item[index].name;
}
}
const manager = new Manager<Test>([{ name: "123" }]);
class Demo<T extends string | number>{
constructor(private item: T[]) {}
getItem(index: number): string|number {
return this.item[index];
}
}
const demo = new Demo(["123"]);
const demo1 = new Demo([123]);
泛型接口
interface IBaseUser<T> {
data: Array<T>;
add: (t: T) => T;
findUserById: (id: number) => T | undefined;
}
//用户信息类
class User {
id?: number;
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
}
class UserCRUD implements IBaseUser<User> {
data: Array<User> = [];
add(user: User) {
if (!user.id) {
user.id = Date.now() + Math.random();
}
this.data.push(user);
return user;
}
findUserById(id: number) {
console.log("id:", id);
return this.data.find((item) => item.id == id);
}
}
const userCRUD = new UserCRUD();
userCRUD.add(new User("张三", 20));
const { id } = userCRUD.add(new User("李四", 21));
userCRUD.add(new User("王五", 22));
userCRUD.add(new User("赵六", 23));
console.log(userCRUD);
if (id) {
console.log(userCRUD.findUserById(id));
}
泛型约束:
如果我们直接对一个泛型参数取 length 属性, 会报错, 因为这个泛型根本就不知道它有这个属性
// 没有泛型约束
function fn <T>(x: T): void {
// console.log(x.length) // error
}
我们可以使用泛型约束来实现
interface Lengthwise {
length: number;
}
// 指定泛型约束
function fn2 <T extends Lengthwise>(x: T): void {
console.log(x.length)
}
我们需要传入符合约束类型的值,必须包含必须 length 属性:
fn2('abc')
// fn2(123) // error number没有length属性
五:namespace:类似于模块化的概念,减少全局变量的产生,把一组相关的内容封装在一起.
class Header {
constructor() {
const ele = document.createElement("div");
ele.innerText = "This is Header";
document.body.appendChild(ele);
}
}
class Content {
constructor() {
const ele = document.createElement("div");
ele.innerText = "This is Content";
document.body.appendChild(ele);
}
}
class Footer {
constructor() {
const ele = document.createElement("div");
ele.innerText = "This is Footer";
document.body.appendChild(ele);
}
}
class Page {
constructor() {
new Header();
new Content();
new Footer();
}
}
上面的方式会产生大量的全局变量.png
我们可以通过namespace来进行优化:通过namespace来创建一个命名空间,通过export来将需要用到的变量进行导出.
namespace Home {
class Header {
constructor() {
const ele = document.createElement("div");
ele.innerText = "This is Header";
document.body.appendChild(ele);
}
}
class Content {
constructor() {
const ele = document.createElement("div");
ele.innerText = "This is Content";
document.body.appendChild(ele);
}
}
class Footer {
constructor() {
const ele = document.createElement("div");
ele.innerText = "This is Footer";
document.body.appendChild(ele);
}
}
export class Page {
constructor() {
new Header();
new Content();
new Footer();
}
}
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<script src="./dist/test.js"></script>
</head>
<body>
<script>
new Home.Page();
</script>
</body>
</html>
优化后我们只能访问到Page.png
如果我们在多个地方引用Header Content Footer的话,再度优化上面的代码.
namespace Component {
export class Header {
constructor() {
const ele = document.createElement("div");
ele.innerText = "This is Header";
document.body.appendChild(ele);
}
}
export class Content {
constructor() {
const ele = document.createElement("div");
ele.innerText = "This is Content";
document.body.appendChild(ele);
}
}
export class Footer {
constructor() {
const ele = document.createElement("div");
ele.innerText = "This is Footer";
document.body.appendChild(ele);
}
}
}
///<reference path="./components.ts"/>
namespace Home {
export class Page {
constructor() {
new Component.Header();
new Component.Content();
new Component.Footer();
}
}
}
像上面这样,我们可以通过引用components.ts对Component中的Header,Content,Footer进行复用.
进一步的优化,通过模块化来处理上面的问题, amd -- > import define
六. 类型定义文件:帮助Ts文件理解引入的Js文件/js库里面的内容.
因为Js是中是TS中要求的类型的概念.(类型描述文件: .d.ts文件 )
以引入jquery为例:
//index.html
<!--
* @Author: itw_liuchao04 itw_liuchao04@tkmail.com
* @Date: 2022-10-26 13:54:30
* @LastEditors: itw_liuchao04 itw_liuchao04@tkmail.com
* @LastEditTime: 2022-10-26 13:57:52
* @FilePath: \Ts\src\index.html
* @Description:
*
* Copyright (c) 2022 by itw_liuchao04 itw_liuchao04@tkmail.com, All Rights Reserved.
-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.1/jquery.js"></script>
<script src="./page.js"></script>
</head>
<body></body>
</html>
//page.ts
$(function () {
console.log("abc");
$("body").html("<div>123</div>");
});
//jquery.d.ts
//定义全局变量
//declare var $: (params: () => void) => void;
//定义全局方法
declare function $(params: () => void): () => void;
interface JqueryInstance {
html(param: string): () => void;
}
declare function $(seclotor: string): JqueryInstance;
//使用 interface语法,实现函数重载
interface JQuery {
(params: () => void): void;
(params: string): JqueryInstance;
}
declare var $: JQuery;
七:泛型中keyof语法的使用
interface Person {
name: string;
age: number;
gender: string;
}
class Teacher {
constructor(private info: Person) {}
getInfo(key: string) {
//类型保护
if (key === "name" || key === "age" || key === "gender") {
return this.info[key];
}
return "非法参数";
}
}
let person = {
name: "张三",
age: 18,
gender: "male",
};
const teacher = new Teacher(person);
console.log(teacher.getInfo("name"));//张三
console.log(teacher.getInfo("hobby"));//非法参数
使用keyof来替换上面的代码实现
interface Person {
name: string;
age: number;
gender: string;
}
class Teacher {
constructor(private info: Person) {}
getInfo<T extends keyof Person>(key: T) {
//类型保护
// if (key === "name" || key === "age" || key === "gender") {
// return this.info[key];
// }
return this.info[key];
}
}
let person = {
name: "张三",
age: 18,
gender: "male",
};
const teacher = new Teacher(person);
console.log(teacher.getInfo("name")); //张三
console.log(teacher.getInfo("hobby")); //Argument of type '"hobby"' is not assignable to parameter of type 'keyof Person'