跳到主要内容

TypeScript 的问题

ts 是 js 的超集。

string, String 的区别是什么?

string, js 的基本类型。(字符串类型)

String, js 中 string 的包装类。

何时使用 string? 何时使用 String?

99% 的情况下,使用 string

let nameObj: String = new String("Alice"); 时才会使用到 String。

ts 可以使用哪些类型?

js 基本类型:string, number, bool,void, null/undefined, Symbol, bigint

ts 定义的类型: void, unknown, never, Array<number>, enum

如何初始化 tsconfig.json ?

tsc --init

TS 类型

  1. top type(顶级类型): any unknown
  2. Object
  3. Number String Boolean
  4. number string boolean
  5. 1 'wwww' false
  6. never

unknown 和 any

任何类型都可以赋值给 any, unknown 类型。

unknown 只能赋值给自身, 或者 any 类型。

let a: any = "1";
let b: unknown = 2;
let c: number = 3;

a = b;
a = c;

b = a;
b = c;

c = a;
// c = b; // unknown 类型只能赋值给 unknown 或 any 类型
console.log(c);

unknown 没有办法读任何属性, 方法也不可以。 (编译器会提示)

let w: unknown = {
name: "wangzhy",
};

// console.log(w.name); //编辑器会报错。 'w' is of type 'unknown'.

unknown 比 any 会更安全。

object, Object, 三个类型的区别

Object 表示包含了所有类型 (string, number, boolean, [], , 函数)

object 表示非原始类型 (非 string, number, boolean, bigint, Symbol, null, undefined 的类型)

{}(字面量) 表示 new Object, 等同于 Object

接口(interface)和对象类型

重名, 重合

interface 定义的结构体,在创建的时候,字段必须一致(不多不少)

interface A {
name: string;
}

let a: A = {
name: "wang",
};

遇到重名时,会自动合并。

interface A {
name: string;
}

interface A {
age: number;
}

let a: A = {
name: "wang",
age: 123,
};

任意 key

interface A {
age: number;
// 注意, 这里 propName 要保存所有字段的类型。 比如,此代码案例中,必须包含 number 类型
[propName: string]: any;
}
interface A {
age: number;
[propName: string]: number | string;
}

let a: A = {
name: "wang",
age: 123,
aaa: "123",
};

可选字段

使用 ?

interface A {
age?: number;
[propName: string]: number | string;
}

let a: A = {
name: "wang",
aaa: "123",
};

readonly

interface A {
readonly age?: number;
[propName: string]: number | string;
}

let a: A = {
name: "wang",
age: 123,
aaa: "123",
};
// a.age = 11; // Cannot assign to 'age' because it is a read-only property.

接口继承

使用 extends 关键字

函数类型

interface Fn {
// 定义一个参数为 string 类型,返回类型为 number[] 的函数
(name: string): number[];
}

const fn: Fn = function (name: string) {
return [1];
};

数组类型

定义一个数组

  1. const arr: number[] = [1, 2, 3, 4];
  2. const arr: Array<number> = [1, 2, 3, 4]; // 泛型

多维数组

const arr: number[][] = [
[1, 2, 3],
[2, 3, 4],
];
const arr: Array<Array<number>> = [
[1, 2, 3],
[2, 3, 4],
];

大杂烩数组

let arr: (string | number | boolean)[] = ["q", 1, "1", true];

...args

...args 是剩余参数语法,将函数多余参数收集到一个数组中。

function fn(...args: number[]) {
console.log(args);
}
fn(1, 2, 3);

IArguments

function arr6(...args: number[]) {
console.log(args);
let a: A = arguments;
for (const key in a) {
console.log(key, a[key]);
}
}

interface A {
callee: Function;
length: number;
[index: number]: any;
}

函数

函数参数默认值

function add(a: number = 10, b: number = 20): number {
return a + b;
}

可选参数

function add(a: number = 10, b?: number): number {
return b ? a + b : a;
}

需要注意的是:可选参数与参数默认值不能同时使用对一个函数参数使用。

