Почему Typescript пытается сопоставить одну ветвь условного типа с другой?

#typescript #return-type #conditional-types

#typescript #возвращаемый тип #условные типы

Вопрос:

У меня есть небольшая служебная функция, и теперь я пытаюсь написать для нее немного более конкретный тип, используя дженерики.

Вот код:

 /* global localStorage */

const ls = localStorage

export type ParsedJSON<T> = T | null
export type Falsy = false | undefined

function get<T = any> (
  key: string,
  parseJSON?: boolean
): typeof parseJSON extends Falsy ? string : ParsedJSON<T> {
  const value = ls.getItem(key) ?? ''

  if (parseJSON === true) {
    let result: ParsedJSON<T> = null

    try {
      result = JSON.parse(value)
    } catch (err) {}

    return result
  } else if ((parseJSON === false)) {
    return value
  }

  return null
}
 

У меня ошибка для ветки else : Type 'string' is not assignable to type 'ParsedJSON<T>' .

(Ветвь else здесь немного избыточна, но я добавил ее явно для расследования этой ошибки).

введите описание изображения здесь

Я не могу понять, почему TS пытается сопоставить string тип с ParsedJSON<T> типом.

Единственное разумное объяснение для меня заключается в том, что false это не может быть присвоено Falsy типу, поэтому возвращаемый тип вычисляется как ParsedJSON<T> . Но я проверил, что это не так:

введите описание изображения здесь

Поэтому, пожалуйста, помогите мне понять, что здесь происходит. ) Спасибо.

Комментарии:

1. Это не решение этой проблемы, но я думаю, вам следует рассмотреть возможность перегрузки функций

Ответ №1:

Похоже, что происходит то, что TS вычисляет выражение типа typeof parseJSON extends Falsy ? string : ParsedJSON<T> не как условное выражение, подлежащее вычислению с общими параметрами при вызове функции, а сразу на основе доступной в данный момент информации о функции.

Зная это, мы можем определить, каким компилятор определяет возвращаемый тип. Выражение

 typeof parseJSON extends Falsy ? string : ParsedJSON<T>
 

parseJSON тип во время вычисления равен boolean | undefined , поэтому он не расширяет Falsy то, что есть false | undefined , и, следовательно, является более узким типом.

Поэтому get тип возвращаемого значения определяется как простой: ParsedJSON<T> .

Единственный способ, которым я могу придумать условный возвращаемый тип, основанный на фактических значениях параметров на сайте вызова, — это, как предположил @Nishant, перегрузка функций. Например.:

 function get<T = any>(key: string, parseJSON: true): ParsedJSON<T>
function get<T = any>(key: string, parseJSON?: false): string
function get<T = any>(key: string, parseJSON?: boolean) {
  const value = ls.getItem(key) ?? ''

  if (parseJSON === true) {
    let result: ParsedJSON<T> = null

    try {
      result = JSON.parse(value)
    } catch (err) { }

    return result
  } else if ((parseJSON === false)) {
    return value
  }

  return null
}
 

Теперь функция будет иметь правильный возвращаемый тип на основе parseJSON ‘s type .

 get<number>('', true) // => ParsedJSON<number>
get<number>('', false) // => string
get<number>('') // => string
 

Смотрите этот код на игровой площадке TS