Рекурсивное определение типа исправления

#typescript #recursive-type

#typescript #рекурсивный тип

Вопрос:

Я пытаюсь написать универсальный тип, который принимает тип в качестве параметра (который может быть простым объектом, массивом, примитивом и т. Д.) И переназначает типы значений, когда это простой объект или массив, Чтобы добавить некоторые директивы конфигурации, описываемые Configuration типом.

Давайте назовем этот гипотетический модификатор Configurable<T> . T может быть любым сложным вложенным объектом. Book может быть значением T , например :

 type Configuration = {
  $test: {
    option1: boolean;
    option2: string;
  }
};

type Book = {
  id: string;
  title: string;
  author: string;
  related: Array<string>;
};

type Result = Configurable<Book>;
 

Затем я хочу Configurable<Book> правильно проверить следующие выражения, в которых значения могут быть фактическими значениями или объектом конфигурации :

 const expr1: Configurable<Book> = {
  id: "1",
  title: "Harry Potter",
  author: "J.K. Rowling",
  related: ["2", "3"]
}

const expr2: Configurable<Book> = {
  id: "2",
  title: "Harry Potter",
  author: {
    $test: {
      option1: true,
      option2: "something"
    }
  },
  related: []
}

const expr3: Configurable<Book> = {
  id: "3",
  title: "Harry Potter",
  author: "J.K. Rowling",
  related: ["2", {
    $test: {
      option1: true,
      option2: "something"
    }
  }]
}

const expr4: Configurable<Book> = {
  id: "4",
  title: true, // ERROR: should be string or Configuration
  author: "J.K. Rowling",
  related: ["2", "3"]
}

const expr5: Configurable<Book> = {
  id: "5",
  title: "Harry Potter",
  author: "J.K. Rowling",
  related: {
    $test: {
      option1: true,
      option2: "something"
    }
  } // ERROR: should be an array of (string | Configuration)
}
 

Вложенный объект или массивы не должны заменяться на Configuration , только там, где ожидается примитивное значение (см. expr5 ).
Вот что я попробовал :

 type Configuration = {
  $test: {
    option1: boolean;
    option2: string;
  };
};

type Configurable<T> = Record<string, any> extends T
  ? {
      [K in keyof T]: Configurable<T[K]> | Configuration;
    }
  : T extends Array<infer U>
  ? Array<Configurable<U>>
  : T;
 

Но это приводит expr2 к сбою и expr3 сбою.

Ссылка на игровую площадку

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

1. Не expr2 требуется related свойство? Я принимаю сумасшедшие таблетки здесь?

2. @jcalz Да, моя ошибка.

Ответ №1:

Если я правильно понимаю ваши требования, вы могли бы использовать это определение Configurable :

 type Configurable<T> = T extends object ?
  { [K in keyof T]: Configurable<T[K]> } :
  T | Configuration;
 

object Тип соответствует любому не примитивному, включая массивы. Если T extends object значение не равно true, то T это примитив, который вы хотите принять T | Configuration . Если T extends object значение равно true, то вы сопоставляете его свойства с Configurable помощью . Это должно автоматически выполнять правильные действия с массивами и кортежами, поскольку сопоставленные кортежи и массивы создают кортежи и массивы.

Давайте попробуем:

 const expr1: Configurable<Book> = {
  id: "1",
  title: "Harry Potter",
  author: "J.K. Rowling",
  related: ["2", "3"]
}

const expr2: Configurable<Book> = {
  id: "2",
  title: "Harry Potter",
  author: {
    $test: {
      option1: true,
      option2: "something"
    }
  },
  related: []
}

const expr3: Configurable<Book> = {
  id: "3",
  title: "Harry Potter",
  author: "J.K. Rowling",
  related: ["2", {
    $test: {
      option1: true,
      option2: "something"
    }
  }]
}
 

Приведенные выше примеры компилируются без ошибок, как и требовалось. Давайте проверим ошибки:

 const expr4: Configurable<Book> = {
  id: "4",
  title: true, // ERROR: should be string or Configuration
  author: "J.K. Rowling",
  related: ["2", "3"]
}

const expr5: Configurable<Book> = {
  id: "5",
  title: "Harry Potter",
  author: "J.K. Rowling",
  related: {
    $test: {
      option1: true,
      option2: "something"
    }
  } // ERROR: should be an array of (string | Configuration)
}
 

Ошибки, которые вы хотите, действительно создаются. Выглядит хорошо!

Ссылка на игровую площадку для кода