web-dev-qa-db-fra.com

Comment Flow peut-il être forcé de convertir une valeur en un autre type?

Est-il possible de transtyper de force une variable dans Flow ?

type StringOrNumber = string | number
const foo: StringOrNumber = 'hello'

// I look for something like `const bar:string = (string) foo`
const bar: string = foo // fails
const bar: string = (foo: string) // also fails
28
czerny

Flow ne fait pas de cast direct d'un type à un autre, mais vous pouvez faire quelque chose comme

const bar: string = (foo: any);

vous convertissez donc foo en any, car any accepte tout type de valeur en entrée. Ensuite, étant donné que le type any vous permet également d'en lire tous les types possibles, vous pouvez affecter la valeur any à bar car un any est également un string.

40
loganfsmyth

Dans l'exemple que vous donnez, vous regardez une "distribution" d'un type d'union à l'un de ses membres. Bien qu'il soit courant de considérer cela comme un casting, ce n'est pas la même chose que le casting de type dans d'autres langues.

En définissant le type de foo sur string | number, nous avons indiqué à Flow que cette valeur pouvait être une chaîne ou un nombre. Nous arrivons alors à y mettre une chaîne, mais Flow ne rejette pas notre assertion directe sur son type à cause de cela, même dans des situations (comme celle-ci) où elle ne pourrait pas changer plus tard.

Pour l'affecter à une variable de type string-, Flow doit savoir que même s'il peut s'agir d'un string ou number, au moment où nous faisons l'affectation, nous sont sûrs qu'il ne peut s'agir que d'un string.

Ce processus de réduction des options possibles est appelé raffinement de type .

Affinements de type

Pour affiner le type, nous devons prouver que ce doit être le type que nous disons qu'il est, d'une manière que Flow comprend.

Dans l'exemple d'origine, vous pouvez le faire en utilisant typeof:

type StringOrNumber = string | number
const foo: StringOrNumber = 'hello'

// This would raise an error here:
// const bar: string = foo

if (typeof foo === "string") {
  // Flow now knows that foo must be a string, and allows this.
  const bar: string = foo
}

Tout ce qu'un humain peut voir comme un raffinement de type n'est pas compris par Flow, donc parfois vous devrez regarder les documents de raffinement pour voir ce qui pourrait le faire comprendre à Flow.

Commentaires de suppression

Parfois, il n'y a aucun moyen d'exprimer la sécurité d'un raffinement à Flow. Nous pouvons forcer Flow à accepter une déclaration en utilisant un commentaire de suppression , qui supprimera une erreur que Flow aurait autrement signalée. Le commentaire de suppression par défaut est $FlowFixMe, mais il peut être configuré pour un ou plusieurs commentaires différents.

Flow signalera une erreur sur la deuxième ligne de ceci, signalant que unionValue peut être de type 'nombre':

const unionValue: StringOrNumber = 'seven'
const stringValue: string = unionValue

Cependant, en utilisant un commentaire de suppression, cela passe Flow:

const unionValue: StringOrNumber = 'seven'
// $FlowFixMe: We can plainly see this is a string!
const stringValue: string = unionValue

Une caractéristique utile des commentaires de suppression est qu'un commentaire de suppression sans erreur suivante à supprimer est considéré comme une erreur. Si nous changeons le type dans l'exemple:

const unionValue: string = 'seven'
// $FlowFixMe: Even though this is a string, suppress it
const stringValue: string = unionValue

Maintenant, Flow signalera une erreur de "suppression inutilisée" à la place, nous alertant. Cela est particulièrement utile lorsque Flow devrait être en mesure de reconnaître un raffinement mais ne peut pas - en utilisant un commentaire de suppression, nous sommes alertés pour supprimer le commentaire (et gagner une sécurité de type supplémentaire) si une future version de Flow reconnaît le code comme type sûr.

Cast-through-any

Si vous ne pouvez vraiment pas l'exprimer d'une manière qui démontre sa sécurité de flux, et que vous ne pouvez pas (ou ne voulez pas) utiliser un commentaire de suppression, vous pouvez convertir n'importe quel type en any et any à n'importe quel type:

const unionValue: StringOrNumber = 'seven'
// Flow will be okay with this:
const stringValue: string = (unionValue: any)

En convertissant une valeur en any, nous demandons à Flow d'oublier tout ce qu'il sait sur le type de la valeur, et supposons que tout ce que nous faisons avec doit être correct. Si nous le mettons plus tard dans une variable typée, Flow supposera que cela doit être correct.

Précautions

Il est important de noter que les commentaires de suppression et les transtypages sont dangereux. Ils remplacent complètement Flow et effectueront avec plaisir des "lancers" complètement absurdes:

const notAString: {key: string, key2: number} = {key: 'value', key2: 123}
// This isn't right, but Flow won't complain:
const stringValue: string = (notAString: any)

Dans cet exemple, stringValue contient le object de notAString, mais Flow est sûr qu'il s'agit d'une chaîne.

Pour éviter cela, utilisez des raffinements que Flow comprend chaque fois que vous le pouvez, et évitez d'utiliser les autres techniques de "casting" dangereuses.

17
Jason Wodicka

Cette réponse n'est qu'une suggestion. En parcourant les solutions aux problèmes de vérification de type liés à Event et HTMLElement, j'ai rencontré beaucoup de gardes invoquant instanceof.

Pour satisfaire les vérifications de type, je viens d'introduire ce garde générique et je l'ai appelé cast (ce qui n'en fait pas bien sûr un cast), car sinon mon code s'est tellement gonflé.

Le coût est bien sûr lié aux performances (assez pertinent lors de l'écriture de jeux, mais je suppose que la plupart des cas d'utilisation bénéficient plus de gardes de type que de millisecondes par itération).

const cast = (type : any, target : any) => {
    if (!(target instanceof type)) {
        throw new Error(`${target} is not a ${type}`);
    }
    return target;
}

Coutumes:

const fooLayer = cast(HTMLCanvasElement, document.getElementById("foo-layer"));
window.addEventListener("click", (ev : Event) =>
  console.log(cast(MouseEvent, ev).clientX - cast(HTMLElement, ev.target).offsetLeft,
    cast(MouseEvent, ev).clientY - cast(HTMLElement, ev.target).offsetTop));
1
renevanderark