TypeScript代码与 JavaScript 代码有非常高的兼容性,无门槛,你把 JS 代码改为 TS 就可以运行。如果没有接触过强类型的编程语言,导致他们认为学习TS需要定义各种类型,还有一些新概念等等,会增加学习成本。TypeScript 应该不会脱离 JavaScript 成为独立的语言。学习 TypeScript 应该主要指的是学习它的类型系统。
什么是 TypeScript
微软发布了一款 JavaScript 超集的编程语言并取名为 TypeScript,由于 TypeScript 是 JavaScript 的严格超集,因此任何 JavaScript 都是合法的 TypeScript(非常像 C 和 Objective-C 的关系)。TypeScript 为 JavaScript 带来了强大的类型系统和对 ES2015的支持,它的编译工具可以运行在任何服务器和任务系统上。
事实上 ES2015 发布之后 JavaScript 取得了巨大的进步,但随着设备性能的提升以及 JavaScript 在应用层上不断占据了重要的位置,对于大型项目,人们显然需要更强大的语言级别的支持(微软发现外部客户在开发大规模 JavaScript 应用的过程中遭遇的语言本身的短板)。
类型系统实际上是非常好的文档,增强了编辑器在 智能提示
,跳转定义
,代码补全
等方向上的功能,并且在编译阶段能发现大部分的错误,对于大型工程的代码可读性和可维护性起到了了不起的作用。
TypeScript 的流行趋势
事实上 TypeScript 拥有活跃的社区,大部分第三方库都有提供 TypeScript 类型定义文件,甚至知名的前端库都完全使用 TypeScript 来进行开发,比如 Google 的 Angular,我们可以通过一些数据来了解 TypeScript 的流行趋势:
TypeScript 的优势和收益是什么
TypeScript 官网上列了很多它的优势,在这里我更愿意自己总结一下:
- 类型系统可在编译阶段发现大部分的错误
- 类型系统也是一个很直观的编程文档,可以查看任何函数或API的输入输出类型
- 类型系统增强了编辑器或IDE的功能
- TypeScript 可以自动的推导类型
- 一切 JavaScript 都是合法的 TypeScript 降低了使用成本
- TypeScript 拥抱 ES2015 以及 ESNext 草案规范
- 几乎第三方库都有 TypeScript 类型定义文件
rollbar 于 2018年统计了前端项目 Top10 的错误:https://rollbar.com/blog/top-10-javascript-errors/,事实上 TypeScript 在编译期的类型检查能解决上述80%的问题,对于大型工程来说收益是非常明显的。
如果你有一个需要长期维护的工程,那么类型系统在可读性和可维护性上拥有比 JavaScript 更强大的动能,在良好的编程语境下,在稳定的工具链帮助下,TypeScript 可以说是目前唯一较好的选择。
当然,凡事都有两面性,TypeScript 有一定的学习成本,比如:Interfaces,Generics,Enums 等前端工程师不是很熟悉的概念,短期内多少会增加一些开发成本,集成和构建一些库会有一定的工作量,比如我们用 React 来开发一个前端工程,那么你就需要进行一些配置,当然你也可以直接使用 create-react-app 来创建一个 TypeScript + React 工程。
TypeScript 与 JavaScript 对比表格
| 对比项目 | TypeScript | JavaScript | 注意 |
| --------| --- | --- | --- |
| 基本类型 | boolean number string Array Tuple Enum any void null undefined never object | string number boolean null undefined symbol | TypeScript 中 object 表示的是不是 JavaScript 基本类型的类型|
| 变量声明 | let const var| let const var | 基本一致 |
| 接口 | interface | 无 | TypeScript 的核心是类型检查,因此接口充当了命名这些类型的角色 |
| 类 | class abstract class readonly … | class 无 abstract class | 基本一致,但不同的可能发生在未来,TypeScript 使用 private 来定义私有,而 JavaScript 未来极有可能将 #.xx 来定义私有写入标准。TypeScript 支持抽象类,只读等等。|
| 函数 | N | N | 基本一致,参数赋默认值,剩余参数等等,唯一不同的是 TypeScript 支持?可选参数 |
| 泛型 | Generics | 无 | 泛型是一个特别灵活的可重用指定不同类型来控制具体类型的类型,TypeScript 支持 |
| 枚举 | Enums | 无 | TypeScript 支持的枚举不仅可以默认从 0 开始,也可以赋值具体的字符串,它的操作空间非常大 |
| 类型推断 | 支持 | 无 | let x = 3; TypeScript 可以通过 3 来推断 x 的类型是 number |
| 高阶类型 | & typeof instanceof … | 无 | TypeScript 独有 |
| Symbols | N | N | Symbol 一样 |
| 迭代器 | N | N | 如果实现了 Symbol.iterator ,那么就被视为可迭代的,术语上和 JavaScript 定义的一样 |
| Generators | N | N | 一样 |
| 模块系统 | N | export import | 事实上 TypeScript 支持多种多样的模块系统,既有 ESModule 也有 Commonjs 规范,甚至还有 AMD UMD 等 |
| 其他 | N | N | 由于 TypeScript 是 JavaScript 的超集,因此 ES2016 之后以及 ESNext 定义的 api 都可以直接在 TypeScript 中使用 并不需要语言支持,至于其他一些比如 JSX Mixins 等等,由于这些不属于 JavaScript 标准因此这里不再复述 。|
环境配置
TypeScript 3.3
既然上文我们了解到 TypeScript 需要编译,那么我们肯定会使用到编译工具,因此在我们开始正式学习 TypeScript 之前需要安装一下编译环境。
- 安装 Node.js 10.15.3 LTS
- 安装 typescript
$ npm install -g typescript
$ tsc --version
国际惯例 Hello World
创建一个 helloworld.ts
文件,然后输入:
let helloworld = "";
console.log(helloworld);
$ tsc helloworld.ts
现在我们稍微改动一行代码,在 JavaScript 中我们完全可以如此:
let helloworld = "";
helloworld = 1;
对于语法而言这完全是正确的,但对于语句来说,不能锁定类型在某些情况下,很容易出现未知异常的问题,现在我们执行 tsc
来编译它:
$ tsc helloworld.ts
helloworld.ts:5:1 - error TS2322: Type '1' is not assignable to type 'string'.
5 helloworld = 1;
~~~~~~~~~~
Found 1 error.
编译器可以明确的告知你不能将 1
赋值给字符串类型,这就是 TypeScript 带来的魅力。
tsconfig.json
tsconfig.json
文件可以指定编译 TypeScript 项目所需的根目录以及编译器选项,如果你的工程中存在 tsconfig.json
则表示 tsconfig.json
所在的目录为你 TypeScript 工程的根目录。
使用 tsconfig.json
的规则:
- 如果你直接运行
tsc
在这种情况下,编译器将从当前目录开始搜索tsconfig.json
并向上查找 - 你也可以直接运行
tsc -p xxx
在这种情况下,xxx 目录上必须存在tsconfig.json
文件 - 如果在命令行中直接输出文件
tsc helloworld.ts
编译器将忽略tsconfig.json
文件
当然你可以直接使用 tsc --init
在当前运行的目录中创建一个 tsconfig.json
(推荐)。
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
// "lib": [],
// "allowJs": true,
// "checkJs": true,
// "jsx": "preserve",
// "declaration": true,
// "declarationMap": true,
// "sourceMap": true,
// "outFile": "./",
// "outDir": "./",
// "rootDir": "./",
// "composite": true,
// "removeComments": true,
// "noEmit": true,
// "importHelpers": true,
// "downlevelIteration": true,
// "isolatedModules": true,
"strict": true,
// "noImplicitAny": true,
// "strictNullChecks": true,
// "strictFunctionTypes": true,
// "strictBindCallApply": true,
// "strictPropertyInitialization": true,
// "noImplicitThis": true,
// "alwaysStrict": true,
// "noUnusedLocals": true,
// "noUnusedParameters": true,
// "noImplicitReturns": true,
// "noFallthroughCasesInSwitch": true,
// "moduleResolution": "node",
// "baseUrl": "./",
// "paths": {},
// "rootDirs": [],
// "typeRoots": [],
// "types": [],
// "allowSyntheticDefaultImports": true,
"esModuleInterop": true
// "preserveSymlinks": true,
// "sourceRoot": "",
// "mapRoot": "",
// "inlineSourceMap": true,
// "inlineSources": true,
// "experimentalDecorators": true,
// "emitDecoratorMetadata": true,
}
}
参考资料:
- tsconfig.json
编译器选项
这些编译器选项也是 tsconfig.json
中的配置项
当你不想通过 tsconfig.json
来配置编译器的话,也可以使用如下的编译器选项通过命令行来运行编译器执行编译任务,这里只介绍几个常用的,其他的配置请参考 Compiler Options 表格。
--watch 或 -w
监视输入文件的更改并触发重新编译--version 或 -v
查看编译器的版本--types=["字符串"]
类型描述文件的名称列表--target=""
指定编译的 ECMAScript 的版本,默认版本是 ES3--strict
启用所有严格类型检查--sourceMap
生成对应的 .map 文件--outFile
输出到单个文件--outDir
输出到目录--module 或 -m
指定使用哪种模块标准来输出代码,选项有 "None", "CommonJS", "AMD", "System", "UMD", "ES6", "ES2015" or "ESNext"--lib
要包含在编译中的库文件列表,比如 ES2015.Promise--jsx
在.tsx
文件中支持JSX
可指定要编译输出的JSX
类型
与 Webpack 集成
事实上我们不可能只单独的使用 TypeScript 而是要将它融入到现有的技术栈中和工具结合起来,至少目前为止多数的前端工程都将 Webpack 视为标准配置,因此 TypeScript 编译器和 Webpack 集成在一起,这非常有用。
awesome-typescript-loader
是 TypeScript 提供的在 Webpack 中使用的 loader,因此我们只需要:
$ yarn add typescript awesome-typescript-loader source-map-loader --dev
然后在你的 webpack.config.json
文件中配置即可:
var fs = require('fs')
var path = require('path')
var webpack = require('webpack')
const { CheckerPlugin } = require('awesome-typescript-loader');
var ROOT = path.resolve(__dirname)
module.exports = {
entry: './src/index.tsx',
devtool: 'inline-source-map',
output: {
path: ROOT + '/dist',
filename: '[name].bundle.js',
sourceMapFilename: '[name].bundle.map.js',
publicPath: '//localhost:8889/dist/',
},
devServer: {
inline: true,
quiet: true,
contentBase: "./",
port: 8889
},
module: {
rules: [
{ test: /\.ts[x]?$/, loader: "awesome-typescript-loader" },
{ enforce: "pre", test: /\.ts[x]$/, loader: "source-map-loader" },
]
},
resolve: {
extensions: [".ts", ".tsx"],
alias: {
'@': path.resolve(ROOT,'src')
}
},
plugins: [
new CheckerPlugin(),
]
}
结语
这篇文章简单介绍了 JavaScript 的历史和 TypeScript 所获取的收益,有一句古话:了解历史才能真正了解这些故事,时代变化 JavaScript 可以说是目前为止唯一实现了 Write Once Run Anywhere 的脚本语言(当然 C 语言才是),它的热度和趋势长久不衰,但 JavaScript 本身也有其语言的缺陷,也许在未来新的标准会慢慢补齐它,至少现在让我们用 TypeScript 来解决你可能面临的问题吧。