ts 中可以定义 this 的类型,在 js 中无法使用。必须第一个参数定义 this 的类型

interface User {
name: string;
age: number;
add: (num: number) => void;
}

let u: User = {
name: "w",
age: 18,
// 第一个参数定义 this 的类型, 传参的时候忽略第一个参数
add(this: User, num: number) {
this.age++;
},
};

u.add(1);

函数重载

let users: number[] = [1, 2, 3];

function findNum(): number[];
function findNum(id: number): number[];
function findNum(add: number[]): number[];
function findNum(ids?: number | number[]): number[] {
if (typeof ids === "number") {
return users.filter((v) => v === ids);
} else if (Array.isArray(ids)) {
users.push(...ids);
return users;
} else {
return users;
}
}

console.log(findNum([5, 1, 7]));

类型断言|联合类型|交叉类型

联合类型

let phone: number | string = "123111111111";

交叉类型

interface People {
name: string;
}

interface Man {
age: number;
}

const w = (man: People & Man): void => {
console.log(man);
};

w({
name: "w",
age: 18,
});

class

借助下面的代码来理解 class

interface Options {
el: string | HTMLElement;
}

// vue 接口
interface VueCls {
options: Options;
init(): void;
}

interface Vnode {
tag: string; // html 标签
text?: string;
children?: Vnode[]; // 子节点
}

// 虚拟 dom
class Dom {
// 创建节点
private createElement(el: string) {
return document.createElement(el);
}
// 填充文本
private setText(el: HTMLElement, text: string | null | undefined) {
el.textContent = text ?? null;
}
// 渲染函数
protected render(data: Vnode) {
let root: HTMLElement = this.createElement(data.tag);
if (data.children && Array.isArray(data.children)) {
data.children.forEach((item) => {
let child = this.render(item);
root.appendChild(child);
});
} else {
this.setText(root, data.text);
}
return root;
}
}

class Vue extends Dom implements VueCls {
// 定义属性
options: Options;

// 构造函数
constructor(options: Options) {
super(); // 调用父类的构造函数 // 父类.prototype.constructor.call
this.options = options; // 初始化属性
this.init();
}
static version(): string {
return "0.0.1";
}
init(): void {
// 虚拟 DOM
let data: Vnode = {
tag: "div",
children: [
{
tag: "section",
text: "我是子节点 1",
children: [
{ tag: "option", text: "o1" },
{ tag: "option", text: "o2" },
{ tag: "option", text: "o3" },
{ tag: "option", text: "o4" },
],
},
{
tag: "section",
text: "我是子节点 2",
},
],
};

let app =
typeof this.options.el === "string"
? document.querySelector(this.options.el)
: this.options.el;
// 渲染
app!.appendChild(this.render(data));
}
}

new Vue({
el: "#app",
});

console.log(Vue.version());

class Ref {
private _value: any;
constructor(value: any) {
this._value = value;
}

get value() {
return this._value + " ^-^";
}

set value(newVal) {
this._value = newVal + "wzy";
}
}

const ref = new Ref("哈哈哈");
console.log(ref.value);
ref.value = "呼呼呼";
console.log(ref.value);

抽象类 abstract

通过 abstract 关键字定义。

抽象方法只是用来描述方法原型,具体实现由字类实现。

抽象类不难被实例化。

import { log } from "node:console";
import { loadavg } from "node:os";

abstract class AVue {
name: string | undefined;
constructor(name?: string) {
this.name = name;
}
abstract init(name: string): void;
}

class React extends AVue {
constructor() {
super();
}
init(name: string): void {}
setName(name: string) {
this.name = name;
}
}

const react = new React();
react.setName("wangzhy");

元组类型

let arr: readonly [x: number, y?: boolean] = [1];
type first = (typeof arr)[0];

枚举类型

enum Color {
red = 1,
green = "green",
blue = 3,
}

const enum Types {
success,
fail,
}

类型推断

let arr = [1, 2, 3]; // number[]

let str1; // any

type S = string & B;

interface A extends B {
name: string;
}

interface B {
age: number;
}

