web-dev-qa-db-fra.com

Comment utiliser/définir des énumérations avec la vérification du type de flux?

J'essaie de migrer une base de code existante pour utiliser Flow. Depuis que ce projet a démarré sans Flow, j'utilise un motif JS assez typique pour les enums et autres. 

Voici quelques définitions que je veux 

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 fonctionne correctement jusqu'à ce que j'importe ce fichier et qu'il indique que je dois ajouter des annotations de type. Cela semble étrange - pourquoi devrais-je annoter des objets entièrement statiques et facilement déduits?

Existe-t-il un moyen de définir son type comme "statique" ou "littéral"?

Alors, je réfléchis à la façon dont je vais ajouter des annotations à ceci. Ma première pensée est simplement {[key: string]: string} et Array<string>. Flow fonctionne, mais je réalise que ces définitions de types ne valent rien. Alors j'essaie cette autre approche:

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"

Et j'utilise les annotations de type {[key: LoanStatusKeys]: LoanStatusValues} et Array<ActiveLoanStatus>. Mais même ces annotations perdent le fait que cela est statique!

Cela semble tellement étrange que je doive écrire autant de code en double. Et puis, si je veux convertir uniquement en Flow, je ne peux pas utiliser les types de JS. Par exemple, je pourrais faire ceci:

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

}

Maintenant, si je veux utiliser les types de flux, je ne peux rien faire de ce genre:

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

if (loan.status isTypeOf ActiveLoanStatus) {

}

Alors, comment suis-je censé utiliser ces énumérations statiques? Je dois faire ça mal!

17
Chet

Voici le moyen le plus concis pour y parvenir:

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]) {

}
12
vkurchatkin

Pour exprimer une énumération avec flux, vous pouvez utiliser $ Values ​​ utility en conjonction avec le type d'objet gelé:

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

type LoanStatus = $Values<typeof LOAN_STATUS>;

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

Cela fonctionne à partir de 0.60.0 version.

6
Aleksey L.

Bien que très prolixe et non évolutif, cela tombe dans le cas " Disjoint Unions " de Flow et ceci peut être implémenté en utilisant ===. Comme ils le mentionnent sur cette page, l'analyse de cas se fait via cet opérateur, comme le fait javascript avec les instructions switch-case.

Dans votre cas, cela équivaut à:

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

Comme je l'ai mentionné, il s'agit d'un code très verbeux qui utilise vos types, mais pour éviter cela, il présente l'avantage de définir vos types sans créer d'objet standard, vous définissez simplement vos options littérales et les unissez ensemble (votre deuxième implémentation).

Cela a l'inconvénient évident de coupler votre définition de type avec vos implémentations de ses consommateurs, donc utilisez avec prudence.

1
ABMagil