Skip to main content

TypeScript

js 与 ts 的关系

1.js本身没有对变量、函数参数进行类型限制;

2.这样虽然灵活但是也有安全隐患;

3.之后facebook 推出了 flow微软推出了 ts,他们都是致力于为 js 提供类型检查,而不是取代;

4.并且在ts 的官方文档上有这么一句话:源于 js,归于 js!;

5.最终ts还是需要转换成 js 代码才能运行的;

6.当然不排除有一天 js 本身也会加入类型检测,那么无论是 ts 还是 flow 都有可能因此退出历史舞台;

型检测

共识

错误出现的越早越好

能在写代码时发现错误,就不要再代码编译时再发现(IDE的优势就是在代码编写过程中帮助我们发现错误);

能在代码编译时发现错误,就不要再代码运行时再发现(类型检测就可以很好的帮助我们做到这一点);

能在开发阶段发现错误,就不要在测试期间发现错误;

能在测试期间发现错误,就不要在上线后发现错误;

来看这么一段代码

function foo(message) {
console.log(message.length);
}
foo("hello");
// 后面有其它代码

运行,结果是 5

这么看来这段代码基本是没有什么问题的;

但是存在一个非常大的隐患:当执行**foo()**不传任何参数时,就报错了;

由于没传参数,导致 foo()中的message 是 undefined,undefined 哪来的 length?

这个报错导致后面的代码无法执行,这就导致一行报错,后面所有代码都无法运行,这是你希望看到的吗?;

上面函数并没有对参数进行校验

  • 参数类型
  • 是否传参

这时,TypeScript 应运而生。

TypeScript

ts 是拥有类型js 超集,它可以编译成普通、干净、完整的 js 代码。

js 所有的特性,ts 都支持,并且它紧随 ECMAScript 的标准,所以 es6、es7、es8 等新语法标准,ts 都是支持的;

在语言层面上,不仅仅增加了类型约束,而且包括一些语法的拓展,比如枚举类型(Enum)、元组类型(Tuple)等;

所以,可以将 ts 理解为加强版的 js

从开发者长远的角度看来,学习 ts 有助于培养类型思维,这种思维对于完成大型项目尤为重要。

TypeScript 官网:https://www.typescriptlang.org/

编译环境

我们知道 ts 是需要转换成 js 的,那谁来负责转换呢?

  • tsc,TypeScript Compiler
  • babel

全局安装

npm install typescript -g

tsc --version 查看版本

体验

来编写一段 ts 代码

function foo(message: string) {
console.log(message.length);
}
foo("hello");

当你执行 foo()不传参数或者传入非 string 类型的参数,编辑器就会提醒你了,不需要等到运行期间才报错;

然后执行 tsc ts文件 命令,转化为 js 文件,这时会出现同名的 js 文件;

转化后的代码

function foo(message) {
console.log(message.length);
}
foo("hello");

搭建 ts 编译环境

当然,真实开发不是写一个 ts 文件转换一个

我们需要:

  • 编写完 ts,它自动转换成 js;
  • 然后自动在浏览器上运行
  • 也可以自动更新内容;

搭建方式有几种:

  • 通过webpack搭建;
  • 安装 node 的一个库ts-node;

使用 ts-node 库搭建

ts-node 做了什么事情?

将 ts 转换成 js,然后在 node 环境上运行

先全局安装 npm install ts-node -g;

而 ts-node 又依赖于两个库,tslib、@types/node,全局安装它们 npm install tslib @types/node -g

测试

编写这么一段 ts 代码

const name: string = "abc";
const age: number = 18;

console.log(name);
console.log(age);

export {};

为什么要写 export {}

因为 ts 文件默认使用全局作用域(所有的 ts 文件),使用 export 语法变成模块,就有独立的作用域了,防止命名冲突

执行 ts-node ts文件 命令,你会看到编辑器的终端打印 abc 和 18

使用 webpack 搭建

本地安装ts-loadertypescript,因为 ts-loader 本身又依赖于 typescript

npm install ts-loader typescript -d

配置 webpack.config.js

module.exports = {
module: {
rules: [
{
test: /\.ts$/,
loader: ts - loader,
},
],
},
};

