Как использовать /define Enums с проверкой типа потока?

Я пытаюсь перенести существующую базу кода, чтобы использовать Flow. Поскольку этот проект начался без потока, я использую довольно типичный шаблон JS для перечислений и т.д.

Вот несколько определений, которые я хочу

export const LOAN_STATUS  = {
  PENDING: 'pending',
  CURRENT: 'current',
  DUE: 'due',
  OVERDUE: 'overdue',
  PENDING_PAYMENT: 'pending_payment',
  CHARGED_OFF: 'charged_off',
  VOIDED: 'voided',
  DISPUTED: 'disputed',
  REFUNDED: 'refunded',
  SETTLED: 'settled',
}

export const ACTIVE_LOAN_STATUS = [
  LOAN_STATUS.OVERDUE,
  LOAN_STATUS.CURRENT,
  LOAN_STATUS.DUE,
  LOAN_STATUS.PENDING_PAYMENT,
]

Flow работает нормально, пока я не импортирую этот файл, и он говорит, что мне нужно добавить аннотации типов. Это кажется странным - почему я должен комментировать объекты, которые полностью статичны и легко выводятся?

Есть ли способ определить его тип как "статический" или "литерал"?

Итак, я размышляю над тем, как я собираюсь добавить аннотации к этому. Моя первая мысль - это просто {[key: string]: string} и Array<string>. Flow работает, но я понимаю, что эти определения типов совершенно бесполезны. Итак, я пробую этот другой подход:

type LoanStatusValues =
  'pending' |
  'current' |
  'due' |
  'overdue' |
  'pending_payment' |
  'charged_off' |
  'voided' |
  'disputed' |
  'refunded' |
  'settled'

type LoanStatusKeys =
  'PENDING' |
  'CURRENT' |
  'DUE' |
  'OVERDUE' |
  'PENDING_PAYMENT' |
  'CHARGED_OFF' |
  'VOIDED' |
  'DISPUTED' |
  'REFUNDED' |
  'SETTLED'

type ActiveLoanStatus = 
"current" |
"due" |
"overdue" |
"pending_payment"

И я использую аннотации типа {[key: LoanStatusKeys]: LoanStatusValues} и Array<ActiveLoanStatus>. Но даже эти аннотации теряют тот факт, что это статично!

Кажется странным, что мне приходится писать этот многократный код. И тогда, если я хочу преобразовать только в Flow, я не могу использовать типы в JS. Например, я могу сделать это:

if (defs.ACTIVE_LOAN_STATUS.indexOf(loan.status) !== -1) {

}

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

type ActiveLoanStatus = 
  "current" |
  "due" |
  "overdue" |
  "pending_payment"

if (loan.status isTypeOf ActiveLoanStatus) {

}

Итак, как я должен использовать эти статические перечисления? Я должен делать это неправильно!

Ответы

Ответ 1

Вот наиболее краткий способ достижения этого:

const activeLoanStatuses = {
  current: 'current',
  due: 'due',
  overdue: 'overdue',
  pending_payment: 'pending_payment'
};

const otherLoanStatuses = {
  pending: 'pending',
  charged_off: 'charged_off',
  voided: 'voided',
  disputed: 'disputed',
  refunded: 'refunded',
  settled: 'settled',
};

type ActiveLoanStatus = $Keys<typeof activeLoanStatuses>;
type LoanStatus = $Keys<typeof otherLoanStatuses> | ActiveLoanStatus;

const activeLoanStatusesMap: { [key: LoanStatus]: ?ActiveLoanStatus} = activeLoanStatuses;

if (activeLoanStatusesMap[loan.status]) {

}

Ответ 2

В то время как невероятно многословный и немасштабируемый, он попадает в поток " Disjoint Unions" и может быть реализован с помощью ===, Как они упоминают на этой странице, Case Analysis выполняется с помощью этого оператора, поскольку javascript, естественно, делает с операторами switch-case.

В вашем случае это эквивалентно:

switch(loan.status) {
  'pending':
  'current':
  'due':
  'overdue':
  'pending_payment':
  'charged_off':
  'voided':
  'disputed':
  'refunded':
  'settled':
    // your behavior here
}

Как я уже упоминал, это очень многословный код, который использует ваши типы, но для того, чтобы противостоять этому, он имеет преимущество в определении ваших типов без создания шаблонного объекта - вы просто определяете свои литературные параметры и объединяете их вместе (вторая реализация).

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