Завдання

Реалізуйте типізовану версію lodash .without(). Without<T, U> приймає масив T, число або масив U і повертає масив без елементів U.

type Res = Without<[1, 2], 1>; // expected to be [2]
type Res1 = Without<[1, 2, 4, 1, 5], [1, 2]>; // expected to be [4, 5]
type Res2 = Without<[2, 3, 2, 3, 2, 3, 2, 3], [2, 3]>; // expected to be []

Розв’язок

Це завдання справді було цікавим. Нам потрібно реалізувати тип, який може фільтрувати елементи з кортежу. Починаємо з порожнього типу:

type Without<T, U> = any;

Оскільки нам потрібно працювати з конкретними елементами в кортежі, я використовую виведення, щоб отримати конкретний елемент і решту кортежу:

type Without<T, U> = T extends [infer H, ...infer T] ? never : never;

Маючи елемент із кортежу, ми можемо перевірити, чи цей елемент типу U. Нам потрібна ця перевірка, щоб вирішити, додавати елемент до результату чи ні:

type Without<T, U> = T extends [infer H, ...infer T]
  ? H extends U
    ? never
    : never
  : never;

Якщо він дорівнює вхідному типу U, це означає, що він нам не потрібен у кінцевому типі. Тому ми просто пропускаємо його і повертаємо кортеж без нього. Але, оскільки нам також потрібно обробити інші елементи, ми повертаємо не порожній кортеж, а кортеж із рекурсивним викликом Without:

type Without<T, U> = T extends [infer H, ...infer T]
  ? H extends U
    ? [...Without<T, U>]
    : never
  : never;

Таким чином, ми пропускаємо все, що відповідає U у нашому T. У випадку коли ми не повинні пропускати елемент, ми повертаємо кортеж із цим елементом:

type Without<T, U> = T extends [infer H, ...infer T]
  ? H extends U
    ? [...Without<T, U>]
    : [H, ...Without<T, U>]
  : never;

Залишився останній тип never, з яким ми маємо розібратися. Оскільки ми працюємо з варіативними типами і ... оператором, замість never ми повинні повертати порожній кортеж:

type Without<T, U> = T extends [infer H, ...infer T]
  ? H extends U
    ? [...Without<T, U>]
    : [H, ...Without<T, U>]
  : [];

Ми отримали робоче рішення для випадку, коли U є примітивним типом. Але в завданні також є випадок, коли він може бути кортежем чисел. Ми можемо це вирішити за допомогою ще одного умовного типу для U в H extends U.

Якщо U — це кортеж, ми повертаємо всі елементи в ньому як об’єднання, інакше — просто U:

type Without<T, U> = T extends [infer H, ...infer T]
  ? H extends (U extends number[] ? U[number] : U)
    ? [...Without<T, U>]
    : [H, ...Without<T, U>]
  : [];

Вітаю! Ми реалізували lodash-версію методу .without() у системі типів.

Посилання