研发资讯

OpenTiny 跨端、跨框架组件库升级 TypeScript

2023-04-11 18:45:45 zhangcb 28

本文分享自华为云社区《历史性的时刻!OpenTiny 跨端、跨框架组件库正式升级 TypeScript,10 万行代码重获新生!》,作者:Kagol。

根据 The Software House 发布的《2022 前端开发市场状态调查报告》数据显示,使用 TypeScript 的人数已经达到 84%,和 2021 年相比增加了 7 个百分点。

3 月 16 日发布了 TypeScript 5.0 版本。TypeScript 可谓逐年火热,使用者呈现逐年上升的趋势,再不学起来就说不过去。

我们 OpenTiny 近期做了一次大的升级,将原来运行了 9 年 的 JavaScript 代码升级到了 TypeScript,并通过 Monorepo 进行子包的管理,还在用 JavaScript 的朋友抓紧升级哦,我特意准备了一份《JS 项目改造 TS 指南》文档供大家参考,顺便介绍了一些 TS 基础知识和 TS 在 Vue 中的一些实践。

图片关键词

通过本文你将收获:

  • 通过了解 TS 的四大好处,说服自己下定决心学习 TS

  • 5 分钟学习 TS 最基础和常用的知识点,快速入门,包教包会

  • 了解如何在 Vue 中使用 TypeScript,给 Vue2 开发者切换到 Vue3 + TypeScript 提供最基本的参考

  • 如何将现有的 JS 项目改造成 TS 项目

1 学习 TS 的好处

1.1 好处一:紧跟潮流:让自己看起来很酷

如果你没学过 TS
你的前端朋友:都 2023 年了,你还不会 TS?给你一个眼色你自己感悟吧

如果你学过 TS
你的前端朋友:哇,你们的项目已经用上 Vue3 + TS 啦,看起来真棒!教教我吧

图片关键词

如果说上面那个好处太虚了,那下面的 3 条好处可都是实实在在能让自己受益的。

1.2 好处二:智能提示:提升开发者体验和效率

当循环一个对象数组时,对象的属性列表可以直接显示出来,不用到对象的定义中去查询该对象有哪些属性。

图片关键词

通过调用后台接口获取的异步数据也可以通过 TS 类型进行智能提示,这样相当于集成了接口文档,后续后台修改字段,我们很容易就能发现。

图片关键词

Vue 组件的属性和事件都可以智能提示。

下图是我们 OpenTiny 跨端跨框架前端组件库中的 Alert 组件,当在组件标签中输入 des 时,会自动提示 description 属性;当输入 @c 时,会自动提示 @close 事件。

图片关键词

1.3 好处三:错误标记:代码哪里有问题一眼就知道

在 JS 项目使用不存在的对象属性,在编码阶段不容易看出来,到运行时才会报错。

图片关键词

在 TS 项目使用不存在的对象属性,在 IDE 中会有红色波浪线标记,鼠标移上去能看到具体的错误信息。

图片关键词

在 JS 项目,调用方法时拼错单词不容易被发现,要在运行时才会将错误暴露出来。

图片关键词

在 TS 项目会有红色波浪线提示,一眼就看出拼错单词。

图片关键词

1.4 好处四:类型约束:用我的代码就得听我的

你写了一个工具函数 getType 给别人用,限定参数只能是指定的字符串,这时如果使用这个函数的人传入其他字符串,就会有红色波浪线提示。

图片关键词

Vue 组件也是一样的,可以限定组件 props 的类型,组件的使用者如果传入不正确的类型,将会有错误提示,比如:我们 OpenTiny 的 Alert 组件,closable 只能传入 Boolean 值,如果传入一个字符串就会有错误提示。

图片关键词

2 极简 TS 基础,5 分钟学会

以下内容虽然不多,但包含了实际项目开发中最实用的部分,对于 TS 入门者来说也是能很快学会的,学不会的找我,手把手教,包教包会,有手就会写。

2.1 基本类型

用得较多的类型就下面 5 个,更多类型请参考:TS 官网文档

  • 布尔 boolean

  • 数值 number

  • 字符串 string

  • 空值 void:表示没有任何返回值的函数

  • 任意 any:表示不被类型检查

用法也很简单:

let isDone: boolean = false;let myFavoriteNumber: number = 6;let myName: string = 'Kagol';function alertName(name: string): void { 
 console.log(`My name is ${name}`); 
}

默认情况下,name 会自动类型推导成 string 类型,此时如果给它赋值为一个 number 类型的值,会出现错误提示。

let name = 'Kagol' name = 6
图片关键词

如果给 name 设置 any 类型,表示不做类型检查,这时错误提示消失。

let name: any = 'Kagol' name = 6
图片关键词

2.2 函数

主要定义函数参数和返回值类型。

看一下例子:

const sum = (x: number, y: number): number => { 
 return x + y
}

以上代码包含以下 TS 校验规则:

  • 调用 sum 函数时,必须传入两个参数,多一个或者少一个都不行

  • 并且这两个参数的类型要为 number 类型

  • 且函数的返回值为 number 类型

少参数:

图片关键词

多参数:

图片关键词

参数类型错误:

图片关键词

返回值:

图片关键词

用问号?可以表示该参数是可选的。

const sum = (x: number, y?: number): number => { 
 return x + (y || 0); 
} 
sum(1)

如果将 y 定义为可选参数,则调用 sum 函数时可以只传入一个参数。