extends 在与 type 使用时表示包含的意思

type num = 1 extends number ? 1 : 0; // 1

never

不存在的状态, 无法达到的状态

type A = number & string; // never

function fn(): never {
throw new Error("err");
}

type B = void | number | never; // void | number

type C = "c" | "x" | "k" | "xxx";

function kun(value: C) {
switch (value) {
case "c":
break;
case "x":
break;
case "k":
break;
default:
// 兜底逻辑, 如果 C 新增了类型,这里会出现报错提示。
// Type '"xxx"' is not assignable to type 'never'.
const error: never = value;
break;
}
}

symbol 类型

let a1: symbol = Symbol(1); // 支持三种类型, string, number, undefined
let a2: symbol = Symbol(1);
console.log(a1 === a2); // false

// 如何让 2 个 symbol 返回 true
console.log(Symbol.for("w") === Symbol.for("w")); // true

// 解决 key 重复的问题
let obj = {
name: "obj",
[a1]: 1,
// ...
[a2]: 20,
};

// for in 不能读到 symbol 类型的 key
for (let key in obj) {
console.log(key);
}

console.log(Object.keys(obj)); // 无法获取到 symbol 类型的 key
console.log(Object.getOwnPropertyNames(obj)); // 无法获取到 symbol 类型的 key
console.log(Object.getOwnPropertySymbols(obj)); // 只能获取 symbol 类型的 key

console.log(Reflect.ownKeys(obj)); // 能够获取到所有的 key

生成器, 迭代器

1. 生成器

function* gen() {
yield Promise.resolve("w1");
yield Promise.resolve("w2");
yield Promise.resolve("w3");
yield Promise.resolve("w4");
yield Promise.resolve("w5");
yield Promise.resolve("w6");
}

const w = gen();

var next = w.next();
while (!next.done) {
console.log(next);
next = w.next();
}
console.log(next);

2. 迭代器

核心是 Symbol.iterator

for...of 的底层实现。对象不可用,对象是没有 Symbol.iterator 方法的。

const each = (value: any) => {
let It: any = value[Symbol.iterator]();
let next: any = {
done: false,
};
while (!next.done) {
next = It.next();
if (!next.done) {
console.log(next.value);
}
}
};

使对象支持迭代器, [...obj]

let obj = {
name: "w",
age: 18,
max: 3,
current: 0,
[Symbol.iterator]() {
return {
max: this.max,
current: this.current,
next() {
if (this.current === this.max) {
return {
value: undefined,
done: true,
};
} else {
return {
value: this.current++,
done: false,
};
}
},
};
},
};

let x = [...obj];
console.log(x);

{...obj} 的底层实现(浅拷贝)

function fn(v: any): any {
let result = {};
let keys = Reflect.ownKeys(v);
for (const key of keys) {
result[key] = v[key];
}
return result;
}

泛型

方法名后面跟 <T>, T 就是泛型。

function fn<T>(a: T, b: T): T[] {
return [a, b];
}

// <number> 可以不用写, 它自己会进行类型推导
console.log(fn<number>(1, 2));
console.log(fn(1, 2));

type/interface 与泛型一起使用

type A<T> = string | number | T;
let a: A<boolean> = "z";
let b: A<boolean> = 1;
let c: A<boolean> = true;
interface Data<T> {
msg: T;
}
let data1: Data<string> = {
msg: "msg",
};
let data2: Data<number> = {
msg: 1,
};
let data3: Data<boolean> = {
msg: true,
};

泛型的其他用法

多个泛型

function fn1<T, K>(a: T, b: K): Array<T | K> {
return [a, b];
}
fn1(1, false);
fn1(1, "msg");

简单封装一个 axios

const axios = {
get<T>(url: string): Promise<T> {
return new Promise((resolve, reject) => {
let xhr: XMLHttpRequest = new XMLHttpRequest();
xhr.open("GET", url);
xhr.onreadystatechange = () => {
if (xhr.readyState == 4 && xhr.status === 200) {
resolve(JSON.parse(xhr.responseText));
}
};
xhr.send(null);
});
},
};