同时还需要tscconfig.js这个属于 ts 的配置文件,可以使用 tsc --init 生成;

最后还需要搭建一个本地服务,利用 webpack-dev-server;

本地安装

npm install webpack-dev-server -d

变量声明

声明的类型可以称之为类型注解

let/const 标识符: 类型 = 值

例子

let msg: string = "hhh";

小写和大写区别

类型的小写:TypeScript 中的类型;

类型的大写:JavaScript 中的类型对应的包装类

类型推断

默认情况下进行赋值时,会将赋值的类型,作为标识符的类型

例子

let msg = "hhh";

msg 默认的类型就是字符串类型

数据类型

  • number 类型
  • boolean 类型
  • 字符串类型
  • Array 类型
  • object 类型
  • null 和 undefined
  • Symbol 类型
  • any 类型
  • unknown 类型
  • void 类型
  • never 类型
  • tuple 类型

Array 类型

规定一个数组类型并且元素是 string 类型的例子

写法一,不推荐,可能会和 jsx 冲突

const names: Array<string> = [];

写法二,推荐

const names: string[] = []

any 类型

类型不限制

tuple 类型

区别于数组类型,数组的元素类型一般一致,只有**“共性”**;

而元组可以保留元素的**“个性”**;

元组通常可以作为返回的值,在使用时会非常方便;

const zsf: [string, number, number] = ["hhh", 18, 100];

联合类型

扩大类型的范围

ts 的类型系统允许使用多种运算符,从现有的类型中构建新类型;

联合类型中每个类型被称之为联合成员(union’s members);

function printID(id: number | string) {
console.log(id);
}

联合类型可选类型有点类似

function printID(id?: number) {
console.log(id);
}

可以转换成

function printID(id: number | undefined) {
console.log(id);
}

结合字面量类型

其实字符串也是当成类型,也就是字面量类型;

而字面量类型的只能和类型一致;

const msg: "hhh" = "hhh";

这时字面量类型就可以和联合类型结合使用了,不和结合类型结合使用,字面量类型没什么意义

let align: "left" | "right" | "center";
align = "right";

类型别名

当联合类型有很多联合成员时,类型会非常长,可读性可能不好;

此时可以给类型起别名,使用关键字type定义;

type IdType = number | string;
type PointType = {
x: number;
y: number;
z?: number;
};

function printID(id: IdType) {
console.log(id);
}

function printPoint(point: PointType) {
console.log(point.x, point.y, point.z);
}

类型断言 as

有时候 ts 无法获取具体的类型信息,这时就需要使用类型断言(Type Assertions);

比如 document.getElementById(),ts 只知道该函数会返回HTMLElement,但并不知道它具体的类型

通过类型断言可以把一个范围较大的类型转化为更为具体的类型;

比如这一段代码

const el = document.getElementById("hhh");
el.src = "...";

当直接使用src属性时,编辑器会提示错误,因为 HTMLElement 类型有很多,有的并没有 src 属性;

而将类型断言为HTMLImageElement,一定有 src 属性;

const el = document.getElementById("hhh") as HTMLImageElement;
el.src = "...";

非空类型断言

看这一段代码

function foo(msg?: string) {
console.log(msg.length);
}

这段编译阶段是不通过的,参数是可选类型,当没传参数时 msg 就是 undefined,undefined 哪来的 length?

一般可以加个if 判断;

function foo(msg?: string) {
if (msg) {
console.log(msg.length);
}
}

也可以使用非空类型判断

function foo(msg?: string) {
console.log(msg!.length);
}

感叹号!就可以确保 msg 一定有值;

可选链

可选链并不是 ts 独有的特性,在 es11 中 js 也增加了这一特性;

操作符是?;

它的作用是当对象的属性不存在时,会短路,直接返回undefined,如果存在,才会继续执行;

type Person = {
name: string;
friend?: {
name: string;
age?: number;
};
};
const info: Person = {
name: "zsf",
};

console.log(info.friend?.name);

??和!!

!!操作符将其它类型转化为boolean 类型

const msg: string = "hh";
const flag = !!msg;
console.log(flag);

空值合并运算符??,es11 新增,类似于三元运算符