需要注意的是,可选参数必须接在必需参数后面。换句话说,可选参数后面不允许再出现必需参数了。

给 y 增加默认值 0 之后,y 会自动类型推导成 number 类型,不需要加 number 类型,并且由于有默认值,也不需要加可选参数。

const sum = (x: number, y = 0): number => { 
 return x + y
}
sum(1) 
sum(1, 2)

2.3 数组

数组类型有两种表示方式:

  • 类型 + 方括号 表示法

  • 泛型 表示法

// `类型 + 方括号` 表示法let fibonacci: number[] = [1, 1, 2, 3, 5]// 泛型表示法let fibonacci: Array<number> = [1, 1, 2, 3, 5]

这两种都可以表示数组类型,看自己喜好进行选择即可。

如果是类数组,则不可以用数组的方式定义类型,因为它不是真的数组,需要用 interface 进行定义

interface IArguments {
 [index: number]: any;
length: number;
 callee: Function;
}function sum() { let args: IArguments = arguments}

IArguments 类型已在 TypeScript 中内置,类似的还有很多:

let body: HTMLElement = document.body;let allDiv: NodeList = document.querySelectorAll('div');document.addEventListener('click', function(e: MouseEvent) { // Do something});

如果数组里的元素类型并不都是相同的怎么办呢?

这时 any 类型就发挥作用啦啦

let list: any[] = ['OpenTiny', 112, { website: 'https://opentiny.design/' }];

2.4 接口

接口简单理解就是一个对象的 “轮廓”

interface IResourceItem {
name: string;
 value?: string | number;
 total?: number;
 checked?: boolean;
}

接口是可以继承接口的

interface IClosableResourceItem extends IResourceItem {
 closable?: boolean;
}

这样 IClosableResourceItem 就包含了 IResourceItem 属性和自己的 closable 可选属性。

接口也是可以被类实现的

interface Alarm {
 alert(): void;
}class Door {
}class SecurityDoor extends Door implements Alarm {
 alert() {
 console.log('SecurityDoor alert')
 }
}

如果类实现了一个接口,却不写具体的实现代码,则会有错误提示

图片关键词

2.5 联合类型 & 类型别名

联合类型是指取值可以为多种类型中的一种,而类型别名常用于联合类型。

看以下例子:

// 联合类型let myFavoriteNumber: string | numbermyFavoriteNumber = 'six'myFavoriteNumber = 6// 类型别名type FavoriteNumber = string | numberlet myFavoriteNumber: FavoriteNumber

当 TypeScript 不确定一个联合类型的变量到底是哪个类型的时候,我们只能访问此联合类型的所有类型里共有的属性或方法:

function getLength(something: string | number): number { return something.length
}
// index.ts(2,22): error TS2339: Property 'length' does not exist on type 'string | number'.
// Property 'length' does not exist on type 'number'.
上例中,length 不是 string 和 number 的共有属性,所以会报错。
访问 string 和 number 的共有属性是没问题的:function getString(something: string | number): string { return something.toString()
}

2.6 类型断言

类型断言(Type Assertion)可以用来手动指定一个值的类型。

语法:值 as 类型,比如:(animal as Fish).swim ()

类型断言主要有以下用途:

  • 将一个联合类型断言为其中一个类型

  • 将一个父类断言为更加具体的子类

  • 将任何一个类型断言为 any

  • 将 any 断言为一个具体的类型

我们一个个来看。

用途 1:将一个联合类型断言为其中一个类型

interface Cat {
name: string;
 run(): void;
}interface Fish {
name: string;
 swim(): void;
}const animal: Cat | Fish = new Animal()
animal.swim()

animal 是一个联合类型,可能是猫 Cat,也可能是鱼 Fish,如果直接调用 swim 方法是要出现错误提示的,因为猫不会游泳。

图片关键词

这时类型断言就派上用场啦啦,因为调用的是 swim 方法,那肯定是鱼,所以直接断言为 Fish 就不会出现错误提示。

const animal: Cat | Fish = new Animal()
(animal as Fish).swim()

用途 2:将一个父类断言为更加具体的子类

class ApiError extends Error {
code: number = 0;
}class HttpError extends Error {
 statusCode: number = 200;
}function isApiError(error: Error) { if (typeof (error as ApiError).code === 'number') { return true;
 } return false;
}

ApiError 和 HttpError 都继承自 Error 父类,error 变量的类型是 Error,去取 code 变量肯定是不行,因为取的是 code 变量,我们可以直接断言为 ApiError 类型。

用途 3:将任何一个类型断言为 any

这个非常有用,看一下例子:

function getCacheData(key: string): any { return (window as any).cache[key];
}interface Cat {
name: string;
 run(): void;
}const tom = getCacheData('tom') as Cat;

getCacheData 是一个历史遗留函数,不是你写的,由于他返回 any 类型,就等于放弃了 TS 的类型检验,假如 tom 是一只猫,里面有 name 属性和 run () 方法,但由于返回 any 类型,tom. 是没有任何提示的。

如果将其断言为 Cat 类型,就可以 点 出 name 属性和 run () 方法。

图片关键词

用途 4:将 any 断言为一个具体的类型

这个比较常见的场景是给 window 挂在一个自己的变量和方法。

window.foo = 1;// index.ts:1:8 - error TS2339: Property 'foo' does not exist on type 'Window & typeof globalThis'.
(window as any).foo = 1;

由于 window 下没有 foo 变量,直接赋值会有错误提示,将 window 断言为 any 就没问题啦啦。


首页
文章
联系