- tsconfig.json 配置文件
{
"compilerOptions": {
"target": "esnext",
"jsx": "preserve",
"jsxImportSource": "vue",
"lib": ["esnext", "dom"],
"useDefineForClassFields": true,
"experimentalDecorators": true,
// baseUrl 用来告诉编译器到哪里去查找模块,使用非相对模块时必须配置此项
"baseUrl": ".",
"module": "esnext",
"moduleResolution": "bundler",
// 非相对模块导入的路径映射配置,根据 baseUrl 配置进行路径计算,与 vite.config 中 alias 配置同步
"paths": {
"@/*": ["src/*"],
"@@/*": ["src/common/*"]
},
"resolveJsonModule": true,
"types": ["vite/client", "element-plus/global"],
// 允许导入 .ts .mts .tsx 拓展名的文件
"allowImportingTsExtensions": true,
// 允许 JS
"allowJs": true,
// TS 严格模式
"strict": true,
"importHelpers": true,
// 不输出任何编译后的文件,只进行类型检查
"noEmit": true,
"sourceMap": true,
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"isolatedModules": true,
"skipLibCheck": true
},
// 需要被编译的文件列表
"include": ["**/*.ts", "**/*.tsx", "**/*.vue", "**/*.d.ts"],
// 从编译中排除的文件列表
"exclude": ["node_modules", "dist"]
}
- 创建一个值为字符串的变量
let message: string = 'Hello TypeScript'
- 布尔值
let isDone:boolean = false;
- 数字
let decimal: number = 6;
let hex: number = 0xf00d;
let binay: number = 0b1010;
let octal: number = 0O744;
- 数组
let list: number[] = [1,2,3];
// 使用泛型数组类型:
let list1: Array<number> = [4,5,6];
- 元组
let x: [string, number];
x = ['hello', 10]
- 枚举
enum Color {Red, Green, Blue}
let c: Color = Color.Green;
- Any
// 不确定变量类型时,可以使用 any 类型:
let notSure: any = 4;
notSure = 'maybe a string instead';
notSure = false;
- Void
// 用于表示没有任何类型,常用于没有返回值的函数
function warnUser(): void {
console.log('this is a viod log')
}
- Null 和 Undefined
let u: undefined = undefined;
let n: null = null;
- Never
// 表示那些用不存在的值的类型,例如:永远不会返回的函数表达式或箭头函数的返回值类型:
function error(message: string): never {
throw new Error(message)
}
- Object
// 表示非原始类型
declare function ceeate(o: object | null): void;
create({ prop: 0 })
create(null);
- 接口
/**
* 接口是 TypeScript 中的核心原则之一,用于定义对象的类型。
* 接口能够描述一个对象的形状,能够检查对象是否符合特定的结构。
* 示例如下:
*/
interface LabelledValue {
label: string;
}
function printLabel(labelledObj: LabelledValue) {
console.log(labelledObj.label);
}
let myObj = { size: 10, label: 'Size 10 Object' };
printLabel(myObj);
/**
* 小结:LabelledValue 接口定义了一个 label 属性,并且 printLabel 函数期望一个实现了 LabelledValue 接口的对象作为参数。即使 myObj 有其他属性,但只要它至少有一个 label 属性,TS 就会认为它是合法的。
*/
- 可选属性
// 可选属性接口允许一些属性存在,也允许一些属性不存在:
interface SquareConfig {
color?: string;
width?: number;
}
function createSquare(config: SquareConfig): {color: string, area: number} {
let newSquare = { color: 'white', area: 100 };
if(config.color) {
newSquare.color = config.color;
}
if(config.width) {
newSquare.area = config.width * config.width;
}
return newSquare;
}
let mySquare = createSquare({ color: 'black' })
// 小结:SquareConfig 接口定义了 color 和 width 可选属性。CreateSquare 函数根据传入 config 对象动态地创建一个新的对象。
- 只读属性
// 一些属性可以在对象刚刚船舰的时候修改其值,而在此之后将是只读的
interface Point {
readonly x: number;
readonly y: number;
}
let p1: Point = { x: 10, y: 20 };
pi.x = 5; // 一旦赋值就会报错误,不能改变只读属性
- 函数类型
// 接口也可以描述函数类型
interface SeachFunc {
(source: string, subString: string): beelean;
}
let mySearch: SearchFunc;
mySearch = function (source: string, subString: string) {
let result = source.Search(subString);
return result > -1;
};
- 可索引类型
// 接口可以描述那些能够通过索引得到某种类型的对象,这在对象上可以用数字索引或字符串索引
interface StringArray {
[index: number]: string;
}
let myArray: StringArray;
myArray = ['Bob', 'Fred'];
let myStr: string = myArray[0];
// StringArray 接口描述了具有数字索引的数组类型,并且返回值是字符串类型
- 类-简单类
class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
greet() {
return 'Hello, ' + this.greeting;
}
}
let greeter = new Greeter('world');
// Greeter 类有一个 greeting 属性和一个 greet 方法, greet 方法返回一个字符串。
- 类-继承
// 类可以扩展其他类,一个类可以继承另一个类的属性和方法;
class Animal {
name: string;
constructor(theName: string) {
this.name = theName;
}
move(distanceInMeters: number = 0) {
console.log(`${this.name} moved ${distanceInMeters}m.`);
}
}
class Snke extends Animal {
constructor(name: string) {
super(name);
}
move(distanceInMeters = 5) {
console.log('Slithering...');
super.move(distanceInMeters);
}
}
let sam = new Snake('Sammy the Python');
sam.move();
// Snke 类扩展了 Animal 类,并且重写了 move 方法.
- 类-公共,私有与受保护修饰符
// TS 中的成员默认是公共的,也可以用 private 修饰符将成员标记为私有的:
class Animal {
private name: string;
constructor(theName: string) {
this.name = theName;
}
}
new Animal('Cat').name; // 错误: name 是私有的
// 可以使用 protected 修饰符声明受保护的成员,这些成员只能在类本身及其子类中访问:
class Person {
protected name: string; // 受保护的成员,只能在类本身及其子类访问
constructor(name: string) {
this.name = name;
}
}
class Employee extends Person {
private department: string;
constructor(name: string, department: string) {
super(name);
this.department = department;
}
getElevatorPitch() {
return `Hello, my name is ${this.name} and I work in ${this.department}.`
}
}
let howard = new Employee('Howard', 'Sales');
console.log(howard.getElevatorPitch()); //有效
console.log(howard.name); // 错误
- 类-readonly 修饰符
// readonly 关键字将属性设置为只读
class Octopus {
readonly name: string;
readonly numberOfLegs: number = 0;
constructor(theName: string) {
this.name = theName;
}
}
let dad = new Octopus('Man width the 8 strong legs');
dad.name = 'Man with the 3'; // 错误 name是只读的;
- 类-存取器
// 支持通过 get 和 set 关键字来定义存取器
class Employee {
private _fullName: string; // 设置私有属性
get fullName(): string {
return this._fullName;
}
set fullName(newName: string) {
this._fullName = newName;
}
}
let employee = new Employee();
employee.fullName = 'Bob Smith';
console.log(employee.fullName)
// Employee 类有一个私有属性 _fullName, 并且通过存取器来设置和获取它的值。
- 函数-(和JavaScript函数类似,但在参数和返回类型上提供了更多类型的检查)
- 函数类型
// 为函数的参数和返回值指定类型
function add(x: number, y: number): number {
return x + y;
}
let myAdd:(x: number, y: number) => number = function (x: number, y: number): number {
return x + y;
}
- 函数-可选参数和默认参数
// 可以通过在参数名旁使用 ? 来实现可选参数
function buildName(firstName: string, lastName?: string): string {
if(lastName) {
return firstName + ' ' + lastName;
} else {
return firstName;
}
}
let result1 = buildName('Bob'); // 有效
let result2 = buildName('Bob', 'Adams'); // 有效
// 还可以为参数提供默认值
function buildName(firstName: string, lastNAme = 'Smith'): string {
return firstName + ' ' + lastName;
}
let result1 = buildName('Bob'); // 有效 返回 'Bob Smith'
let result2 = buildName('Bob', 'Adams'); // 有效 返回 'Bob Adams'
- 函数-剩余参数
// 可以使用 ... 语法将所有参数收集到一个变量中
function buildName(firstName: string, ...restOfName: string[]) {
return firstNAme + '' + resetOfName.join('');
}
let employeeName = buildName('Joseph', 'Samuel', 'Lucas', 'MackinZie');
- 函数-this和箭头函数
// 在 TypeScript 中, this 的值取决于函数调用的位置,可以使用箭头来正确的捕获this 的值
let dect = {
suits: ['hearts', 'spades', 'cluds', 'diamonds'],
cards: Array(52),
createCardPicker: function () {
return () => {
let pickedCard = Math.floor(Math.random() * 52);
let pickedSuit = Math.floor(pickedCard / 13);
return { suit: this.suits[pickedSuit], card: pickedCard % 13 };
};
};
};
let cardPicker = deck.createCardPicker();
let pickedCard = cardPicker();
console.log('card:', pickedCard.card)
console.log('suit:', pickedCard.suit)
// 例子中,箭头函数不会创建自己的 this, 它会捕获 deck 对象的 this 值。
- 泛型-泛型函数
function identity<T>(arg: T): T {
return arg;
}
let output = identity<string>('mtstring'); // 手动指定类型
let output2 = identity('mystring'); // 类型推断
- 泛型-泛型接口
interface CenericIdentityFn<T> {
(arg: T): T;
}
function identity<T>(arg: T): T {
return arg;
}
let myIdentity: GenericIdentityFn<number> = identity;
// 上述例子中,GenericIdentityFn 接口描述了一个泛型函数类型,并且 myIdentity 是一个特定类型的泛型函数。
- 泛型- 泛型类
class GenericNumber<T> {
zeroValue: T;
add: (x: T, y: T) => T;
}
let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function (x, y) {
return x + y;
}
// GenericNumber 是一个泛型类,它可以处理任意类型的数字
- 泛型-泛型约束
// 想要限制某种类型的泛型函数,就可以使用泛型约束
interface Lengthwise {
length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length)
return arg;
}
loggingIdentity({length: 10, value: 2});
// loggingIdentity 函数要求传入的参数必须有 length 属性。
- 泛型-在泛型约束中使用类型参数
function getProperty<T, K extends keyof T>(obj: T, key: K) {
return obj[key];
}
let x = {a: 1, b: 2, c: 3, d: 4};
getProperty(x, 'a') // 有效
getProperty(x: 'm') // 错误:类型‘m’的参数不能赋给类型"a" | "b" | "c" | "d" 的参数
// 上述例子中,getProperty 函数接收一个对象和一个属性名称,并返回该属性的值,K被约束为 T的属性名称。
- 类型推断
- TypeScript 能够根据代码中的一些简单的规则推断变量的类型。如果变量声明时没有指定类型,TypeScript会自动推断出一个类型
- 类型推断-基础示例
let x = 3; // x 被推断为 number 类型
- 类型推断-最佳通用类型
// 当需要从多个表达式中推断类型时, TypeScript 会选择最合适的通用类型
let x = [0, 1, null]; // x 的类型推断为(number | null)
- 类型兼容性
- TypeScript 中的类型兼容性是基于结构子类型的。结构类型系统是基于类型的成员来确定类型的兼容性。
- 类型兼容性-接口兼容性
interface Named {
name: string;
}
class Person {
name: string;
}
let p: Named;
p = new Person(); // 正确 因为 Person 有一个兼容的 name 属性
- 类型兼容性-函数兼容性
let x = (a: number) => 0;
let y = (b: number, s: string) => 0;
y = x; // OK
x = y; // Err
- 高级类型
- TypeScript 提供了许多高级类型操作,可以在编辑复杂类型定义时提供更强的灵活性
- 高级类型-交叉类型
// 交叉类型 & 是将多个类型合并为一个类型
function extend<T, U>(first: T, second: U): T & U {
let result = <T & U>{};
for(let id in first) {
(result as any)[id] = (first as any)[id]
}
for(let id in second) {
if(!result.hasOwnProperty(id)) {
(result as any)[id] = (second as any)[id];
}
}
return result;
}
let x = extend({ a: 'hello' }, { b: 42 });
let a = x.a; // string
let b = x.b; // number
- 高级类型-联合类型
- 联合类型 | 表示一个值可以是几种类型之一
function padLeft(value: string, padding: string | number) {
if(typeof padding === 'number') {
return Array(padding + 1).join(' ') + value;
}
if(typeof padding === 'string') {
return padding + value;
}
throw new Error(`Expected string or number, got ${typeof padding}.`);
}
padLeft('Hello world', 4); // 返回 " Hello world"
padLeft('Hello world', '>>>'); // 返回 ">>>Hello world"
- 高级类型- 类型别名
- 类型别名可以类型起一个新名字
type Name = string;
type NameResolver = () => string;
type NameOrResolver = Name | NameResolver;
function getName(n: NameOrResolver): Name {
if(typeof n === 'string') {
return n;
} else {
return n();
}
}
- 高级类型-字面量类型
- 字面量类型约束一个变量的值只能是某个特定的值
type Easing = 'ease-in' | 'ease-out' | 'ease-in-out';
class UIElement {
animate(dx: number, dy: number, easing: Easing) {
if(easing === 'ease-in') {
// do something...
} else if(easing === 'ease-out') {
// do something...
} else if(easing === 'ease-in-out') {
// do something
} else {
throw new Error('参数必须是ease-in, ease-out,ease-in-out')
}
}
}
let button = new UIElement();
button.animate(0, 0, 'ease-in'); // ok
button.animate(0, 0, 'unesay'); // error
- 装饰器
- 装饰器是一个特殊类型的声明,能够被附加到类声明、方法、访问器、属性或参数上,装饰器使用 @expressoon 这种形式, expression必须求值为一个函数,它将在运行时被调用,被装饰的声明信息作为参数传入。
- 装饰器-类装饰器
function sealed(constructor: Function) {
Object.seal(constructor);
Object.seal(constructor.prototype);
}
@sealed
class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
greet() {
return 'Hellow, ' + this.greeting;
}
}
- 装饰器-方法装饰器
function enumerable(value: boolean) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
descriptor.enumerable = value;
}
}
class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
@enumerable(false)
greet() {
return 'Hello, ' + this.greeting;
}
}
- 编译选项
- TypeScript 编译器 可以通过命令行参数和 tsconfig.json 文件进行配置。
- tsconfig.json 文件用于配置 TypeScript 项目,示例如下:
{
"compilerOptions": {
"target": "es6",
"module": "commonjs",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "**/*.spec.ts"]
}
- 常见问题与最佳时间
- 如何进行类型定义
- 解答: 在 TypeScript 中进行类型定义时,推荐尽量使用接口,因为接口可以被类实现和扩展,并且更加灵活和易于阅读。
- 何时使用类型断言?
- 解答: 类型断言用于告诉编译器某个值的具体类型:
let someValue: any = 'this is a string';
let strLength: number = (someValue as string).length;
// 尽量避免过度使用类型断言,因为它可能隐藏潜在的类型错误
- 如何处理第三方库的类型定义
- 解答:可以使用 DefinitelyTyped 项目提供的类型定义文件
npm install @type/jquery --save-dev
- 这样就可以在 TypeScript 项目中使用 jQuery 的类型定义文件
- 如何调试 TypeScript 代码
- 解答: 可以通过生成 Source Map 文件来调试 TypeScript 代码
{
"compilerOptions": {
"sourceMap": true
}
}
// 这样就可以在调试工具中直接看到 TypeScript 代码,并且设置断点进行调试
- 如何提高代码质量
- 启用严格模式:在 tsconfig.json 中启用 strict选项
- 使用代码格式化工具, 如 Prettier 来保持代码风格一致。
- 使用代码静态分析工具: 如 ESLint 来发现和修复代码中的潜在问题
- 编写单元测试: 确保代码的正确性和稳定性
增加常用方法
- 如何避免冗余定义类型
- 使用 type 或 interface 复用类型
示例:避免重复定义多个 type
不推荐(冗余定义)
type UserName = string;
type UserAge = number;
type User1 = {
name: UserName;
age: UserAge;
};
type User2 = {
name: UserName;
age: UserAge;
gender: string;
};
- 推荐(复用 UserBase)
type UserBase = {
name: string;
age: number;
};
type User1 = UserBase;
type User2 = UserBase & { gender: string };
- 使用 extends 继承 interface
适用于对象类型的复用。这样 Admin 和 Guest 只扩展 BaseUser,避免重复定义 id 和 name。
interface BaseUser {
id: number;
name: string;
}
interface Admin extends BaseUser {
role: "admin";
}
interface Guest extends BaseUser {
role: "guest";
}
- 使用 Partial<>、Pick<>、Omit<> 进行类型复用
示例:避免重复定义修改某个字段的类型
interface User {
id: number;
name: string;
age: number;
email?: string;
}
/* 只需要部分字段 */
type UserPreview = Pick<User, "id" | "name">;
/* 省略某些字段 */
type UserWithoutEmail = Omit<User, "email">;
/* 将所有字段变为可选 */
type PartialUser = Partial<User>;
/* 将所有字段变为必填 */
type RequiredUser = Required<User>;
这样我们可以复用 User,而不需要手写多个类似的 interface。
- 使用泛型减少类型重复
适用于某些通用的数据结构,如 API 请求响应、列表数据。
示例:
// 定义一个通用 API 响应类型
interface ApiResponse<T> {
code: number;
message: string;
data: T;
}
// 具体的用户数据类型
interface User {
id: number;
name: string;
}
// 使用泛型,不用重复定义多个响应类型
type UserResponse = ApiResponse<User>;
type UsersResponse = ApiResponse<User[]>;
这样 ApiResponse 可以适用于不同的 data,避免写多个类似的 Response 类型。
- 使用 Record<K, V> 代替重复的键值定义
示例:
不推荐(冗余定义多个 key-value 对象)
type UserRole = {
admin: string;
user: string;
guest: string;
};
推荐(用 Record<K, V> 复用)
type UserRole = Record<"admin" | "user" | "guest", string>;
这样如果要添加新的角色,比如 “moderator”,只需要加到 Record 里即可。
如果想要在两个已有类型种选取呢?
- 使用 & 取交集
如果 TypeA 和 TypeB 有重叠字段,你可以用 & 组合它们:
type TypeA = { id: number; name: string; age: number };
type TypeB = { name: string; gender: string };
type Selected = TypeA & TypeB;
✅ 最终 Selected 结构
{
id: number;
name: string;
age: number;
gender: string;
}
适用场景:用于合并两个对象类型,避免重复定义相同的字段。
- 使用 Pick<> 选取公共字段
如果你只想要 TypeA 和 TypeB 里 相同的字段,可以用 keyof 获取公共字段:
type TypeA = { id: number; name: string; age: number };
type TypeB = { name: string; gender: string };
type CommonKeys = Extract<keyof TypeA, keyof TypeB>; // "name"
type Selected = Pick<TypeA, CommonKeys>;
✅ 最终 Selected 结构
{
name: string;
}
适用场景:当你只想保留两个类型中 相同的字段。
- 选取两个类型中的某些字段
如果你要选取 两个类型中的部分字段,可以先 创建联合类型,再用 Pick<>:
type TypeA = { id: number; name: string; age: number };
type TypeB = { name: string; gender: string };
type SelectedKeys = "id" | "name" | "gender"; // 自定义想要的字段
type Selected = Pick<TypeA & TypeB, SelectedKeys>;
✅ 最终 Selected 结构
{
id: number;
name: string;
gender: string;
}
适用场景:当你想在 两个类型里手动挑选字段,而不只是取公共部分。
- 使用 Omit<> 选择非交集字段
如果你想 去掉交集部分,保留两个类型独有的字段,可以用 Omit<>:
type TypeA = { id: number; name: string; age: number };
type TypeB = { name: string; gender: string };
type UniqueA = Omit<TypeA, keyof TypeB>; // 去掉 `name`
type UniqueB = Omit<TypeB, keyof TypeA>; // 去掉 `name`
✅ 最终 UniqueA 结构
{
id: number;
age: number;
}
✅ 最终 UniqueB 结构
{
gender: string;
}
适用场景:当你只想保留两个类型中 不重叠的部分。
- 组合 Pick<> 和 Omit<> 进行复杂选取
如果你需要更灵活的选择方式,例如:
选取 A 中某些字段
选取 B 中某些字段
合并它们
type TypeA = { id: number; name: string; age: number };
type TypeB = { name: string; gender: string };
type Selected = Pick<TypeA, "id"> & Pick<TypeB, "gender">;
✅ 最终 Selected 结构
{
id: number;
gender: string;
}```
适用场景:当你需要在 不同类型里选取特定字段,然后合并。