let msg: stirng | null = null;
const content = msg ?? "hhh";
console.log(content);

等价于

let msg: stirng | null = null;
const content = msg ? msg : "hhh";
console.log(content);

类型缩小

可以通过类似 typeof padding === 'number'的判断语句来改变 ts 的执行路径

在给定的执行路径中,可以缩小比声明时更小的类型,这个过程叫类型缩小,也叫类型保护

常见的类型保护有:

  • typeof
  • 平等缩小(===、!==)
  • instanceof
  • in
  • 等等
type IDType = number | string;
function printID(id: IDType) {
if (typeof id === "string") {
console.log(id.toUpperCase());
} else {
console.log(id);
}
}

本来是number 和 string 的联合类型typeof id === 'string'这个判断将类型缩小为 string 类型

这样才能使用toUpperCase(),number 类型是没有这个方法的;

函数相关的类型

参数

function sum(num1: number, num2: number) {
return num1 + num2;
}

匿名函数的参数类型

const names = ["hhh", "abc", "nb"];
names.forEach((item) => {
console.log(item.split(""));
});

item 的类型可以根据上下文推断出来,这时可以不加类型注解;

复杂的参数一般用对象类型

比如打印一个点坐标的函数,参数是坐标,较为复杂,可以使用对象类型限制

function printPoint(point: { x: number; y: number }) {
console.log(point.x, point.y);
}

返回值

可以不写返回值类型,会自动推断;

function sum(num1: number, num2: number): number {
return num1 + num2;
}

如果没有返回值

function sum(num1: number, num2: number): void {
return num1 + num2;
}

可选类型

有些点没有 z 坐标,所以 z 参数可选;

可选类型放必选类型后面

function printPoint(point: { x: number; y: number; z?: number }) {
console.log(point.x, point.y, point.z);
}

函数的类型

在 js 中,函数是重要的组成部分,并且函数可以作为一等公民(可以作为参数,也可以作为返回值);

既然 ts 中传递参数要类型注解,那把函数当参数传递时应该怎么写类型注解呢?

function foo() {}
type FooType = () => void;
function bar(fn: FooType) {
fn();
}
bar(foo);

声明函数时,另一种编写类型的方式

type AddFnType = (num1: number, num2: number) => number;
const add: AddFnType = (a1: number, a2: number) => {
return a1 + a2;
};

可推导的 this 类型

this在不同情况下会绑定不同的值,所以对于它的类型就更难把握了;

默认推导
const info = {
name: "zsf",
eating() {
console.log(this.name + "eating");
},
};
info.eating();

ts 默认推导 this 就是 info 对象;

如果 ts 能推导出 this,那就可以放心使用 this;

但是下面这种情况 ts 推导不出 this

function eating() {
console.log(this.name + "eating");
}
const info = {
name: "zsf",
eating: eating,
};
info.eating();

这段代码如果放在 js 中,this 绑定的是 info 对象;

但在 ts 中,它推导不出 this,要是想能让 ts 推导出,第一个参数得传 this;

type ThisType = {
name: string;
};
function eating(this: ThisType) {
console.log(this.name + "eating");
}
const info = {
name: "zsf",
eating: eating,
};
info.eating();

函数的重载

先来实现一个简单的函数:对两个数字或者字符串执行+的操作

以前的做法: 使用联合类型,以及一些逻辑判断(类型缩小)

type AddType = number | string;
function add(a1: AddType, a2: AddType) {
if (typeof a1 === "number" && typeof a2 === "number") {
return a1 + a2;
} else if (typeof a1 === "string" && typeof a2 === "string") {
return a1 + a2;
}
}
add(10, 20);

使用联合类型实现有两个缺点:

  • 进行很多的逻辑判断(类型缩小);
  • 返回值的类型依然是不能确定的;

这时使用函数重载就可以啦

ts 中的函数重载是函数名一样,参数不一样(类型、数量),不用具体实现

而函数的具体实现,参数用的是广泛的any 类型

function add(num1: number, num2: number): number;
function add(num1: string, num2: string): string;
function add(num1: any, num2: any) {
return num1 + num2;
}
add(10, 20);

