TypeScript 是微软开发和控制的开源项目,我在应用 Anguar 2+ 和 Ionic 2+ 框架开发系统时使用的就是 TypeScript,但我一直把 TypeScript 当作 JavaScript 来用的,甚至是照猫画虎。
所以我想正儿八经的学习下 TypeScript,弄明白我糊里糊涂使用的东西,这篇 Chat 分享算是我的一个学习笔记,适合于有一定 JavaScript 基础的人,在此基础上学习 TypeScript 的主要差异。
0. 背景
0.1. 什么是 TypeScript
TypeScript 是一种由微软开发的自由和开源的编程语言,它是 JavaScript 的一个超集,而且本质上向这个语言添加了可选的静态类型和基于类的面向对象编程。
TypeScript 扩展了 JavaScript 的语法,所以任何现有的 JavaScript 程序可以不加改变的在 TypeScript 下工作。
这就是为什么我一直用 Javascript 开发 TypeScript 的 Angular 的原因:TS 是 JS 的一个超集。
0.2. 使用 TypeScript 的优势
为什么要学习 TypeScript 了,实际上 JavaScript 已经够用了,而且像我没学过 TypeScript 也照样开发得很好,我们来看看它的优势:
更多的规则和类型限制,让代码预测性更高,可控性更高,易于维护和调试。
对模块、命名空间和面向对象的支持,更容易组织代码开发大型复杂程序。
TypeScript 的编译步骤可以捕获运行之前的错误。
Angular2+ 和 Ionic2+ 默认使用 TypeScript(我就是工作中接触了这两个框架才开始学习 TS 的,而且这两个框架我感觉挺有前途的。)。
1. 安装和环境搭建
安装需要 Node.js 和 npm,Node.js 自带 npm 这里就不深入了,假设我们已经安装好了 Node.js。
TypeScript 的安装使用也很简单,官网首页简单粗暴的给出了一些信息:
首先,通过 npm 全局安装 TypeScript:
npm i -g typescript
我安装完成是这个样子,注意-g 参数,表示是全局安装,这样我们在其他工程中也能使用 TypeScript 的命令行编译工具。
安装完成后我们可以使用 TypeScript 的编译工具 tsc 验证一下:
tsc -v Version 2.6.2
打开控制台窗口,其中运行 tsc -v 打印出安装的版本号。
1.1. 第一次接触
有很多 IDE 支持 TypeScript 的编写,你可以找一个你喜欢的,我用的是 VSCode,免费好用不折腾。
我们先来写第一个 TyepScript 代码,就抄官网上的:
function greeter(person) {return "Hello, " + person; }let user = "Jane User";document.body.innerHTML = greeter(user);
保存为 main.ts,然后编译:
tsc main.ts
此时,tcs 将把 main.ts 翻译成 JavaScript 代码,并在当前目录下保存为同名的 js 文件 main.js。
tsc 还支持一次编译多个文件列表或者使用通配符编译全部文件,例如:
tsc main.ts mod.ts
编译文件夹下所有 ts 文件:
tsc *.ts
还有一个高级的玩法是监听文件变化,当发生改变时自动编译,这样我们在学习 TS 的时候写一下,然后很方便的看看是什么意思:
tsc main.ts --watch
1.2. 使用 webpack 搭建环境
Webpack 是一个前端资源加载 / 打包工具,它将根据模块的依赖关系进行静态分析,然后将这些模块按照指定的规则生成对应的静态资源。
项目开发中有很多文件,依赖也各不相同,使用 webpack 可以方便的组织管理我们的代码。
1.2.1 初始化工程
现在把这个目录变成 npm 项目,初始化语句如下:
npm init
你会看到一些提示,放心地使用默认值就可以了。 当然,你也可以随时到生成的 package.json 文件里修改。
package.json 配置
{ "name": "code", "version": "1.0.0", "description": " 测试 ", "main": "main.ts", "scripts": { "test": "test", }, "keywords": [ "testcode" ], "author": "sunsi", "license": "ISC", "devDependencies": { } }
1.2.2. 安装配置 Webpack
首先确保已经全局安装了 Webpack。
npm install -g webpack webpack-dev-server
Webpack 这个工具可以将你的所有代码和可选择地将依赖捆绑成一个单独的 .js 文件。
webpack-dev-server,可以让我们在开发的时候启动一个 web 服务用于运行测试网页程序。
安装好后需要编辑 webpack.config.js 以适应项目,因为多数项目开发有着一定的共性,有大同小异或者是约定俗成的配置,所以我们可以把精力集中在开发上,而不用纠结代码的组织。
module.exports = { // 入口文件 entry: "./src/main.ts", // 输出文件和目录 output: { filename: "bundle.js", path: __dirname + "/dist" }, // 打开 sourcemaps 调试 webpack 的输出 devtool: "source-map", resolve: { // 添加 '.ts' 和 '.tsx' 后缀可以被处理 extensions: [".ts", ".tsx", ".js", ".json", ".html"] }, module: { rules: [ // '.ts' or '.tsx' 后缀的文件将被 loadr 'awesome-typescript-loader' 处理。 { test: /.tsx?/, loader: "source-map-loader" } ] } };
npm 可以简化我们的命令行,将 package.json 的 scripts 修改,例如:
"scripts": { "build": "webpack", "dev2": "webpack-dev-server --client-log-level none --color --inline --hot --config webpack.dev.js", "dev": "webpack-dev-server --client-log-level none --color --inline --hot" }
上面的代码让我没可以使用 npm 的命令简化执行。
执行 npm run build 相当于使用 webpack 命令编译;执行 npm run dev 相当于启动一个服务。
1.2.3. 安装配置 TypeScript
接下来,我们要添加开发时依赖 awesome-typescript-loader 和 source-map-loader,有时也用 ts-loader 的。
npm install --save-dev typescript awesome-typescript-loader source-map-loader
这些依赖会让 TypeScript 和 webpack 在一起良好地工作,webpack 可以通过很多第三方的 loader 来扩展功能,awesome-typescript-loader 可以让 Webpack 使用 TypeScript 的标准配置文件 tsconfig.json 编译 TypeScript 代码。
source-map-loader 使用 TypeScript 输出的 sourcemap 文件来告诉 webpack 何时生成自己的 sourcemaps,这就允许你在调试最终生成的文件时就好像在调试 TypeScript 源码一样。
注意我们安装 TypeScript 为一个开发依赖,也就是开发的时候需要运行的时候不需要。 我们还可以使用 npm link typescript 来链接 TypeScript 到一个全局拷贝,但这不是常见用法。
然后,我们配置 TypeScript,编写一个文件:
{ "compilerOptions": { "outDir": "./dist/", "sourceMap": true, "noImplicitAny": true, "module": "commonjs", "target": "es5" }, "include": [ "./src/*/" ] }
json 文件不能添加注释,但这些简单的配置一看即知。
1.2.4. 组织代码和目录
最后,我们的 package.json 变成这个样子:
package.json 配置
{ "name": "code", "version": "1.0.0", "description": " 测试 ", "main": "main.ts", "scripts": { "build": "webpack", "dev2": "webpack-dev-server --client-log-level none --color --inline --hot --config webpack.dev.js", "dev": "webpack-dev-server --client-log-level none --color --inline --hot" }, "keywords": [ "testcode" ], "author": "sunsi", "license": "ISC", "devDependencies": { "awesome-typescript-loader": "^3.4.1", "source-map-loader": "^0.2.3", "typescript": "^2.6.2" } }
刚才,我们为开发环境编写了一些配置文件,最终的目录应该是这样的:
\PrjName // 项目根目录 | index.html // 网页,将引用 dist/bundle.js | package-lock.json // webpack 自己生成的文件,现在忽略 | package.json // npm init 配置文件 | tsconfig.json // TypeScript 配置文件 | webpack.config.js // webpack 默认配置文件 | +---dist // 输出目录 | bundle.js // 输出文件 | bundle.js.map // 输出 map 文件 | ---src // 源码目录 main.ts // 源码,也是入口文件
下面我们新建 src 目录、index.html 和 main.ts 文件:
在 Main.ts 中使用 TypeScript 编写代码
function greeter(person:string) { return "Hello, " + person; } let user = "TypeScript User"; document.body.innerHTML = greeter(user);
最后,我们运行测试下代码,在命令行中输入:
npm run build npm run dev
浏览器中输入 http://localhost:8080,可以看到:
[图片上传失败...(image-ea6a79-1534226186080)]
2. TypeScript 的类型
假如我们已经使用过 JavaScript,那么你要知道 TypeScript 最大的不同就是 Type,这是我们这次分享的重点,我们得花点时间来感受下类型。
2.1. 静态类型
TypeScript 的一个非常显著的特点是静态类型的支持,这意味着您可以声明变量的类型,并且编译器将确保它们没有被分配到错误类型的值。如果省略类型声明,它们将从您的代码中自动推断出来。
看看下面的代码,就是就是 JavaScript 再加上类型定义而已:
function greeter(person: string) {return "Hello, " + person; }let user = "Jane User";document.body.innerHTML = greeter(user);
由于 JavaScript 是不 care 类型的,所以编译后的代码类型被移除了,编译后变成我们熟悉的 JavaScript:
function greeter(person) {return "Hello, " + person; }var user = "Jane User";document.body.innerHTML = greeter(user);
如果我们把一个数组传入方法:
function greeter(person: string) {return "Hello, " + person; }let user = [0, 1, 2];document.body.innerHTML = greeter(user);
这时就会产生一个错误:
main.ts(7,35): error TS2345: Argument of type 'number[]' is not assignable to parameter of type 'string'. 15:34:56 - Compilation complete. Watching for file changes.
TypeScript 提供了静态的代码分析,它可以分析代码结构和提供的类型注解。要注意的是尽管有错误,main.js 文件还是被创建了。
就算你的代码里有错误,你仍然可以使用 TypeScript。但在这种情况下,TypeScript 会警告你代码可能不会按预期执行。
很明显,TypeScript 就是一个有型的 JavaScript。
2.2. 基础类型
这里列出一些常用的数据类型:
- 布尔值
最基本的数据类型就是简单的 true/false 值,在 JavaScript 和 TypeScript 里叫做 boolean(其它语言中也一样),使用 0 和 1 将导致编译错误。
let isDone: boolean = false;
- 数字
和 JavaScript 一样,TypeScript 里的所有数字都是浮点数,这些浮点数的类型是 number。
除了支持十进制和十六进制字面量,TypeScript 还支持 ECMAScript 2015 中引入的二进制和八进制字面量。没有分别定义为 integers、floats 或是其它值。
let decLiteral: number = 6;let hexLiteral: number = 0xf00d;let binaryLiteral: number = 0b1010;let octalLiteral: number = 0o744;
- 字符串
JavaScript 程序的另一项基本操作是处理网页或服务器端的文本数据。
像其它语言里一样,我们使用 string 表示文本数据类型,和 JavaScript 一样,可以使用双引号( “)或单引号(’)表示字符串。
let name: string = "bob"; name = "smith";
开发过程中拼字符串是最常用的了,使用模版字符串比 format 更加强大,它可以定义多行文本和内嵌表达式。
这种字符串是被反引号包围( `)(键盘布局左上方数字键 1 左边),可以通过 ${ expr } 这种形式嵌入表达式
let name: string = Gene
;let age: number = 37;let sentence: string = Hello, my name is ${ name }. I'll be ${ age + 1 } years old next month.
;
这与下面加号拼接的方式效果相同,但是明显字符串模版更简单强大:
let sentence: string = "Hello, my name is " + name + ".\n\n" +"I'll be " + (age + 1) + " years old next month.";
- 数组
TypeScript 像 JavaScript 一样可以操作数组元素,有两种方式可以定义数组。
第一种,可以在元素类型后面接上 [],表示由此类型元素组成的一个数组:
let list: number[] = [1, 2, 3];
第二种方式是使用数组泛型,Array<元素类型>:
let list: Array<number> = [1, 2, 3];
let list: any[] = [1, true, "free"]; list[1] = 100;
- Any
有时候,我们会想要为那些在编程阶段还不清楚类型的变量指定一个类型,这些值可能来自于动态的内容,比如来自用户输入或第三方代码库。
这种情况下,我们不希望类型检查器对这些值进行检查而是直接让它们通过编译阶段的检查,那么我们可以使用 any 类型来标记这些变量:
let notSure: any = 4; notSure = "maybe a string instead"; notSure = false; // okay, definitely a boolean
在对现有代码进行改写的时候,any 类型是十分有用的,它允许你在编译时可选择地包含或移除类型检查。
你可能认为 Object 有相似的作用,就像它在其它语言中那样。 但是 Object 类型的变量只是允许你给它赋任意值 - 但是却不能够在它上面调用任意的方法,即便它真的有这些方法:
let notSure: any = 4; notSure.ifItExists(); // okay, ifItExists might exist at runtimenotSure.toFixed(); // okay, toFixed exists (but the compiler doesn't check)let prettySure: Object = 4; prettySure.toFixed(); // Error: Property 'toFixed' doesn't exist on type 'Object'.
具有这种类型的变量可以将它的值设置为字符串、数字或其他任何东西。
- Void
用于不返回任何东西的函数。
某种程度上来说,void 类型像是与 any 类型相反,它表示没有任何类型。 当一个函数没有返回值时,你通常会见到其返回值类型是 void:
function warnUser(): void { alert("This is my warning message"); }
声明一个 void 类型的变量没有什么大用,因为你只能为它赋予 undefined 和 null:
let unusable: void = undefined;
- Null 和 Undefined
TypeScript 里,undefined 和 null 两者各自有自己的类型分别叫做 undefined 和 null。和 void 相似,它们的本身的类型用处不是很大:
// Not much else we can assign to these variables!let u: undefined = undefined;let n: null = null;
默认情况下 null 和 undefined 是所有类型的子类型,就是说你可以把 null 和 undefined 赋值给 number 类型的变量。
然而,当你指定了—strictNullChecks 标记,null 和 undefined 只能赋值给 void 和它们各自,这能避免很多常见的问题。
也许在某处你想传入一个 string 或 null 或 undefined,你可以使用联合类型 string | null | undefined。
注意:尽可能地使用—strictNullChecks。
- Never
never 类型表示的是那些永不存在的值的类型。
例如, never 类型是那些总是会抛出异常或根本就不会有返回值的函数表达式或箭头函数表达式的返回值类型; 变量也可能是 never 类型,当它们被永不为真的类型保护所约束时。
never 类型是任何类型的子类型,也可以赋值给任何类型;然而,没有类型是 never 的子类型或可以赋值给 never 类型(除了 never 本身之外),即使 any 也不可以赋值给 never。
下面是一些返回 never 类型的函数:
// 返回 never 的函数必须存在无法达到的终点function error(message: string): never {throw new Error(message); }// 推断的返回值类型为 neverfunction fail() {return error("Something failed"); }// 返回 never 的函数必须存在无法达到的终点function infiniteLoop(): never {while (true) { } }
2.3. 类型转换
好比其它语言里的类型转换,但是不进行特殊的数据检查和解构,它对运行时时没有影响,只在编译时起作用。
TypeScript 会假设你知道自己在干什么,已经进行了必须的检查。
有两种形式。 其一是 “尖括号” 语法:
let someValue: any = "this is a string";let strLength: number = (<string>someValue).length;
另一个为 as 语法:
let someValue: any = "this is a string";let strLength: number = (someValue as string).length;
两种形式是等价的,至于使用哪个大多数情况下是凭个人喜好;然而,当你在 TypeScript 里使用 JSX 时,只有 as 语法断言是被允许的。