interface Data {
compilerOptions: {
[key: string]: string;
};
}

axios.get<Data>("./tsconfig.json").then((res) => {
console.log(res.compilerOptions);
});

泛型约束

<T extends number>

<T extends Data>

function fn<T extends object, K extends keyof T>(obj: T, key: K) {
return obj[key];
}
type Options<T extends object> = {
[key in keyof T]?: T[key];
};
type B = Options<Data>;

模块扩展语法 (Module Augmentation)

tsconfig.json

生成 tsconfig.json 配置文件

tsc --init

namespace

  • namespaceinterface 一样, 同名会进行合并。
  • namespace 定义的方法和变量需要导出 export
namespace Test {
// 变量, 方法
export let a = 1;
// 嵌套 namespace
export namespace Test2 {
export let a = 121;
}
}

namespace Test {
export let b = 11;
}

模块解析

Commonjs --> Nodejs

require("xxx");
exports.xxx = function () {};
module.exports = xxx;

AMD --> require.js

define("module", ["dep1", "dep2"], function (dep1, dep2) {
// ...
});

require(["module", "../app"], function (module, app) {
// ...
});

CMD --> seajs

define(function (require, exports, module) {
var a = require("./a");
a.doSomething();

var b = require("./b");
b.doSomething();
});

UMD --> AMD 和 CommonJS 的糅合

(function (window, factory) {
// nodejs 环境
if (typeof module === "object" && typeof module.exports === "objects") {
module.exports = factory();
}
// 是不是 amd 规范
else if (typeof define === "function" && define.amd) {
define(factory);
}
// 使用浏览器环境
else {
window.eventUtil = factory();
}
})(this, function () {
// ...
});

ES6 规范, import/export

// import test, { name, hello, cur } from "./test";

// console.log(test);
// console.log(name);

// hello();
// console.log(cur.toString());

// import * as api from "./test";
// console.log(api);

if (true) {
// 动态引入
import("./test").then((res) => {
console.log(res);
});
}
export default {
age: 18,
};

export const name = "wangzhy";

export const hello = () => {
console.log("hello");
};

const cur = Date.now();

export { cur };

声明文件 d.ts (declare.ts)

d.ts 文件的作用就是告诉别人你写的代码应该怎么用(参数类型,可以让编译器给出提示)。

declare 关键字。

使用第三方库(js 写的)时, 需要引入对应的 types 库, npm i --save-dev @types/express

也可以自己手写 d.ts 文件。 如 express.d.ts

自己实现一个 express.d.ts

import express from "express";

const app = express();

const router = express.Router();
app.use("/api", router);

router.get("/api", (req, res) => {
res.json({
code: 200,
msg: "success",
});
});

app.listen(9001, () => {
console.log(":9001");
});

express.d.ts

declare module "express" {
interface Router {
get(path: string, cb: (req: any, res: any) => void): void;
}

interface App {
use(path: string, router: any): void;
listen(port: number, cb?: () => void): void;
}

interface Express {
(): App;
Router(): Router;
}
const express: Express;

export default express;
}

mixin

对象合并

  1. 浅拷贝
  • { ...a, ...b }
  • Object.assign({}, a, b)
  1. 深拷贝
  • structuredClone(a)

类的混入

class Logger {
log(msg: string) {
console.log(msg);
}
}

class Html {
render() {
console.log("render");
}
}

class App {
run() {
console.log("run");
}
}

// 定义一个类型别名, 表示一个构造函数
type Custructor<T> = new (...args: any[]) => T;

function pluginMixins<T extends Custructor<App>>(Base: T) {
// 返回一个新的 class
return class extends Base {
private Logger: Logger;
private Html: Html;
constructor(...args: any) {
super(...args);
this.Logger = new Logger();
this.Html = new Html();
}

run() {
this.Logger.log("run");
}
render() {
this.Html.render();
}
};
}

const mixins = pluginMixins(App);

const app = new mixins();

app.run();

infer

用于在条件类型中动态推断类型。