有时候,联合类型函数重载都可以实现某个逻辑,优先使用联合类型,如果联合类型实现起来复杂,那才考虑重载

ts 中类的使用

基本使用

class Person {
name: string;
age: number;

constructor(name: string, age: number) {
this.name = name;
this.age = age;
}

eating() {
console.log(this.name + "eat");
}
}

const p = new Person("zsf", 18);

继承,多态,重写等等语法类似~

成员修饰符

在 TypeScript 中,类的成员支持三种修饰符:public、private、protected;

public修饰任何地方可见公有的成员,默认;

protected修饰的是仅在类自身及子类中可见受保护的成员;

private修饰的是仅在同一类中可见私有的成员;

只读属性

readonly,是属性的修饰符

只读属性只能在构造器中赋值,且赋值后不可改变;

属性本身不能修改,但属性要是对象类型,那对象的内容可以修改(和const声明的对象类似);

class Person {
readonly name: string;

constructor(name: string) {
this.name = name;
}
}

const p = new Person("zsf");
console.log(p.name);

访问器

和 setter/getter 写法有点区别;

有个规范,私有属性一般在前面加个下划线_;

class Person {
private _name: string;

constructor(name: string) {
this._name = name;
}

set name(newName) {
this._name = newName;
}

get name() {
return this._name;
}
}

const p = new Person("zsf");
console.log(p.name);
p.name = "hhh";

静态成员

static 修饰

不用实例化,可以通过类直接访问

抽象类

定义通用接口时,通常会让调用者传入父类,通过多态来实现更加灵活的调用方式;

但是,父类本身可能并不需要对某些方法进行具体实现,这时就可以定义为抽象方法

function makeArea(shape: Shape) {
return shape.getArea();
}

abstract class Shape {
abstract getArea();
}

class Rectangle extends Shape {
private width: number;
private height: number;

constructor(width: number, height: number) {
super();
this.width = width;
this.height = height;
}

getArea() {
return this.width * this.height;
}
}

class Circle extends Shape {
private r: number;

constructor(r: number) {
super();
this.r = r;
}

getArea() {
return this.r * this.r * 3.14;
}
}

const rectangle = new Rectangle(20, 30);
const circle = new Circle(10);

console.log(makeArea(rectangle));
console.log(makeArea(circle));

防止 makeArea()传入其它类型导致无法计算面积,应该限制只能传Shape类型;

所以CircleRectangle应该继承 Shape

抽象类不能被实例化,防止makeArea(new Shape()) ,应该将 Shape 类变成抽象类;

抽象类的抽象方法没有具体实现(没有方法体),具体实现交给子类

抽象类的子类,必须实现其父类的抽象方法;

所以你看,这样的代码不是严谨、安全许多了吗?

类的类型

和使用 type 给类型起别名有点类似~

class Person {
name: string;
}

const p: Person = {
name: "hhh",
};

ts 中接口的使用

我们知道,通过 type 可以声明对象类型

type InfoType = {
name: string;
};

const info: InfoType = {
name: "hhh",
};

其实,还可以通过interface声明对象类型,用法类似 class;

有个接口规范,就是在接口前面加个大写字母I

class IInfoType {
name: string;
}

const info: IInfoType = {
name: "hhh",
};

索引类型

通过 interface 可以定义索引类型,使对象的 key-value 保持类型的统一;

interface IndexLang {
[index: number]: string;
}

const frontLang: IndexLang = {
0: "html",
1: "css",
2: "js",
3: "vue",
};

函数类型

以前可以通过 type 定义一个函数类型

type CalcFn = (n1: number, n2: number) => number;
function calc(num1: number, num2: number, calcFn: CalcFn) {
return calcFn(num1, num2);
}

const add: CalcFn = (num1, num2) => {
return num1 + num2;
};

calc(10, 20, add);

interface 也可以定义一个函数类型

interface CalcFn {
(n1: number, n2: number): number;
}
function calc(num1: number, num2: number, calcFn: CalcFn) {
return calcFn(num1, num2);
}

const add: CalcFn = (num1, num2) => {
return num1 + num2;
};

calc(10, 20, add);

接口继承

接口支持多继承,这是结合多个接口的一个方式;

