TypeScript
安装
typescript
编译器
npm i typescript -g
tsc index.ts
编译index.ts并输出为js
tsc --init
初始化 tsconfig.js
配置文件
ts-node
node下ts编译后执行(可选)
npm i ts-node -g
ts-node index.ts
立即执行 index.ts
并输出结果
类型标注
声明赋值,类型确定
let a = '你好'
提前标注
let b: string = '下午好'
基础类型
数字/字符串/布尔值
let num: number = 1;
let str: string = '';
let loading: boolean = false;
数组类型
const list: string[] = [];
const arr: Array<string> = []; // 推荐
对象类型
定义接口
interface Article {
title: string,
id: number,
published: boolean
category?: string // 可选属性
}
标注对象
const currentArticle: Article = {
title: '',
id: 0,
published: false
}
标注类型
const articleList: Array<Article> = [];
interface Author {
name: string,
artist: Array<Article>
}
枚举
Ts通过带名字的常量实现枚举有限范围的值,枚举值允许数字/字符串类型
enum ArticleStatus {
DRAFT = 0,
PUBLISHED = 1
DELETED, // 忽略,则基于上一个自增
}
console.log(ArticleStatus.DRAFT)
console.log(article.status === ArticleStatus.DRAFT);
Map
const map = new Map<number, string>(); // 标着映射类型
map.set(1, '1');
Set
const set = new Set<number>(); // 标着映射类型
set.add(1);
函数类型
标注参数/输出类型
const getArticleStatus = (status: ArticleStatus): string => {
switch(status) {
case ArticleStatus.DRAFT:
return 'draft';
case ArticleStatus.PUBLISHED:
return 'published';
case ArticleStatus.DELETED:
return 'deleted';
default:
return 'unknown';
}
}
function getArticleStatus2(status: ArticleStatus): string {
switch(status) {
case ArticleStatus.DRAFT:
return 'draft';
case ArticleStatus.PUBLISHED:
return 'published';
case ArticleStatus.DELETED:
return 'deleted';
default:
return 'unknown';
}
}
void:无返回语句
// 标记返回值void类型
const fun = function(p1: string): void {
console.log(p1)
}
// 标记返回值undefiend类型
const handle = (p1: string): undefined {
console.log(p1)
return; // 必须返回undefined类型值
}
类型别名/接口的函数
type Add = (x: number, y: number) => number;
interface Add {
(x: number, y: number): number;
}
interface api {
request(path: string): Promise<any>
}
函数重载
Ts实现了类型层面的重载,罗列函数参数所有类型组合
function getDomClassList(id: string): string;
function getDomClassList(list: Array<string>): string;
function getDomClassList(id: string | Array<string>): string {
if(typeof id === 'string') return id;
if(id instanceof Array) return id.join(' ');
return '';
};
Class类
为class类增加类型描述
class Book {
pageTotal: number;
constructor(name: string, author: Author){
}
getBookPageTotal(): number {
return this.pageTotal;
}
}
// public 关键词
class A {
constructor(public name: string, public age: number){
}
private ... // 仅类内可访问
protected ... // 仅类内和子类内可访问
}
any
任意类型,即所有其他类型,这使TypeScript无法类型检查从而可能导致不可预见错误;
unknown
任意类型,但是保留类型检查
通过类型断言,在未标注类型下赋予变量类型,让开发者按预期类型处理
(param as unknown[]).map(item => item);
(str as any).handler();
const { name, job = {} as IJob } = user;
联合/交叉类型
类型别名
通过类型别名实现类型复用
type computed = (unknown) => string;
const name: computed = (person) => person.name;
const id: computed = (person) => person.id;
或者类似 Interface
作用
type Person {
name: string,
age: number
}
联合类型
通过多类型配合类型别名创建自定义的联合类型
使用 |
或关系联合类型
type isSuccess = number | boolean;
let isSuccess: isSuccess = 0;
isSuccess = true;
type User = VisitorUser | CommonUser | VIPUser | AdminUser;
使用 &
且关系联合类型
const temp = (Animal | Person) & (GameObject)
字面量联合类型
变量类型只能是指定值的类型且相等
type Status = 'pengding' | 'success' | 'failure';
type code = 200 | 400 | 500
泛型
动态可复用的类型
如配合类型别名创建工具类型,其中泛型作为参数存在(主动赋值)
type Status<T> = number | boolean | T;
let status: Status<string> = 'success'
或者在标注类型时,使用泛型表达一个动态的未来类型,并对它复用(自动推导)
function fun<T>(param: T): T {
return param
}
// 箭头函数使用泛型
const createList = <T>(): T[] => {
return [];
};
let stringList = createList<string>();
类型断言
允许开发者在代码中“断言”某个值的类型。TypeScript 一旦发现存在类型断言,就不再对该值进行类型推断,而是直接采用断言给出的类型。
interface Task {
__type: "task";
id: string;
// .....
scheduledAt: Date | null;
}
const task: Task = {
// .....
scheduledAt: task.scheduledAt ? Timestamp.fromDate(task.scheduledAt as Date) : null
}
// 断言为HTMLDivElement,不为null
const container = document.getElementById("root") as HTMLDivElement;
ReactDOM.createRoot(container).render(
<React.StrictMode>
<App />
</React.StrictMode>,
);
断言unknown类型
export type AppRouteObject = {
order?: number;
meta?: RouteMeta;
children?: AppRouteObject[];
} & Omit<RouteObject, 'children'>;
const routes: AppRouteObject[] = []; // 自定义的AppRouteObject类型
const router = createHashRouter(routes as unknown as RouteObject[]);
request<T = any>(config: AxiosRequestConfig): Promise<T> {
return new Promise((resolve, reject) => {
axiosInstance
.request<any, AxiosResponse<Result>>(config)
.then((res: AxiosResponse<Result>) => {
resolve(res as unknown as Promise<T>);
})
.catch((e: Error | AxiosError) => {
reject(e);
});
});
}
非空断言
const root = document.getElementById('root')!;
const UserContext = createContext<User | null>(null);
const userId: string = user!.uid; // 断言user不为null
工具类型
前面提到通过泛型实现工具类型,Ts内置了一些工具类型:
Partial
接收一个对象类型,并将这个对象类型的所有属性都标记为可选
type User = {
name: string;
age: number;
email: string;
};
const girl: Partial<User> ={};
type PartialUser = Partial<{
name: string;
age: number;
email: string;
}>;
const boy: PartialUser = {};
Readonly
接收一个对象类型,并将这个对象类型的所有属性都标记为只读
type ReadonlyUser = Readonly<{
name: string;
age: number;
email: string;
}>;
Record
快速对象类型的键值类型标注
type Field = 'name' | 'job' | 'email'
type User = Record<Field, string>
const user: User = {
name: 'xxx',
job: 'worker',
email: 'xxx.com'
};
const user: Record<string,string> = {
name: 'xxx',
job: 'worker',
email: 'xxx.com'
};
Pick & Omit
Pick:对传入对象类型取其中部分键值类型值作为新类型返回
Omit:对传入对象类型排除部分键值类型值作为新类型返回
type BasicInfo = Pick<User, 'name' | 'age'>;
type MoreInfo = Omit<User, 'name' | 'age'>;
interface NewTask extends Pick<Task, "__type" | "done" | "name"> {}
Exclude & Extract
Exclude:对联合类型执行差集操作后类型值作为新类型返回
Extract:对联合类型执行交集操作后类型值作为新类型返回
type UserProps = 'name' | 'age' | 'email' | 'phone' | 'address';
type RequiredUserProps = 'name' | 'email';
type OptionalUserProps = Exclude<UserProps, RequiredUserProps>;
type RequiredUserPropsOnly = Extract<UserProps, RequiredUserProps>;
Parameters & ReturnType
Parameters:从函数类型中提取入参类型作为新类型返回
ReturnType:从函数类型中提取返回值类型作为新类型返回
type Add = (x: number, y: number) => number;
type AddParams = Parameters<Add>; // [number, number] 类型
type AddResult = ReturnType<Add>; // number 类型
const addParams: AddParams = [1, 2];
const addResult: AddResult = 3;
typeof 关键字
对一个Js变量值提取Ts类型值作为新类型返回
const addHandler = (x: number, y: number) => x + y;
type Add = typeof addHandler; // (x: number, y: number) => number;
type User = typeof person;
Awaited
提取一个 Promise
类型的返回值类型
async function getPromise() {
return new Promise<string>((resolve) => {
setTimeout(() => {
resolve("Hello, World!");
}, 1000);
});
}
type Result = Awaited<ReturnType<typeof getPromise>>; // string 类型
模块
export interface A {
foo: string;
}
export let a = 123;
import { type A, a } from './a';
import type { A } from './a';
import type * as TypeNS from 'moduleA';
declare
declare 关键字用来告诉编译器,某个类型是存在的,可以在当前文件中使用。
它的主要作用,就是让当前文件可以使用其他文件声明的类型。如:
向TS编译器描述项目外部的类型,只声明不实现
// plugins.d.ts
declare module "qs";
declare module "nprogress";
declare module "js-md5";
declare module "react-transition-group";
使用declare global {}语法,为 JavaScript 引擎的原生对象添加属性和方法
// * global 拓展全局类型
declare global {
interface Window {
__REDUX_DEVTOOLS_EXTENSION_COMPOSE__: any;
}
interface Navigator {
msSaveOrOpenBlob: (blob: Blob, fileName: string) => void;
browserLanguage: string;
}
}
export {}; // 标识
类型声明.d.ts
单纯的类型声明:只为TS编译器声明类型,不包含实现;
全局的类型声明:TS将 .d.ts
声明的类型视为全局(文件须在include/files指定范围下)
类型来源
除了模块内声明和导入模块的类型,TS还会扫描类型声明文件.d.ts作为全局类型
TypeScript内置类型
根据 target
、 lib
配置项扫描 node_modules/typescript/lib/lib.xx.d.ts
内置类型
外部类型声明文件
如果外部库自带 [vendor].d.ts
文件,使用这个库可能需要单独加载它的类型声明文件。
否则在官方文档或者 DefinitelyTyped 仓库 单独安装以 @types
空间命名的类型声明npm包
TypeScript 会自动加载 node_modules/@types
下作为全局模块,相关配置:
"compilerOptions": {
"typeRoots": ["./typings", "./vendor/types"] // 修改自动加载作为全局模块路径
}
TypeScript 会自动加载 typeRoots
目录里的所有模块,types
指定仅加载模块:
"compilerOptions": {
"types": ["node", "express"]
}
tsconfig.json
配置项:include,files 下项目的 xx.d.ts
会作为全局类型
使用 declaration
选项,编译器就会在编译时自动生成单独的类型声明文件:
"compilerOptions": {
"declaration": true
}
tsc index.ts --declaration
配合declare关键字自己声明
// 模块声明
declare module App {
create(string?: string): string
...
}
// 变量声明
declare var window: Window & typeof globalThis;
模块发布
在ts开发的npm,如果包含自己的类型声明文件,一般放在项目根目录下 index.d.ts
文件
使TS能够在库被引入时,根据 index.d.ts
获取类型信息;
通过 package.jsopn 的types
或 typings
配置字段,自定义类型声明文件位置:
{
"main": "./dist/index.js",
"types": "./dist/index.d.ts", // 包的类型声明文件
}
如果类型声明发布为单独 npm 包,须额外指定:
"dependencies": {
"@types/browserify": "latest"
}
模板字符串类型
ts支持字符串类型额外的模板结构描述,如
type Tel = `${number}-${number}`
type Version = `${number}.${number}.${number}`
type SayHello = `Hello ${string | number}`
const sayStr: Say = 'Hello name'
const sayNum: Say = 'Hello 666'
type SayHello<T extends string | number> = `Hello ${T}`;
配合联合类型,得到更多组合模式的联合类型
type Brand = 'iphone' | 'xiaomi' | 'honor';
type Memory = '16G' | '64G';
type ItemType = 'official' | 'second-hand';
type SKU = `${Brand}-${Memory}-${ItemType}`;
编译配置
Ts编译主要负责语法降级和类型定义的生成,编译配置类别 tsconfig.json
包括产物控制、输入与输出控制、类型声明、代码检查等
产物控制
target: 控制产物语法的 ES 版本
module: 控制产物模块化规则
outDir: 输出目录
types: 仅加载指定类型定义包,未指定下加载@types下所有包;也支持指定额外加载类型,如 "types": ["vite/client"]
{
"compilerOptions": {
"module": "ESNext"/"commonjs", // TS生成代码的模块规范
"moduleResolution": "Bundler", // 指定TS如何解析模块导入,Node/Bundler
"lib": ["DOM", "DOM.Iterable", "ESNext"], // 指定加载内置类型库,未指定下target计算
"skipLibCheck": true, // 跳过对所有声明文件(.d.ts 文件)的类型检查
"target": "ESNext", // TS编译结果的ECMAScript版本:ES5、ES6、ESNext
"outDir": "dist" // 输出目录
"types": ["node", "react"], // 仅加载 @types/node、@types/react 类型包
// more
"allowImportingTsExtensions": , // 允许导入.ts和.tsx文件
"jsx": "react-jsx", // 指定TS如何处理jsx语法
"noEmit": true, // 仅进行类型检查和编译,不生成.js与.d.ts文件
"esModuleInterop": true, // 允许import非ES模块,启用ES模块互操作性支持
"allowSyntheticDefaultImports": true, // 允许使用默认导入语法,即使模块实际没有默认导出
"baseUrl": ".", // TS编译器解析模块的基准目录,通常与paths使用
"paths": { "@/*": ["./src/*"] }, // 声明解析别名
"files": ["global.d.ts"], // 限定编译文件(不支持目录)
"forceConsistentCasingInFileNames": true, // 模块导入时强制文件名大小写一致性
"sourceMap": true, // 是否生成源映射文件用于调试
"plugins": [{ "name": "typescript-plugin-css-modules" }] // ts 插件
"useDefineForClassFields": true,
"allowJs": false,
"strict": true,
"resolveJsonModule": true,
"isolatedModules": true,
}
"include": ["./src", "auto-imports.d.ts"], // 限定编译文件
}
输入控制
include: 输入文件范围
exclude: 输入文件排除范围
{
"include": [
"src/**/*",
"auto-imports.d.ts" // 引入全局变量,自动引入的变量
],
"exclude": [
"src/excludeDir",
"**/*.spec.ts"
]
}
类型相关配置
declaration: 是否生成 .d.ts
emitDeclarationOnly: 仅生成.d.ts
noEmit: 不生成任何文件,仅类型检查
类型检查配置
noImplicitAny: 是否不允许隐式any存在