TS中type和interface的区别

299 阅读3分钟

官网文档: www.typescriptlang.org/docs/handbo…

该使用哪个?

官方文档原话:

If you would like a heuristic, use interface until you need to use features from type.

大致意思是 当你需要为类(或对象)指明其结构, 请使用接口, 直到你需要使用type的功能.

当看完本文后, 我们也可总结出一些使用规律:

  • 当需要利用接口自动合并的特性时使用interface
  • 定义对象类型且不需要使用type的功能时使用interface
  • 定义基本类型的别名时, 使用type
  • 定义元组、函数、联合、映射类型时,使用type

二者的定义

interface用于描述对象的形状.

interface Person {
	name: string;
  age: number;
}
const person: Person = {
  name: "lalala",
  age: 18
}

type是类型别名, 它不是一个类型, 只是一个别名.

// 返回string或者函数的函数
function fn1(): string | (() => string) { return '123'}
// 使用类型别名后:
type FnType = string | (() => string) // FnType类型可代替string | (() => string), 更简洁
function fn2(): FnType { return '123'}
function fn3(): FnType { return () => '123'}

共同点

  • 都支持扩展
interface Animal {
    name: string;
}
interface Bear extends Animal {
    boney: boolean;
}
const bear = getBear()
bear.name
bear.honey
type Animal = {
  name: string;
}
type Bear = Animal & {
  honey: boolean;
}
const bear = getBear()
bear.name
bear.honey

不同点

  1. 同名interface可以合并, 但type同名会报错
interface Window {
  title: string;
}
interface Window {
  ts: TypeScriptAPI;
}
window.ts.api();
type Window = {
  title: string;
}
type Window = {
  ts: TypeScriptAPI;
}
// ERROR: type 不可重复声明

不只是Window, vue中有时我们也需要为vue实例声明全局属性, 全局属性的类型定义也是基于interface的扩展性:

export {};

declare module "vue" {
  // export interface ComponentCustomOptions { }

  /** 定义在vue实例上自定义的全局属性的类型 */
  export interface ComponentCustomProperties {
    $filters: typeof import("@/utils/filters");
  }
  // export interface GlobalDirectives { }
  // export interface GlobalComponents { }
}
  1. 接口只能用于声明对象的形状, 不能重命名基础类型:
// An interface cannot extend a primitive type like 'string'; an interface can only extend named types and classes
interface X extends string { // error
}

Vue3的App对象也是使用interface定义的.

而我们常用的Partial、Required、Pick、Record 和 Exclude 等工具类型都是以 type 方式来定义的.

type Name = string // 为基本类型指定别名
type arrItem = number | string // 联合类型

type Person = {
  name: Name
}
type Student = Person & { grade: number } // 交叉类型
type StudentAndTeacherList = [Student, Teacher] // 元组类型

微妙的例子:

interface MyInterface {
  foobar: string;
}
type MyType = {
  foobar: string;
}
const exampleInterface: MyInterface = { foobar: "hello" };
const exampleType: MyType = { foobar: "hello" };

let record: Record<string, string> = {};

record = exampleType;
record = exampleInterface; // Index signature is missing(缺少索引签名)

// 问题来源: https://stackoverflow.com/questions/64970414/typescript-assigning-an-interface-or-a-type-to-a-recordstring-string
// 原因: 当声明同名的interface时, 是会合并的, 也就是说, MyInterface的类型是不确定的, 也许最终
// 他会是: interface MyInterface {
//   foobar: string;
//   laz: number;
// },  这显然与record的类型不匹配!
// 但type是类型别名, 而且不存在同名合并, 其类型是确定的, 与record的类型匹配.
interface Person1 {
  name: string;
  age: number;
}
type Person2 = {
  name: string;
  age: number;
}
type Test1 = Person1 extends Record<string, any> ? true: false; // type Test1 = true
type Test2 = Person2 extends Record<string, any> ? true: false; // type Test2 = true

type Test3 = Person1 extends Record<string, unknown> ? true : false; // type Test3 = false
type Test4 = Person2 extends Record<string, unknown> ? true : false; // type Test4 = true

// type Test3 = false  同样是因为缺少索引签名
// 当你按照如下写法赋值时, 就可看到错误提示:
let example: Person1 = { name: "alala", age: 1 }; 
let record: Record<string, unknown> = {};
record = example // error: Index signature for type 'string' is missing in type 'Person1'.