interface ISwim {
swimming: () => void;
}
interface IFly {
flying: () => void;
}

interface IAction extends ISwim, IFly {}

const action: IAction = {
swimming() {},
flying() {},
};

交叉类型

交叉类型和联合类型有所联系;

联合类型只要符合一个即可;

交叉类型需要全符合

这是结合多个接口的一个方式;

interface ISwim {
swimming: () => void;
}
interface IFly {
flying: () => void;
}
type MyType1 = ISwim | IFly;
type MyType2 = ISwim & IFly;

const action1: MyType1 = {
swimming() {},
};
const action2: MyType2 = {
swimming() {},
flying() {},
};

接口的实现

可以实现多个接口;

interface ISwim {
swimming: () => void;
}

interface IFly {
flying: () => void;
}

class Person implements ISwim {
swimming() {
console.log("swim");
}

eating() {
console.log("eat");
}
}

interface 和 type 的区别

interface 和 type 都可以定义对象类型,那开发中选哪一个呢?

如果定义的是非对象类型,推荐使用type

如果定义对象类型,可以interface重复的定义某个接口来定义属性和方法,而type定义的是别名,不能重复

interface IFoo {
name: string;
}

interface IFoo {
age: number;
}

const p: IFoo = {
name: "zsf",
age: 18,
};

ts 中的枚举类型

枚举类型其实是将一组可能出现的值,列举出来,定义在一个类型中;

枚举运行定义一组命名常量,常量可以是数字字符串

enum Direction {
LEFT,
RIGHT,
TOP,
BOTTOM,
}

function turn(direction: Direction) {}

turn(Direction.LEFT);

ts 中泛型的使用

类型参数化

定义函数时,不决定参数类型;

调用函数时,让调用者告知参数类型;

换句话说,参数的类型也是参数

function sum<Type>(num1: Type, num2: Type) {}
sum<number>(20, 30);

调用时要是不传参数类型,它也会进行类型推导,字面量类型;

function sum<Type>(num1: Type, num2: Type) {}
sum(20, 30);

当然也可以接收多种类型;

function sum<T, E>(num1: T, num2: E) {

}
sum<number, string>(20, 30)

平时开发中常用的名称:

  • T:Type 的缩写,类型;
  • K、V:key 和 value 的缩写,键值对;
  • E:Element 的缩写,元素;
  • O:Object 的缩写,对象;

泛型接口

函数调用才有类型推导,但是接口没有类型推导;

interface IPerson<T1, T2> {
name: T1;
age: T2;
}

const p: IPerson<string, number> = {
name: "zsf",
age: 18,
};

要想接口也可以类型推导,需要给泛型默认值

interface IPerson<T1 = string, T2 = number> {
name: T1;
age: T2;
}

const p: IPerson = {
name: "zsf",
age: 18,
};

泛型类

class Point<T> {
x: T;
y: T;
z: T;

constructor(x: T, y: T, z: T) {
this.x = x;
this.y = y;
this.z = z;
}
}

const p = new Point("1", "2", "3");

new Point()也是函数调用,支持类型推导~

泛型的类型约束

interface ILength {
length: number;
}

function getLength<T extends ILength>(arg: T) {
return arg.length;
}

getLength("abc");

只是含有 length 属性的参数才能传进getLength(),

ts 的模块化开发

ts 的模块化开发有两种:

  • ES Module 和 CommonJS;
  • 命名空间;

ES Module 不再多说,就是 js 加上类型检测;

命名空间

namespace

早期时,TypeScript 称命名空间为内部模块,解决命名冲突问题;

namespace time {
export function format(time: string) {
return "2022-02-22";
}
}

namespace price {
export function format(price: number) {
return "99.99";
}
}

一个模块中有两个同名函数,使用命名空间可以让他们有独立的作用域,防止命名冲突;

类型查找

ts 对类型是有管理查找规则的;

ts 文件除了以.ts结尾,还有以.d.ts结尾的,它是用来做类型声明(declare)的;

它仅仅用来做类型检测,告知 ts 有哪些类型,不需要具体实现或赋值

那 ts 会在哪里查找类型声明呢?

  • 内置类型声明;
  • 外部定义的类型声明(第三方发库有的会声明);
  • 自己定义的类型声明;

