TypeScript 类型体操练习,持续更新中...

164 阅读4分钟

前言

Github 地址:github.com/type-challe…
TypeScript 是趋势,早学晚学,早晚都要学,就以这个练习开始,边练边学,边学边总结。

正文

热身题

Hello World

TS Playground:github.com/type-challe…

13 - Hello World
-------
by Anthony Fu (@antfu) #warm-up

### Question

Hello, World!

In Type Challenges, we use the type system itself to do the assertion.

For this challenge, you will need to change the following code to make the tests pass (no type check errors).


// expected to be string
type HelloWorld = any


// you should make this work
type test = Expect<Equal<HelloWorld, string>>

Click the `Take the Challenge` button to start coding! Happy Hacking!

> View on GitHub: https://tsch.js.org/13

/* _____________ Your Code Here _____________ */

type HelloWorld = any // expected to be a string

/* _____________ Test Cases _____________ */
import type { Equal, Expect, NotAny } from '@type-challenges/utils'

type cases = [
  Expect<NotAny<HelloWorld>>,
  Expect<Equal<HelloWorld, string>>,
]

答案

type HelloWorld = string

总结
类型变量的定义方式有三种,typeinterfaceenum,三者都相当于 JS 中的const,一旦定义就不可改变,区别是:

  • neum仅用来定义枚举类型;
  • interface可用来定义函数、对象、类;
  • type用于绝大多数类型,如普通的值、对象、函数、数组、元组等

TS 的基础类型:

  • 布尔:boolean
  • 数字:number
  • 字符串:string
  • 数组:number[]/Array<number>
  • 元组:[number, string]
  • 枚举:enum Color { RED, GREEN, BLUE }
  • any
  • void
  • nullundefined
  • never
  • object

简单题

实现Pick

TS Playground:github.com/type-challe…

4 - Pick
-------
by Anthony Fu (@antfu) #easy #union #built-in

### Question

Implement the built-in `Pick<T, K>` generic without using it.

Constructs a type by picking the set of properties `K` from `T`

For example:

interface Todo {
    title: string
    description: string
    completed: boolean
}

type TodoPreview = MyPick<Todo, 'title' | 'completed'>

const todo: TodoPreview = {
  title: 'Clean room',
  completed: false,
}

> View on GitHub: <https://tsch.js.org/4>

/* _____________ Your Code Here _____________ */

type MyPick<T, K> = any

/* _____________ Test Cases _____________ */
import type { Equal, Expect } from '@type-challenges/utils'

type cases = [
  Expect<Equal<Expected1, MyPick<Todo, 'title'>>>,
  Expect<Equal<Expected2, MyPick<Todo, 'title' | 'completed'>>>,
  // @ts-expect-error
  MyPick<Todo, 'title' | 'completed' | 'invalid'>,
]

interface Todo {
  title: string
  description: string
  completed: boolean
}

interface Expected1 {
  title: string
}

interface Expected2 {
  title: string
  completed: boolean
}

答案

type MyPick<T, K extends keyof T> = {
    [key in K]: T[key]
};

总结

keyof:取interface的键保存为联合类型

interface user {
    name: string,
    age: number
}
type keyofUser = keyof user;
// keyofUser = 'name' | 'age'

in:取联合类型的值,主要用于数组和对象的构建

type name = 'firstname' | 'lastname';
type TName = {
    [key in name]: string
};
// TName = { firstname: string, lastname: string };

实际运用举例:

function getVal(o: object, k: string) {
    return o[k];
}
const user = { name: '张三', age: 18 };
const nameVal = getVal(user, 'name'); // 张三

以上写法的缺点:

  1. 无法确定返回值类型
  2. 无法对 key 进行约束
function getVal<T extends object, K extends keyof T>(o: T, k: K): T[K] {
    return o[k];
}
const user = { name: '张三', age: 18 };
const nameVal = getVal(user, 'name'); // 张三

此时,如果第二个参数 k 不是 user 的 key 值(name/age)就会报错

实现Readonly

TS Playground:www.typescriptlang.org/play?#code/…

