import encodeParam from './encodeParam'

interface UrlParam<T> {
  name: string
  defaultValue: T
  encode: (value: T) => string | null
  decode: (value: string) => T
}

function buildQueryString<T>(...params: [UrlParam<T>, T][]) {
  const searchParams = new URLSearchParams()

  params.forEach(([param, value]) => {
    const newValue = encodeParam(param, value)

    if (newValue) {
      searchParams.set(param.name, newValue)
    }
  })

  return searchParams.toString()
}

const one: UrlParam<string> = {
  name: 'one',
  defaultValue: '',
  encode: (val) => val,
  decode: (val) => val,
}

const two: UrlParam<number> = {
  name: 'two',
  defaultValue: 0,
  encode: (val) => val.toString(),
  decode: (val) => Number(val),
}

const query = buildQueryString(
  [one, 'foo'],
  [two, 42], // This will not compile because only UrlParam<string> is expected.
)

Я написал функцию под названием buildQueryString для кодирования серии параметров запроса с использованием кортежей, содержащих определение параметра (UrlParam) и значение, которое следует использовать. Я изо всех сил пытаюсь настроить сигнатуру метода для приема смешанного типа UrlParam<T>, как это можно сделать?

Это, конечно, должно работать так, чтобы ограничение [UrlParam<T>, T] кортежа по-прежнему проверялось, поэтому типы any не используются.

1
Jon Koops 1 Дек 2020 в 20:18

1 ответ

Лучший ответ

Playground Link

Ключ к типизации таких функций - убедиться, что универсальный тип может содержать всю необходимую информацию. С помощью всего лишь T все преобразуется в объединения, и мы теряем порядок. Вместо этого мы можем использовать универсальный тип массив / кортеж (который я назвал A), где каждая запись соответствует каждому кортежу аргументов:

type UrlParamPair<T> = [ UrlParam<T>, T ];
type UrlParamPairs<A extends any[]> = { [K in keyof A]: UrlParamPair<A[K]> };

function buildQueryString<A extends any[]>(...params: UrlParamPairs<A>) {
    ...
}

Тип UrlParamPairs (массив) преобразует массив типов в массив кортежей, например UrlParamPairs<[string, number]> становится [ UrlParamPair<string>, UrlParamPair<number> ]

Вывод типа для вызова функции показывает, как это работает:

function buildQueryString<[string, number]>(
    params_0: UrlParamPair<string>, params_1: UrlParamPair<number>): string
1
Mingwei Samuel 1 Дек 2020 в 18:15