1、环境搭建
IDE使用VS Code进行安装:
npm i -g typescript
tsc -v // 查看安装的ts版本
npm i -g ts-node //用来直接运行ts文件的node环境
复制代码
Demo编写:在练习目录下输入tsc --init
可以快速创建一个 tsconfig.json
文件或者手动新建一个tsconfig.json
文件。默认配置如下:
{
"compilerOptions": {
/* Visit https://aka.ms/tsconfig.json to read more about this file */
/* Basic Options */
// "incremental": true, /* Enable incremental compilation */
"target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ES2021', or 'ESNEXT'. */
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', 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', 'react', 'react-jsx' or 'react-jsxdev'. */
// "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 */
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
// "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. */
// "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an 'override' modifier. */
// "noPropertyAccessFromIndexSignature": true, /* Require undeclared properties from index signatures to use element accesses. */
/* 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. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
/* 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. */
/* Advanced Options */
"skipLibCheck": true, /* Skip type checking of declaration files. */
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
}
}
复制代码
然后,在练习目录新建一个hello.ts
,输入如下代码:
function sayHello(person: string) {
return "Hello, " + person;
}
console.log(sayHello("user"));
复制代码
使用 tsc(TypeScript Compiler)
命令将 .ts 文件转译为 .js 文件:
tsc hello.ts
复制代码
得到hello.js
:
function sayHello(person) {
return "Hello, " + person;
}
console.log(sayHello("user"));
复制代码
也可以直接使用 ts-node
直接运行hello.ts
ts-node hello.ts // Hello, user
复制代码
注意:指定转译的目标文件后,tsc 将忽略当前应用路径下的 tsconfig.json 配置,因此我们需要通过显式设定如下所示的参数,让 tsc 以严格模式检测并转译 TypeScript
代码。
tsc HelloWorld.ts --strict --alwaysStrict false
tsc HelloWorld.ts --strict --alwaysStrict false --watch // 监听文件内容变更,实时进行类型检测和代码转译
复制代码
VS Code天然支持TS,注意自带的TS版本和工作区的TS版本差异!!!如果两个版本之间存在不兼容的特性,就会造成开发阶段和构建阶段静态类型检测结论不一致的情况,因此,我们务必将 VS Code 语言服务配置成使用当前工作区的 TS版本。
2、简单基础类型
TypeScript 其实就是类型化的 JavaScript,它不仅支持 JavaScript 的所有特性,还在 JavaScript 的基础上添加了静态类型注解扩展。可以这么理解:TypeScript 其实就是 JavaScript 的超集。
基本语法
let num: number = 1;
复制代码
上面的number
表示数字类型,:
用来分割变量和类型的分隔符。number
可以换成其他JS
原始类型:number、string、boolean、null、undefined、symbol 等。
原始类型
在 JavaScript 中,原始类型指的是非对象且没有方法的数据类型,它包括 string、number、bigint、boolean、undefined 和 symbol 这六种 (补充:BigInt
也是原始类型,且注意它和number类型不兼容)。
静态类型检测
在编译时期,静态类型的编程语言即可准确地发现类型错误,可以将很多低级错误早发现早治疗。
const goodStr: string = 'abc';
const badStr: string = 123; // 不能将类型“number”分配给类型“string”。ts(2322)
复制代码
3、复杂基础类型
数组和元组
数组类型(Array)
const arrayOfNumber: number[] = [1, 2, 3];
const arrayOfString: string[] = ['a', 'b', 'c'];
// 或者使用泛型。但是更推荐上面的写法,因为泛型写法与JSX语法冲突
const arrayOfNumber: Array<number> = [1, 2, 3];
const arrayOfString: Array<string> = ['a', 'b', 'c'];
复制代码
元组类型(Tuple)
元组最重要的特性是可以限制数组元素的个数和类型,它特别适合用来实现多值返回。
但是,在 JavaScript 中并没有元组的概念,作为一门动态类型语言,它的优势是天然支持多类型元素数组。但是TypeScript
的元组类型正好弥补了这个不足,使得定义包含固定个数元素、每个元素类型未必相同的数组成为可能。
注意:数组类型的值只有显示添加了元组类型注解后(或者使用
as const
,声明为只读元组),TypeScript
才会把它当作元组,否则推断出来的类型就是普通的数组类型(第 4 讲会介绍类型推断)。
特殊类型
any
any
指的是一个任意类型,它是官方提供的一个选择性绕过静态类型检测的作弊方式。【不推荐使用!Any is Hell(Any 是地狱)】除非有充足的理由,否则我们应该尽量避免使用 any ,并且开启禁用隐式 any 的设置。
我们可以把任何类型的值赋值给 any 类型的变量,也可以把 any 类型的值赋值给任意类型(除 never 以外)的变量。
let anything: any = {};
anything.doAnything(); // 不会提示错误
anything = 1; // 不会提示错误
anything = 'x'; // 不会提示错误
let num: number = anything; // 不会提示错误
let str: string = anything; // 不会提示错误
复制代码
unknown
unknown 是 TypeScript 3.0 中添加的一个类型,它主要用来描述类型并不确定的变量。
比如在多个 if else 条件分支场景下,它可以用来接收不同条件下类型各异的返回值的临时变量,如下代码所示:
let result: unknown;
if (x) {
result = x();
} else if (y) {
result = y();
} ...
复制代码
在 3.0 以前的版本中,只有使用 any 才能满足这种动态类型场景。
与 any 不同的是,unknown 在类型上更安全。比如我们可以将任意类型的值赋值给 unknown,但 unknown 类型的值只能赋值给 unknown 或 any,如下代码所示:
let result: unknown;
let num: number = result; // 提示 ts(2322)
let anything: any = result; // 不会提示错误
复制代码
使用 unknown 后,TypeScript
会对它做类型检测。但是,如果不缩小类型(Type Narrowing),我们对 unknown 执行的任何操作都会出现如下所示错误:
let result: unknown;
result.toFixed(); // 提示 ts(2571)
复制代码
而所有的类型缩小手段对 unknown 都有效,如下代码所示:
let result: unknown;
if (typeof result === 'number') {
result.toFixed(); // 此处 hover result 提示类型是 number,不会提示错误
}
复制代码
void、undefined、null
它们实际上并没有太大的用处,三个废柴。
void 类型,它仅适用于表示没有返回值的函数,即如果该函数没有返回值,那它的类型就是 void。在 strict 模式下,声明一个 void 类型的变量几乎没有任何实际用处,因为我们不能把 void 类型的变量值再赋值给除了 any 和 unkown 之外的任何类型变量。
undefined 类型 和 null 类型,它们是 TypeScript
值与类型关键字同名的唯二例外。
let undeclared: undefined = undefined; // 鸡肋
let nullable: null = null; // 鸡肋
复制代码
undefined 的最大价值主要体现在接口类型上,它表示一个可缺省、未定义的属性。
null 的价值主要体现在接口制定上,它表明对象或属性可能是空值。
此外,undefined 和 null 类型还具备警示意义,它们可以提醒我们针对可能操作这两种(类型)值的情况做容错处理。
我们需要类型守卫(Type Guard)在操作之前判断值的类型是否支持当前的操作。类型守卫既能通过类型缩小影响 TypeScript 的类型检测,也能保障 JavaScript 运行时的安全性,如下代码所示:
const userInfo: {
id?: number;
name?: null | string
} = { id: 1, name: 'Captain' };
if (userInfo.id !== undefined) { // Type Guard
userInfo.id.toFixed(); // id 的类型缩小成 number
}
复制代码
不建议随意使用非空断言来排除值可能为 null 或 undefined 的情况,因为这样很不安全。
userInfo.id!.toFixed(); // ok,但不建议
userInfo.name!.toLowerCase() // ok,但不建议
复制代码
而比非空断言更安全、类型守卫更方便的做法是使用单问号(Optional Chain)、双问号(空值合并),我们可以使用它们来保障代码的安全性,如下代码所示:
userInfo.id?.toFixed(); // Optional Chain
const myName = userInfo.name?? `my name is ${info.name}`; // 空值合并。当左侧的操作数为 null 或者 undefined 时,返回其右侧操作数,否则返回左侧操作数。
复制代码
never
never 表示永远不会发生值的类型。
// 这个统一抛出错误的函数永远不会有返回值,所以它的返回值类型就是 never
function ThrowError(msg: string): never {
throw Error(msg);
}
// 死循环的函数,永远不会有返回值
function InfiniteLoop(): never {
while (true) {}
}
复制代码
never 是所有类型的子类型,它可以给所有类型赋值,如下代码所示:
let Unreachable: never = 1; // ts(2322)
Unreachable = 'string'; // ts(2322)
Unreachable = true; // ts(2322)
let num: number = Unreachable; // ok
let str: string = Unreachable; // ok
let bool: boolean = Unreachable; // ok
复制代码
但是反过来,除了 never 自身以外,其他类型(包括 any 在内的类型)都不能为 never 类型赋值。
在恒为 false 的类型守卫条件判断下,变量的类型将缩小为 never(never 是所有其他类型的子类型,所以是类型缩小为 never,而不是变成 never)。因此,条件判断中的相关操作始终会报无法更正的错误(我们可以把这理解为一种基于静态类型检测的 Dead Code 检测机制),如下代码所示:
const str: string = 'string';
if (typeof str === 'number') {
str.toLowerCase(); // Property 'toLowerCase' does not exist on type 'never'.ts(2339)
}
复制代码
基于 never 的特性,我们还可以使用 never 实现一些有意思的功能。比如我们可以把 never 作为接口类型下的属性类型,用来禁止写接口下特定的属性,示例代码如下:
const props: {
id: number,
name?: never
} = {
id: 1
}
props.name = null; // ts(2322))
props.name = 'str'; // ts(2322)
props.name = 1; // ts(2322)
复制代码
无论我们给 props.name 赋什么类型的值,它都会提示类型错误,实际效果等同于 name 只读 。
object
object 类型表示非原始类型的类型,也是个没有什么用武之地的类型,如下所示的一个应用场景是用来表示 Object.create 的类型。
declare function create(o: object | null): any;
create({}); // ok
create(() => null); // ok
create(2); // ts(2345)
create('string'); // ts(2345)
复制代码
类型断言(Type Assertion)
const arrayNumber: number[] = [1, 2, 3, 4];
const greaterThan2: number = arrayNumber.find(num => num > 2); // 提示 ts(2322)
复制代码
静态类型对运行时的逻辑无能为力。即使我们知道上面有大于2的元素3和4,但是,在 TypeScript 看来,greaterThan2 的类型既可能是数字,也可能是 undefined,所以上面的示例中提示了一个 ts(2322) 错误,此时我们不能把类型 undefined 分配给类型 number。
我们可以使用一种笃定的方式——类型断言(类似仅作用在类型层面的强制类型转换)告诉 TypeScript 按照我们的方式做类型检查。
比如,我们可以使用 as 语法做类型断言,如下代码所示:
const arrayNumber: number[] = [1, 2, 3, 4];
const greaterThan2: number = arrayNumber.find(num => num > 2) as number;
复制代码
或者是使用尖括号 + 类型的格式做类型断言,如下代码所示:
const arrayNumber: number[] = [1, 2, 3, 4];
const greaterThan2: number = <number>arrayNumber.find(num => num > 2); //<>与JSX语法冲突,推荐as的写法
复制代码
常量断言:“字面量值 + as const”语法
/** str 类型是 '"str"' */
let str = 'str' as const;
/** readOnlyArr 类型是 'readonly [0, 1]' */
const readOnlyArr = [0, 1] as const;
复制代码
4、字面量类型、类型推断、类型拓宽和类型缩小
在很多情况下,TypeScript
会根据上下文环境自动推断出变量的类型,无须我们再写明类型注解。
{
let str = 'this is string'; // 等价str: string
let num = 1; // 等价num: number
let bool = true; // 等价bool: boolean
}
{
const str = 'this is string'; // 不等价str: string, 等价str: 'this is string'
const num = 1; // 不等价num: number, 等价num: 1
const bool = true; // 不等价bool: boolean, 等价bool: true
}
复制代码
类型推断
在 TypeScript
中,具有初始化值的变量、有默认值的函数参数、函数返回的类型都可以根据上下文推断出来。
{
/** 根据参数的类型,推断出返回值的类型也是 number */
function add1(a: number, b: number) {
return a + b;
}
const x1= add1(1, 1); // 推断出 x1 的类型也是 number
/** 推断参数 b 的类型是数字或者 undefined,返回值的类型也是数字 */
function add2(a: number, b = 1) {
return a + b;
}
const x2 = add2(1);
const x3 = add2(1, '1'); // ts(2345) Argument of type '"1"' is not assignable to parameter of type 'number | undefined
}
复制代码
上下文推断
变量的类型除了可以通过被赋值的值进行推断,某些特定的情况下,也可以通过变量所在的上下文环境推断变量的类型,具体示例如下:
{
type Adder = (a: number, b: number) => number;
const add: Adder = (a, b) => {
return a + b;
}
const x1 = add(1, 1); // 推断出 x1 类型是 number
const x2 = add(1, '1'); // ts(2345) Argument of type '"1"' is not assignable to parameter of type 'number
}
复制代码
字面量类型
在 TypeScript
中,字面量不仅可以表示值,还可以表示类型,即所谓的字面量类型。包括字符串字面量类型、数字字面量类型、布尔字面量类型,对应的字符串字面量、数字字面量、布尔字面量分别拥有与其值一样的字面量类型。
{
let specifiedStr: 'this is string' = 'this is string';
let specifiedNum: 1 = 1;
let specifiedBoolean: true = true;
}
复制代码
字面量类型是集合类型的子类型,它是集合类型的一种更具体的表达。
字面量类型真正的应用场景是可以把多个字面量类型组合成一个联合类型,用来描述拥有明确成员的实用的集合。
type Direction = 'up' | 'down';
function move(dir: Direction) {
// ...
}
move('up'); // ok
move('right'); // ts(2345) Argument of type '"right"' is not assignable to parameter of type 'Direction'
复制代码
Literal Widening
所有通过 let 或 var 定义的变量、函数的形参、对象的非只读属性,如果满足指定了初始值且未显式添加类型注解的条件,那么它们推断出来的类型就是指定的初始值字面量类型拓宽后的类型,这就是字面量类型拓宽。
{
let str = 'this is string'; // 类型是 string
let strFun = (str = 'this is string') => str; // 类型是 (str?: string) => string;
const specifiedStr = 'this is string'; // 类型是 'this is string'
let str2 = specifiedStr; // 类型是 'string'
let strFun2 = (str = specifiedStr) => str; // 类型是 (str?: string) => string;
}
复制代码
基于字面量类型拓宽的条件,我们可以通过如下所示代码添加显示类型注解控制类型拓宽行为。
{
const specifiedStr: 'this is string' = 'this is string'; // 类型是 '"this is string"'
let str2 = specifiedStr; // 即便使用 let 定义,类型是 'this is string'
str2 = 'aaa' // 不能将类型“"aaa"”分配给类型“"this is string"”。ts(2322)
}
复制代码
Type Widening
比如对 null 和 undefined 的类型进行拓宽,通过 let、var 定义的变量如果满足未显式声明类型注解且被赋予了 null 或 undefined 值,则推断出这些变量的类型是 any:
{
let x = null; // 类型拓宽成 any
let y = undefined; // 类型拓宽成 any
/** -----分界线------- */
const z = null; // 类型是 null
/** -----分界线------- */
let anyFun = (param = null) => param; // 形参类型是 null, 因为开启了 strictNullChecks=true
let z2 = z; // 类型是 null, 因为开启了 strictNullChecks=true
let x2 = x; // 类型是 null, 因为开启了 strictNullChecks=true
let y2 = y; // 类型是 undefined, 因为开启了 strictNullChecks=true
}
复制代码
Type Narrowing
通过某些操作将变量的类型由一个较为宽泛的集合缩小到相对较小、较明确的集合,这就是 “Type Narrowing”(类型缩小)。
比如,我们可以使用类型守卫(可以理解成条件判断)将函数参数的类型从 any 缩小到明确的类型,具体示例如下:
{
let func = (anything: string | number) => {
if (typeof anything === 'string') {
return anything; // 类型是 string
} else {
return anything; // 类型是 number
}
};
}
复制代码
同样,我们可以使用类型守卫将联合类型缩小到明确的子类型,具体示例如下:
{
let func = (anything: string | number) => {
if (typeof anything === 'string') {
return anything; // 类型是 string
} else {
return anything; // 类型是 number
}
};
}
复制代码
也可以通过字面量类型等值判断(===)或其他控制流语句(包括但不限于 if、三目运算符、switch 分支)将联合类型收敛为更具体的类型,如下代码所示:
{
type Goods = 'pen' | 'pencil' |'ruler';
const getPenCost = (item: 'pen') => 2;
const getPencilCost = (item: 'pencil') => 4;
const getRulerCost = (item: 'ruler') => 6;
const getCost = (item: Goods) => {
if (item === 'pen') {
return getPenCost(item); // item => 'pen'
} else if (item === 'pencil') {
return getPencilCost(item); // item => 'pencil'
} else {
return getRulerCost(item); // item => 'ruler'
}
}
}
复制代码