TypeScript challenges

1,199 阅读5分钟

这是我参与8月更文挑战的第2天,活动详情查看:8月更文挑战

背景

因为平常自己用TS,用的不深,只是简单的使用,然后想做做题加深理解,下面是自己做的一些题目,长期更新

自己做的题库

github.com/type-challe…

TS 练习题

  1. 实现Pick
interface Todo {
  title: string
  description: string
  completed: boolean
}

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

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

答案:

type MyPick<T, K extends keyof T> = {
      [P in K]: T[K]
  };
  1. 为 numCompare 函数添加类型注解。
function numCompare(first, second) {
  return first >= second ? first : second;
}

答:

function numCompare(first: number, second: number): number {
  return first >= second ? first : second;
}
  1. 解决 inputBgChange 函数报错,使函数能正常运行
function inputBgChange(): void {
    let oInput: HTMLInputElement;
    if (document.querySelector('.oInput')) {
        oInput = document.querySelector('#oInput');
        oInput.style.background = 'red';
    } else {
        oInput = document.createElement('input');
        oInput.id = 'oInput';
        oInput.style.background = 'red';
        document.body.appendChild(oInput);
    }
}

答:

使用类型断言解决问题

function inputBgChange(): void {
    let oInput: HTMLInputElement;
    if (document.querySelector('.oInput')) {
        oInput = document.querySelector('#oInput') as HTMLInputElement;
        oInput.style.background = 'red';
    } else {
        oInput = document.createElement('input');
        oInput.id = 'oInput';
        oInput.style.background = 'red';
        document.body.appendChild(oInput);
    }
}
  1. 补充完整接口 User 的定义,并且为 users 提供更加准确的类型注解。

interface User {
  // todo
}

// 将 unknow 替换成更准确的类型
let users: unknown = [
  {
    name: 'Jack Ma',
    age: 17,
    sex: 'male',
  },
  {
    name: 'Tony Ma',
    age: 18,
  },
]

答:

interface User {
    // todo
    name: string,
    age: number,
    sex?: string
}

// 将 unknow 替换成更准确的类型
let users: Array<User> = [
    {
        name: 'Jack Ma',
        age: 17,
        sex: 'male',
    },
    {
        name: 'Tony Ma',
        age: 18,
    },
]
  1. 实现一个 If 工具泛型,接受一个条件 C,若 C 为 true 返回类型 T 类型,否则返回 F 类型。
type If<C, T, F> = any; // todo

type T1 = If<true, boolean, number>;
// T1 为 boolean 类型

type T2 = If<false, boolean, number>;
// T2 为 number 类型

答:

type If<C extends boolean, T, F> = C extends true ? T : F; // todo

type T1 = If<true, boolean, number>;
// T1 为 boolean 类型

type T2 = If<false, boolean, number>;
// T2 为 number 类型
  1. 实现一个 post 方法,当我们请求地址 url 为 /user/add 时,请求参数 params 中必传字段为名字 name 和年龄 age,性别 sex 为选填字段,其中除了 age 字段类型为 number,其余字段类型都为 string。
import axios from 'axios';

function post(url: any, params: any) {
  return axios.post(url, params)
}

post('/user/del', {name: 'jack Ma', age: 17});
// 报错, url 传参错误

post('/user/add', {name: 'jack Ma'});
// 报错, 缺少请求参数 age 字段

post('/user/add', {name: 'jack Ma', age: 17});
// 请求成功

post('/user/add', {name: 'jack Ma', age: 17, sex: 'male'})
// 请求成功

答案:

interface API {
    '/user/add': {
        name: string,
        age: number,
        sex?: string
    }
}

function post<T extends keyof API>(url: T, params: API[T]) {
  return axios.post(url, params);
}

post('/user/del', {name: 'jack Ma', age: 17});
// 报错, url 传参错误

post('/user/add', {name: 'jack Ma'});
// 报错, 缺少请求参数 age 字段

post('/user/add', {name: 'jack Ma', age: 17});
// 请求成功

post('/user/add', {name: 'jack Ma', age: 17, sex: 'male'})
// 请求成功
  1. 实现一个 Includes<T, K> 工具泛型,T 为一个数组类型,判断 K 是否存在于 T 中,若存在返回 true,否则返回 false
type T1 = Includes<['name','age','sex'], 'name'>
// T1 的期望为 true

type T2 = Includes<['name','age','sex'], 'name'>
// T2 的期望为 false


答案:

type Includes<T extends any[], K> = K extends T[number] ? true : false;

  1. 实现一个 MyReadOnly<T, K> ,K 应为 T 的属性集,若指定 K ,则将 T 中对应的属性修改成只读属性,若不指定 K ,则将所有属性变为只读属性
interface Person {
  name: string,
  age: number,
}
  
type MyReadOnly<T, K> = any;

const jack: MyReadOnly<Person, 'name'> = {
  name: 'jack',
  age: 17,
}

jack.name = 'jack';
// error

