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-loader和typescript,因为 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类型;
所以Circle和Rectangle应该继承 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 并接受两个泛型参数:SingleSelectTreeWidgetProps 和 WidgetState;
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中的所有属性标记为必需属性,即不能为undefined或null。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,需要给个默认值