Typescript: Vaikuttaako parametrien järjestys tyyppi-inferenssiin?
Minulla on seuraavat tyypit:
type State<C, S> = {
onEnter?: (context: C) => void;
onExit?: (context: C) => void;
transitions?: (context: C) => S | undefined;
};
class FSM<C, S extends Record<keyof S, State<C, keyof S>>> {
constructor(public readonly states: S, public readonly context: C) {
/* ... */
}
}
Jos luon ne ilmentymiksi näin:
const context = { foo: "bar" };
const f = new FSM(
{
a: { transitions: c => "b" }, // ERROR: c is implicitly any
b: { transitions: c => "a" } // ERROR: c is implicitly any
},
context
);
Kääntäjä valittaa, että c on implisiittisesti any ja se ei pysty selvittämään S:n tyyppiä (se ilmestyy never:nä). c:n eksplisiittinen tyypittäminen korjaa ongelman, esimerkiksi:
a: { transitions: (c:typeof context) => "b" },
b: { transitions: (c:typeof context) => "a" }
Miksi näin on? Eikö sen pitäisi pystyä päättelemään C FSM-konstruktorin context-parametrista?
Arvaus hatusta: Onko parametrien järjestyksellä FSM-konstruktorissa merkitystä? Yrittääkö se ensin ratkaista states-tyypin eikä tiedä vielä context-tyypistä? Jos näin on, onko olemassa tapa auttaa kääntäjää "katselemaan eteenpäin"?
HUOM: Toinen asia, jota minun on vaikea ymmärtää tässä, on se, että jos kirjoitan eksplisiittisesti c satunnaisella tyypillä - esimerkiksi transitions: (c:number)=>"a"; kääntäjä valittaa, että c ei vastaa kontekstin tyyppiä. Joten kääntäjä tietää, mikä tyyppi c on, mutta sen täytyy saada minut osoittamaan, että tiedän sen myös?
Vastaus #1
Parametrien järjestyksellä voi olla merkitystä päättelyssä, jos on useita ehdokkaita, mutta tässä ei ole kyse siitä. Mitä todella haluat tässä, on käyttää kontekstuaalista tyyppitystä; tässä tapauksessa kääntäjä päättelee parametrien tyypin sen perusteella, mihin funktio aiotaan liittää.
En ole varma mekaniikasta, milloin kontekstuaalinen tyypitys luovuttaa, mutta jos määritetty tyyppi vaatii muita tyyppiparametreja, on suuri mahdollisuus, että se lakkaa toimimasta. Yksinkertainen kiertotapa on auttaa kontekstuaalista tyypitystä määrittämällä yksinkertaisempi tyyppi parametrin tyypissä typpiparametrin leikkauksena. Tämä varmistaa, että sieppaamme edelleen S:ssä todellisen tyypin, joka annettiin, mutta annamme kontekstuaaliselle tyypitykselle yksinkertaisemman reitin päätellä parametrit:
type State<C, S> = {
onEnter?: (context: C) => void;
onExit?: (context: C) => void;
transitions?: (context: C) => S | undefined;
};
class FSM<C, S extends Record<keyof S, State<C, keyof S>>> {
// Record<string, State<C, keyof S>> added for contextual typing
constructor(public readonly states: S & Record<string, State<C, keyof S>>, public readonly context: C) {
/* ... */
}
}
const context = { foo: "bar" };
const f = new FSM(
{
a: { transitions: (c) => c.foo != "" ? "a" : "b" },
b: { transitions: (c) => "a" },
c: { transitions: (c) => "d" }, // Error, d is not in the state keys
},
context
);
Leikkipuiston linkki