7 - Readonly
-------
by Anthony Fu (@antfu) #easy #built-in #readonly #object-keys

### Question

Implement the built-in `Readonly<T>` generic without using it.

Constructs a type with all properties of T set to readonly, meaning the properties of the constructed type cannot be reassigned.

For example:

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

> View on GitHub: <https://tsch.js.org/7>

/* _____________ Your Code Here _____________ */

type MyReadonly<T> = any

/* _____________ Test Cases _____________ */
import type { Equal, Expect } from '@type-challenges/utils'

type cases = [
  Expect<Equal<MyReadonly<Todo1>, Readonly<Todo1>>>,
]

interface Todo1 {
  title: string
  description: string
  completed: boolean
  meta: {
    author: string
  }
}

答案

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

总结
readonly

  • readonly修饰符,首先是一个关键字
  • 对类中的属性成员进行修饰
  • 修饰之后,该属性成员就不能修改,那么这个属性成员就成为类的参数属性
  • 只能访问
  • 在构造函数中是可以对只读属性进行修改
  • 在类的普通方法中不能被修改
class Person {
    readonly name: string,
    constructor(name: string) {
        this.name = name;
    }
    say() {
        console.log(`我的名字叫${this.name}`);
    }
}
let person = new Person('Haha');

Tuple to Object

TS Playground:www.typescriptlang.org/play?#code/…

题目

11 - Tuple to Object
-------
by sinoon (@sinoon) #easy #object-keys

### Question

Given an array, transform it into an object type and the key/value must be in the provided array.

For example:

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'}

> View on GitHub: <https://tsch.js.org/11>

* _____________ Your Code Here _____________ */

type TupleToObject<T extends readonly any[]> = any

/* _____________ Test Cases _____________ */
import type { Equal, Expect } from '@type-challenges/utils'

const tuple = ['tesla', 'model 3', 'model X', 'model Y'] as const
const tupleNumber = [1, 2, 3, 4] as const
const tupleMix = [1, '2', 3, '4'] as const

type cases = [
  Expect<Equal<TupleToObject<typeof tuple>, { tesla: 'tesla'; 'model 3': 'model 3'; 'model X': 'model X'; 'model Y': 'model Y' }>>,
  Expect<Equal<TupleToObject<typeof tupleNumber>, { 1: 1; 2: 2; 3: 3; 4: 4 }>>,
  Expect<Equal<TupleToObject<typeof tupleMix>, { 1: 1; '2': '2'; 3: 3; '4': '4' }>>,
]

// @ts-expect-error
type error = TupleToObject<[[1, 2], {}]>

答案

// 方法一
type TupleToObject<T extends readonly (string | number | symbol)[]> = {
    [K in T[number]]: K
};
// 方法二
type TupleToObject<T extends readonly PropertyKey[]> = {
    [K in T[number]]: K
};
// 方法三
type TupleToObject<T extends readonly (keyof any)[]> = {
    [K in T[number]]: K
};
// 方法四
type TupleToObject<T extends ReadonlyArray<PropertyKey>> = {
    [K in T[number]]: K
};
// 方法五
type TupleToObject<T extends ReadonlyArary<string | number | symbol>> = {
    [K in T[number]]: K
};

总结
as关键字表示断言,as const是 TS 中的一个修饰符,可以用来修改类型推断的行为。当as const修饰符用在变量声明或表达式的类型上时,会强制 TS 将变量或表达式的类型视为不可变的(immutable)。就意味着,如果你尝试对变量或表达式进行修改,TS 会报错。 as const可以帮助我们提高代码类型安全性,避免在不应该修改的地方进行修改。

const a = [1, 2] as const;
a.push(3); // TS 报错:因为 foo 类型被声明为不可变的

const b = { x: 1, y: -1 } as const;
b.x = 2; // TS 报错,因为 b 类型被声明为不可变的

多个类型集合用小括号括起来;
PerpertyKey是 TS 的一种内置类型;
ReadonlyArray是 TS 中特殊的类型,用于生成不应该被更改的数组,ReadonlyArray<Type>可简写为readonly Type[],它只能作为类型使用,不能当成构造函数使用。