معمارية Flux وZustand
اتّبعنا الممارسة التي توصي بها React لإدارة حالة التطبيق، وذلك بتعريف الحالة التي تحتاجها مكوّنات متعددة والدوال التي تتعامل معها في المكوّنات العليا في التسلسل الهرمي للمكوّنات. وعادةً ما كانت معظم الحالة والدوال التي تتعامل معها تُعرَّف مباشرة في المكوّن الجذري وتُمرَّر عبر props إلى المكوّنات التي تحتاجها. وهذا ينجح إلى حدٍّ ما، لكن مع نمو التطبيق تصبح إدارة الحالة صعبة.
معمارية Flux
طوّرت Facebook معمارية Flux في بدايات تاريخ React للتخفيف من مشكلات إدارة الحالة. في Flux، تُفصَل إدارة حالة التطبيق بالكامل في stores خارجية خارج مكوّنات React. ولا تُغيَّر الحالة في store مباشرة، بل عبر actions محددة تُنشأ لهذا الغرض.
عندما يُرسَل action ويغيّر حالة store، تُعاد عرض الواجهات:

إذا استدعى استخدام التطبيق (مثل الضغط على زر) تغيير الحالة، فيحدث التغيير عبر action. وهذا بدوره يؤدي إلى إعادة عرض الواجهة:

وهكذا يوفّر Flux طريقة معيارية لكيفية ومكان حفظ حالة التطبيق ولإجراء تغييرات عليها.
Redux
كان Redux، الذي يتبع معمارية Flux، الحل المهيمن لإدارة الحالة في تطبيقات React لما يقارب عقداً كاملاً. وفي هذه الدورة أيضاً استُخدم Redux حتى ربيع 2026. وقد لازمت Redux دائماً مشكلة التعقيد وكثرة الشيفرة النمطية المتكررة (boilerplate). تحسّن الوضع كثيراً مع ظهور Redux Toolkit، لكن رغم ذلك واصل المجتمع تطوير حلول بديلة لإدارة الحالة، مثل MobX وRecoil وJotai. وقد تفاوتت شعبيتها.
أكثر الوافدين الجدد إثارةً للاهتمام، وبلا شك الأكثر شعبية، هو Zustand، وهو أيضاً اختيارنا لحل إدارة الحالة. ويبدو أن Zustand قد لحق فعلاً بـ Redux في الشعبية:

Zustand
لنتعرّف على Zustand من خلال تنفيذ تطبيق عدّاد مرة أخرى:

