TS 基础
背景
-
强类型 vs 弱类型 :
类型安全,更强的类型约束。 强类型不允许任意隐式类型转换,弱类型允许任意类型转换。
-
静态类型语言 vs 动态类型语言:
类型检查的时机不同。静态类型在编译阶段就确定每个变量的类型,而动态类型在运行阶段才确定每个变量的类型。

JS:动态类型语言,弱类型。 被设计时,小规模脚本,且不需要编译。TS 也是弱类型,不会修改 JS 运行时的特性。
强类型:错误更早暴露。代码更智能。重构更牢靠。
TS 特性
- 类型系统是核心特性。类型安全,定义了类型后的方法提示。
- 静态类型的弱类型。
Flow
JS 的类型检查工具
JS 的类型检查,是一个小工具,在需要的时候添加类型,学习成本较小。
-
使用方式
yarn add flow-binyarn flow inityarn flow工具推荐: vscode 插件 Flow Language Support
-
移除类型注解
-
yarn add flow-remove-typesyarn flow-remove-types . -d dist//转换到某个目录下 -
yarn add @babel/core @babel/cli @babel/preset-flow --dev{"presets":["@babel/preset/preset-flow"]}//.babelrcyarn babel src -d dist
-
类型推断:如果未添加类型注解,则自动推断类型的特性,但尽可能还是添加类型注解。
思路:编写的代码和实际运行的代码分开,添加了编译的环节。
// @flow 加在开头使用flow检测类型.也可用 /* @flow */
function sum(a: number, b: number) {
//类型注解
return a + b;
}
function foo(): void {
//返回值的注解
//类型注解:函数返回值为空
//no return
}
//标识数组类型
//由数字组成的数组
const arr1: Array<number> = [1, 2, 3];
const arr2: number[] = [1, 2, 3];
//元组:固定长度,固定类型的数组
const foo: [string, number] = ["foo", 1];
//标识对象类型
const obj1: { foo: string, bar: number } = { foo: "string", bar: 1 }; //至少有foo,bar这两个成员
const obj2: { foo?: string, bar: number } = { foo: "string", bar: 1 }; //添加?表示可选的
const obj3: { [string]: string } = {}; //k限制key和值的类型,不限制key的个数
//标识函数类型
function foo(callback: (string, number) => void) {
//回调函数的参数以及返回值的类型注解
callback("string", 1);
}
//特殊类型
const a: "foo" = "foo"; //字面量类型,只能是'foo'
const type: "success" | "warning" | "danger" = "success"; //联合类型,只能是其中之一
const b: string|number //string 或者字符串
const StringOrNumber = string | number //给类型定义别名,可复用了
const b: StringOrNumber
const gender:?number = null //加?则可以使用null/undefined
const gender: number | null | undefined //与上面一致
//mixed any 所有类型都可
//mixed :强类型,不能隐式类型转换
//any :不限制,可以隐式类型转换,可以兼容老代码,一般不要使用
function passMixed (value:mixed){
//...
}
function passMixed (value:any){
//...
}
更多类型查询: https://www.saltycrane.com/cheat-sheets/flow-type/latest/
tsc 命令
.ts文件不能直接在 node/浏览器环境中执行,需要先编译为 js 文件。如果希望在 node 环境直接运行 ts ,可以使用ts-node包 ,全局安装后,命令行使用ts-node hello.ts。
tsc hello.ts 以上命令可以编译 hello.ts文件到 js,会在同一个目录下生成同名 js 文件。
tsc xx -w watch mode ,内容改变则重新编译。
yarn tsc --init 生成tsconfig.json
tsc 直接运行,使用 tsconfig.json配置文件,会编译当前目录所有 ts 文件。
{
"include": [ //包含的文件
"**/*.ts"
],
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"moduleResolution": "node",
"sourceMap": true,
"declaration": true,
"outDir": "./dist",
"rootDir": "./src",
"lib": [
"es2015",
"dom",
"es2016",
"es2017",
"esnext"
],
"types": [
"node"
]
}
}
TypeScript: Documentation - What is a tsconfig.json
yarn tsc --local zh-CN 中文错误消息。
TypeScript 只会在编译时对类型进行静态检查,如果发现有错误,编译的时候就会报错。而在运行时,与普通的 JavaScript 文件一样,不会对类型进行检查。
TypeScript 编译的时候即使报错了,还是会生成编译结果,我们仍然可以使用这个编译之后的文件。
如果要在报错的时候终止 js 文件的生成,可以在 tsconfig.json 中配置 noEmitOnError
tsup
另一个 ts 的构建工具,基于 esBuild,打包速度快,可以生成多个支持不同模块规范的包。
安装之后默认不需要配置即可使用。也可以配置入口文件、打包类型、是否生成类型文件等。
类型声明
没有类型声明文件的库,可以自己写类型声明对全局文件进行类型定义。
declare function replace(input: string): string; //如果没有类型声明,自己声明一下类型。
declare function replace(input: number): number; //可以相同的函数名字,参数不同,函数重载
// 命名空间的嵌套
declare namespace $$ {
namespace hh {
function getName(): string;
}
namespace fn {
class init {}
}
}
//声明模块
declare module 'xx' {
export function getName(): string;
}
有类型声明的库,如果声明缺失内容不符合当前业务场景,可以扩展类型声明文件,实现类型融合的特性。
declare module Express {
export interface Request {
user: {
name: string;
};
}
}
- 使用第三方库的 TS 支持问题
很多第三方库原生支持 TS,在使用时就能获得代码补全和提示。
而有些第三方库原生不支持 TS, 可以安装社区维护的类型声明库来获得代码补全的能力。比如使用npm install --save-dev @types/react安装 React 的类型声明库。
DefinitelyTyped 组织的类型定义,包含大多数流行的包的类型定义:DefinitelyTyped: The repository for high quality TypeScript type definitions.,他们的包名是@types/pkgName。
类似 node 包查找顺序的递归查找
-
局部作用域:当前文件
-
项目类型声明文件:
*.d.ts -
node_modules/@types可以通过 tsconfig.json 中 typeRoot 指定类型声明文件位置,默认为@types,会引入所有的类型声明。
可以通过 compilerOptions.types 配置 控制需要引入哪些包的类型声明,以避免全局变量污染的问题。
TS 语法
数据类型
const a: string = 'foo';
const b: number = 100; //NaN,Infinity
const c: boolean = true;
const d: string = null; //严格模型不行
const e: void = undefined; //null/undefined
const f: null = null;
const g: undefined = undefined;
const h: symbol = Symbol();
let myFavoriteNumber: any = 'seven'; //任意类型的值并且可以改变,一般不要使用,兼容老代码使用。
myFavoriteNumber = 7;
//Object
const foo: object = function () {}; // 也可以是[] // {}
const obj: { foo: number; bar: string } = { foo: 123, bar: 'foo' }; //定义普通的对象,key要完全一致,不能多也不能少
//Array
const arr1: Array<number> = [1, 2];
const arr2: number[] = [1, 2];
const arr3: (string | number)[] = [1, '2'];
//Tuple 元组类型:固定长度,固定类型的数组
//应用: React的useState, es2017的Object.entries({foo:123})
const tuple: [number, string] = [18, 'foo'];
const age = tuple[0];
const [age, name] = tuple;
- Enum 枚举类型:只存在几个固定的值 枚举类型会被编译成双向的map,入侵了运行时。
// const postStatus ={Draft:0,Unpublished:1,Published:2} //js模拟枚举类型
enum postStatus { //枚举类型
Draft = 0, //不指定值的话,从0开始累加。只指定第一个则从指定的值开始累加。也可以使用字符串。
Unpublished = 1,
Published = 5,
}
console.log(postStatus[5]); //也可以通过 value 拿到 key
const post = {
status: postStatus.Draft,
};
//枚举类型会入侵到编译后的代码。
//会被编译成双向键值对的对象:可以通过key读取,也可以通过value读取。
const Size = {};
(function (Size) {
Size[(Size['small'] = 3)] = 'small';
Size[(Size['big'] = 4)] = 'big';
Size[(Size['large'] = 5)] = 'large';
})(Size);
//常量枚举
//如果不通过索引值的方式读取枚举类型,推荐使用常量枚举。编译后枚举类型会被移除,使用的枚举值会被替换掉,以注释的形式标注。
const enum postStatus {}
//...
- never:底部类型(bottom type),是其他任意类型的子类型。
never为空类型和顶部类型。never类型的变量无法被赋值。
type Exclude<T, U> = T extends U ? never : T; //在T中去除U中包含的部分
T | never; // 结果为T
T & never; // 结果为never
场景:获取类型为函数的propName
interface SomeProps {
a: string;
b: number;
c: (e: MouseEvent) => void;
d: (e: TouchEvent) => void;
}
// 如何得到 'c' | 'd' ?
type GetKeyByValueType<T, Condition> = {
[K in keyof T]: T[K] extends Condition ? K : never;
}[keyof T];
type FunctionPropNames = GetKeyByValueType<SomeProps, Function>; // 'c' | 'd'
-
unknown:不可预先定义的类型。大部分用于替代any同时保留静态检查的能力。
-
联合类型
type UnionType = string | number | boolean;
- 交叉类型
type IntersectionType = { foo: string } & { bar: number };
- 函数
//函数声明式
function func1(a: string, b?: number): string {
//添加参数和返回值的类型注解
//参数个数也必须一致,不能多或少。
//可选参数:添加问号或者使用参数默认值,必须在参数的最后一位
//不限制参数个数 :...rest:number
return 'foo';
}
//有什么好处?
function sum(...args: number[]) {
//确保传过来的参数都是数字,不用单独进行类型判断。可靠。
return args.reduce((prev, curr) => prev + curr, 0);
}
const func = (str: string): number => {
return parseInt(str);
};
const func: (str: string) => number = (str) => {
return parseInt(str);
};
函数重载:
// 重载签名(函数类型定义)
function toString(x: string): string;
function toString(x: number): string;
// 实现签名(函数体具体实现)
function toString(x: string | number) {
return String(x);
}
重载签名的类型不会合并:
function stringOrNumber(x): string | number {
return x ? '' : 0;
}
// input 是 string 和 number 的联合类型
// 即 string | number
const input = stringOrNumber(1);
toString(input); //错误
作用域问题
在ts中的多个文件声明同名的变量、函数、类、接口时,会产生命名冲突,在全局作用域中所有声明的命名空间是共享的。需要声明为模块,模块内的作用域是局部的,不会影响全局作用域。
//一个文件加上export即可声明为模块
export {}; //以模块形式导出,一般不这样做,因为一般每个文件(组件)会以模块形式使用
const a = 123;
类型注解与类型推断
type annotation 类型注解,告诉 ts 是什么类型
type inference 类型推断,ts 会自动推断类型
let count: number; //类型注解
count = 123;
let age = 18;
age = 'foo'; //报错,被推断为number
let foo; //被推断为any
foo = 100;
foo = 'foo';