内置类型声明

看这么一段代码

const el = document.getElementById("hhh") as HTMLImageElement;
el.src = "...";

在 tsc 环境下,哪来的 document、HTMLImageElement?为什么不会报错?

原因是我们之前搭建 ts 环境时,也安装了一个tslib 库,而 tslib 库有个lib.dom.d.ts 文件,其中内置 document、HTMLImageElement 等等类型;

内置类型除了 DOM API,还包括 Math、Date 等内置类型;

外部定义类型声明

第三方库一般有两种类型声明方式:

  • 1.在自己库中进行类型声明(编写.d.ts 文件,比如 axios);
  • 2.通过社区的一个公有库 DefinitelyTyped 存放类型声明文件;

DefinitelyTyped 库的 github 地址:https://github.com/DefinitelyTyped/DefinitelyTyped/ ;

该库查找声明安装方式的地址:https://www.typescriptlang.org/dt/search?search= ;

比如安装 react 的类型声明:npm i @types/react --save-dev

自己定义类型声明

项目下新建一个xxx.d.ts 文件

declare module "lodash" {
export function join(arr: any[]): void;
}

别的文件使用

import lodash from "lodash";
console.log(lodash.join("abc", "cba"));

文件模块是这样声明的

declare module "*.jpg";

这样,所有以.jpg结尾的模块都可以在 ts 中使用啦~

其它文件也是类似这个用法

同时 declare 也可以声明命名空间变量等等;

关键字

Omit

省略类型中某个属性

场景:想复用现有的某个类型,但是其中一个属性不想要

type OriginalType = {
property1: string;
property2: number;
unwantedProperty: boolean;
};

// 创建一个省略 unwantedProperty 的新类型
type NewType = Omit<OriginalType, "unwantedProperty">;

// 现在 NewType 就是省略 unwantedProperty 的类型

函数

参数数量不一致

// 定义
export const getShouldFocusPropertyPath = createSelector(
[
getFocusablePropertyPaneField,
(_state: AppState, key: string | undefined) => key,
],
(focusableField: string | undefined, key: string | undefined): boolean => {
return !!(key && focusableField === key);
}
);
// 调用
getShouldFocusPropertyPath(
state,
dataTreePath,
hasDispatchedPropertyFocus.current,
),

提示:应有 2 个参数,但获得 3 个。

泛型

class SingleSelectTreeWidget extends BaseWidget<
SingleSelectTreeWidgetProps,
WidgetState>

这段代码是 TypeScript 中的类定义,它定义了一个名为 SingleSelectTreeWidget 的类,该类继承自 BaseWidget 并接受两个泛型参数:SingleSelectTreeWidgetPropsWidgetState;

  • BaseWidget:这是 SingleSelectTreeWidget 类的父类或基类,通过 extends 关键字将其作为父类进行继承。继承允许子类继承父类的属性和方法,并且可以在子类中添加自己的属性和方法,或者覆盖父类的属性和方法。
  • SingleSelectTreeWidgetProps:这是一个泛型参数,用于指定 SingleSelectTreeWidget 类接受的属性的类型。泛型参数允许在类或函数定义中使用不具体指定类型的占位符,以便在实际使用时指定具体类型。
  • WidgetState:这是另一个泛型参数,用于指定 SingleSelectTreeWidget 类的状态的类型。状态通常用于存储组件内部的数据,并在组件渲染和交互过程中进行管理和更新。

通过使用泛型参数,SingleSelectTreeWidget 类可以在实例化时指定具体的属性和状态类型,以符合特定的需求和使用场景。这样可以增加代码的可复用性和类型安全性,同时提供更好的开发和维护体验。

举例

class Container<T> {
private value: T;

constructor(initialValue: T) {
this.value = initialValue;
}

getValue(): T {
return this.value;
}

setValue(newValue: T): void {
this.value = newValue;
}
}
  • Container 类有一个私有属性 value,其类型是泛型参数 T。这意味着 value 可以是任意类型,具体的类型由在实例化时传入的参数决定。
  • 构造函数 constructor 接受一个初始值 initialValue,类型为 T,并将其赋值给 value 属性。
  • getValue 方法返回 value 属性的值,类型为 T
  • setValue 方法接受一个新值 newValue,类型为 T,并将 value 属性更新为新值。
