• בלוג
  • תרגיל טייפסקריפט: פעולת עדכון גנרית ב Redux

תרגיל טייפסקריפט: פעולת עדכון גנרית ב Redux

07/01/2025

בדוגמת ההתחלה המהירה של Redux Toolkit הם מציעים את הקוד הבא עבור סלייס של מונה:

import { createSlice } from '@reduxjs/toolkit'

const initialState = {
  value: 0,
}

export const counterSlice = createSlice({
  name: 'counter',
  initialState,
  reducers: {
    increment: (state) => {
      state.value += 1
    },
    decrement: (state) => {
      state.value -= 1
    },
    incrementByAmount: (state, action) => {
      state.value += action.payload
    },
  },
})

// Action creators are generated for each case reducer function
export const { increment, decrement, incrementByAmount } = counterSlice.actions

export default counterSlice.reducer

לא מפתיע שזה מתאים בדיוק לתפיסת העולם של Redux - מעט מידע, שניתן לשינוי במספר דרכים (פעולות), כל דרך שינוי עם המגבלות והבדיקות שלה. אבל בעולם האמיתי יש עוד מקרה נפוץ שלא מדברים עליו בתיעוד שלהם, וזה אוביקט עם הרבה מידע שמאפשר שינויים יחסית גמישים. דמיינו את אוביקט המידע הבא:

export interface CounterSliceState {
  value: number
  status: "idle" | "loading" | "failed"
  foo: string
  bar: number
  buz: Array<string>
}

ואולי יהיו שם עוד 20 שדות מטיפוסים שונים. כתבו פעולת Set גנרית (אחת) שתקבל מפתח וערך מהסוג שמתאים לו ותכתוב את זה לאוביקט המידע.

תוכן עניינים

  1. פיתרון

1. פיתרון

בשביל הפיתרון עלינו תחילה לכתוב את ה Reducer. אפשר להגדיר משהו כזה:

extraReducers: (builder) => {
  builder.addCase(setField, (state, action) => {
    const { key, value } = action.payload;
    // @ts-ignore
    state[key] = value;
  });
},

אני מוכן להתעלם כאן מבעיות טייפסקריפט כי הוולידציה מתרחשת קודם, בעת יצירת ה Action. הקוד הבא יוצר Action גנרי עבור כתיבה לשדה כלשהו באוביקט מידע:

type SetFieldPayload = {
  [K in keyof CounterSliceState]: {
    key: K;
    value: CounterSliceState[K];
  };
}[keyof CounterSliceState];

export const setField = createAction<SetFieldPayload2>('counter/set');

ועכשיו הקוד הזה מתקמפל:

setField({key: 'bar', value: 5}) // compiles OK

אבל זה זורק שגיאה:

setField({key: 'bar', value: '?'}) // compilation error

נ.ב. אפשר להרחיב את הפיתרון ולכתוב גם למפתח מקונן (בקשו מ Chat GPT את הקוד אם לא מצאתם את הרקורסיה), אבל אני לא ממליץ. ככל שטייפסקריפט נהיה מסובך מדי הוא מפסיק להיות שימושי, ובאופן כללי רידאקס עובד יותר טוב עם סלייסים שטוחים. יותר קל לקחת את החלק המקונן ולהפריד אותו לסלייס משלו.