多多读书
1264 字
6 分钟
TypeScript挑战第二天

对象属性只读#

不要使用内置的Readonly<T>,自己实现一个。

泛型Readonly<T>会接收一个泛型参数,并返回一个完全一样的类型,只是所有属性都会是只读 (readonly) 的。

也就是不可以再对该对象的属性赋值。

例如:

interface Todo {
  title: string
  description: string
}

const todo: MyReadonly<Todo> = {
  title: 'Hey',
  description: 'foobar',
}

todo.title = 'Hello' // Error: cannot reassign a readonly property
todo.description = 'barFoo' // Error: cannot reassign a readonly property

解答#

要实现一个自定义的Readonly<T>类型,可以在 TypeScript 中使用映射类型。映射类型允许你基于现有的类型创建新类型,并且可以对属性进行转换。在这个例子中,你需要将所有属性标记为readonly

实现示例:

type MyReadonly<T> = {
  readonly [P in keyof T]: T[P]
}

这里,MyReadonly<T>使用映射类型来遍历T的所有属性 (keyof T),并将每个属性 (P) 映射为它自己的类型 (T[P]),但加上了readonly修饰符。这样,任何使用 MyReadonly<T>的对象,其所有属性都会变为只读。

元组转换为对象#

将一个元组类型转换为对象类型,这个对象类型的键/值和元组中的元素对应。

例如:

const tuple = ['tesla', 'model 3', 'model X', 'model Y'] as const

type result = TupleToObject<typeof tuple> // expected { tesla: 'tesla', 'model 3': 'model 3', 'model X': 'model X', 'model Y': 'model Y'}

解答#

为了将一个元组类型转换为一个对象类型,其中对象的键和值都对应于元组中的元素,可以使用 TypeScript 中的映射类型。关键是使用typeof获取元组的类型,然后通过映射类型遍历该元组类型的所有索引。

实现示例:

type TupleToObject<T extends readonly (string | number | symbol)[]> = {
  [P in T[number]]: P
}

在这个实现中,TupleToObject接受一个类型参数T,它是一个元组。这里的T extends readonly (string | number | symbol)[]确保T是一个不可变的数组类型。然后,我们使用T[number]来获取数组中所有值的联合类型,并通过映射类型 ([P in T[number]]: P) 来创建一个新对象,其中每个元组值都作为键和值。

第一个元素#

实现一个First<T>泛型,它接受一个数组T并返回它的第一个元素的类型。

例如:

type arr1 = ['a', 'b', 'c']
type arr2 = [3, 2, 1]

type head1 = First<arr1> // 应推导出 'a'
type head2 = First<arr2> // 应推导出 3

解答#

要实现获取第一个元素类型,可以定义一个First<T>泛型,它接受一个数组类型T并返回该数组的第一个元素的类型。需要确保这个类型能正确处理常规数组和空数组。在处理空数组时,它应该返回never类型。

实现示例:

type First<T extends any[]> = T extends [] ? never : T[0]
// 其他解法
// type First<T extends any[]> = T extends [infer F, ...any[]] ? F : never;

在这个实现中,First<T>首先检查T是否是一个空数组(T extends [])。如果是空数组,返回never;否则,返回T[0],即数组的第一个元素的类型。这样,First<T>可以根据传入的数组类型准确地返回其第一个元素的类型。

获取元组长度#

创建一个Length泛型,这个泛型接受一个只读的元组,返回这个元组的长度。

例如:

type tesla = ['tesla', 'model 3', 'model X', 'model Y']
type spaceX = ['FALCON 9', 'FALCON HEAVY', 'DRAGON', 'STARSHIP', 'HUMAN SPACEFLIGHT']

type teslaLength = Length<tesla> // expected 4
type spaceXLength = Length<spaceX> // expected 5

解答#

为了实现Length泛型,这个泛型接受一个只读的元组,并返回这个元组的长度,我们可以利用 TypeScript 中的T["length"]属性。对于元组类型,T["length"]将返回该元组的长度。

实现示例:

type Length<T extends readonly any[]> = T['length']

在这个实现中,Length<T>接受一个类型参数T,它被约束为只读的数组类型readonly any[]。然后,T['length']直接获取了这个数组类型的长度。

实现 Exclude#

实现内置的 Exclude<T, U> 类型,但不能直接使用它本身。

从联合类型 T 中排除 U 中的类型,来构造一个新的类型。

例如:

type Result = MyExclude<'a' | 'b' | 'c', 'a'> // 'b' | 'c'

解答#

为了实现MyExclude<T, U>类型,我们需要从联合类型T中排除那些也存在于U中的类型。这可以通过使用条件类型来实现。条件类型允许我们对类型进行检查,并根据检查的结果返回不同的类型。

实现示例:

type MyExclude<T, U> = T extends U ? never : T

在这个实现中,MyExclude<T, U>使用了条件类型T extends U ? never : T。这里,我们对于T中的每个类型,检查它是否可以赋值给U。如果可以(T extends U),则结果类型为never(这意味着这个类型将被排除)。如果不可以,我们就保留这个类型(T)。通过这种方式,MyExclude<T, U>有效地从T中排除了所有也存在于U的类型。

TypeScript挑战第二天
https://fuwari.vercel.app/posts/20231128/
作者
我也困了
发布于
2023-11-28
许可协议
CC BY-NC-SA 4.0