Проблема с вводом общей структуры машинописного текста

#typescript

#typescript

Вопрос:

Я хотел бы иметь дело с такими данными, как

 const stateTest  = {
  natures : {
    nature1: {
      amounts: {
        column1: 10,
        column2: 10,
      },
      natureDetails : {
        detail1 : {
          amounts: {
            column1: 10,
            column2: 10,
          },
          descriptionShown: false
        }
      }
    }
  }
}
 

где список столбцов (здесь column1,column2) является параметром

итак, я объявил следующие типы и интерфейсы

 type State<T extends GenericColumnList> = T amp; {
  natures: naturesSet<T>
}
type GenericColumnList = Record<string, number>
type naturesSet<T extends GenericColumnList> = Partial<Record<string, RowNature<T>>>
export interface RowNature<T extends GenericColumnList> {
  natureDetails: NatureDetailsSet<T>
  amounts: T
}
type NatureDetailsSet<T> = Partial<Record<string, RowDetailNature<T>>>
export interface RowDetailNature<T> {
  amounts: T
  descriptionShown: boolean
}
 

но когда я пытаюсь передать следующий интерфейс списка столбцов в качестве параметра для состояния

 export interface MyColumns  {
  column1?: number
  column2?: number
}
 

то есть :

 const stateTest: State<MyColumns>  = {
  natures : {
    nature1: {
      amounts: {
        column1: 10,
        column2: 10,
      },
      natureDetails : {
        detail1 : {
          montants: {
            column1: 10,
            column2: 10,
          },
          descriptionShown: false
        }
      }
    }
  }
}
 

Компилятор typescript жалуется, что

‘Тип MyColumns не соответствует записи<string, number> ‘ и я не понимаю, почему.

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

1. Предположительно montants , это опечатка?

Ответ №1:

Тип Record<string, number> имеет подпись строкового индекса. interface MyColumns У него нет подписи индекса, и поэтому типы несовместимы.


Существует такая концепция, как неявная индексная подпись, в которой типы без явной индексной подписи рассматриваются как совместимые с индексируемым типом, если все известные свойства соответствуют индексной подписи… но это не относится к типам, объявленным как an interface ; это работает только с анонимными типами (или type псевдонимами таких анонимных типов). В GitHub, microsoft / TypeScript # 15300, обсуждается открытая проблема. Оказывается, что, во всяком случае, на данный момент, такое поведение является преднамеренным.

Итак, один из способов справиться с этим — создать MyColumns псевдоним типа анонимного типа вместо любого интерфейса, например:

 export type MyColumns = {
  column1?: number
  column2?: number
}
 

Затем вам нужно будет включить undefined в домен GenericColumnList , потому что тип MyColumns['column1'] is number | undefined , а не только number (при условии, что мы используем рекомендуемые --strict параметры компилятора, в том числе --strictNullChecks ):

 type GenericColumnList = Record<string, number | undefined>
 

И тогда ваше назначение работает:

 const stateTest: State<MyColumns> = {
  natures: {
    nature1: {
      amounts: {
        column1: 10,
        column2: 10,
      },
      natureDetails: {
        detail1: {
          amounts: {
            column1: 10,
            column2: 10,
          },
          descriptionShown: false
        }
      }
    }
  }
}; // okay
 

Предполагая, что мы не хотим требовать, чтобы люди использовали не- interface типы для T , мы могли бы вместо этого сделать GenericColumnList его универсальным, T чтобы вместо подписи индекса он имел те же ключи, что и T :

 type GenericColumnList<T> = { [K in keyof T]?: number } 
 

Здесь мы сделали свойства необязательными ( ? ) по той же причине, что и при добавлении | undefined ранее: чтобы сделать необязательные / отсутствующие ключи совместимыми при наличии --strictNullChecks .

Это требует некоторого разбрызгивания <T> вашего кода:

 type State<T extends GenericColumnList<T>> = T amp; {
  natures: NaturesSet<T>
}
type NaturesSet<T extends GenericColumnList<T>> = Partial<Record<string, RowNature<T>>>
export interface RowNature<T extends GenericColumnList<T>> {
  natureDetails: NatureDetailsSet<T>
  amounts: T
}
 

и снова, ваше назначение будет работать:

 const stateTest: State<MyColumns> = {
  natures: {
    nature1: {
      amounts: {
        column1: 10,
        column2: 10,
      },
      natureDetails: {
        detail1: {
          amounts: {
            column1: 10,
            column2: 10,
          },
          descriptionShown: false
        }
      }
    }
  }
}; // okay
 

Игровая площадка ссылка на код