TypeScript 上手教程
无疑,对于大型项目来说,Vanilla Js 无法满足工程需求。早在 2016 年 Anuglar 在项目中引入 TypeScript 时,大概也是考虑到强类型约束对于大型工程的必要性,具体选型考虑可参考这篇文章。然后可以看到 TypeScript 在社区中逐渐升温。但凡社区中举足轻重的库,如果不是原生使用 TypeScript 编写,那么也是通过声明文件的方式对 TypeScript 提供支持,比如 React(虽然不是包含在官方仓库中,而是通过 所以 TypeScript 绝对是趋势。它所带来的工程效率上的提升,是在使用 Vanilla Js 时无法体会到的。可能前期反而会因为类型约束而变得束手束脚影响效率,但这是个学习成本的问题,对于任何一门技术而言都会存在。 如果你有 Java 或 C# 的基础,那 TypeScript 学起来几乎没什么成本。 安装与配置安装$ npm install -g typescript
# or
$ yarn global add typescript
安装成功后,其 CLI 命令为 $ tsc --version
Version 3.3.3333
常用的命令: 编译文件$ tsc main.ts
编译时传递编译参数: $ tsc --target es3 main.ts
完整的编译参数可在官网 Compiler Options 文档中查阅。 初始化配置文件除了通过 CLI 传递编译参数控制编译的行为,也可通过创建 $ tsc --init
message TS6071: Successfully created a tsconfig.json file.
该命令在当前目录创建一个
tsconfig.json
{
"compilerOptions": {
/* Basic Options */
"target": "es5" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */,
"module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */,
// "lib": [], /* Specify library files to be included in the compilation. */
// "allowJs": true, /* Allow javascript files to be compiled. */
// "checkJs": true, /* Report errors in .js files. */
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
// "declaration": true, /* Generates corresponding '.d.ts' file. */
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
// "sourceMap": true, /* Generates corresponding '.map' file. */
// "outFile": "./", /* Concatenate and emit output to single file. */
// "outDir": "./", /* Redirect output structure to the directory. */
// "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
// "composite": true, /* Enable project compilation */
// "removeComments": true, /* Do not emit comments to output. */
// "noEmit": true, /* Do not emit outputs. */
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
/* Strict Type-Checking Options */
"strict": true /* Enable all strict type-checking options. */,
// "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
// "strictNullChecks": true, /* Enable strict null checks. */
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
// "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
// "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
/* Additional Checks */
// "noUnusedLocals": true, /* Report errors on unused locals. */
// "noUnusedParameters": true, /* Report errors on unused parameters. */
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
/* Module Resolution Options */
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
// "typeRoots": [], /* List of folders to include type definitions from. */
// "types": [], /* Type declaration files to be included in compilation. */
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
"esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
/* Source Map Options */
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
/* Experimental Options */
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
}
}
VS Code 上手TS 带来的一大好处是其静态类型检查能跟编辑器很好地结合,智能健全的自动提示自不必说。推荐 VS Code 作为编辑,其对 TypeScript 有原生的支持。 用好这几个快捷键,更是提升效率的关键。 重命名通过 F2 对标识符重重命名。这里标识符可以是变量名,方法函数名,类名或者其他字面量。如果写代码过程中发现命名不合理想重命名,一定使用这个快捷键来操作,它的好处是,只需改一处,其他与该标识符有关的地方,将自动被批量替换成新的,甚至该标识符使用的地方不在同一个文件中,也能被正确地自动应用上变更后的名称。省去了人工替换和检查代码的麻烦。关键人工容易出错,搜索加替换的方式只是根据字符串来进行的,而该命令是通过分析代码的语法树进行的。 使用 F2 进行变量重命名的展示 快速跳转
跳转到定义
标识符间的跳转切换
在报错处循环切换
光标位置的来回切换 命令面板通过 command + shift + p 打开命令面板。几乎所有功能可以通过这里的命令来完成。 比如,
代码折叠与展开
主题的切换 最后,你始终可通过搜索 快捷键列表 在线工具如果本地没有环境,可通过 Playground ・ TypeScript 这个在线的编辑器,编辑 TypeScript 和时实查看输出。 类型声明TypeScript 中,通过在变量后面添加冒号指定其类型。 let fruit: string;
// Variable 'fruit' is used before being assigned.
console.log(fruit);
当声明 函数的类型包含了入参的类型和返回值的类型。入参自不必说,像上面那样冒号后指定,而返回值的类型,则是通过在入参列表结束的括号后添加冒号来指定的。 function addOne(num: number): number {
return num + 1;
}
如果每次写个变量或函数都需要手动指定其类型,岂不是很麻烦。所以,在一切能够推断类型的情况下,是不必手动指定的。比如声明变量并初始化,会根据初始化的值来推断变量类型。函数会根据其 return 的值来推断其返回类型。 /** 推断出的函数类型为:(num: number) => number */
function addOne(num: number) {
return num + 1;
}
/** age:number */
const age = 18;
const virtualAge = addOne(age);
console.log(`在下虚岁 ${virtualAge}`);
TypeScript 中的类型JavaScript 中原生有 7 中数据类型,其中 Ojbect 为可看成数据集合,而其他 6 种(布尔,字符串,数字, 虽然 JavaScript 中有数据类型的概念,但它是动态的,变量的类型根据所存储的值而变化。TypeScript 作为其超集,将上面的数据类型进行了扩充,在 TypeScript 里,可以通过各种组合创建出更加复杂的数据类型。同时,TypeScript 让数据类型固定,成为静态可分析的。 比如,如果一个函数的入参指定为数字,那么调用的时候传递了字符串,这个错误在写码过程中就直接可检查到并抛出。 function addOne(num: number) {
return num + 1;
}
/** Argument of type '"blah"' is not assignable to parameter of type 'number'. */
addOne("blah");
JavaScript 原始类型加上扩展的几个类型(Any, Never, Void, Enum)组成了 TypeScript 中基本的类型。更加详细的信息可参考 Basic Types。 Boolean布尔值,其值只能是 let isEmployee: boolean = false;
function hasPermission(role: string): boolean {
return role === "admin" ? true : false;
}
Number数字类型,不区分整形与浮点,所有数字均当作浮点数字对待。同时也支持二进制,八进制,十六进制数字。 let decimal: number = 6;
let hex: number = 0xf00d;
let binary: number = 0b1010;
let octal: number = 0o744;
String字符串类型。TypeScript 中可使用 ES6 以之后这些还未实现的功能,所以模板字符串是可以放心使用的。 let fruit: string = "Apple";
console.log(`My favourite fruit is ${fruit}`);
SymbolES6 中新增,由 let sym = Symbol("foo");
typeof sym; // "symbol"
sym.toString(); // Symbol(foo)
注意,因为是新特性,需要在 {
"lib": ["dom","es2015"] /* Specify library files to be included in the compilation. */
}
Object除了 JavaScript 中 6 个原始类型之外的类型。 function create(source: Object) {
return Object.create(source);
}
// ✅
create({});
// ✅
create(window);
// Argument of type 'undefined' is not assignable to parameter of type 'Object'
create(null);
// Argument of type 'undefined' is not assignable to parameter of type 'Object'.ts(2345)
create(undefined);
Null 与 Undefined两者其实是其他任意类型的子类型。比如,一个变量定义后没有初始化,此时其值自动为 let age: number;
console.log(age); // undefined
age = 9;
console.log(age); // 9
age = null;
console.log(age); // null
当开启 对于这两种类型,在强制检查下,除非显式对变量进行声明其可空可未初始化。 + let age: number | null | undefined;
console.log(age); // undefined
age = 9;
console.log(age); // 9
age = null;
console.log(age); // null
这里 一般来说,建议开启强制检查,这样 TypeScript 能够最大化帮我们发现代码中的错误,在写码时就发现问题。 Any表示任意类型。此时等同于普通的 JavaScript 代码,因为标记为 let someVar: any;
someVar = "饭后百步走,活到 99"; // ✅
someVar = 99; // ✅
someVar = undefined; // ✅
someVar = null; // ✅
即便在开启强制检查的情况下,上面的操作是没有任何问题的。一般情况下,只在一些特殊情况下使用 any,比如老代码的兼容,三方库代码的引入。 declare var $: any;
$.extenfd({}, { foo: "foo" });
这里,因为 jQuery 是没有类型的三方库代码,但我们知道页面中引入后是可以调用它上面的方法的,只是 TypeScript 不识别,所以我们通过声明一个 Void常见于函数没有返回值的情况。 /** () => void */
function foo() {
console.log("foo works");
}
如果将变量显式设置为 Never这个类型就比较有意思了,正如其名,表示永远也不会发生的类型。 function error(message: string): never {
throw new Error(message);
}
关于 interface Square {
kind: "square";
size: number;
}
interface Rectangle {
kind: "rectangle";
width: number;
height: number;
}
interface Circle {
kind: "circle";
radius: number;
}
type Shape = Square | Rectangle | Circle;
function assertNever(x: never): never {
throw new Error("Unexpected object: " + x);
}
function area(s: Shape) {
switch (s.kind) {
case "square":
return s.size * s.size;
case "rectangle":
return s.height * s.width;
case "circle":
return Math.PI * s.radius ** 2;
default:
return assertNever(s); // error here if there are missing cases
}
}
这里定义了三种基础的形状类型 数组数组本身是容器,需要上面的基本类型联合使用。 /** 字符串数组 */
let names: Array<string>;
/** 存放数字的数组 */
let nums: Array<number>;
/** 数组中各元素类型不确定 */
let data: Array<any>;
还可通过下面的方式来表示: /** 字符串数组 */
let names: string[];
/** 存放数字的数组 */
let nums: number[];
/** 数组中各元素类型不确定 */
let data: any[];
当数组中元数个数有限且提前知晓每个位置的类型时,可将这种数据声明成元组(tuple,如果你用过 Python 应该不会陌生)。 let point: [number, number] = [7, 5];
let formValidateResult: [booelan, string] = [false, "请输入用户名"];
枚举枚举类型在强类型语言中是很常见的,用来标识变量可取的候选值。 enum Gender {
Male,
Female
}
console.log(Gender.Female===1); // true
枚举实质上是通过更加语义化的符号来表示数字类型的值,比如上面 可通过手动指定的方式来改变默认的 0。 enum Gender {
+ Male = 1,
Female
}
console.log(Gender.Female); // 2
当然,你也可以让枚举表示其他类型的值,而不是数字。只不过需要手动指定。如果手动指定非数字类型的值,那么枚举中的项是无法像数字那样自动自增以初始化自己,所以需要手动为每个项都显式指定一下它应该代表的值。 enum Gender {
Male = "male",
Female // Enum member must have initializer.
}
正确的做法: enum Gender {
Male = "male",
Female = "female" // ✅
}
console.log(Gender.Female); // female
枚举中的值也不一定都得是同一类型,所以下面这样也是可以的: enum Gender {
Male = "male",
Female = 2 // ✅also ojbk
}
console.log(Gender.Female); // 2
函数类型函数的类型包含了入参及返回值两部分。 (num: number) => string;
看起来像其他静态类型语言比如 Java 中的抽象方法,只有声明没有实现的样子。 interface Calculator {
name: string;
calculate: (x: number, y: number) => number;
}
class Computer implements Calculator {
constructor(public name: string) {}
calculate(x: number, y: number) {
return x + y;
}
}
const counter: Calculator = {
name: "counter",
calculate: (x: number, y: number) => {
return x - y;
}
};
|