angular - ngrx payload i reducer handlingen kompilerer ikke
Jeg prøver at migrere min Angular 2-app til Angular 4 og Ngrx 4.
Jeg håndterer et mærkeligt TypeScript-kompileringsproblem, som jeg ikke havde før denne opdatering. Jeg arbejder i et offline-miljø, så jeg kan ikke dele den præcise kode her.
Jeg ledte efter problemet online, det nærmeste jeg kunne finde var dette spørgsmål: spørgsmål om payload-typer Koden i dette spørgsmål er på en eller anden måde lig det, jeg prøver at gøre, men forskellen er, at jeg fjernede ActionTypes-objektet fra handlingen (som er det foreslåede svar til det spørgsmål), men det løste ikke min kompilationsfejl.
Jeg kan ikke indsætte den præcise kode, jeg arbejder med her, men jeg baserer fuldstændigt min kode på ngrx-eksemplet, det officielle ngrx-eksempel på GitHub
Lad os for eksempel se på bogreduceren og boghandlinger boghandlinger bogreducer
Min kode er stort set den samme, og fejlen vil opstå for hver reducer-tilfælde (i switch-tilfældet), når jeg prøver at få payloadet ud af handlingen, fortæller den mig noget som:
Type 'string | Book | Book[]' is not assignable to type 'Book'. Type 'string' is not assignable to type 'Book'.
(I min kode har jeg forskellige klasser i stedet for Bog, men det er stort set den samme idé)
Indtil videre løser jeg fejlene ved at caste handlingstypen fra A | B | B til at være den specifikke handlingstype, der svarer til tilfældet i switch (ved at bruge as Typescript-nøgleordet). Denne løsning føles dårlig og improviseret, jeg vil gerne vide, om der er en bedre løsning.
De versioner, jeg bruger: typescript: 2.4.1 (jeg mistænker, at det kan være forbundet med, at ngrx-eksemplerne ikke bruger den samme opdaterede typescript-version som mig) ngrx@store: 4.0.3 ngrx@core: 1.2.0 angular: (den nyeste pr. 17.9.17, der er mange pakker inde i angular, og jeg tror faktisk ikke, det betyder noget, hvilken angular jeg bruger for denne fejl, men jeg siger det bare for at være på den sikre side) typescript: (2.4.1) (jeg læste den officielle ngrx-migreringsdokumentation om behovet for at opgradere typescript, og det gjorde jeg)
OPDATERING: Da jeg har fået et svar, der ikke er relevant for mit problem, og også er blevet spurgt, her er den del af min kode, der fejler (jeg kan ikke poste det præcise indlæg, men jeg kopierer de relevante dele manuelt):
mission-reducer.ts:
import * as MissionActionsFile from "./mission-actions";
export type State = .... // doesnt matter
const initialState: State = // doesnt matter as well
export function reducer(state: State = initialState, action:
MissionActionsFile.Actions): State {
switch(action.type) {
case MissionActionsFile.ADD_SUCCESS:
{
const mission: Mission = action.payload; // This line generates the
// following error:
// Type 'string | number | Mission | MissionRealTime | ...' is not
// assignable
// to type 'Mission'. Type 'string' is not assignable to type 'Mission'
return {}; // This doesnt matter too
}
// There are more cases and a deafult one, but that doesn't matter
}
}
mission-actions.ts:
// I will only give the actual action defenition for AddSuccess and for AddFailed since we are using AddSuccess in the example and
// addFailed is an example where the payload type is string, which according to the error is the problem, though
// there are more action types and other payload types with other classes as the quoted error implies.
export const ADD_SUCCESS: string = "[Mission] AddSuccess";
export const ADD_FAILED: string = "[Mission] AddFailed";
export class AddSuccessAction implements Action {
public readonly type: string = ADD_SUCCESS;
constructor(public payload: Mission) {}
}
export class AddFailedAction implements Action {
public readonly type:string = ADD_FAILED;
constructor(public payload: string) {}
}
// More actions are defined here but im not gonna copy all
...
export type Actions =
AddSuccessAction |
AddFailedAction;
Så for at konkludere, kan jeg se, hvorfor det giver mening for TypeScript at tænke, at handlingens payload-type kan være string | Mission | ..., men i det ngrx-eksempel, som jeg baserede dette på, ser det ud til, at TypeScript kan inferere den specifikke type i casen, men for mig virker det ikke af en grund, jeg ikke forstår, måske har det noget at gøre med, at jeg bruger TypeScript 2.4.1? Ikke sikker, har brug for hjælp med dette
Svar #1
Problemet er, at adfærden af koden og dens typeannoteringer går imod hinanden.
Faktisk vil jeg sige, at koden er overdrevent annoteret.
Unionstyper og den tilfældeanalyse, de muliggør, fungerer via typeafledning og kontrolstrømsbaseret typeanalyse, to af TypeScripts mest kraftfulde funktioner. Den proces, hvorigennem sproget eliminerer muligheder fra unionen, er kendt som indsnævring.
Som koden antyder, kan en unionstype opdeles i dens bestanddele ved at udføre en værditest mod en egenskab kendt som diskriminanten.
En diskriminant er en egenskab, hvis type har et begrænset sæt af mulige værdier, hvor hver værdi generelt svarer til en sag i unionen.
Typen string er ikke en gyldig diskriminant, men typen "hello world" er, fordi en union af string-typer, der inkluderer string, som en supertype af alle mulige strenge, kollapser til kun string. For eksempel er typen string | "hello world" præcis typen string.
Når vi definerer en const eller en readonly egenskab i TypeScript, antager compileren, at dens type er typen af det initialiserende literal.
Overvej:
const kind = "first";
Her er typen af kind ikke string, men "first".
På samme måde, givet
class Kindred {
readonly kind = "first";
}
Typen af ejendommen kind er ikke string, men "first".
I vores kode er diskriminanten en egenskab kaldet type, som defineres af hver enkelt bestanddel af unionen.
Men selvom du korrekt har angivet unikke værdier for hvert medlem, har du overangivet dem med typeannoteringer, der forhindrer indsnævring.
Hvad du har:
export class AddSuccessAction {
public readonly type: string = ADD_SUCCESS;
constructor(public payload: Mission) {}
}
export class AddFailedAction {
public readonly type: string = ADD_FAILED;
constructor(public payload: string) {}
}
Hvad du vil have:
export class AddSuccessAction {
readonly type = ADD_SUCCESS;
constructor(public payload: Mission) {}
}
export class AddFailedAction {
readonly type = ADD_FAILED;
constructor(public payload: string) {}
}
En fungerende switch
switch (action.type) {
// action.type is `"[Mission] AddSuccess" | "[Mission] AddFailed"`
// action.payload is `string | Mission`
case missionActions.ADD_SUCCESS:
// action.type is `"[Mission] AddSuccess"`
// action.payload is `Mission`
const mission = action.payload;
}
Hvorfor dette er vigtigt:
En streng-literal type er en almindelig og idiomatisk måde at skelne mellem mulighederne i en union, men ved at erklære de implementerende egenskaber som værende af typen string, en type der er en supertype af alle streng-literal typer, hæmmede vi typeinferens og forhindrede dermed indsnævring. Bemærk, at string ikke er en type, der kan indsnævres fra.
Generelt er det bedst at udnytte TypeScripts typeinferens, når en værdi har en initialisator. Ud over at muliggøre det forventede scenarie, vil du blive imponeret over mængden af fejl, som typeinfereren vil fange. Vi bør ikke fortælle compileren mere, end den behøver at vide, medmindre vi med vilje ønsker en type, der er mere generel end den, den infererer. Med --noImplicitAny vil den altid lade os vide, når vi har brug for at angive ekstra information i form af typeannoteringer.
Bemærkninger:
Du kan teknisk set specificere typerne og stadig bevare indsnævringsadfærden ved at specificere den litterale værdi som typen af const eller readonly egenskaben. Dette øger dog vedligeholdelsesomkostningerne og er ret overflødigt.
For eksempel er følgende gyldigt:
export const ADD_FAILED: "[Mission] AddFailed" = "[Mission] AddFailed";
men du gentager dig bare unødigt.
I deres svar giver ilyabasiuk nogle gode referencelinks om brugen af literale typer specifikt med ngrx og hvordan det har udviklet sig over de seneste iterationer af TypeScript-sproget.
For at forstå, hvorfor bogstavelige typer udledes i uforanderlige positioner, skal man overveje, at det muliggør kraftig statisk analyse og dermed bedre fejlfinding.
Overvej:
type Direction = "N" | "E" | "S" | "W";
declare function getDirection(): Direction;
const currentDirection = getDirection();
if (currentDirection === "N") { // currentDirection is "N" | "E" | "S" | "W"
}
// Error: this is impossible, unreachable code as currentDirection is "E" | "S" | "W"
// the compiler knows and will give us an error here.
else if (currentDirection === "N") {
}