jcak.age = 18;
// success

答案:

type MyReadOnly<T, K extends keyof T = keyof T> = {
      readonly [P in K]: T[P]
  } & T;
  1. 实现一个 AppendArgX<Fn, X> 工具泛型,对于给定的函数类型 Fn, 和任意的类型 X,在 Fn 函数的传参末尾追加类型为 X 的参数
type Fn = (a: number, b: string) => number

type NewFn = AppendArgX<Fn, boolean> 
// NewFn 期望是 (a: number, b: string, x: boolean) => number

答案:

  type AppendArgX<Fn, X> = Fn extends (...args: infer P) => infer R ? (...args: [...P, X])=>R : never;
  1. 实现一个 GetRequired 工具泛型,将 T 中的所有必需属性提取出来
type RequiredKeys<T> = keyof T extends infer K
  ? K extends keyof T
    ? T[K] extends Required<T>[K]
      ? K
      : never
    : never
  : never;
// Required<T> 是 TypeScript 内置的工具泛型,可以将 T 所有属性设置为必需属性

type GetRequired<T> = {
  [key in RequiredKeys<T>]: T[key]
};


  1. 实现 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

答案:

 type MyReadonly<T> = {
      readonly [P in keyof T]: T[P]
  };
  1. 元组转换为对象
const tuple = ['tesla', 'model 3', 'model X', 'model Y'] as const

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

答案:

type TupleToObject<T extends readonly any[]> = {
    [Property in T[number]]: Property
};
  1. 实现Record

答案:

type MyRecord<T extends typeof any, U> = {
  [K in T]: U
}
  1. 实现 Exclude

把某个类型中,属于另一个的类型移除掉

答案:

// 如果T能够赋值给U类型的话,那么就会返回never类型,否则返回T类型
// 实现的效果就是把T类型中的U类型给排除掉
type Exclude<T, U> = T extends U ? never: T;

  1. 去除所有never成员

答案:

使用{}[keyof T] 把对象类型变成联合类型

type OmitNever<T> = Pick<T, {
    [K in keyof T]: T[K] extends never ? never : K
}[keyof T]>

type T123 = {
    a: string,
    b: never,
    c: string,
}

type L = OmitNever<T123>;

  1. 实现一个通用First,它接受一个数组T并返回它的第一个元素的类型。
type arr1 = ['a', 'b', 'c']
type arr2 = [3, 2, 1]

type head1 = First<arr1> // expected to be 'a'
type head2 = First<arr2> // expected to be 3

答案:

type First<T extends any[]> = T[0] extends T[number] ? T[0] : never; 
  1. 对于给定的元组,您需要创建一个通用的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

答案:

type Length<T extends any[]> = T['length'];
  1. 元组转换为联合类型
type TTuple = [string, number];
type Union = ElementOf<TTuple>; // Union 类型为 string | number

答案:

type ElementOf<T> = T extends (infer R)[] ? R : never;
  1. 实现一个工具 If,它接受条件C、true返回类型T和false返回类型F。C可以是true或false,而T和F可以是任何类型。
type A = If<true, 'a', 'b'>  // expected to be 'a'
type B = If<false, 'a', 'b'> // expected to be 'b'

答案:

type If<C extends boolean, T, F> = C extends true ? T: F;
  1. 在类型系统中实现JavaScript Array.concat函数。类型接受两个参数。输出应该是一个新的数组,其中包括ltr顺序的输入
type Result = Concat<[1], [2]> // expected to be [1, 2]

答案:

type Concat<T extends any[], U extends any[]> = [...T, ...U];
type isPillarMen = Includes<['Kars', 'Esidisi', 'Wamuu', 'Santana'], 'Dio'> // expected to be `false`

答案:

type Includes<T extends any[], U> = {
    [K in T[number]]: true
}[U] extends true ? true : false;
  1. 关于infer的经典题目,实现ReturnType
const fn = (v: boolean) => {
  if (v)
    return 1
  else
    return 2
}


type a = MyReturnType<typeof fn> // 应推导出 "1 | 2"
type MyReturnType<T> = T extends (...args: any) => infer R ? R : never;  

  1. 不使用 Omit 实现 TypeScript 的 Omit<T, K> 范型。Omit 会创建一个省略 K 中字段的 T 对象。
 interface Todo {
    title: string
    description: string
    completed: boolean
  }
  type TodoPreview = MyOmit<Todo, 'description' | 'title'>;
  
  const todo1233: TodoPreview = {
    completed: false,
  }

答案:

type MyOmit<T, K> = {
    [P in Exclude<keyof T, K>]: T[P]
};
  1. 写一个deepReadonly
type deepReadonly<T> = {
      readonly [K in keyof T]: T[K] extends any[] | number | string | boolean | Function ? T[K] : deepReadonly<T[K]>
  }

  1. 元组转合集
type TupleToUnion<T extends any[]> = T[number]

未完待续。。。

参考资料

github.com/type-challe…