redux - TypeScript Immer Argument av typen är inte tilldelningsbar till parametern av typen DraftArray
Jag försöker använda immer https://github.com/mweststrate/immer för mina reducers men får följande fel från TypeScript
Argument of type 'ReadonlyArray<IBidding>' is not assignable to parameter of type '(this: DraftArray<IBidding>, draftState: DraftArray<IBidding>, ...extraArgs: any[]) => void | Rea...'.
Type 'ReadonlyArray<IBidding>' provides no match for the signature '(this: DraftArray<IBidding>, draftState: DraftArray<IBidding>, ...extraArgs: any[]): void | ReadonlyArray<IBidding>'.
Jag har types.ts som detta
export interface IBidding {
readonly id: string
readonly ownerId: string
readonly name: string
readonly description: string
readonly startDate: Date
readonly endDate: Date
readonly suppliers: ReadonlyArray<ISupplier>
readonly inquiryCreator: string
readonly bidStep: number,
readonly bids: ReadonlyArray<IBid>,
readonly startingBid: number,
readonly img: string
}
interface ISupplier {
id: string
name: string
}
interface IBid {
ownerId: string
value: number
createdAt: Date
}
export type IBiddingsState = ReadonlyArray<IBidding>
export const enum BiddingsActionTypes {
BID = '@@biddings/BID'
}
Och här är min reducer.ts
import { Reducer } from 'redux'
import produce from 'immer'
import { IBiddingsState, BiddingsActionTypes } from './types'
import { biddingsReducerInitialState as initialState } from './fixtures'
/**
* Reducer for biddings list
*/
const reducer: Reducer<IBiddingsState> = (state = initialState, action) => {
return produce<IBiddingsState>(state, draft => {
switch (action.type) {
case BiddingsActionTypes.BID:
const {
biddingId,
bid
} = action.payload
const biddingIndex = draft.findIndex(elem => elem.id === biddingId)
draft[biddingIndex].bids.push(bid)
return draft
default: {
return state
}
}
})
}
export { reducer as biddingsReducer }
Det verkar som om jag gjorde allt enligt dokumentationen, men får ändå felet. Varför händer det?
Svar #1
Tyvärr, när ett anrop till en överlagrad funktion som produce inte matchar någon av överlagringarna, är TypeScript ganska dålig på att gissa vilken överlagring du avsåg för att ge en meningsfull rapport om vilken argument som är fel. Om du lägger till några typanteckningar i receptet:
const reducer: Reducer<IBiddingsState> = (state = initialState, action) => {
return produce<IBiddingsState>(state, (draft: Draft<IBiddingsState>): IBiddingsState => {
switch (action.type) {
case BiddingsActionTypes.BID:
const {
biddingId,
bid
} = action.payload
const biddingIndex = draft.findIndex(elem => elem.id === biddingId)
draft[biddingIndex].bids.push(bid)
return draft
default: {
return state
}
}
})
}
då ser du att problemet är med return draft och du får lite mer information:
[ts]
Type 'DraftArray<IBidding>' is not assignable to type 'ReadonlyArray<IBidding>'.
Types of property 'includes' are incompatible.
Type '(searchElement: DraftObject<IBidding>, fromIndex?: number) => boolean' is not assignable to type '(searchElement: IBidding, fromIndex?: number) => boolean'.
Types of parameters 'searchElement' and 'searchElement' are incompatible.
Type 'IBidding' is not assignable to type 'DraftObject<IBidding>'.
Types of property 'suppliers' are incompatible.
Type 'ReadonlyArray<ISupplier>' is not assignable to type 'DraftArray<ISupplier>'.
Property 'push' is missing in type 'ReadonlyArray<ISupplier>'.
Arrayer förväntas vara kovarianta, och som stöd för det är den första parametern till includes bivariat, men tyvärr har TypeScript gissat fel riktning för att rapportera ett fel. Vi förväntar oss att DraftObject<IBidding> ska kunna tilldelas IBidding, inte tvärtom. Om vi testar det direkt:
import { Draft } from 'immer'
import { IBidding } from './types'
let x: Draft<IBidding>;
let y: IBidding = x;
då ser vi äntligen den grundläggande orsaken:
[ts]
Type 'DraftObject<IBidding>' is not assignable to type 'IBidding'.
Types of property 'startDate' are incompatible.
Type 'DraftObject<Date>' is not assignable to type 'Date'.
Property '[Symbol.toPrimitive]' is missing in type 'DraftObject<Date>'.
Och detta beror på att DraftObject definieras enligt följande:
// Mapped type to remove readonly modifiers from state
// Based on https://github.com/Microsoft/TypeScript/blob/d4dc67aab233f5a8834dff16531baf99b16fea78/tests/cases/conformance/types/conditional/conditionalTypes1.ts#L120-L129
export type DraftObject<T> = {
-readonly [P in keyof T]: Draft<T[P]>;
};
och keyof inkluderar inte välkända symboler såsom Symbol.toPrimitive (TypeScript-problem).
Som en tillfällig lösning kan du förgrena immer-typerna och ändra definitionen av Draft på följande sätt:
export type Draft<T> =
T extends any[] ? DraftArray<T[number]> :
T extends ReadonlyArray<any> ? DraftArray<T[number]> :
T extends Date ? Date : // <-- insert this line
T extends object ? DraftObject<T> :
T;
Eller om du inte har många förekomster, lägg bara till typtolkningar i din kod efter behov.
Svar #2
Jag hade ett liknande problem idag, jag använder immer 7.0.9. Jag kollade i dokumentationen och de nämner en hjälpfunktion castDraft som en lösning. https://immerjs.github.io/immer/docs/typescript#cast-utilities
