一、TypeScript介绍
都9102年了,你肯定听说过TypeScript(简称ts),要是说没用过ts,那就真是out了。ts是由微软开发的脚本语言,目前已经有很多项目是由ts开发的。前端的三大框架中,Angular的源码就是采用ts编写;React对ts支持也比较好;Vue宣布在3.0完全支持ts。说到ts,肯定就是灵魂三问——什么是ts?为什么要用ts?ts怎么用?
什么是ts
先看一下官方的解释:JavaScript的超集。众所周知,JavaScript是一个脚本语言,脚本语言的超集就意味着TypeScript也是一门编程语言,并且还是支持JavaScript语法的。
官方的补充说明如下:
- TypeScript具有类型系统,且是JavaScript的超集。
- 它可以编译成普通的JavaScript代码。
- TypeScript支持任意浏览器,任意环境,任意系统并且是开源的。
那相比js而言,ts的特点就是有类型、兼容js写法、开源、跨平台等。
为什么要用ts
js作为弱类型的语言,最大的优点就是灵活、简洁。但是灵活也给js带来一个严重的问题,就是只有在运行的时候才能检测到一些错误。 ts相比js而言,有静态类型检查、代码重构、语言服务等优势 ,这些优势在项目体积比较庞大的时候体现的特别明显。 但是ts并不是强类型的语言,而是增加类型检查的弱类型语言。
静态类型检查:ts定义了基础数据的类型,可以使用类、抽象类、接口来定义引用类型,为数据添加泛型来约束值的类型,在编译阶段就可以通过类型检测类型不匹配的问题。有些问题不需要等到运行就能发现。缺点在于舍弃了js的灵活性,增加开发的工作量;不正确的使用类型或只使用any去定义类型,那ts的使用就没有任何意义了。
代码重构:可以使用ts提供的类、抽象类、接口、泛型、枚举、装饰器来重构代码。使用ts重构的代码阅读性比js更强。
语言服务:使用ts后,编译器可以对编程语言提供更多功能,像代码提示、类型检查提示、自动补全、基础重构功能、调试接口助手、ts独有的增量编译。
ts怎么使用
有两种主要的方式可以使用ts:
- 通过npm安装
- 安装Visual Studio的TypeScript插件
针对使用npm的用户,运行npm install -g typescript
全局安装ts。
新建一个文件叫test.ts
,将下面代码添加到文件里
const name: string = 'World';
console.log('Hello ' + name + '!');
然后打开命令行工具,运行TypeScript编译器tsc test.ts
。这样,最简单的用法就学会了。
二、在项目使用TypeScript
Angular、React和Vue对ts都有一定的支持,目前Angular支持最好,ts中所有的特性都可以在Angular项目中使用。为了方便开发者,脚手架中都提供有ts的配置。情况如下:
- Angular官方脚手架@angular/cli生成的项目默认就是使用的ts
- Vue的vue-cli v3.0可以配置使用ts
脚手架生成的项目很容易,直接开发就好了。像React这种,似乎脚手架是没有提供这个功能的,就得自己配置了。
这里以React项目为例,配置ts
环境:npm包管理工具、全局webpack
第一步,初始化工程npm init
,添加项目信息,添加目录。目录结构如下:
proj/
├─ dist/
└─ src/
└─ components/
第二步,安装依赖。
react相关依赖,运行npm install --save react react-dom @types/react @types/react-dom
。
react
和react-dom
是react的基础依赖,使用@types/前缀表示我们额外要获取React和React-DOM的声明文件。
ts相关依赖,运行npm install --save-dev typescript awesome-typescript-loader source-map-loader
。
awesome-typescript-loader
可以让Webpack使用TypeScript的标准配置文件 tsconfig.json
编译TypeScript代码。source-map-loader
使用TypeScript输出的sourcemap文件来告诉webpack何时生成 自己的sourcemaps。 这就允许你在调试最终生成的文件时就好像在调试TypeScript源码一样。
第三步,配置TypeScript文件
在根目录下添加tsconfig.json
文件,添加以下内容:
{
"compilerOptions": {
"outDir": "./dist/",
"sourceMap": true,
"noImplicitAny": true,
"module": "commonjs",
"target": "es5",
"jsx": "react"
},
"include": [
"./src/**/*"
]
}
第四步,配置webpack
在根目录下添加webpack.config.js
文件,添加以下内容:
module.exports = {
entry: "./src/index.tsx",
output: {
filename: "bundle.js",
path: __dirname + "/dist"
},
// Enable sourcemaps for debugging webpack's output.
devtool: "source-map",
resolve: {
// Add '.ts' and '.tsx' as resolvable extensions.
extensions: [".ts", ".tsx", ".js", ".json"]
},
module: {
rules: [
// All files with a '.ts' or '.tsx' extension will be handled by 'awesome-typescript-loader'.
{ test: /\.tsx?$/, loader: "awesome-typescript-loader" },
// All output '.js' files will have any sourcemaps re-processed by 'source-map-loader'.
{ enforce: "pre", test: /\.js$/, loader: "source-map-loader" }
]
},
// When importing a module whose path matches one of the following, just
// assume a corresponding global variable exists and use that instead.
// This is important because it allows us to avoid bundling all of our
// dependencies, which allows browsers to cache those libraries between builds.
externals: {
"react": "React",
"react-dom": "ReactDOM"
}
};
第五步,写点代码
下面使用React写一段TypeScript代码。 首先,在 src/components
目录下创建一个名为Hello.tsx的文件,代码如下:
import * as React from "react";
export interface HelloProps { compiler: string; framework: string; }
// 'HelloProps' describes the shape of props.
// State is never set so we use the '{}' type.
export class Hello extends React.Component<HelloProps, {}> {
render() {
return <h1>Hello from {this.props.compiler} and {this.props.framework}!</h1>;
}
}
接下来,在src下创建index.tsx
文件,源码如下:
import * as React from "react";
import * as ReactDOM from "react-dom";
import { Hello } from "./components/Hello";
ReactDOM.render(
<Hello compiler="TypeScript" framework="React" />,
document.getElementById("example")
);
在根目录 proj创建一个名为index.html
的文件,如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Hello React!</title>
</head>
<body>
<div id="example"></div>
<!-- Dependencies -->
<script src="./node_modules/react/umd/react.development.js"></script>
<script src="./node_modules/react-dom/umd/react-dom.development.js"></script>
<!-- Main -->
<script src="./dist/bundle.js"></script>
</body>
</html>
第六步,运行
在命令行中运行webpack
这里项目配置的例子来自于官网的翻译文档,顺序做了一个调整。详细的见地址
三、JavaScript迁移
有的同学会问,如果项目已经开发完了,又想把js迁移成ts怎么办。这里有一个非常高效的办法,也正好利用了ts的特性,可以迅速将js的改变成ts。
这个方法就是——把文件的扩展名.js
改成.ts
就行,毕竟是js的超集。
js迁移ts就到这里了,完结撒花✿✿✿
开个玩笑!开发的世界里,除了容易出bug之外就没有这么容易的事
ts的中文网站中就有迁移的文档,写的比较详细,也包括对CommonJS、AMD等模块加载如何改成ts的引入形式,本文不再详述。JavaScript的迁移
四、ts的常用功能
1. 基础类型
js的基础类型有number、string、boolean、null、undefined、Symbols(ES6新增)。ts中对应有的基础类型的写法如下(null、undefined是值,作为类型而言作用不大):
// const | var | let 变量名: 变量类型
const num: number = 1;
const str: string = 'abc';
const isNum: boolean = false;
let u: undefined = undefined;
let n: null = null;
当你不知道这个变量会是什么类型的时候,可以将它声明为any
,被声明为any的变量在编译阶段不会进行类型检查。
const p: any;
ts中声明数组的方式比较多:
const x1: []; // 存放类型为any的数组
const x2: number[]; // 存放类型为number的数组
const x3: Array<number>; // 存放类型为number的数组
ts对比js还提供枚举类型,像C#、Java等语言一样,使用枚举类型可以为一组数值赋予友好的名字。枚举的声明使用关键字enum
。
enum Color {Red, Green, Blue}
let c: Color = Color.Green;
Void类型,了解后端语言的同学知道,函数的返回值有类型声明,当什么都不返回时,声明为void。
// 方法可以这么使用void
function warnUser(): void {
console.log("This is my warning message");
}
// 声明一个void类型的变量没有什么大用,因为你只能为它赋予undefined和null
let unusable: void = undefined;
2. 类
与es6里提供的class一样,ts的类也是使用class关键字声明。但是对类的成员变量使用上就会不一样,区别如下:
greeting
就是Greeter类的成员变量,es6中得使用this
的方法在constructor
的方法体中声明,ts中的声明方式如下,一种是在类的代码块里声明,另一种是在constructor
的方法参数里声明。- ts的成员变量和方法都可以添加访问修饰符:public、private、protected。默认使用public。
- ts在
constructor
的方法参数里声明的成员变量,在实例化的时候需要传递参数。 - 抽象类,与Java等语言的抽象类一样,声明为抽象的方法可以放在继承的子类实现。
abstract class People {
private name: string;
constructor(name: string, age: number) {
this.name = name;
}
greet() {
return "Hello, My name is " + this.name;
}
abstract getAge(): void;
}
class Student extends People {
constructor() {
super();
}
getAge(): void {
console.log('I am ' + this.age + 'years old');
}
}
let student = new Student("张三", 18);
3. 接口
ts新增的接口的功能,作用就是为这些类型命名和为你的代码或第三方代码定义契约。也就是说,接口里定义的变量和方法,在实现类里必须得写上。例子如下:
interface Car {
name: string; // 接口的变量和方法不能添加访问修饰符
run(): void;
}
class Bus implements Car {
constructor() {}
// 不写run方法会报错
run(): void {
console.log('bus run');
}
}
4. 泛型
泛型是ts提供的一个非常强大的功能了,限制了参数的输入,在编译的时候就能检查出一部分问题。常见的使用就是数组,和rxjs中的观察者对象,代码如下:
const arr: Array<number>;
const ob: Observable<string>;
为自定义的类添加泛型:
class People<T> {
name: T;
}
const people = new People<string>();
5. as 和 高级类型
上面四个是ts最最常用的特性,在使用上面四个特性后,你在编写代码的时候就可以清楚的知道变量的类型,如果是引用类型的话还可以知道它有哪些属性。在前后端定义接口的时候采用这种方式,当接口字段发生变化时,ts检查就能告诉我们哪些地方需要修改,避免出现低级的错误。
下面介绍的两个的特性就是ts提供的骚气操作,as
和高级类型
。
编写代码的时候会遇到一种情况,你使用对象字面量语法声明的变量和你定义的一个类的属性一模一样,但是直接使用就会引起类型检查的错误。这时,你就可以使用as
,这时,这个变量就会和as的类拥有同样的能力。
class People {
name: string;
age: number;
}
var p = {name: '张三', age: 18};
p as People
高级类型有交叉类型、联合类型两种。
交叉类型是这个变量同时拥有被交叉的类的能力,使用&
连接类型。一般在混入(mixins)或其它不适合典型面向对象模型的地方看到交叉类型的使用。下面是官方的例子:
function extend<T, U>(first: T, second: U): T & U {
let result = <T & U>{};
for (let id in first) {
(<any>result)[id] = (<any>first)[id];
}
for (let id in second) {
if (!result.hasOwnProperty(id)) {
(<any>result)[id] = (<any>second)[id];
}
}
return result;
}
class Person {
constructor(public name: string) { }
}
interface Loggable {
log(): void;
}
class ConsoleLogger implements Loggable {
log() {
// ...
}
}
var jim = extend(new Person("Jim"), new ConsoleLogger());
var n = jim.name;
jim.log();
联合类型是这个变量拥有多个类型其中的一个类的能力,使用|
连接类型。比如说,一个变量可以是字符,也可以数字。
cosnt p: string | number;