const numberContainer = new Container<number>(10);
console.log(numberContainer.getValue()); // 输出:10
numberContainer.setValue(20);
console.log(numberContainer.getValue()); // 输出:20

const stringContainer = new Container<string>("Hello");
console.log(stringContainer.getValue()); // 输出:Hello
stringContainer.setValue("World");
console.log(stringContainer.getValue()); // 输出:World

在这个示例中,我们首先创建了一个 Container 实例 numberContainer,并指定泛型参数为 number。然后,我们使用 getValue 方法打印出初始值。接着,我们使用 setValue 方法将值更新为 20,并再次打印出新值。

类似地,我们还创建了一个 Container 实例 stringContainer,并指定泛型参数为 string,然后进行相应的操作。

export interface TreeSelectProps
extends Required<
Pick<
SelectProps,
"disabled" | "placeholder" | "loading" | "dropdownStyle" | "allowClear"
>
  • Required<T> 是 TypeScript 中的类型操作符,用于将接口或类型 T 中的所有属性标记为必需属性,即不能为 undefinednull
  • Pick<SelectProps, "disabled" | "placeholder" | "loading" | "dropdownStyle" | "allowClear"> 表示从 SelectProps 接口中选择特定的属性,形成一个新的类型。在这里,我们选择了 "disabled""placeholder""loading""dropdownStyle""allowClear" 这些属性。

疑难杂症

This comparison appears to be unintentional because the types 'TypeOptions' and '"loading"' have no overlap.

错误来源:

if (payload.style === "loading") {
iconStr = "loading";
}

style 的类型:TypeOptions;

这个错误表明 "loading" 类型和 TypeOptions 类型没有交集,也就是说 "loading" 不是 TypeOptions 类型的一部分。

如果要修改,则需要扩大 TypeOptions 的类型范围,包含 loading;

Function components cannot be given refs

const AppsmithLink = styled((props) => {
// we are removing non input related props before passing them in the components
return <Link {...props} />;
});
`
height: 24px;
min-width: 24px;
width: 24px;
display: inline-block;
img {
min-width: 24px;
width: 24px;
height: 24px;
}
`;

这样使用时,会有 Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?

在使用 styled 函数时,如果要在样式化组件中访问 ref 属性,您需要使用 React.forwardRef 方法来包装组件。

可以这么写

type LinkProps = {
to: string;
};

const AppsmithLink = styled(
React.forwardRef<HTMLAnchorElement, LinkProps>((props, ref) => {
// we are removing non input related props before passing them in the components
return <Link ref={ref} {...props} />;
})
)`
/* your styles here */
`;

<AppsmithLink to={APPLICATIONS_URL}>
<PagePlugLogoImg
alt="PagePlug logo"
className="t--appsmith-logo"
src={PagePlugLogo}
/>
</AppsmithLink>;
  • HTMLAnchorElement 指定了 ref 的类型为 HTMLAnchorElement,这表示 ref 应该是一个指向 <a> 元素的引用。
  • LinkProps 指定了 props 的类型为 LinkProps,这表示组件的其余属性应符合 LinkProps 类型的要求。

使用

<AppsmithLink to={APPLICATIONS_URL}>
<PagePlugLogoImg
alt="PagePlug logo"
className="t--appsmith-logo"
src={PagePlugLogo}
/>
</AppsmithLink>

这样会提示 No overload matches this call.

可以这样使用:

<AppsmithLink to={APPLICATIONS_URL} as={Link}>
<PagePlugLogoImg
alt="PagePlug logo"
className="t--appsmith-logo"
src={PagePlugLogo}
/>
</AppsmithLink>

通过 as 属性,你在使用 styled-components 包装的组件时,可以明确告诉 TypeScript 实际使用的组件类型。

排除某个类型?

参数不匹配

使用时传的参数少

Type 'Setting[] | undefined' must have a [Symbol.iterator]() method that returns an iterator.

使用...运算符时,主体有可能是 undefined,需要给个默认值