سننشئ تطبيق Vite جديداً ونثبّت Zustand:
npm install zustand
النسخة الأولى، التي تعمل فيها زيادة العدّاد فقط، هي كما يلي:
import { create } from 'zustand'
const useCounterStore = create(set => ({
counter: 0,
increment: () => set(state => ({ counter: state.counter + 1 })),
}))
const App = () => {
const counter = useCounterStore(state => state.counter)
const increment = useCounterStore(state => state.increment)
return (
<div>
<div>{counter}</div>
<div>
<button onClick={increment}>plus</button>
<button>minus</button>
<button>zero</button>
</div>
</div>
)
}
يبدأ التطبيق بإنشاء store، أي الحالة العامة، باستخدام دالة create في Zustand:
import { create } from 'zustand'
const useCounterStore = create(set => ({
counter: 0,
increment: () => set(state => ({ counter: state.counter + 1 })),
}))
تستقبل الدالة كمعامل دالة تُعيد الحالة المطلوب تعريفها للتطبيق. إذن المعامل هو كما يلي:
set => ({
counter: 0,
increment: () => set(state => ({ counter: state.counter + 1 })),
})
وهكذا تحتوي الحالة على counter بقيمة صفر، وincrement وهي دالة.
يمكن لمكوّنات التطبيق الوصول إلى القيم والدوال المعرّفة في الحالة عبر دالة useCounterStore المعرّفة باستخدام create في Zustand. ويستخدم مكوّن App المحدّدات (selectors) لاسترجاع قيمة counter ودالة increment من الحالة:
const App = () => {
// استخدام selector لاختيار الجزء الصحيح من حالة store
const counter = useCounterStore(state => state.counter)
const increment = useCounterStore(state => state.increment)
return (
<div>
<div>{counter}</div>
<div>
<button onClick={increment}>plus</button>
<button>minus</button>
<button>zero</button>
</div>
</div>
)
}
تخزّن الشيفرة قيمة counter الخاصة بـ store في متغير كما يلي:
const counter = useCounterStore(state => state.counter)
تُستخدم دالة محدّد state => state.counter ، وهي تحدد ما يُعاد من محتويات store. وبالطريقة نفسها، تُسترجع الدالة المخزّنة في store إلى المتغير increment.
تُعطى دالة الحالة increment ، التي عُرِّفت كما يلي، كمعالج نقر لزر "plus":
const useCounterStore = create(set => ({
counter: 0,
increment: () => set(state => ({ counter: state.counter + 1 })),
}))
لننظر إلى تعريف الدالة بشكل منفصل:
() => set(state => ({ counter: state.counter + 1 }))
هذه دالة تستدعي دالة set وتمرّر لها دالة أخرى كمعامل. وهذه الدالة الممرَّرة كمعامل تحدد كيف تتغير الحالة:
state => ({ counter: state.counter + 1 })
وهي صيغة مختصرة لـ:
state => {
return { counter: state.counter + 1 }
}
تُعيد الدالة حالة جديدة تحسبها بناءً على الحالة القديمة التي يمكنها الوصول إليها عبر المعامل state. فإذا كانت الحالة القديمة، على سبيل المثال:
{
counter: 1,
increment: // تعريف الدالة
}
تصبح الحالة الجديدة:
{
counter: 2,
increment: // تعريف الدالة
}
تحتوي الحالة دائماً أيضاً على الدالة increment التي تغيّر الحالة.
دالة انتقال الحالة
state => ({ counter: state.counter + 1 })
تؤثر فقط على قيمة counter في الحالة.
لا شيء يمنع تغيير الدالة الموجودة في الحالة داخل دالة انتقال الحالة؛ فمثلاً لو عرّفناها كما يلي:
state => {
return {
counter: state.counter + 1 ,
increment: console.log('increment broken')
}
}
فلن يعمل زر الزيادة إلا في المرة الأولى؛ وبعدها لن يؤدي الضغط على الزر إلا إلى الطباعة في الطرفية.
عندما تُضبط الحالة الجديدة كما يلي:
state => ({ counter: state.counter + 1 })
لا تُحدَّث إلا قيمة المفتاح counter في الحالة؛ إذ تُحصل على الحالة الجديدة بدمج الحالة القديمة مع القيمة التي تُعيدها دالة تغيير الحالة. ولهذا فإن دالة انتقال الحالة التالية:
state => ({})
لا تؤثر على الحالة إطلاقاً.
لنكمل التطبيق لبقية الأزرار أيضاً:
const useCounterStore = create(set => ({
counter: 0,
increment: () => set(state => ({ counter: state.counter + 1 })),
decrement: () => set(state => ({ counter: state.counter - 1 })),
zero: () => set(() => ({ counter: 0 })),
}))
const App = () => {
const counter = useCounterStore(state => state.counter)
const increment = useCounterStore(state => state.increment)
const decrement = useCounterStore(state => state.decrement)
const zero = useCounterStore(state => state.zero)
return (
<div>
<div>{counter}</div>
<div>
<button onClick={increment}>plus</button>
<button onClick={decrement}>minus</button>
<button onClick={zero}>zero</button>
</div>
</div>
)
}
من أين يأتي set و state؟
من أين يأتي set؟ إنها دالة مساعدة توفّرها دالة create في Zustand، وتُستخدم لتحديث الحالة. تستدعي create الدالة الممرَّرة إليها كمعامل وتمرّر لها set تلقائياً. لا تحتاج إلى استدعائها أو استيرادها بنفسك؛ يتولى Zustand ذلك.
من أين يأتي state؟ عندما تُمرَّر دالة كمعامل إلى set (بدلاً من كائن حالة جديد مباشرة)، يستدعي Zustand تلك الدالة مع الحالة الحالية لـ store كوسيط لها. بهذه الطريقة، يمكن لدوال تحديث الحالة الوصول إلى الحالة القديمة لحساب الحالة الجديدة.
استخدام الحالة من مكوّنات مختلفة
لنُعِد هيكلة التطبيق بحيث يُنقل تعريف store إلى ملفه الخاص store.js، وتُقسَّم الواجهة إلى مكوّنات متعددة، كل منها معرّف في ملفه الخاص.
محتويات store.js بسيطة ومباشرة:
export const useCounterStore = create(set => ({
counter: 0,
increment: () => set(state => ({ counter: state.counter + 1 })),
decrement: () => set(state => ({ counter: state.counter - 1 })),
zero: () => set(() => ({ counter: 0 })),
}))
يُبسَّط مكوّن App كما يلي:
import Display from './Display'
import Controls from './Controls'
const App = () => {
return (
<div>
<Display />
<Controls />
</div>
)
}
export default App
الجدير بالملاحظة هنا أن مكوّن App لم يعد يمرّر الحالة إلى مكوّناته الفرعية. بل إن المكوّن لا يلمس الحالة بأي شكل؛ فقد فُصل تعريف store بالكامل خارج المكوّن.
المكوّن الذي يعرض قيمة العدّاد بسيط:
import { useCounterStore } from './store'
const Display = () => {
const counter = useCounterStore(state => state.counter)
return (
<div>{counter}</div>
)
}
export default Display
يصل المكوّن إلى قيمة العدّاد عبر دالة useCounterStore التي تعرّف store. وهذا مريح من نواحٍ عديدة، فمثلاً لا حاجة لتمرير الحالة إلى المكوّن عبر props.
المكوّن الذي يعرّف الأزرار يبدو هكذا:
import { useCounterStore } from './store'
const Controls = () => {
const increment = useCounterStore(state => state.increment)
const decrement = useCounterStore(state => state.decrement)
const zero = useCounterStore(state => state.zero)
return (
<div>
<button onClick={increment}>plus</button>
<button onClick={decrement}>minus</button>
<button onClick={zero}>zero</button>
</div>
)
}
export default Controls
تأخذ دالة useCounterStore دالة محدّد (selector) كمعامل لها، وهي تحدد أي جزء من الحالة سيُستخدم. على سبيل المثال:
const increment = useCounterStore(state => state.increment)
هنا تنتقي دالة المحدّد state => state.increment قيمة المفتاح increment من الحالة، أي الدالة التي تزيد العدّاد، وتخزّنها في المتغير increment.
يمكننا أيضاً الوصول إلى الحالة بأكملها كما يلي:
const state = useCounterStore()
// يفعل الشيء نفسه مثل useCounterStore(state => state)، أي يختار الحالة بأكملها
يمكننا حينها الإشارة إلى قيمة العدّاد والدوال باستخدام الترميز النقطي (dot notation)، أي state.counter وstate.increment.
يطرح سؤال طبيعي نفسه: هل يمكن استخدام أجزاء متعددة من الحالة عبر التفكيك (destructuring):
import { useCounterStore } from './store'
const Controls = () => {
const { increment, decrement, zero } = useCounterStore()
return (
<div>
<button onClick={increment}>plus</button>
<button onClick={decrement}>minus</button>
<button onClick={zero}>zero</button>
</div>
)
}
export default Controls
الحل يعمل، لكن له عيب كبير. يؤدي التفكيك إلى إعادة عرض مكوّن Controls في كل مرة تتغير فيها قيمة العدّاد، مع أن المكوّن يعرض الأزرار فقط ولا يعرض القيمة نفسها.
لذا فإن أفضل ممارسة في Zustand هي ألا تختار من الحالة إلا الأجزاء التي يحتاجها المكوّن المعني. ولا يُعاد عرض المكوّن إلا عندما يتغير الجزء الذي اختاره من الحالة. أما عند كتابة:
const increment = useCounterStore(state => state.increment)
const decrement = useCounterStore(state => state.decrement)
const zero = useCounterStore(state => state.zero)
فلم يعد المكوّن يتفاعل مع تغيّرات قيمة العدّاد لأنه لم يخترها من الحالة.
إعادة تنظيم الحالة
يمكننا الوصول إلى حل أنيق تماماً بإعادة تنظيم الحالة كما يلي:
export const useCounterStore = create(set => ({
counter: 0,
actions: {
increment: () => set(state => ({ counter: state.counter + 1 })),
decrement: () => set(state => ({ counter: state.counter - 1 })),
zero: () => set(() => ({ counter: 0 })),
}
}))
أصبحت دوال تغيير الحالة الآن مجمّعة تحت مفتاحها الخاص actions، ويمكن اختيارها ككتلة واحدة وتفكيكها:
const Controls = () => {
const { increment, decrement, zero } = useCounterStore(state => state.actions)
return (
<div>
<button onClick={increment}>plus</button>
<button onClick={decrement}>minus</button>
<button onClick={zero}>zero</button>
</div>
)
}
الآن لا تحدث أي إعادة عرض، لأن الدوال فقط هي التي اختيرت من الحالة، وهي تبقى نفسها طوال عمر store.
وفقاً لبعض أفضل الممارسات، لا يُنصح بتصدير الدالة التي تعرّف الحالة بأكملها لاستخدامها في كل أنحاء التطبيق. وبدلاً من ذلك، ينبغي إنشاء عروض أصغر منها لا تكشف إلا الأجزاء اللازمة من الحالة. لنعدّل store.js كما يلي:
import { create } from 'zustand'
const useCounterStore = create(set => ({
counter: 0,
actions: {
increment: () => set(state => ({ counter: state.counter + 1 })),
decrement: () => set(state => ({ counter: state.counter - 1 })),
zero: () => set(() => ({ counter: 0 })),
}
}))
// دوال الخطافات المستخدمة في مواضع أخرى من التطبيق
export const useCounter = () => useCounterStore(state => state.counter)
export const useCounterControls = () => useCounterStore(state => state.actions)
الآن، خارج الوحدة التي تعرّف الحالة، أصبحت الدالتان useCounter، التي تُعيد قيمة العدّاد عند استدعائها، وuseCounterControls، التي تُعيد الدوال التي تعدّل قيمة العدّاد، متاحتين. ويتغير الاستخدام قليلاً:
import { useCounter } from './store'
const Display = () => {
const counter = useCounter()
return (
<div>{counter}</div>
)
}
import { useCounterControls } from './store'
const Controls = () => {
const { increment, decrement, zero } = useCounterControls()
return (
<div>
<button onClick={increment}>plus</button>
<button onClick={decrement}>minus</button>
<button onClick={zero}>zero</button>
</div>
)
}
عند استخدام الحالة بهذه الطريقة، لم تعد هناك حاجة لاستخدام دوال المحدّد، إذ أصبح استخدامها مخفياً داخل تعريف الدوال المساعدة الجديدة.
لاحظ الأكثر انتباهاً أن الدوال المتعلقة بـ Zustand تبدأ أسماؤها بكلمة use. والسبب في ذلك أن الدالة التي تُعيدها دالة create في Zustand، وهي useCounterStore في مثالنا، هي دالة React custom hook. كما أن دوالنا المساعدة useCounter وuseCounterControls هي أيضاً في جوهرها خطافات مخصصة لأنها تخفي استخدام الخطاف المخصص useCounterStore بداخلها.
تأتي الخطافات المخصصة بمجموعة من القواعد، فمثلاً يُتوقع أن تبدأ أسماؤها دائماً بـ use. كما تنطبق قواعد الخطافات التي تناولناها في الجزء 1 على الخطافات المخصصة أيضاً!
خطافات مخصصة مع حالة أكثر تعقيداً
افترض أننا نريد أيضاً عدّ عدد المرات التي تغيّرت فيها حالة العدّاد. يمكننا توسيع store كما يلي:
const useCounterStore = create(set => ({
counter: 0,
changes: 0,
actions: {
increment: () => set(state => ({
counter: state.counter + 1,
changes: state.changes + 1
})),
decrement: () => set(state => ({
counter: state.counter - 1,
changes: state.changes + 1
})),
zero: () => set(state => ({
counter: 0,
changes: state.changes + 1
})),
}
}))
يمكننا حينها أن نحاول كتابة الخطاف الخاص بالوصول إلى القيم كما يلي:
export const useValues = () => useCounterStore(state => ({
counter: state.counter,
changes: state.changes
})
هذا لا يعمل، لأن دالة المحدّد تُعيد كائناً جديداً في كل عرض، حتى عندما لا تكون القيم الأساسية قد تغيرت. يقارن Zustand نتيجة المحدّد السابقة والجديدة بفحص تساوي المراجع (===)، لذا يُرى الكائن الجديد دائماً على أنه "متغيّر". وهذا يؤدي إلى إطلاق إعادة عرض تستدعي المحدّد من جديد، الذي يُعيد كائناً جديداً آخر، وهكذا، في حلقة عرض لا نهائية.
الحل هو تغليف المحدّد بـ useShallow، وهو يخبر Zustand بمقارنة حقول الكائن المُعاد بفحص تساوٍ سطحي (shallow equality) بدلاً من مقارنة مراجع الكائنات:
import { useShallow } from "zustand/react/shallow"
export const useValues = () => useCounterStore(state => {
useShallow((state) => ({
counter: state.counter,
changes: state.changes,
})),
)
الآن لا يُطلق Zustand إعادة عرض إلا عندما تحصل counter أو changes فعلاً على قيمة جديدة، وليس فقط عند إنشاء كائن جديد.
1. عودة إلى Unicafe
2. تشغيل الاختبارات
ملاحظات Zustand
هدفنا هو إنشاء نسخة قائمة على Zustand من تطبيق الملاحظات العريق.
النسخة الأولى من التطبيق هي التالية. مكوّن App:
import { useNotes } from './store'
const App = () => {
const notes = useNotes()
return (
<div>
<ul>
{notes.map(note => (
<li key={note.id}>
{note.important ? <strong>{note.content}</strong> : note.content}
</li>
))}
</ul>
</div>
)
}
export default App
يُعرَّف store في البداية كما يلي:
import { create } from 'zustand'
const useNoteStore = create(set => ({
notes: [
{
id: 1,
content: 'Zustand is less complex than Redux',
important: true,
},
],
}))
export const useNotes = () => useNoteStore(state => state.notes)
في الوقت الحالي، لا يملك التطبيق وظيفة إضافة ملاحظات جديدة، ولا يدعمها store بعد. وقد هُيِّئت الحالة بملاحظة واحدة مضافة مسبقاً حتى نتحقق من أن التطبيق قادر على عرض الحالة بنجاح.
الدوال النقية والكائنات غير القابلة للتغيير
المحاولة الأولى لكتابة action يضيف ملاحظة هي التالية:
note => set(
state => {
state.notes.push(note)
return state
}
)
تستقبل الدالة ملاحظة كمعامل وتُعيد حالة أُضيفت فيها الملاحظة الجديدة إلى الحالة القديمة state.
غير أن محاولتنا ليست سليمة. يذكر توثيق Zustand أنه كما هو الحال مع useState في React، نحتاج إلى تحديث الحالة بشكل غير قابل للتغيير (immutably). وكما نعلم، فإن state.notes.push تعدّل (أي mutate) كائن الحالة، لذا يجب تغيير الحل.
الطريقة السليمة هي استخدام، على سبيل المثال، دالة Array.concat ، التي لا تعدّل الحالة الموجودة بل تنشئ نسخة جديدة منها مع إضافة الملاحظة الجديدة:
note => set(
state => {
return { notes: state.notes.concat(note) }
}
)
أصبح تعريف store الآن كما يلي:
import { create } from 'zustand'
const useNoteStore = create(set => ({
notes: [],
actions: {
add: note => set(
state => ({ notes: state.notes.concat(note) })
)
}
}))
export const useNotes = () => useNoteStore(state => state.notes)
export const useNoteActions = () => useNoteStore(state => state.actions)
صيغة نشر المصفوفة
من الطرق الأخرى الشائعة لفعل الشيء نفسه استخدام صيغة النشر (spread) للمصفوفات:
state => ({ notes: [...state.notes, note] })
هنا تتكوّن مصفوفة بنشر كل عنصر من عناصر مصفوفة state.notes باستخدام صيغة النشر، ثم إلحاق الملاحظة الجديدة في النهاية. والاختيار بين استخدام spread أو دالة concat مسألة تفضيل شخصي.
من الناحية التقنية، الحالة المنشأة بـ Zustand غير قابلة للتغيير (immutable)، ويجب أن تكون دوال action التي تعدّل الحالة دوالاً نقية (pure functions).
الدوال النقية هي التي لا تُنتج آثاراً جانبية وتُعيد دائماً النتيجة نفسها عند استدعائها بالمعاملات نفسها.
النموذج غير المتحكَّم به
لنضف إلى التطبيق القدرة على إنشاء ملاحظات جديدة:
import { useNotes, useNoteActions } from './store'
const App = () => {
const notes = useNotes()
const { add } = useNoteActions()
const generateId = () => Number((Math.random() * 1000000).toFixed(0))
const addNote = (e) => {
e.preventDefault()
const content = e.target.note.value
add({ id: generateId(), content, important: false })
e.target.reset()
}
return (
<div>
<form onSubmit={addNote}>
<input name="note" />
<button type="submit">add</button>
</form>
<ul>
{notes.map(note => (
<li key={note.id}>
{note.important ? <strong>{note.content}</strong> : note.content}
</li>
))}
</ul>
</div>
)
}
التنفيذ مباشر إلى حد كبير. والجدير بالملاحظة عند إضافة ملاحظة جديدة أننا، بخلاف نماذجنا السابقة المنفذة بـ React، لم نربط قيمة حقل النموذج بحالة مكوّن App. ويسمي React مثل هذه النماذج غير المتحكَّم بها (uncontrolled).
للنماذج غير المتحكَّم بها قيود معينة. فهي لا تسمح، مثلاً، بتقديم رسائل تحقق فورية، ولا بتعطيل زر الإرسال بناءً على المحتوى، وما إلى ذلك. لكنها مناسبة لحالتنا هذه المرة. ويمكنك قراءة المزيد عن الموضوع هنا إن أردت.
النموذج بسيط جداً:
<form onSubmit={addNote}>
<input name="note" />
<button type="submit">add</button>
</form>
الجدير بالملاحظة في النموذج أن حقل الإدخال له اسم (name). وهذا يتيح لدالة المعالج الوصول إلى قيمة الحقل.
معالج الإضافة مباشر أيضاً:
const addNote = (e) => {
e.preventDefault()
const content = e.target.note.value
add({ id: generateId(), content, important: false })
e.target.reset()
}
يُسترجع المحتوى من حقل نص النموذج باستخدام e.target.note.value إلى متغير، يُستخدم كمعامل في استدعاء دالة إضافة الملاحظة add.
والسطر الأخير، e.target.reset()، يفرّغ النموذج.
الشيفرة الحالية للتطبيق متاحة بالكامل على GitHub، في الفرع part6-1.
مزيد من المكوّنات والوظائف
لنقسّم التطبيق إلى مزيد من المكوّنات. سنفصل إنشاء ملاحظة جديدة، وقائمة الملاحظات، وعرض ملاحظة واحدة، إلى مكوّنات خاصة بها.
مكوّن App بعد التغيير بسيط:
const App = () => (
<div>
<NoteForm />
<NoteList />
</div>
)
إنشاء الملاحظة، أي NoteForm، لا يحتوي على شيء مثير، لذا لا نعرض الشيفرة هنا.
المكوّن المسؤول عن سرد الملاحظات، NoteList، يبدو كما يلي:
import { useNotes } from './store'
import Note from './Note'
const NoteList = () => {
const notes = useNotes()
return (
<ul>
{notes.map(note => (
<Note key={note.id} note={note} />
))}
</ul>
)
}
يجلب المكوّن قائمة الملاحظات من store وينشئ مكوّن Note مقابلاً لكل ملاحظة، ويمرّر بيانات الملاحظة كـ props:
const Note = ({ note }) => (
<li>
{note.important ? <strong>{note.content}</strong> : note.content}
</li>
)
لنضف أيضاً القدرة على تبديل أهمية الملاحظة. المكوّن بعد التغيير هو التالي:
import { useNoteActions } from './store'
const Note = ({ note }) => {
const { toggleImportance } = useNoteActions()
return (
<li>
{note.important ? <strong>{note.content}</strong> : note.content}
<button onClick={() => toggleImportance(note.id)}>
{note.important ? 'make not important' : 'make important'}
</button>
</li>
)
}
يفكّك المكوّن دالة تبديل الأهمية من القيمة المُعادة لـ useNoteActions، ويستدعيها عند النقر على زر التبديل.
تنفيذ دالة تبديل الأهمية يبدو كما يلي:
import { create } from 'zustand'
const useNoteStore = create(set => ({
notes: [],
actions: {
add: note => set(
state => ({ notes: state.notes.concat(note) })
),
toggleImportance: id => set(
state => ({
notes: state.notes.map(note =>
note.id === id ? { ...note, important: !note.important } : note
)
})
)
}
}))
تستقبل الدالة معرّف الملاحظة المطلوب تعديلها كمعامل. وتتكوّن الحالة الجديدة من الحالة القديمة باستخدام دالة map بحيث تُضمَّن كل الملاحظات القديمة، باستثناء الملاحظة المطلوب تعديلها، إذ تُنشأ لها نسخة تُبدَّل فيها أهميتها:
{ ...note, important: !note.important }
الشيفرة الحالية للتطبيق متاحة بالكامل على GitHub، في الفرع part6-2.
3. anecdotes الخطوة 1
4. anecdotes الخطوة 2
5. anecdotes الخطوة 3
6. anecdotes الخطوة 4
بعد إكمال التمارين، ينبغي أن يبدو التطبيق هكذا:
