این آموزش، درس اول دورهٔ دوم جاوااسکریپت و ادامهٔ مسیر «از پایه تا پروژه» است. پیشنیاز آن تسلط به تابع، آرایه، شیء و Promise است. در این دوره بهجای حفظ دستورهای بیشتر، روی قابلپیشبینیبودن رفتار برنامه کار میکنیم: وضعیت، جستوجو، پاسخهای ناهمزمان، صف کار، کش، ذخیرهسازی، رابط کاربری و آزمون. مثالها مستقلاند و در پایان میتوان آنها را به یک کتابخانهٔ آموزشی کوچک وصل کرد.
فرض کنید کاربر یک درس را نشان میکند و بعد فیلتر جستوجو را تغییر میدهد. اگر چند تابع، یک شیء مشترک را بیقاعده تغییر دهند، پیدا کردن علت تغییر رابط دشوار میشود. راه سادهتر این است که هر اتفاق را به یک پیام، یا action، تبدیل کنیم. تابع reducer وضعیت قبلی و پیام را میگیرد و وضعیت بعدی را برمیگرداند. این الگو به کتابخانهٔ خاصی وابسته نیست. در مثال ما، ذخیرهسازی و کار با صفحه بیرون از reducer میمانند؛ به همین دلیل میتوان رفتار آن را بدون مرورگر آزمایش کرد.
نمونهٔ اجرایی
function reducer(state, action) {
switch (action.type) {
case "query":
if (typeof action.value !== "string") return state;
return { ...state, query: action.value };
case "bookmark": {
const id = action.id;
if (typeof id !== "string" || !id.trim()) return state;
const ids = state.saved.includes(id)
? state.saved.filter(value => value !== id)
: [...state.saved, id];
return { ...state, saved: ids };
}
default: return state;
}
}
const before = { query: "", saved: [] };
const after = reducer(before, { type: "bookmark", id: "js-1" });
console.log(before.saved.length, after.saved.length); // 0 1
console.log(reducer(after, { type: "bookmark", id: "js-1" }).saved.length); // 0در حالت bookmark، هم شیء بیرونی و هم آرایهٔ saved تازه ساخته میشوند. فقط نوشتن {...state} و سپس اجرای push روی state.saved کافی نیست؛ آن کپی سطحی هنوز به آرایهٔ قبلی اشاره میکند. در مقابل، شاخهٔ query نیازی به کپی آرایهٔ saved ندارد، چون آن را تغییر نمیدهد. هدف، کپیکردن بیدلیل همهٔ دادهها نیست؛ فقط مسیر تغییر باید تازه ساخته شود.
خالصبودن تابع یعنی برای ورودی یکسان، خروجی قابلپیشبینی داشته باشیم و اثر جانبی بیرونی ایجاد نکنیم. بنابراین زمان جاری، درخواست شبکه و localStorage را در این تابع قرار نمیدهیم. اگر ثبت زمان لازم باشد، آن را هنگام ساخت action تولید و به reducer ارسال میکنیم. قرارداد این مثال میگوید state.saved همیشه آرایهای از شناسههای معتبر است؛ دادهٔ ورودی خارجی باید قبل از رسیدن به این لایه اعتبارسنجی شود.
اجرای نمونه در کنسول مرورگر، Node.js یا فضای کنسولی درسآوند ممکن است. خروجی نخست نشان میدهد وضعیت اولیه تغییر نکرده و خروجی دوم نشان میدهد نشانکردن دوباره، درس را از فهرست برمیدارد.
تمرین و پاسخ تشریحی
تمرین: بدون تغییر reducer، بررسی کنید دو بار فرستادن پیام bookmark وضعیت نشانها را به حالت اولیه برمیگرداند. سپس پیام ناشناختهای بفرستید و بررسی کنید همان مرجع قبلی بازگردد. چرا برابری ظاهری JSON، آزمون مناسبی برای ثابتماندن مرجع نیست؟
const start = { query: "", saved: [] };
const once = reducer(start, { type: "bookmark", id: "x" });
const twice = reducer(once, { type: "bookmark", id: "x" });
if (start.saved.length !== 0 || twice.saved.length !== 0) throw Error("mutation");
if (reducer(start, { type: "unknown" }) !== start) throw Error("identity");
console.log("state checks passed");پاسخ تشریحی: عملگر !== هویت شیء را میسنجد، ولی JSON فقط نمایش داده را مقایسه میکند. دو شیء مستقل میتوانند JSON یکسان داشته باشند. در پروژهٔ نهایی، reducer مرجع قواعد تغییر وضعیت است و نمایش صفحه مسئولیتی جدا دارد.
منبع فنی برای مرور اشیا و ارجاعها: MDN، Working with objects. طراحی مثال و تمرین این درس تألیف آموزشی درسآوند است.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Working_with_objects
پیشنیاز و محیط اجرای مثالها
سطح متوسط؛ پیشنیاز: دورهٔ مقدماتی JavaScript درسآوند. مثالهای این درس در Node.js 24، کنسول مرورگر یا فضای اجرای کنسولی سایت قابل اجرا هستند. تمرین را ابتدا بدون دیدن پاسخ حل کنید.
پیشرفت روی همین مرورگر ذخیره میشود.