מבט נוסף על Jotai
כתבתי בעבר על jotai ואני מודה שבאותו זמן הוא לא היה ספריית ניהול הסטייט המועדפת עליי. חלפה שנה וחצי והיום אני כבר הרבה יותר אוהב אותו וממליץ עליו לפרויקטים חדשים. בואו נראה למה.
1. איך הסתבכתי עם Jotai
בדוגמה בפוסט הקודם שכתבתי על Jotai יצרתי מערך של מונים, כולם אטומים ויצרתי אטום שמחזיק את הערך המקסימלי. אני עדיין חושב שזה רעיון טוב וזה היה הקוד היום עם תוספת תמיכה בטייפסקריפט:
import { Atom, atom, PrimitiveAtom } from "jotai";
export const countersAtom = atom<Array<PrimitiveAtom<number>>>([]);
export const maxValueAtom = atom((get) =>
get(countersAtom).reduce((acc, val) => (acc > get(val) ? acc : get(val)), 0)
);
export type CounterAtomType = typeof countersAtom extends Atom<Array<infer T>> ? T : never;
אבל אז ניסיתי לעדכן את האטום מתוך הקומפוננטות למשל:
'use client';
import { useAtom, atom } from "jotai";
import { maxValueAtom, countersAtom } from "@/lib/counters";
import Counter from './counter';
export default function App() {
const [counters, setCounters] = useAtom(countersAtom);
const [maxValue, _] = useAtom(maxValueAtom);
const createCounter = () => {
setCounters((counters) => [...counters, atom(0)]);
};
return (
<div className="App">
<h1>Hello Jotai</h1>
<p>Max value = {maxValue}</p>
<button onClick={createCounter}>Create Counter</button>
{counters.map((counter, idx) => (
<Counter myAtom={counter} />
))}
</div>
);
}
וככה נתקעתי עם הפונקציה createCounter
בתוך הקומפוננטה והגדרת האטום בקובץ נפרד.
2. איך אני משתמש ב Jotai היום
במבט שני כל מה שהיה צריך זה להזיז קצת פונקציות ממקום למקום כדי לשפר את הקוד. אני מזיז את הלוגיקה לתוך הקובץ בו האטומים הוגדרו ומקבל:
export const createCounterAtom = atom(null,
(_get, set, _update) => set(countersAtom, counters => [...counters, atom(0)]));
export const deleteCounterAtom = atom(null,
(_get, set, toDelete: PrimitiveAtom<number>) => set(countersAtom, counters => counters.filter(c => c !== toDelete)))
בעצם אנחנו צריכים לחשוב על העדכונים בתור אטומים נגזרים לכתיבה בלבד. כתיבה לתוך אטום createCounterAtom
יוצרת מונה חדש בתוך המערך countersAtom
. כתיבה לתוך האטום deleteCounterAtom
מוחקת מונה. מתוך הקומפוננטה זה נראה ככה:
'use client';
import { useAtomValue, useSetAtom } from "jotai";
import { maxValueAtom, countersAtom, createCounterAtom } from "@/lib/counters";
import Counter from './counter';
export default function App() {
const counters = useAtomValue(countersAtom);
const maxValue = useAtomValue(maxValueAtom);
const createCounter = useSetAtom(createCounterAtom)
return (
<div className="App">
<h1>Hello Jotai</h1>
<p>Max value = {maxValue}</p>
<button onClick={createCounter}>Create Counter</button>
{counters.map((counter, idx) => (
<Counter myAtom={counter} key={idx} />
))}
</div>
);
}
ועכשיו אין לנו בכלל לוגיקה בתוך הקומפוננטות והרבה יותר קל לקרוא ולתחזק אותן.
3. בדיקת אטומים בלי ריאקט
משחק נוסף שלא הכרתי כשכתבתי את הפוסט הקודם היה האפשרות לעבוד עם האטומים ולבדוק את הלוגיקה גם מחוץ לקומפוננטות ריאקט. נתבונן בקוד הבדיקה הבא:
import {test} from 'node:test';
import assert from 'node:assert';
import { maxValueAtom, countersAtom, createCounterAtom } from "@/lib/counters";
import { createStore } from 'jotai'
test('demo test', () => {
const store = createStore()
store.set(createCounterAtom, null);
store.set(createCounterAtom, null);
store.set(createCounterAtom, null);
const countersAtomValue = store.get(countersAtom);
store.set(countersAtomValue[0], 4);
store.set(countersAtomValue[1], 2);
store.set(countersAtomValue[2], 1);
const maxValue = store.get(maxValueAtom);
assert(maxValue === 4);
})
כדאי לחשוב על אטומים ב Jotai בתור מצביעים לתוך מידע ששמור במקום אחר, ולמקום האחר הזה קוראים store. עכשיו ברור שאם רק יהיה לנו store נוכל לעבוד עם האטומים גם מחוץ לקומפוננטות ולכן הבדיקה מתחילה ביצירת store, ואז אפשר ליצור מונים, לכתוב אליהם מספרים ולוודא שהערך המקסימלי הוא באמת הגדול ביותר שנכתב.
כשאנחנו רושמים את הלוגיקה יחד עם האטומים ומשתמשים ב store בקוד הבדיקות, אנחנו יכולים גם לממש לוגיקה מורכבת וגם לבדוק אותה ולהיות בטוחים שהכל תקין.