带你读《现代TypeScript高级教程》十二、类型兼容:结构化类型(1)https://developer.aliyun.com/article/1348481?groupCode=tech_library
3. 鸭子类型的局限性
尽管鸭子类型有许多优点,但它也有一些局限性。
1) 类型安全
鸭子类型可能会降低代码的类型安全性。因为 TypeScript 的类型检查器只检查对象是否满足接口的结构,而不检查对象是否真的是接口所期望的类型。如果一个对象恰好有与接口相同的属性和方法,但实际上它并不是接口所期望的类型,TypeScript 的类型检查器可能无法发现这个错误。
例如,我们可能有一个 Dog 类型和一个 Cat 类型,它们都有一个 bark 方法。我们可能会错误地将一个 `Cat对象传递给一个期望Dog` 对象的函数,而 TypeScript 的类型检查器无法发现这个错误。
interface Dog { bark: () => void;} function letDogBark(dog: Dog) { dog.bark();} const cat = { bark: () => console.log('Meow...'), // Cats don't bark! purr: () => console.log('Purr...')}; letDogBark(cat); // No error, but it's wrong!
在这种情况下,我们需要更仔细地设计我们的类型和接口,以避免混淆。
2) 易读性和可维护性
鸭子类型可能会降低代码的易读性和可维护性。因为我们的代码只依赖于对象的结构,而不是对象的具体类型,这可能会使代码更难理解和维护。
为了提高易读性和可维护性,我们需要清晰地记录我们的接口和函数期望的对象结构。TypeScript 的类型注解和接口提供了一种强大的工具来实现这一点。
4. 使用鸭子类型的最佳实践
在使用鸭子类型时,有一些最佳实践可以帮助我们避免上述问题,并充分利用鸭子类型的优点。
1) 清晰地定义接口
我们应该清晰地定义我们的接口,以描述我们的函数和方法期望的对象结构。这有助于提高代码的易读性和可维护性。
例如,如果我们有一个函数,它期望一个具有 name 和 age 属性的对象,我们应该定义一个接口来描述这个结构。
interface Person { name: string; age: number;} function greet(person: Person) { console.log(`Hello, my name is person.nameandI′m{person.name} and I'm {person.age} years old.`);}
2) 适度使用鸭子类型
我们应该适度地使用鸭子类型。虽然鸭子类型有许多优点,但如果过度使用,可能会导致类型安全性的问题,以及易读性和可维护性的降低。我们应该在类型安全性、易读性、可维护性和灵活性之间找到一个平衡。
在某些情况下,我们可能更希望使用类和继承,而不是鸭子类型。例如,如果我们有一组紧密相关的类型,它们有共享的行为和状态,使用类和继承可能更合适。
interface Named { name: string;} class Person { name: string; constructor(name: string) { this.name = name; }} let p: Named;// OK, because of structural typing p = new Person('mike');
在这个例子中,尽管 Person 类并没有显式地实现 Named 接口,但是因为 Person 类有一个 name 属性,所以我们可以把 Person 的实例赋值给 Named 类型的变量。这是由于 TypeScript 的 "鸭子类型" 或 "结构化类型" 系统导致的。