相关文章
typeScript易错点浅析
一、Interfaces 和 Type 的区别
语法
- interface 使用 interface 关键字来定义。
- type 使用 type 关键字来定义。
1 2 3
| interface Person {}
type Person = {};
|
对象结构
- interface 仅用于描述对象结构。
- type 不仅可以描述对象结构,还可以为任何类型创建别名。
1 2 3 4 5 6 7 8 9 10 11
| interface Point { x: number; y: number; }
type Point = { x: number; y: number; };
type x : number
|
可扩展性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| interface Shape { color: string; }
interface Square extends Shape { sideLength: number; }
type Shape = { color: string; };
|
合并
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| interface A { prop: number; }
interface A { otherProp: string; }
type B = { prop: number; };
type B = { otherProp: string; };
|
二、JavaScript和TypeScript中 class 的区别
类型检查
TypeScript允许在class的成员(属性和方法)上定义类型注解,以确保在编译时进行类型检查。这有助于在开发过程中捕获潜在的类型错误,并提供更好的代码提示和文档。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| class Person { name: string; age: number;
constructor(name: string, age: number) { this.name = name; this.age = age; }
greet(otherPerson: Person): string { return `Hello, ${otherPerson.name}! My name is ${this.name}.`; } }
const person1 = new Person("Alice", 30); const person2 = new Person("Bob", 25);
const greeting: string = person1.greet(person2); console.log(greeting);
|
访问修饰符:
TypeScript引入了访问修饰符(public、private、protected),用于限制class成员的访问权限。这有助于封装和确保代码的安全性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| class Person { public name: string; private age: number; protected gender: string;
constructor(name: string, age: number, gender: string) { this.name = name; this.age = age; this.gender = gender; }
public introduce(): void { console.log(`Hello, my name is ${this.name}. I am ${this.age} years old.`); } }
class Student extends Person { constructor(name: string, age: number, gender: string) { super(name, age, gender); console.log(`My gender is ${this.gender}.`); } }
const person = new Person("Alice", 30, "female");
console.log(person.name);
const student = new Student("Bob", 25, "male"); student.introduce();
|
构造函数参数属性
TypeScript允许在class中的构造函数参数上直接定义属性,从而避免了在构造函数中显式声明属性并进行赋值的步骤。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| class Person { constructor(public name: string, public age: number) { }
public introduce(): void { console.log(`Hello, my name is ${this.name}. I am ${this.age} years old.`); } }
const person = new Person("Alice", 30);
console.log(person.name); console.log(person.age);
person.introduce();
|
而在js的版本中,这样写属性是不会自动赋值的
接口实现和继承
TypeScript支持接口,可以使用implements关键字来实现接口,以确保class实现了接口中定义的所有成员。此外,TypeScript还支持class之间的继承,可以使用extends关键字来创建class的继承关系。
使用implements关键字来实现接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| interface Shape { calculateArea(): number; }
class Rectangle implements Shape { constructor(private width: number, private height: number) {}
calculateArea(): number { return this.width * this.height; } }
|
假如没有实现了接口中定义的所有成员
class之间的继承
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
|
interface Shape { calculateArea(): number; }
class Rectangle implements Shape { constructor(private width: number, private height: number) {}
calculateArea(): number { return this.width * this.height; } }
class Square extends Rectangle { constructor(sideLength: number) { super(sideLength, sideLength); } }
const square = new Square(4); console.log("Square area:", square.calculateArea());
|
抽象类
TypeScript支持抽象类,这是一种不能被直接实例化的类,通常用作其他类的基类。抽象类可以包含抽象方法,这些方法必须在子类中实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| abstract class Shape { abstract calculateArea(): number; }
class Rectangle extends Shape { constructor(private width: number, private height: number) { super(); }
calculateArea(): number { return this.width * this.height; } }
const rectangle = new Rectangle(5, 10); console.log("Rectangle area:", rectangle.calculateArea());
|
类型推断和泛型
TypeScript提供更强大的类型推断能力,并支持泛型,这使得在class中使用更复杂的数据类型变得更容易和安全。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| class Box<T> { private item: T;
constructor(item: T) { this.item = item; }
getItem(): T { return this.item; } }
const numberBox = new Box<number>(10); console.log("Number from box:", numberBox.getItem());
const stringBox = new Box<string>("Hello"); console.log("String from box:", stringBox.getItem());
const inferredBox = new Box("World"); console.log("Inferred string from box:", inferredBox.getItem());
|
三、类型断言
类型断言(Type Assertion)是在TypeScript中用于告诉编译器一个值的具体类型的一种技术。它类似于其它编程语言中的类型转换,但是在编译时不会改变实际的数据结构,只是告诉编译器在编译时将某个值视为特定的类型。TypeScript中有两种方式进行类型断言:
两种方式进行类型断言
尖括号语法
1 2
| let someValue: any = "hello world"; let strLength: number = (<string>someValue).length;
|
as语法
1 2
| let someValue: any = "hello world"; let strLength: number = (someValue as string).length;
|
这两种语法的效果是完全相同的,只是两种风格之间的选择取决于你的偏好或项目的约定。
类型断言可以用于以下几种情况
类型转换
当你从一个更通用的类型转换为一个更具体的类型时,比如从any转换为一个具体的类型。
1 2
| let someValue: any = "this is a string"; let strLength: number = (someValue as string).length;
|
解决联合类型的类型推断问题
当你处理联合类型时,你可能知道某个值实际上是其中的某个特定类型。
1 2
| let someValue: string | number = "hello world"; let strLength: number = (someValue as string).length;
|
断言为函数类型
你可以使用类型断言指定函数的参数类型和返回类型,这在处理函数式编程和回调函数时很有用。
1 2 3 4 5
| interface MyFunction { (x: number, y: number): number; }
let myFunction: MyFunction = ((x, y) => x + y) as MyFunction;
|
断言为类类型
你可以使用类型断言将一个对象视为特定类的实例,从而调用特定类的方法或访问特定类的属性。
1 2 3 4 5 6 7 8 9 10
| class Animal {}
class Dog extends Animal { bark() { console.log('Woof! Woof!'); } }
let myAnimal: Animal = new Dog(); (myAnimal as Dog).bark();
|
四、索引类型
索引类型是TypeScript中一种特殊的类型,它允许我们根据对象的键来访问相应的属性值。在JavaScript中,我们可以使用对象的键来访问属性值,而索引类型在TypeScript中使这种行为更加类型安全。
在TypeScript中,有两种主要的索引类型:字符串索引类型和数字索引类型。
字符串索引类型
字符串索引类型允许我们使用字符串来索引对象的属性。这对于对象的键是字符串的情况非常有用。
1 2 3 4 5 6 7 8
| interface Dictionary { [key: string]: string; }
let myDictionary: Dictionary = { "a": "apple", "b": "banana" };
console.log(myDictionary["a"]); console.log(myDictionary["b"]);
|
数字索引类型
数字索引类型允许我们使用数字来索引对象的属性。这对于对象的键是数字的情况非常有用。
1 2 3 4 5 6 7 8
| interface NumberArray { [index: number]: number; }
let myArray: NumberArray = [1, 2, 3, 4, 5];
console.log(myArray[0]); console.log(myArray[1]);
|
五、泛型
泛型(Generics)是一种在编程语言中用于创建可重用、通用代码的技术。它允许在定义函数、类或接口时使用类型参数,这些类型参数可以在使用时被具体指定,从而增加代码的灵活性和复用性。泛型使得我们能够编写与数据类型无关的通用代码,从而提高代码的可维护性和可扩展性。
基本语法
泛型的基本语法包括使用尖括号(< >)来指定类型参数,将类型参数放在函数名或类名后面,并在函数体或类体中使用这些类型参数。
函数泛型
1 2 3 4 5 6 7
| function identity<T>(arg: T): T { return arg; }
let output = identity<string>("hello");
let output = identity("hello");
|
在上面的示例中,<T>
表示identity函数接受一个类型参数T,并返回类型为T的参数。当调用identity函数时,可以显式指定类型参数(例如<string>
),也可以通过类型推断省略类型参数。
类泛型
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| class Box<T> { private item: T;
constructor(item: T) { this.item = item; }
getItem(): T { return this.item; } }
let numberBox = new Box<number>(10); let stringBox = new Box<string>("hello");
|
在上面的示例中,Box<T>
表示Box类接受一个类型参数T,在创建Box类的实例时,可以通过尖括号指定具体的类型参数。
泛型约束
有时候我们需要对泛型进行约束,以确保某些操作在所有类型上都是有效的。泛型约束可以通过扩展(extends)关键字来实现。
1 2 3 4 5 6 7 8 9 10
| interface Lengthwise { length: number; }
function loggingIdentity<T extends Lengthwise>(arg: T): T { console.log(arg.length); return arg; }
loggingIdentity({ length: 10, value: 3 });
|
在上面的示例中,T extends Lengthwise表示泛型T必须符合Lengthwise接口的约束,即具有length属性。这样,我们就可以在泛型函数loggingIdentity中使用arg.length,而不会出现类型错误。
复杂点的例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| class Stack<T> { private elements: T[] = [];
push(element: T): void { this.elements.push(element); }
pop(): T | undefined { return this.elements.pop(); }
peek(): T | undefined { return this.elements[this.elements.length - 1]; }
isEmpty(): boolean { return this.elements.length === 0; } }
const numberStack = new Stack<number>(); numberStack.push(1); numberStack.push(2); numberStack.push(3);
console.log("Peek:", numberStack.peek());
while (!numberStack.isEmpty()) { console.log("Pop:", numberStack.pop()); }
|
在上面的示例中,我们定义了两个接口 Dog 和 Cat,分别表示狗和猫。然后我们使用交叉类型 Dog & Cat 将这两个接口合并为一个新的类型 DogCat。最后,我们创建了一个 DogCat 类型的变量 pet,它具有 Dog 和 Cat 接口中的所有成员,因此可以调用 bark() 和 meow() 方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| function mapTuple<T, U>(tuple: [T, T], fn: (item: T) => U): [U, U] { return [fn(tuple[0]), fn(tuple[1])]; }
function toUpperCase(str: string): string { return str.toUpperCase(); }
const originalTuple: [string, string] = ["hello", "world"]; const uppercasedTuple: [string, string] = mapTuple(originalTuple, toUpperCase);
console.log(uppercasedTuple);
|
在这个例子中,mapTuple 函数有两个泛型参数 T 和 U。T 表示输入元组的元素类型,U 表示转换后的元素类型。该函数接受一个元组 tuple 和一个转换函数 fn 作为参数,然后将转换函数应用于元组的每个元素,并返回一个新的元组。
六、交叉类型
交叉类型(Intersection Types)是 TypeScript 中一种用于组合多种类型的机制。通过交叉类型,我们可以将多个类型合并为一个类型,从而创建出一个新的类型,它具有所有类型的成员。
使用 & 符号来表示交叉类型。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| interface Dog { bark(): void; }
interface Cat { meow(): void; }
type DogCat = Dog & Cat;
let pet: DogCat = { bark() { console.log('Woof! Woof!'); }, meow() { console.log('Meow! Meow!'); } };
pet.bark(); pet.meow();
|
七、any类型
any 类型是 TypeScript 中的一种特殊类型,它表示任意类型。当我们将一个变量声明为 any 类型时,TypeScript 将不会对这个变量进行类型检查,从而允许我们在编码时可以使用任何类型的值赋给这个变量,也可以调用任何方法,访问任何属性,甚至可以对它进行任何操作,而不会报错。
any 类型的主要特点包括:
类型推断
如果不显示指定类型,TypeScript 会将不带类型注释的变量自动推断为 any 类型。
1 2 3
| let variable variable = 10 variable = "hello"
|
兼容性
any 类型兼容所有类型,所有类型也兼容 any 类型。
1 2
| let value: any = 10; let numberValue: number = value;
|
取消类型检查
在使用 any 类型时,TypeScript 不会对它进行类型检查,因此可以绕过类型检查,但也会失去 TypeScript 的类型安全性。
1 2
| let data: any; data.someMethod(); // 合法,但可能在运行时出错
|
灵活性
any 类型可以让我们处理动态类型的数据,或者处理无法确定类型的情况,例如从第三方库中获取的数据。
1 2
| let dynamicData: any = fetchDataFromThirdParty(); // 在处理从第三方库获取的数据时,可以将其声明为 any 类型以适应不同的数据类型
|
八、keyof关键字
keyof 是 TypeScript 中的一个关键字,用于获取某个类型的所有键(属性名)的联合类型。它常用于与泛型结合,以提取类型的键集合并进行类型约束或操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| interface Person { username: string; age: number; email: string; }
type PersonKey = keyof Person;
function getProperty<T, K extends keyof T>(obj: T, key: K) { return obj[key]; }
const person: Person = { username: "John", age: 30, email: "john@example.com" };
const username = getProperty(person, "username"); const age = getProperty(person, "age"); const email = getProperty(person, "email");
console.log(username) console.log(age) console.log(email)
|
九、命名空间
命名空间(Namespace)是 TypeScript 中用来组织代码的一种方式。它类似于其他编程语言中的模块(Module)的概念,但在 TypeScript 中,命名空间主要用于在全局范围内组织代码,并防止命名冲突。
基本语法
在 TypeScript 中,使用 namespace 关键字来定义命名空间,然后在命名空间中定义变量、函数、类等。命名空间内的成员只在该命名空间内部可见,如果要在外部访问命名空间内的成员,需要使用命名空间的全限定名称。
1 2 3 4 5 6 7 8 9 10 11
| namespace MyNamespace { export const foo = 123;
export function bar() { console.log("bar"); }
export class Baz { constructor(public name: string) {} } }
|
使用方法
导出成员
命名空间中的成员默认是私有的,如果要在外部访问,需要使用 export 关键字进行导出。
1 2 3
| namespace MyNamespace { export const foo = 123; }
|
访问命名空间成员
可以使用点运算符来访问命名空间中的成员,也可以使用全限定名称。
1 2 3 4
| MyNamespace.foo; MyNamespace.bar(); const baz = new MyNamespace.Baz("example"); console.log(baz.name);
|
嵌套命名空间
命名空间也可以嵌套定义,以进一步组织代码。
1 2 3 4 5
| namespace OuterNamespace { export namespace InnerNamespace { export const x = 10; } }
|
十、never类型
在 TypeScript 中,never 类型表示的是那些永远不会出现的值的类型。它通常用于以下几种情况:
函数返回值
当一个函数永远不会正常返回时,可以将其返回类型标注为 never。
1 2 3
| function throwError(message: string): never { throw new Error(message); }
|
在这个例子中,throwError 函数抛出一个错误,因此它永远不会正常返回,因此返回类型是 never。
永远不会发生的条件
在某些条件判断中,编译器可以推断出某些分支永远不会执行,因此可以将其类型标注为 never。
1 2 3 4 5 6 7 8 9 10 11 12 13
| function checkType(x: string | number) { if (typeof x === "string") { console.log(x.toUpperCase()); } else if (typeof x === "number") { console.log(x.toFixed(2)); } else { const exhaustiveCheck: never = x; console.log(exhaustiveCheck); } }
|
在这个例子中,由于 x 可能是 string 或 number,因此在前两个条件分支中,编译器能够推断出 x 的具体类型。但是,在最后一个条件分支中,由于前面的条件已经覆盖了所有可能的类型,因此这个条件分支是永远不会执行的,所以 x 被推断为 never 类型。
空数组
never[] 类型表示一个永远不会包含任何元素的数组。
1
| let emptyArray: never = ;
|
never 类型在 TypeScript 中通常用于表示不可能发生的值或情况,它可以帮助开发者更准确地描述代码的行为,并帮助编译器进行类型检查。