共计 7792 个字符,预计需要花费 20 分钟才能阅读完成。
一篇文章学会 TypeScript(内部分享标题:TypeScript 基础)
这篇文章是我在公司前端小组内部的演讲分享稿,目的是教会大家使用 TypeScript,
这篇文章虽然标着基础,但我指的基础是学完后就能够胜任 TypeScript 的开发工作。
从我分享完的效果来看,大家都学会了,效果还是好的。我把这篇分享稿发布出来,
希望能对大家的 TypeScript 学习有所帮助。
1、什么是 TypeScript?
TypeScript 是由微软发布的一款开源的编程语言。它是 JavaScript 的超集(兼容 JavaScript 代码),其代码必须经过编译后,方可在 JavaScript 环境中运行,核心功能是 类型系统 和可以提前使用 ES 的新特性。
接下来来看一段代码示例:
// TypeScript 语法
function add(x: number, y: number): number {
const res: number = x + y;
return res;
}
// 与 C 语言比较
int add(int x, int y) {
int res = x + y;
return res;
}
当类型不对的时候,IDE 会提示错误:
编译后:
// JavaScript 语法
function add(x, y) {
const res = x + y;
return res;
}
联想:大致可以把它看成是加了类型系统的 Babel。
问题:为什么要学习 TypeScript?
- 类型系统可以在代码编译阶段就可以发现潜在的变量类型问题,甚至在开发阶段,IDE 就可以帮助我们找到这些潜在的问题,避免了项目在上线后才发现变量类型不对的错误。
- TypeScript 是前端开发的趋势,有些特性还可能会成为 ES 的新标准,Vue 3.0 也会采用 TypeScript 作为开发语言,当你想看源码的时候不至于看不懂,跟着趋势走肯定是没错的。
- 减少类型校验等无用的代码书写。
- 可以提前享受 ES 新特性带来的便利。
- 向 qian 看,qian 途。
2、快速上手,三步走
第一步:全局安装 TypeScript
$ yarn global add typescript
// 测试是否安装成功
$ tsc -v
Version 3.4.5
第二步:创建 .ts 文件
// add.ts
function add(x: number, y: number): number {
const res: number = x + y;
return res;
}
export default add;
第三步:编译
$ tsc add.ts
// add.js
function add(x, y) {
const res = x + y;
return res;
}
export default add;
文件监听,实时编译
适用于边开发边看结果的情况:
$ tsc -w add.ts
Starting compilation in watch mode...
Watching for file changes.
3、TypeScript 的基本类型
// 布尔值
let isDone: boolean = false;
// 数值
let age: number = 12;
// 字符串
let name: string = 'Jay';
// 数组
let list: number[] = [1, 2, 3];
let list: Array<number> = [1, 2, 3];
// 对象
let mokey: object = {name: 'wu kong'};
// 空值,表示没有返回值
function print(): void {}
// 任意值
let goods: any = {};
let goods: any = 2019;
// 未指定类型,视为任意值
let goods;
// 类型推断
let name = 'Jay';
name = 123; // 报错
// 联合类型
let name: string | number;
name = 'Jay';
name = 123;
4、类和接口
类
类是对属性和方法的封装
类的示例:
// 跟 JavaScript 的类相似,多了访问控制等关键词
class Monkey {
public height: number;
private age: number = 12;
public static mkName: string = 'kinkong';
private static action: string = 'jump';
constructor(height: number) {this.height = height;}
public getAge(): number {return this.age;}
}
const monkey = new Monkey(120);
monkey.getAge();
const height = monkey.height;
const age = monkey.age; // 报错
const mkName = Monkey.mkName;
const action = Monkey.action; // 报错
类的继承
class Animal {move(distanceInMeters: number = 0) {console.log(`Animal moved ${distanceInMeters}m.`);
}
}
class Dog extends Animal {bark() {console.log('Woof! Woof!');
}
}
const dog = new Dog();
dog.bark();
dog.move(10);
protected、private、readonly 和 super
- protected 和 private 都不能从外部访问
- protected 可以被继承,private 不可以
class Person {
protected name: string;
private age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
}
class Employee extends Person {
private readonly initial: string = 'abc';
private readonly department: string;
constructor(name: string, department: string) {super(name, 1);
this.department = department;
}
public getElevatorPitch() {return `Hello, my name is ${this.name} and I work in ${this.department}.`;
}
public getAge() {return this.age;}
}
let howard = new Employee("Howard", "Sales");
console.log(howard.getElevatorPitch());
console.log(howard.name); // error
let passcode = "secret passcode";
class Employee {
private _fullName: string;
get fullName(): string {return this._fullName;}
set fullName(newName: string) {if (passcode && passcode == "secret passcode") {this._fullName = newName;}
else {console.log("Error: Unauthorized update of employee!");
}
}
}
let employee = new Employee();
employee.fullName = "Bob Smith";
if (employee.fullName) {console.log(employee.fullName);
}
类可以用于接口继承
class Point {
x: number;
y: number;
}
interface Point3d extends Point {z: number;}
let point3d: Point3d = {x: 1, y: 2, z: 3};
接口
接口是用来定义规范的,常常用作类型说明。
接口的示例:
// 定义规范,限定类型
interface Point {
x: number;
y: number;
// 可选属性
z?: number;
}
let pt: Ponit = {x: 1, y: 1, z: 1};
function setPoint(point: Point): void {}
setPoint({x: 1, y: 1});
// 实现接口
class point implements Point {
x: number;
y: number;
constructor(x: number, y: number) {
this.x = x;
this.y = y;
}
public getPoint(): object {
return {
x: this.x,
y: this.y,
}
}
}
const p = new point(3, 4);
p.getPoint();
接口继承接口
interface Shape {color: string;}
interface Square extends Shape {sideLength: number;}
初始化参数规范
interface ClockConstructor {new (hour: number, minute: number);
}
class Clock implements ClockConstructor {
currentTime: Date;
constructor(h: number, m: number) {}}
5、type、namespace 和泛型
type 定义新类型
当 TypeScript 提供的类型不够用时,可以用来自定义类型,供自己使用。
// type 定义新类型,相当于类型别名
type myType = string | number | boolean
// 使用:const foo: myType = 'foo'
// type 定义函数类型
type hello = (msg: string) => void
// type 对象类型
type WebSite = {
url: string;
title: number;
}
namespace 命名空间
命名空间主要有两个方面的用途:
- 组织代码。组织一些具有内在联系的特性和对象,能够使代码更加清晰。
- 避免名称冲突。
namespace Validator {
export interface StringValidator {isAcceptable(s: string): boolean;
}
const lettersRegexp = /^[A-Za-z]+$/;
const numberRegexp = /^[0-9]+$/;
// 当声明一个命名空间的时候,所有实体部分默认是私有的,可以使用 export 关键字导出公共部分。export class LettersOnlyValidator implements StringValidator {isAcceptable(s: string) {return lettersRegexp.test(s);
}
}
export class ZipCodeValidator implements StringValidator {isAcceptable(s: string) {return s.length === 5 && numberRegexp.test(s);
}
}
}
new Validator.LettersOnlyValidator();
interface MyClassMethodOptions {
name?: string;
age?: number;
}
namespace MyClass {
export interface MyClassMethodOptions {
width?: number;
height?: number;
}
}
使用外部定义的 namespace
/// <reference path="validate.ts" />
const v = new Validator.LettersOnlyValidator();
泛型
泛型允许在强类型程序设计语言中编写代码时,使用一些以后才指定的类型,在实际调用时才去指定泛型的类型。
举例:
// 模拟服务,提供不同的数据。这里模拟了一个字符串和一个数值
const service = {getStringValue: function() {return "a string value";},
getNumberValue: function() {return 20;}
};
// 处理数据的中间件
function middleware(value: string): string {return value;}
// 没问题
middleware(service.getStringValue());
// 报错:参数类型不对
middleware(service.getNumberValue());
// 设成 any 行不行?function middleware(value: any): any
// 多写几个 middleware 行不行?function middleware1(value: string): string {...}
function middleware2(value: number): number {...}
// 改为泛型
function middleware<T>(value: T): T {return value;}
// 使用
middleware<string>(service.getStringValue());
middleware<number>(service.getNumberValue());
更多内容查看文章:泛型
6、xxx.d.ts
声明文件
如果把 add.js 发布成一个 npm 包,别人在使用的时候 IDE 的提示往往都不太友好:
那么如何能让 IDE 给 add 方法加上类型提示和参数提示呢?
TypeScript 提供了 xxx.d.ts
文件来给 IDE 使用。
xxx.d.ts
叫做声明文件,一般用于类型提示和模块补充,可以自动生成,也可以手动编写,一般情况下是不用我们手动编写的。
// 加上 -d 参数,就会生成 add.d.ts 文件
$ tsc -d add.ts
// add.d.ts
declare function add(x: number, y: number): number;
export default add;
declare 关键字
用于声明你需要的变量、函数、类等,声明以后 IDE 就可以根据它来进行类型提示。
如果有 add.d.ts 文件,那么它就承担了类型提示的任务:
同时也相当于 add 方法的接口文档:
手动创建声明文件
有以下两个场景会去手动创建这个文件:
- 给未使用 TypeScript 的 js 模块编写声明文件,可以当做交接文档用;使用别人没有写声明文件的 js 库。
- 模块补充。
// 定义模块补充的语法
declare module 'filePath' {}
module-augmentation
使用场景:vue-shim.d.ts 文件,为了让 TypeScript 识别 .vue 文件。
// 文件内容
declare module "*.vue" {
import Vue from "vue";
export default Vue;
}
增强类型以配合插件使用
7、配置文件
冗长的参数
指定输出目录:
$ tsc -d --strict -m ESNext --outDir lib index.ts
tsc 的所有参数可以通过执行:tsc -h
来查看。
使用 tsconfig.json 配置文件
避免书写冗长的命令,丰富的配置项。
生成 tsconfig.json 文件:
$ tsc --init
常用配置项解读:
{
"compilerOptions": {"target": "ES5", /* target 用于指定编译之后的版本目标 version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */
"module": "ESNext", /* 用来指定要使用的模块标准: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
"declaration": true, /* 生成对应的 '.d.ts' 声明文件 */
"outDir": "./lib", /* 指定编译输出目录 */
"esModuleInterop": true, /* 通过为导入内容创建命名空间,实现 CommonJS 和 ES 模块之间的互操作性 */
"experimentalDecorators": true, /* 启用 ES7 装饰器语法 */
}
}
查看更多配置项
配置后,执行以下命令,就会读取配置文件:
$ tsc add.ts
8、使用 TypeScript 开发 web 应用
上述的方法适用于开发命令行工具,比如 @xiyun/cli,开发 web 应用还是不太方便。
如果想要方便地开发 web 应用,比如:开启前端服务、模块热加载等,就需要配合构建工具使用。
配合 webpack 开发 web 应用
点击查看配合 webpack 开发 web 应用源码
配合 parcel,零配置开发 web 应用
parcel:极速零配置 Web 应用打包工具。
联想:可以看成是已经配置好了的 webpack。
全局安装 parcel 工具:
$ yarn global add parcel-bundler
运行:
$ parcel index.html
它就会帮你生成 package.json,自动安装 typeScript,启动好开发服务,并支持热加载。
9、在 Vue 和 React 项目中使用
让 Vue 支持 TypeScript
最方便快捷:vue create my-app
,选择 TypeScript。
如果要改造现有项目:
- 增加 dev 依赖包:
"devDependencies": {
"@vue/cli-plugin-typescript": "^3.8.0",
"typescript": "^3.4.3",
}
- 增加 tsconfig.json 配置
- 更改文件后缀:
main.js --> main.ts
- 组件中指定 lang:
<script lang="ts">
</script>
- 在 src 目录下加上:vue-shim.d.ts 文件,为了让 TypeScript 识别 .vue 文件。
// 文件内容
declare module "*.vue" {
import Vue from "vue";
export default Vue;
}
Vue 支持 TypeScript 的配置参考
另外一种语法
如果想要用这种语法来写 Vue 组件:
import {Component, Prop, Vue} from 'vue-property-decorator';
@Component
export default class HelloWorld extends Vue {@Prop() private msg!: string;
}
那么需要加上这两个依赖:
"dependencies": {
// 官网出的 ts 支持包
"vue-class-component": "^7.0.2",
// 对 ts 支持包更好封装的装饰器
"vue-property-decorator": "^8.1.0"
},
Vue 官方对 TypeScript 的说明
创建支持 TypeScript 的 React 项目
创建应用时加上 --typescript
参数即可:
$ create-react-app my-app --typescript
参考资料
TypeScript 官方文档
TypeScript Declare Keyword
Vue 对 模块补充的说明