redux - TypeScript Immer Türü argümanı, DraftArray türündeki parametreye atanamaz
Reducer'larım için immer https://github.com/mweststrate/immer kullanmaya çalışıyorum ama TypeScript'ten şu hatayı alıyorum
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>'.
Bunun gibi bir types.ts dosyam var
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'
}
Ve işte benim reducer.ts dosyam
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 }
Görünüşe göre her şeyi belgelerdeki gibi yaptım, ama yine de hata alıyorum. Bu neden oluyor?
Cevap #1
Maalesef, produce gibi aşırı yüklenmiş bir fonksiyona yapılan bir çağrı, herhangi bir aşırı yüklemeyle eşleşmediğinde, TypeScript hangi aşırı yüklemeyi vermek istediğinizi anlamada ve hangi argümanın yanlış olduğunu anlamlı bir şekilde raporlamada oldukça kötüdür. Tarife bazı tip açıklamaları eklerseniz:
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
}
}
})
}
sonra sorunun return draft ile olduğunu görüyorsunuz ve biraz daha bilgi alıyorsunuz:
[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>'.
Dizilerin kovaryant olması beklenir ve bunu desteklemek için, includes'in ilk parametresi bivaryanttır, ancak ne yazık ki TypeScript hatayı bildirmek için yanlış yönü tahmin etmiştir. DraftObject<IBidding>'in IBidding'e atanabilir olmasını bekliyoruz, bunun tersi değil. Bunu doğrudan test edersek:
import { Draft } from 'immer'
import { IBidding } from './types'
let x: Draft<IBidding>;
let y: IBidding = x;
sonunda kök nedeni görüyoruz:
[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>'.
Ve bunun nedeni DraftObject'in aşağıdaki şekilde tanımlanmış olmasıdır:
// 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]>;
};
ve keyof, Symbol.toPrimitive gibi iyi bilinen sembolleri içermiyor (TypeScript sorunu).
Bir çözüm olarak, immer türlerini çatallayabilir ve Draft tanımını aşağıdaki gibi değiştirebilirsiniz:
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;
Veya çok fazla örneğiniz yoksa, gerektiği şekilde kodunuza tür doğrulamalarını ekleyin.
Cevap #2
Bugün benzer bir sorun yaşadım, ben immer 7.0.9 kullanıyorum. Belgelerde baktım ve bir geçici çözüm olarak castDraft adında bir yardımcı fonksiyondan bahsediyorlar. https://immerjs.github.io/immer/docs/typescript#cast-utilities
