一篇文章学会 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 -vVersion 3.4.5
第二步:创建 .ts 文件
// add.tsfunction add(x: number, y: number): number { const res: number = x + y; return res;}export default add;
第三步:编译
$ tsc add.ts
// add.jsfunction add(x, y) { const res = x + y; return res;}export default add;
文件监听,实时编译
适用于边开发边看结果的情况:
$ tsc -w add.tsStarting 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.tsdeclare 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';@Componentexport 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 对 模块补充的说明