Сопоставленная запись вызывает тип any при рекурсивном использовании

#typescript

#typescript

Вопрос:

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

Есть ли способ предотвратить переход к типу any?

 type DataType<T extends Record<keyof T, Obj<any, any>>> = {
  [P in keyof T]: T[P]['data']
}

// I need to be able to pass in a Data generic
// to this object
class Obj<
  Data extends DataType<T>,
  T extends Record<keyof Data, Obj<any, any>>
> {
  constructor(public fields: T) {}

  public data: Data
}

const recursive = new Obj({
  // With this field, the 'recursive' variable becomes type 'any'
  get query() {
    return recursive
  },
  test: new Obj({})
})

// Without recursive field it works as expected
const nonRecursive = new Obj({ test: new Obj({}) })
nonRecursive.data.test // okay
  

Ответ №1:

Хм, вы используете здесь множество общих типов, ссылающихся друг на друга, и, признаюсь, я почти не представляю, что здесь происходит, и действительно ли это лучший способ представления вашей структуры данных, без вариантов использования и примеров. Я бы предположил, что вы, вероятно, могли бы обойтись одним параметром типа, подобным этому:

 class Obj<T>
{
    constructor(public fields: { [K in keyof T]: Obj<T[K]> }) {
    }
    public data!: T
}
  

Но даже если вы это сделаете, у вас все равно будет та же проблема, которая возникает, если вы включите более строгие параметры компилятора, такие как --noImplicitAny или --strict . Затем вы увидите, что ваше определение recursive выдает следующие ошибки:

'recursive' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer.

и

'query' implicitly has return type 'any' because it does not have a return type annotation and is referenced directly or indirectly in one of its return expressions.

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

Вот один из способов сделать это с типами, которые у вас есть:

 // explicit type
interface RecursiveObj extends Obj<
    DataType<
        { readonly query: RecursiveObj; test: Obj<{}, {}>; }
    >, { readonly query: RecursiveObj; test: Obj<{}, {}>; }> {
}

const recursive = new Obj({
    // explicitly annotated return type
    get query(): RecursiveObj {
        return recursive
    },
    test: new Obj({})
}); // okay now
  

И вот как я бы сделал это с упрощенным Obj<T> типом, который я перечислил выше:

 // explicit type
interface RecursiveData {
    readonly query: RecursiveData,
    test: {}
}

const recursive = new Obj({
    // explicitly annotated return type
    get query(): Obj<RecursiveData> {
        return recursive
    },
    test: new Obj({})
}); // okay now
  

Хорошо, надеюсь, это поможет. Удачи!