type alias
type alias 是用来指定变量的类型。我们可以直接通过类型注释来指定特定变量的类型情况,设想一个场景,如果我们对某个类型有多次重复使用的需求,这时我们就可以使用 type alias
来进行抽象,一次定义多次使用。
type User = {
name: string;
id: number;
}
// 类型别名也可以用来创建 union type
type ID = number | string;
一个相同的类型别名不可进行随意的扩展,不能创建不同版本的类型别名。
interfaces
interface 是用来定义一个对象的类型结构。
interface User {
name: string;
id: number;
}
typescript 只关注我们所定义的 interface 结构及其内部的类型,这也对应了 TS 结构化类型系统的特性。
type alias 和 interfaces 的异同
类型的扩展
type alias 使用 intersection 来扩展类型
type Animal = {
name: string;
}
type Bear = Animal & {
honey: boolean;
}
const bear = getBear();
bear.name;
bear.honey;
inteface 使用 extends 关键字来进行扩展
interface Animal {
name: string;
}
interface Bear extends Animal {
honey: boolean;
}
const bear = getBear();
bear.name;
bear.honey;
添加新的 fields
type alias 定义完之后就是不可变的
type Window = {
title: string;
}
type Window = {
name: string;
}
// Error💥: Duplicate identifier 'Window'
interface 是可以进行随意添加的
interface Window {
title: string;
}
interface Window {
name: string;
}
window.name = 'hello';
正是因为了某些特性的差异,导致在不同的场景也会出现不同的表现。
Index access types
假设我们需要一个函数来发送请求,它需要传入数据和对应的请求头
declare function send(data: any, headers: Record<string, string>): void;
Record<string, string>
等价于 { [key: string]: string }
,它可以更好的表示 index access。
假设我们有两种定义的类型,一种使用 type alias
,另一种使用 interface
:
type HTTPHeaders = {
Accept: string;
Cookie: string;
}
interface HTTPHeadersInterface {
Accept: string;
Cookie: string;
}
此时,如果我们将经过类型注释的变量进行传递的时候,一切都是正常的:
const headers: HTTPHeaders = {
Accept: 'text/html',
Cookie: ''
}
send({}: headers) // ✅
但如果此时将 HTTPHeaders
修改为 HTTPHeadersInterface
,事情就不一样了
const headers: HTTPHeadersInterface = {
Accept: 'text/html',
Cookie: ''
}
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 alias
和 interface
在设计层面的不同考量,一个用来指定类型,一个用来指定特定结构。
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’ }
```typescript
// merge 或者 extends
interface ElementStylingProps {
className?: string
}
interface ButtonProps extends ElementStylingProps {
children: string
size?: 'small' | 'large'
width?: 'fixed' | 'fit' | 'fill'
}
type ElementStylingProps = {
classNames?: string
}
type ButtonProps = ElementStylingProps & {
children: string
size?: 'small' | 'large'
width?: 'fixed' | 'fit' | 'fill'
}
这里需要注意的是,interface 也可以 extend type alias。同样的,type alias 也可以 intersect interface
// 使用范型
interface AsProp<C extends React.ElementType> {
as?: C
}
type AsProp<C extends React.ElementType> = {
as?: C
}
适合 type alias 的场景
- 定义 descriminated unions
- 扩展 descriminated unions
- 使用内置的类型工具或者自定义的范型类型 ```typescript type TruncatePrpos = | { truncate?: fase; showExpanded?: never } | { truncate?: true; showExpanded?: boolean }
type TextProps = TruncatedProps & { children: React.ReactNode }
这种情况下不可以使用 interface,会出现报错
> An interface can only extend an object type or intersection of object types with statically known members.
```typescript
type ButtonProps = {
children: string
size?: 'small' | 'large'
width?: 'fixed' | 'fit' | 'fill'
}
type OptionalButtonProps = Partial<ButtonProps>
type ButtonLayoutProps = Pick<ButtonProps, 'size' | 'width'>
使用 interface 也可以达到类似的效果,但会很丑陋:
interface OptionalButtonProps extends Partial<ButtonProps> {}
interface ButtonLayoutProps extends Pick<ButtonProps, 'size' | 'width'> {}
适合 interface 的场景
当我们想要覆盖一个对象类型的属性的时候,使用 interface 会很方便
interface ButtonProps {
children: string
size?: 'small' | 'large'
width?: 'fixed' | 'fit' | 'fill'
}
interface NumberButtonProps extends ButtonProps {
children: number
}
// NumberButtonProps['children'] -> 'number'
type ButtonProps = {
children: string
size?: 'small' | 'large'
width?: 'fixed' | 'fit' | 'fill'
}
type NumberButtonProps = ButtonProps & {
children: number
}
// NumberButtonProps['children'] -> 'never'
// (string & number) -> 'never'
type NumberButtonProps = Omit<ButtonProps, 'children'> & {
children: number
}
总结
如果你是在做一个函数库,里面有很多的 API 彼此依赖并且可以相互拓展,这时候使用 interface
就是一个非常好的选择,而除此之外的场景 type alias
会是一个更好的选择,他可以使你的代码更为简洁。