type alias

type alias 是用来指定变量的类型。我们可以直接通过类型注释来指定特定变量的类型情况,设想一个场景,如果我们对某个类型有多次重复使用的需求,这时我们就可以使用 type alias 来进行抽象,一次定义多次使用。

  1. type User = {
  2. name: string;
  3. id: number;
  4. }
  5. // 类型别名也可以用来创建 union type
  6. type ID = number | string;

一个相同的类型别名不可进行随意的扩展,不能创建不同版本的类型别名。

interfaces

interface 是用来定义一个对象的类型结构

  1. interface User {
  2. name: string;
  3. id: number;
  4. }

typescript 只关注我们所定义的 interface 结构及其内部的类型,这也对应了 TS 结构化类型系统的特性。

type alias 和 interfaces 的异同

类型的扩展

type alias 使用 intersection 来扩展类型

  1. type Animal = {
  2. name: string;
  3. }
  4. type Bear = Animal & {
  5. honey: boolean;
  6. }
  7. const bear = getBear();
  8. bear.name;
  9. bear.honey;

inteface 使用 extends 关键字来进行扩展

  1. interface Animal {
  2. name: string;
  3. }
  4. interface Bear extends Animal {
  5. honey: boolean;
  6. }
  7. const bear = getBear();
  8. bear.name;
  9. bear.honey;

添加新的 fields

type alias 定义完之后就是不可变的

  1. type Window = {
  2. title: string;
  3. }
  4. type Window = {
  5. name: string;
  6. }
  7. // Error💥: Duplicate identifier 'Window'

interface 是可以进行随意添加的

  1. interface Window {
  2. title: string;
  3. }
  4. interface Window {
  5. name: string;
  6. }
  7. window.name = 'hello';

正是因为了某些特性的差异,导致在不同的场景也会出现不同的表现。

Index access types

假设我们需要一个函数来发送请求,它需要传入数据和对应的请求头

  1. declare function send(data: any, headers: Record<string, string>): void;

Record<string, string> 等价于 { [key: string]: string },它可以更好的表示 index access。
假设我们有两种定义的类型,一种使用 type alias,另一种使用 interface

  1. type HTTPHeaders = {
  2. Accept: string;
  3. Cookie: string;
  4. }
  5. interface HTTPHeadersInterface {
  6. Accept: string;
  7. Cookie: string;
  8. }

此时,如果我们将经过类型注释的变量进行传递的时候,一切都是正常的:

  1. const headers: HTTPHeaders = {
  2. Accept: 'text/html',
  3. Cookie: ''
  4. }
  5. send({}: headers) // ✅

但如果此时将 HTTPHeaders 修改为 HTTPHeadersInterface,事情就不一样了

  1. const headers: HTTPHeadersInterface = {
  2. Accept: 'text/html',
  3. Cookie: ''
  4. }
  5. send({}: headers) // 💥 Index signature is missing in type 'HTTPHEadersInterface'

TS 会报出 index signature is missing 的异常。只有当所有类型都已经标明后,TS 才可以进行正确的检查,检查我们声明的内容是否可以被赋值于我们定义在 send 中的 Record<string, string> 类型。因为,interface 本身是可以被随意扩展的,因此我们就无法掌握所有的 interface 中的类型,TS 无法辨别 index signature 是否和 Record<string, string> 是兼容的。这也体现了 type aliasinterface 在设计层面的不同考量,一个用来指定类型,一个用来指定特定结构。

type alias 和 interface 不同场景的使用

相同点

相同点是以下的这几种场景两者会用不同的方式来进行标识

  • 定义对象的结构
  • merge 或者 extends 结构
  • 使用范型 ```typescript // 定义对象结构 interface ButtonProps { children: string size?: ‘small’ | ‘large’ width?: ‘fixed’ | ‘fit’ | ‘fill’ }

type ButtonProps = { children: string size?: ‘small’ | ‘large’ width?: ‘fixed’ | ‘fit’ | ‘fill’ }

  1. ```typescript
  2. // merge 或者 extends
  3. interface ElementStylingProps {
  4. className?: string
  5. }
  6. interface ButtonProps extends ElementStylingProps {
  7. children: string
  8. size?: 'small' | 'large'
  9. width?: 'fixed' | 'fit' | 'fill'
  10. }
  11. type ElementStylingProps = {
  12. classNames?: string
  13. }
  14. type ButtonProps = ElementStylingProps & {
  15. children: string
  16. size?: 'small' | 'large'
  17. width?: 'fixed' | 'fit' | 'fill'
  18. }

这里需要注意的是,interface 也可以 extend type alias。同样的,type alias 也可以 intersect interface

  1. // 使用范型
  2. interface AsProp<C extends React.ElementType> {
  3. as?: C
  4. }
  5. type AsProp<C extends React.ElementType> = {
  6. as?: C
  7. }

适合 type alias 的场景

  • 定义 descriminated unions
  • 扩展 descriminated unions
  • 使用内置的类型工具或者自定义的范型类型 ```typescript type TruncatePrpos = | { truncate?: fase; showExpanded?: never } | { truncate?: true; showExpanded?: boolean }

type TextProps = TruncatedProps & { children: React.ReactNode }

  1. 这种情况下不可以使用 interface,会出现报错
  2. > An interface can only extend an object type or intersection of object types with statically known members.
  3. ```typescript
  4. type ButtonProps = {
  5. children: string
  6. size?: 'small' | 'large'
  7. width?: 'fixed' | 'fit' | 'fill'
  8. }
  9. type OptionalButtonProps = Partial<ButtonProps>
  10. type ButtonLayoutProps = Pick<ButtonProps, 'size' | 'width'>

使用 interface 也可以达到类似的效果,但会很丑陋:

  1. interface OptionalButtonProps extends Partial<ButtonProps> {}
  2. interface ButtonLayoutProps extends Pick<ButtonProps, 'size' | 'width'> {}

适合 interface 的场景

当我们想要覆盖一个对象类型的属性的时候,使用 interface 会很方便

  1. interface ButtonProps {
  2. children: string
  3. size?: 'small' | 'large'
  4. width?: 'fixed' | 'fit' | 'fill'
  5. }
  6. interface NumberButtonProps extends ButtonProps {
  7. children: number
  8. }
  9. // NumberButtonProps['children'] -> 'number'
  1. type ButtonProps = {
  2. children: string
  3. size?: 'small' | 'large'
  4. width?: 'fixed' | 'fit' | 'fill'
  5. }
  6. type NumberButtonProps = ButtonProps & {
  7. children: number
  8. }
  9. // NumberButtonProps['children'] -> 'never'
  10. // (string & number) -> 'never'
  11. type NumberButtonProps = Omit<ButtonProps, 'children'> & {
  12. children: number
  13. }

总结

如果你是在做一个函数库,里面有很多的 API 彼此依赖并且可以相互拓展,这时候使用 interface 就是一个非常好的选择,而除此之外的场景 type alias 会是一个更好的选择,他可以使你的代码更为简洁。