Рекурсивный универсальный тип для расширения типов - странное поведение, это ошибка? - PullRequest
0 голосов
/ 05 декабря 2018

Учитывая интерфейс

interface Test {
  inner: {
    value: boolean,
  }
}

и класс

class ContextualData<T> {
   constructor(public data: T) {}
}

Я хотел бы иметь возможность сделать так:

const original: Test = {
  inner: {
    value: true,
  },
}

// Wrap the value in a ContextualData object.
original.inner.value = new ContextualData<boolean>(original.inner.value)

Который я 'я пытаюсь достичь, объявив следующие типы:

export type Primitive = undefined | null | boolean | string | number | Function

export type Contextuable<T> = T | ContextualData<T>

export type DeepContextuable<T> =
  T extends Primitive ? Contextuable<T> : DeepContextuableObject<T>

export type DeepContextuableObject<T> = {
  [K in keyof T]: DeepContextuable<T[K]>
}

и затем используя DeepContextual для преобразования моего Test интерфейса:

const original: DeepContextual<Test> = {
  inner: {
    value: new ContextualData<boolean>(true),
  },
}

Это прекрасно работает.

Теперь давайте добавим еще один метод к нашему ContextualData классу:

class ContextualData<T> {
   constructor(public data: T) {}

   public map<U>(mapFn: (current: T) => U): U {
     return mapFn(this.data)
   }
}

Даже без использования новой функции наш контекстный интерфейс с value: ContextualData<boolean>(true) теперь выдает следующую ошибку TS:

TS2322: Type 'ContextualData<boolean>' is not assignable to type 
'boolean | ContextualData<true> | ContextualData<false>'.

Чего мне не хватает?Это ошибка?

1 Ответ

0 голосов
/ 06 декабря 2018

Вы столкнулись с распределительным поведением условных типов.Это (очень полезное) поведение требует, чтобы условный тип распределялся по элементам объединения параметра «голый» тип.Сопоставьте это с тем фактом, что Typescript воспринимает boolean как true | false как, и мы получаем это.

DeepContextuable<boolean> = DeepContextuable<true | false>  
   = DeepContextuable<true> | DeepContextuable<false>  
   = (true | Contextuable<true>) | (false | Contextuable<false>)
   = boolean | Contextuable<true> |  Contextuable<false

Такое поведение имеет место только для параметров открытого типа.Чтобы отключить его, мы можем поместить параметр в кортеж, и все будет работать так, как вы ожидали.

export type Primitive = undefined | null | boolean | string | number | Function

export type Contextuable<T> = T | ContextualData<T>

export type DeepContextuable<T> =
[T] extends [Primitive] ? Contextuable<T> : DeepContextuableObject<T>

export type DeepContextuableObject<T> = {
    [K in keyof T]: DeepContextuable<T[K]>
}

interface Test {
    inner: {
        value: boolean,
    }
}

class ContextualData<T> {
    constructor(public data: T) { }

    public map<U>(mapFn: (current: T) => U): U {
        return mapFn(this.data)
    }
}

const original: DeepContextuable<Test> = {
    inner: {
        value: new ContextualData<boolean>(true),
    },
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...