简介
什么是 TypeScript
TypeScript 是由微软开发并维护的一种开源编程语言,是 JavaScript 的超集。它在 JavaScript 的基础上添加了静态类型和面向对象编程的支持。
TypeScript 的优势
- 类型安全:通过静态类型,能够在编译阶段就捕获错误,减少运行时错误。
- 代码可读性和可维护性:通过接口、泛型等特性,可以使代码更加清晰和易于维护。
- 现代 JavaScript 特性:支持最新的 ECMAScript 标准特性,比如装饰器、异步函数等。
- 强大的工具链:强大的 IDE 支持,类型检查、智能提示、自动补全等功能大大提升了开发体验。
- 与现有 JavaScript 代码无缝集成:可以逐步将 JavaScript 代码迁移到 TypeScript,无需一次性重写所有代码。
基础知识
基本类型
布尔值
let isDone: boolean = true;
数字
所有数字都是浮点数。
let decimal: number = 6;
let hex: number = 0xf00d;
let binary: number = 0b1010;
let octal: number = 0o744;
字符串
可以使用单引号或双引号来表示字符串,也可以使用模板字符串。
let color: string = "blue";
let fullName: string = `Bob Bobbington`;
let age: number = 37;
let sentence: string = `Hello, my name is ${fullName}. I'll be ${age + 1} years old next month.`;
数组
let list: number[] = [1, 2, 3];
let list: Array<number> = [1, 2, 3];
元组
元组类型允许表示一个已知元素数量和类型的数组。
let x: [string, number];
x = ["hello", 10];
枚举
枚举类型是对 JavaScript 标准数据类型的一个补充。
enum Color {Red, Green, Blue}
let c: Color = Color.Green;
Any
表示可以是任何类型的值。这在编写第三方库时非常有用。
let notSure: any = 4;
notSure = "maybe a string instead";
notSure = false;
Void
表示没有任何类型。通常用在没有返回值的函数中。
function warnUser(): void {
console.log("This is my warning message");
}
Null 和 Undefined
let u: undefined = undefined;
let n: null = null;
Never
表示那些永不存在的值的类型。例如,那些总是会抛出异常或根本不会有返回值的函数表达式或箭头函数表达式的返回值类型。
function error(message: string): never {
throw new Error(message);
}
接口
接口是 TypeScript 的核心原则之一,它的作用是为类型命名和为你的代码或第三方代码定义契约。
interface LabeledValue {
label: string;
}
function printLabel(labeledObj: LabeledValue) {
console.log(labeledObj.label);
}
let myObj = {size: 10, label: "Size 10 Object"};
printLabel(myObj);
类
访问修饰符
TypeScript 中有三种主要的修饰符:public, private 和 protected。
class Animal {
private name: string;
constructor(theName: string) { this.name = theName; }
}
class Rhino extends Animal {
constructor() { super("Rhino"); }
}
class Employee {
private name: string;
constructor(theName: string) { this.name = theName; }
}
let animal = new Animal("Goat");
let rhino = new Rhino();
let employee = new Employee("Bob");
animal = rhino;
// animal = employee; // 错误: Animal 与 Employee 不兼容.
继承
TypeScript 支持在继承时通过公共的接口来实现类的重用。
class Animal {
name: string;
constructor(name: string) { this.name = name; }
move(distanceInMeters: number = 0) {
console.log(`${this.name} moved ${distanceInMeters}m.`);
}
}
class Snake extends Animal {
constructor(name: string) { super(name); }
move(distanceInMeters = 5) {
console.log("Slithering...");
super.move(distanceInMeters);
}
}
class Horse extends Animal {
constructor(name: string) { super(name); }
move(distanceInMeters = 45) {
console.log("Galloping...");
super.move(distanceInMeters);
}
}
let sam = new Snake("Sammy the Python");
let tom: Animal = new Horse("Tommy the Palomino");
sam.move();
tom.move(34);
抽象类
抽象类作为其它派生类的基类使用。 它们一般不会直接被实例化。
abstract class Department {
constructor(public name: string) {
}
printName(): void {
console.log('Department name: ' + this.name);
}
abstract printMeeting(): void; // 必须在派生类中实现
}
class AccountingDepartment extends Department {
constructor() {
super('Accounting and Auditing'); // 在派生类的构造函数中必须调用 super()
}
printMeeting(): void {
console.log('The Accounting Department meets each Monday at 10am.');
}
generateReports(): void {
console.log('Generating accounting reports...');
}
}
函数
函数类型
给函数定义类型有两种方式:函数声明和函数表达式。
// 函数声明
function add(x: number, y: number): number {
return x + y;
}
// 函数表达式
let myAdd = function(x: number, y: number): number { return x + y; };
// 完整函数类型
let myAdd: (baseValue: number, increment: number) => number =
function(x: number, y: number): number { return x + y; };
可选参数和默认参数
function buildName(firstName: string, lastName?: string) {
if (lastName)
return firstName + " " + lastName;
else
return firstName;
}
let result1 = buildName("Bob"); // works correctly now
let result2 = buildName("Bob", "Adams", "Sr."); // error, too many parameters
let result3 = buildName("Bob", "Adams"); // ah, just right
// 默认参数
function buildName(firstName: string, lastName = "Smith") {
return firstName + " " + lastName;
}
剩余参数
function buildName(firstName: string, ...restOfName: string[]) {
return firstName + " " + restOfName.join(" ");
}
let employeeName = buildName("Joseph", "Samuel", "Lucas", "MacKinzie");
泛型
泛型提供了一种方法来在预先不知道类、接口和函数的具体类型时对它们进行定义。
function identity<T>(arg: T): T {
return arg;
}
let output = identity<string>("myString"); // type of output will be 'string'
let output = identity("myString"); // type of output will be 'string'
类型推论
当类型没有明确指定时,TypeScript 会推测出一个类型。这就是类型推论。
let x = 3; // 'x' 的类型被推断为 'number'
高级类型
交叉类型
交叉类型是将多个类型合并为一个类型。
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;
}
联合类型
联合类型表示一个值可以是几种类型之一。
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}'.`);
}
类型保护
为了消除类型检查时的歧义,TypeScript 提供了类型保护机制。
function isNumber(x: any): x is number {
return typeof x === "number";
}
function isString(x: any): x is string {
return typeof x === "string";
}
function padLeft(value: string, padding: string | number) {
if (isNumber(padding)) {
return Array(padding + 1).join(" ") + value;
}
if (isString(padding)) {
return padding + value;
}
throw new Error(`Expected string or number, got '${typeof padding}'.`);
}
类型声明文件
当你使用第三方库时,通常需要引用它们的声明文件,从而获得它们的类型信息。
npm install @types/jquery --save-dev
类型兼容性
TypeScript 采用的是结构性类型系统,也就是说,类型兼容性由成员来决定。
interface Named {
name: string;
}
class Person {
name: string;
}
let p: Named;
// OK, because of structural typing
p = new Person();
装饰器
装饰器是一种特殊的声明,它能够被附加到类声明、方法、访问符、属性或参数上,可以修改类的行为。
function sealed(constructor: Function) {
Object.seal(constructor);
Object.seal(constructor.prototype);
}
@sealed
class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
greet() {
return "Hello, " + this.greeting;
}
}
实用工具类型
TypeScript 提供了一些内置的实用工具类型,方便我们操作类型。
Partial
Partial 构造一个类型,其所有属性都设置为可选。
interface User {
id: number;
name: string;
age: number;
}
const updateUser = (id: number, newValues: Partial<User>): User => {
// 更新用户的逻辑
return { id, ...newValues } as User;
};
Pick
Pick<T, K> 构造一个类型,其类型为 T 中的部分属性 K。
interface User {
id: number;
name: string;
age: number;
}
const getUserInfo = (user: User): Pick<User, 'name' | 'age'> => {
return {
name: user.name,
age: user.age
};
};
Omit
Omit<T, K> 构造一个类型,其类型为 T 中除了 K 之外的所有属性。
interface User {
id: number;
name: string;
age: number;
}
const createUser = (user: Omit<User, 'id'>): User => {
return {
id: Math.floor(Math.random() * 1000),
...user
};
};
tsconfig.json 配置详解
tsconfig.json
文件用于指定 TypeScript 项目的根文件和编译选项。
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"strict": true,
"esModuleInterop": true,
...
},
"include": [
"src/**/*"
],
"exclude": [
"node_modules",
"**/*.spec.ts"
]
}
常用 Compiler Options
target
:指定 ECMAScript 目标版本。常见值有es5
,es6
,es2015
,es2016
,es2017
,es2018
,esnext
。module
:指定使用的模块系统。常见值有commonjs
,amd
,es2015
,esnext
。strict
:启用所有严格类型检查选项。esModuleInterop
:为import
语义生成代码支持,以便与非 TypeScript 的 CommonJS 模块兼容。
常用 Type Checking Options
strictNullChecks
:当启用时,null
和undefined
只能赋值给自身及void
。这可以帮助捕获null
或undefined
引起的大多数错误。noImplicitAny
:在表达式和声明上有隐含的any
类型时报错。有助于快速定位未表达类型的地方。alwaysStrict
:在每个文件里加上"use strict"
。
在 Vue 中使用 TypeScript
创建项目
使用 Vue CLI 创建一个 TypeScript 项目:
vue create my-vue-app
选择 TypeScript
选项。
编写组件
这里是一个简单的 Vue 组件示例:
<template>
<div class="hello">
<h1>{{ msg }}</h1>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
import Component from 'vue-class-component';
@Component
export default class HelloWorld extends Vue {
msg: string = 'Hello World';
}
</script>
<style scoped>
h1 {
color: #42b983;
}
</style>
使用 Vuex 和 TypeScript
定义类型:
interface State {
count: number;
}
定义 store:
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
export default new Vuex.Store<State>({
state: {
count: 0
},
mutations: {
increment(state) {
state.count++;
}
}
});
在 React 中使用 TypeScript
创建项目
使用 Create React App 创建一个 TypeScript 项目:
npx create-react-app my-react-app --template typescript
编写组件
这里是一个简单的 React 组件示例:
import React from 'react';
interface Props {
message: string;
}
const Hello: React.FC<Props> = ({ message }) => {
return <h1>{message}</h1>;
};
export default Hello;
使用 Redux 和 TypeScript
定义类型:
interface State {
count: number;
}
interface Action {
type: string;
}
const initialState: State = {
count: 0
};
定义 reducer:
const reducer = (state = initialState, action: Action): State => {
switch (action.type) {
case 'INCREMENT':
return { count: state.count + 1 };
default:
return state;
}
};
export default reducer;
在 Node.js 中使用 TypeScript
创建项目
创建一个新的 Node.js 项目并初始化 TypeScript:
mkdir my-node-app && cd my-node-app
npm init -y
npm install typescript @types/node ts-node --save-dev
npx tsc --init
配置 tsconfig.json
确保你的 tsconfig.json
文件包含以下配置:
{
"compilerOptions": {
"target": "es6",
"module": "commonjs",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true
}
}
编写代码
创建一个简单的 Node.js 应用程序:
src/index.ts
import * as http from 'http';
const server = http.createServer((req, res) => {
res.statusCode = 200;
res.setHeader('Content-Type', 'text/plain');
res.end('Hello World\n');
});
server.listen(3000, () => {
console.log('Server running at http://localhost:3000/');
});
编译和运行
编译 TypeScript 文件:
npx tsc
运行编译后的 JavaScript 文件:
node dist/index.js
或者,使用 ts-node 直接运行 TypeScript 文件:
npx ts-node src/index.ts
相关面试题
基础知识
-
TypeScript 和 JavaScript 有什么区别?
答案:
- TypeScript 是由微软开发并维护的开源编程语言,它是 JavaScript 的超集。
- TypeScript 增加了可选的静态类型、类、接口等,从而使得代码在开发阶段更易于维护和扩展。
- TypeScript 代码需要编译为 JavaScript 才能在浏览器中运行。
-
如何在 TypeScript 中定义和使用接口?
答案:
interface Person { name: string; age: number; } const greetPerson = (person: Person) => { console.log(`Hello, ${person.name}`); }; let user: Person = { name: 'John', age: 30 }; greetPerson(user);
-
TypeScript 支持的基本数据类型有哪些?
答案:
string
:表示字符串类型。number
:表示数字类型,可以是整数或浮点数。boolean
:表示布尔值类型。array
:表示数组类型,例如number[]
或Array<number>
。tuple
:表示固定数量和类型的数组,例如[string, number]
。enum
:表示枚举类型。any
:表示任意类型。void
:表示没有任何类型,通常用于函数返回值类型。null
和undefined
:这两种类型分别表示 null 和 undefined。never
:表示那些永不存在的值的类型。
-
什么是元组(Tuple),以及如何使用?
答案: 元组是一种允许表示一个已知数量和类型的数组的一种类型,每个元素的类型可以不相同。
let person: [string, number]; person = ["John", 30]; // 正确 // person = [30, "John"]; // 错误:顺序和类型不匹配
-
TypeScript 中的 Enum 是什么,如何使用?
答案: 枚举(Enum)是一个用于定义数值集合的类型。
enum Color { Red, Green, Blue } let c: Color = Color.Green; console.log(c); // 输出 1,默认枚举从 0 开始编号
-
如何定义泛型(Generics)?可以给一个例子吗?
答案: 泛型提供了创建可重用组件的能力,使组件不仅能够支持当前的数据类型,还能够支持未来的数据类型。
function identity<T>(arg: T): T { return arg; } let output1 = identity<string>("myString"); // 使用字符串类型 let output2 = identity<number>(100); // 使用数字类型
-
TypeScript 中
any
、unknown
、void
和never
有什么区别?答案:
any
:表示任意类型,绕过类型检查。unknown
:表示未知类型,使用之前需要进行类型判断或类型断言,安全性优于any
。void
:表示没有任何类型,通常用于函数返回值类型。never
:表示永远不存在的类型,通常用于不可能返回的函数 (例如抛出异常或无限循环)。
-
TypeScript 中的类型推论是什么?可以给一个例子吗?
答案: 类型推论是指 TypeScript 会根据变量的赋值自动推断变量的类型。
let x = 3; // 'x' 的类型被推断为 'number' let y = "hello"; // 'y' 的类型被推断为 'string'
高级类型
-
解释一下联合类型(Union Types)和交叉类型(Intersection Types)。
答案:
-
联合类型(Union Types)表示一个值可以是几种类型之一。
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}'.`); }
-
交叉类型(Intersection Types)表示将多个类型合并为一个类型。
interface A { a: string; } interface B { b: string; } type C = A & B; let obj: C = { a: "hello", b: "world" };
-
-
TypeScript 中的类型守卫(Type Guards)是什么?如何使用?
答案: 类型守卫用于对不确定的类型进行类型保护,使得代码更加安全。
function isNumber(x: any): x is number { return typeof x === "number"; } function padLeft(value: string, padding: string | number) { if (isNumber(padding)) { return Array(padding + 1).join(" ") + value; } if (typeof padding === "string") { return padding + value; } throw new Error(`Expected string or number, got '${typeof padding}'.`); }
-
Partial、Pick 和 Omit 这些实用工具类型分别有什么用?
答案:
-
Partial<T>
:构造一个类型,使类型 T 的所有属性变为可选。interface User { id: number; name: string; age: number; } const updateUser = (id: number, newValues: Partial<User>): User => { // 更新用户的逻辑 return { id, ...newValues } as User; };
-
Pick<T, K>
:构造一个类型,它包括 T 的部分属性 K。interface User { id: number; name: string; age: number; } const getUserInfo = (user: User): Pick<User, 'name' | 'age'> => { return { name: user.name, age: user.age }; };
-
Omit<T, K>
:构造一个类型,它包括 T 的所有属性,除了 K。interface User { id: number; name: string; age: number; } const createUser = (user: Omit<User, 'id'>): User => { return { id: Math.floor(Math.random() * 1000), ...user }; };
-
-
TypeScript 中如何进行类型转换?请给出示例。
答案: 类型转换可以通过类型断言来实现。类型断言有两种语法形式:
// 方式一:使用尖括号 let someValue: any = "this is a string"; let strLength: number = (<string>someValue).length; // 方式二:使用 `as` 语法 let strLength2: number = (someValue as string).length;
面向对象编程
-
如何在 TypeScript 中定义和实现类?
答案:
class Animal { private name: string; constructor(theName: string) { this.name = theName; } move(distanceInMeters: number = 0) { console.log(`${this.name} moved ${distanceInMeters}m.`); } } let dog = new Animal("Dog"); dog.move(10);
-
TypeScript 中的访问修饰符有哪些?请解释它们的用途。
答案:
public
:默认修饰符,表示可在任何地方访问。private
:表示只能在类内部访问。protected
:表示可以在类和子类中访问,但不能在类的实例中访问。
class Person { public name: string; private age: number; protected height: number; constructor(name: string, age: number, height: number) { this.name = name; this.age = age; this.height = height; } } class Employee extends Person { constructor(name: string, age: number, height: number) { super(name, age, height); } displayHeight() { console.log(this.height); // 允许访问 } } let emp = new Employee('John', 30, 180); console.log(emp.name); // 允许访问 // console.log(emp.age); // 错误:不能访问 private 属性 // console.log(emp.height); // 错误:不能访问 protected 属性
-
如何在 TypeScript 中实现抽象类和接口?
答案: 抽象类和接口都可以用于定义抽象的行为,但抽象类可以包含实际的实现。
abstract class Department { constructor(public name: string) {} printName(): void { console.log('Department name: ' + this.name); } abstract printMeeting(): void; // 必须在派生类中实现 } class AccountingDepartment extends Department { constructor() { super('Accounting and Auditing'); } printMeeting(): void { console.log('The Accounting Department meets each Monday at 10am.'); } generateReports(): void { console.log('Generating accounting reports...'); } } let department: Department; // department = new Department(); // 错误: 不能创建一个抽象类的实例 department = new AccountingDepartment(); // 允许创建一个具体类的实例 department.printName(); department.printMeeting();
-
TypeScript 支持装饰器吗?什么是装饰器?
答案: 装饰器是一种特殊的声明,它能够被附加到类声明、方法、访问符、属性或参数上,可以修改类的行为。装饰器在执行时,实际上是一个函数。
function sealed(constructor: Function) { Object.seal(constructor); Object.seal(constructor.prototype); } @sealed class Greeter { greeting: string; constructor(message: string) { this.greeting = message; } greet() { return "Hello, " + this.greeting; } }
函数相关
-
如何在 TypeScript 中定义函数类型?
答案:
// 定义函数类型 let myAdd: (x: number, y: number) => number; // 实现函数 myAdd = function(x: number, y: number): number { return x + y; };
-
如何在 TypeScript 中处理函数的可选参数和默认参数?
答案:
// 可选参数 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 buildNameWithDefault(firstName: string, lastName: string = "Smith"): string { return firstName + " " + lastName; } let result3 = buildNameWithDefault("Bob"); // "Bob Smith" let result4 = buildNameWithDefault("Bob", "Adams"); // "Bob Adams"
-
如何在 TypeScript 中处理剩余参数?请给出示例。
答案:
function buildName(firstName: string, ...restOfName: string[]): string { return firstName + " " + restOfName.join(" "); } let employeeName = buildName("Joseph", "Samuel", "Lucas", "MacKinzie"); // employeeName 现在是 "Joseph Samuel Lucas MacKinzie"
编译配置
-
tsconfig.json
文件的作用是什么?答案:
tsconfig.json
文件用于指定 TypeScript 项目的根文件和编译选项。通过配置该文件,可以有效地管理项目的编译过程。 -
有哪些常用的 TypeScript 编译配置选项?解释它们的作用。
答案:
target
:指定 ECMAScript 的目标版本,例如es5
,es6
。module
:指定使用的模块系统,例如commonjs
,amd
。strict
:启用所有严格类型检查选项。esModuleInterop
:生成代码支持import
语义,以便与非 TypeScript 的 CommonJS 模块兼容。
{ "compilerOptions": { "target": "es5", "module": "commonjs", "strict": true, "esModuleInterop": true } }
-
如何设置 TypeScript 项目的
strict
模式?它包括哪些检查?答案: 在
tsconfig.json
文件中设置strict
为true
,启用所有严格类型检查选项。它包括:noImplicitAny
:不能隐式推断出any
类型。strictNullChecks
:严格的空值检查。strictFunctionTypes
:对函数类型进行严格检查。strictPropertyInitialization
:确保类的实例属性在构造函数中初始化。noImplicitThis
:不能隐式地将this
上下文类型推断为any
。alwaysStrict
:在每个文件里加上"use strict"
。
{ "compilerOptions": { "strict": true } }
工具集成
-
如何在一个 Vue 项目中集成 TypeScript?
答案: 使用 Vue CLI 创建一个包含 TypeScript 支持的 Vue 项目。
vue create my-vue-app
在创建过程中选择 TypeScript 选项。然后,你可以在项目中编写
.ts
文件和使用<script lang="ts">
标签来编写 TypeScript 代码。<template> <div class="hello"> <h1>{{ msg }}</h1> </div> </template> <script lang="ts"> import Vue from 'vue'; import Component from 'vue-class-component'; @Component export default class HelloWorld extends Vue { msg: string = 'Hello World'; } </script> <style scoped> h1 { color: #42b983; } </style>
-
如何在一个 React 项目中集成 TypeScript?
答案: 使用 Create React App 创建一个包含 TypeScript 支持的 React 项目。
npx create-react-app my-react-app --template typescript
然后,你可以在项目中编写
.tsx
文件来编写 TypeScript 代码。import React from 'react'; interface Props { message: string; } const Hello: React.FC<Props> = ({ message }) => { return <h1>{message}</h1>; }; export default Hello;
-
如何在 Node.js 项目中使用 TypeScript?
答案: 创建一个新的 Node.js 项目并初始化 TypeScript。
mkdir my-node-app && cd my-node-app npm init -y npm install typescript @types/node ts-node --save-dev npx tsc --init
在
tsconfig.json
中配置 TypeScript 的编译选项。{ "compilerOptions": { "target": "es6", "module": "commonjs", "outDir": "./dist", "rootDir": "./src", "strict": true, "esModuleInterop": true } }
编写一个简单的 Node.js 应用程序:
src/index.ts
import * as http from 'http'; const server = http.createServer((req, res) => { res.statusCode = 200; res.setHeader('Content-Type', 'text/plain'); res.end('Hello World\n'); }); server.listen(3000, () => { console.log('Server running at http://localhost:3000/'); });
编译 TypeScript 文件:
npx tsc
运行编译后的 JavaScript 文件:
node dist/index.js
或者,使用 ts-node 直接运行 TypeScript 文件:
npx ts-node src/index.ts
错误处理与调试
-
如何在 TypeScript 中处理编译时错误?
答案: 在 TypeScript 中,通过配置
tsconfig.json
和使用严格的类型检查选项(如strict
模式),可以在编译时捕获类型错误。编译过程中出现的错误将被展示在终端或代码编辑器中。 -
如何为现有的 JavaScript 项目逐步引入 TypeScript?
答案:
-
安装 TypeScript 和相关类型声明:
npm install typescript @types/node --save-dev
-
添加一个
tsconfig.json
文件:{ "compilerOptions": { "outDir": "./dist", "allowJs": true, "
-