وب مدرن، رابط کاربری و تجربهی وب · فصل ۴ — React، State و Visual Regression · 45
React Fundamentals و Component State
میتواند «React Fundamentals و Component State» را در یک feature محدود طراحی/پیادهسازی کند و correctness، accessibility یا failure behavior مرتبط را اثبات کند.
خلاصهٔ درس
میتواند «React Fundamentals و Component State» را در یک feature محدود طراحی/پیادهسازی کند و correctness، accessibility یا failure behavior مرتبط را اثبات کند.
خروجی این درس
میتواند «React Fundamentals و Component State» را در یک feature محدود طراحی/پیادهسازی کند و correctness، accessibility یا failure behavior مرتبط را اثبات کند.
پیشنیازها
یک editor که درست به نظر میرسد میتواند state model اشتباهی داشته باشد
یک profile editor دوپنجرهای را در نظر بگیرید. پنجره سمت چپ profileها را filter و انتخاب میکند و پنجره سمت راست draft مربوط به profile انتخابشده را ویرایش میکند. requirement محصول روشن است: profile انتخابشده باید بین هر دو pane مشترک باشد، متن filter با تغییر profile حفظ شود و draft هنگامی که هویت profile عوض میشود reset شود. یک پیادهسازی میتواند از نظر ظاهری کاملاً درست باشد اما هر سه قانون را نقض کند.
مشکل اصلی معمولاً syntax نیست. مسئله این است که چه چیزی باید بین renderها در حافظه بماند، owner آن کدام component است، چه چیزی از دادههای موجود قابل derive شدن است و کدام component identity باید state را preserve یا reset کند. یک child میتواند prop را در state کپی کند و بعد دیگر تغییرات prop را دنبال نکند. دو sibling میتوانند هر کدام selectedId جدا داشته باشند و از هم فاصله بگیرند. یک summary میتواند در state duplicate شود و با draft ناسازگار بماند. همچنین ممکن است برای «sync کردن» دادهای که در render قابل محاسبه است Effect اضافه شود و در نتیجه بهجای مدل بهتر، مسیر update اضافی ایجاد شود.
خروجی این درس React Component State Boundary Record v1 است. این artifact باید هر requirement را به source of truth، owner component، نوع representation یعنی props/state/derived value، render-tree identity، evidence رفتاری، mutation عمدی، repair، transfer در context جدید و یک residual limitation متصل کند. screenshot نهایی کافی نیست، چون بسیاری از خطاهای state فقط بعد از چند interaction مشخص ظاهر میشوند.
محدوده، مسیر یادگیری و evidence bar
WEB-001 prerequisite سخت این درس است. اگر diagnostic repair لازم شد، evidence محدود آن را reuse کنید؛ قرار نیست پایههای کل وب دوباره تدریس شود. مالکیت WEB-011 شامل local component state، state shape، shared ownership، preserve/reset عمدی و مرز render logic با Effect است. server-state/query lifecycle و typed routing متعلق به WEB-012، visual regression و component catalog متعلق به WEB-018 و build/assets/splitting متعلق به WEB-014 است.
در Foundation route، matrix مالکیت و identity کنار کد نگه داشته میشود و هر بار یک source of truth بررسی میشود. در Repair route فقط misconception تشخیصدادهشده هدف قرار میگیرد. در Experienced route میتوان توضیح مقدماتی useState را فشرده کرد، اما ownership، derived data، identity reset، mutation و transfer حذف نمیشوند.
Observation را از inference جدا کنید. «بعد از انتخاب profile B، input نام اولیه B را نشان داد» observation است. «پس هر تغییر prop بهصورت خودکار state را reset میکند» inference غلط است. «متن filter بعد از تغییر profile باقی ماند» observation است، ولی از آن نمیتوان نتیجه گرفت همه local stateها همیشه باید preserve شوند.
مدل ذهنی: memory ضروری → owner → render → identity → evidence
این زنجیره را ثابت نگه دارید:
requirement → essential source of truth → نزدیکترین owner مسئول → props/local state/derived value → render-tree identity → interaction sequence → evidence.
React state را بهعنوان memory اختصاصی component بین renderها توضیح میدهد. اما این تعریف به معنی «هر مقداری که component استفاده میکند باید state باشد» نیست. اگر یک مقدار در render از props و state فعلی قابل محاسبه است، غالباً derived value است نه source of truth دوم. [S-REACT-STATE] [S-REACT-STRUCTURE]
وقتی دو component باید روی یک fact مشترک هماهنگ باشند، آن fact را به نزدیکترین common owner منتقل کنید و data و event handler را از آنجا عبور دهید. این به معنی «همه stateها را تا root بالا ببرید» نیست؛ قاعده این است: یک fact مشترک، یک owner معتبر در نزدیکترین سطح لازم. [S-REACT-SHARING]
preserve/reset نیز به متن JSX متصل نیست. React state را با identity component در render tree مرتبط میکند و position، type و key در حفظ یا reset آن نقش دارند. بنابراین میتوان draft profile A را هنگام رفتن به profile B عمداً reset کرد، درحالیکه filter workspace حفظ میشود. [S-REACT-IDENTITY]
پایه: پنج contract مستقل
۱. Local State
state محلی را برای اطلاعاتی نگه دارید که UI باید بین renderها به خاطر بسپارد و آن component واقعاً مالک آن است. در این سناریو filter text نمونه خوبی است؛ تغییر profile نباید filter را پاک کند، چون filter memory مستقل workspace است.
۲. State Shape
حداقل factهای ضروری را ذخیره کنید. اگر draft شامل firstName و lastName است، fullName معمولاً باید در render derive شود:
const fullName = `${draft.firstName} ${draft.lastName}`.trim()
اگر fullName را state جدا کنید، یک source of truth اضافی و خطر تناقض میسازید. راهنمای فعلی React نیز حذف stateهای redundant و duplicated را توصیه میکند. [S-REACT-STRUCTURE]
۳. Shared Ownership
اگر هر دو pane به selected profile نیاز دارند، نباید selectedId جداگانه داشته باشند. آن را در نزدیکترین common owner مثل ProfileWorkspace قرار دهید و value و callback را به childها بدهید. [S-REACT-SHARING]
۴. Component Identity
draft local state است، اما requirement میگوید این draft متعلق به هویت profile است. اگر با رفتن از A به B باید draft تازه B ایجاد شود، identity را صریح کنید:
<ProfileEditor key={selectedProfile.id} profile={selectedProfile} />
key یک دکمه جادویی refresh نیست. اینجا اعلام میکند editor مربوط به A و editor مربوط به B برای این requirement دو identity متفاوتاند. [S-REACT-IDENTITY]
۵. Effect Boundary
Effect برای synchronization با سیستم بیرونی React است؛ نه روش پیشفرض برای محاسبه render data یا اجرای منطق معمول event. اگر fullName از draft فعلی قابل محاسبه است، همانجا derive شود. اگر Save با کلیک کاربر رخ میدهد، event handler محل طبیعی آن است. [S-REACT-EFFECT]
چرا shortcutهای ظاهراً درست شکست میخورند
این الگو را ببینید:
function ProfileEditor({ profile }) {
const [name, setName] = useState(profile.name)
}
initializer فقط مقدار اولیه state همان component identity را تعیین میکند. اگر همان identity بعداً prop جدید بگیرد، React آن را فرمان overwrite کردن state محلی تلقی نمیکند. در بعضی productها همین رفتار مطلوب است، اما در این سناریو requirement میگوید draft با تغییر profile identity reset شود. repair باید از requirement بیاید، نه از قاعده ساختگی «هر وقت prop عوض شد Effect بنویس».
حالا دو sibling را در نظر بگیرید که هر کدام selection جدا دارند. تا وقتی interaction ساده است ممکن است هر دو یک profile را نشان دهند، اما دو source of truth وجود دارد و divergence ممکن است. با lift کردن selection به common owner، امکان این disagreement از ساختار حذف میشود. [S-REACT-SHARING]
summary duplicated نیز خطر مشابه دارد. اگر summary در state ذخیره و با Effect از draft update شود، حداقل یک مسیر update اضافی ساخته میشود و امکان intermediate render وجود دارد که draft جدید است ولی summary هنوز مقدار قبلی را نشان میدهد. اگر summary pure render data است، derive کنید. Effect را برای synchronization واقعی با external system نگه دارید. [S-REACT-EFFECT]
در نهایت identity را با داده اشتباه نگیرید. component میتواند prop جدید بگیرد اما در همان position و با همان type/key همچنان همان identity باشد. تغییر key میتواند subtree را identity جدید کند و local state را reset کند. سؤال مهندسی این است: این local state طبق requirement به چه چیزی تعلق دارد؟ در این editor، draft به profile انتخابشده تعلق دارد، ولی filter به workspace تعلق دارد؛ پس یکی reset و دیگری preserve میشود.
مثال کامل: یک workspace، یک selection و draft وابسته به profile
چهار بخش را جدا کنید:
filterText— state ضروری UI درProfileWorkspace، چون باید با تغییر profile حفظ شود.selectedId— shared state ضروری درProfileWorkspace، چون هر دو pane به آن وابستهاند.selectedProfile— derived value ازprofilesوselectedId؛ نباید object دیگری در state duplicate شود.draft— local state داخلProfileEditorکه به identity profile انتخابشده وابسته است.
مدل دفاعپذیر:
function ProfileWorkspace({ profiles }) {
const [filterText, setFilterText] = useState('')
const [selectedId, setSelectedId] = useState(profiles[0].id)
const selectedProfile = profiles.find(p => p.id === selectedId)!
return (
<>
<ProfileList
profiles={profiles}
filterText={filterText}
onFilterTextChange={setFilterText}
selectedId={selectedId}
onSelect={setSelectedId}
/>
<ProfileEditor key={selectedProfile.id} profile={selectedProfile} />
</>
)
}
در editor، draft از profile initialize میشود و summary از draft در render محاسبه میشود. key باعث میشود تغییر identity profile یک state تازه برای editor بسازد، درحالیکه workspace key نشده و filter خود را حفظ میکند.
در WEB-011-TAB1 برای هر requirement این موارد را ثبت کنید: source of truth، owner، props/state/derived representation، identity boundary، interaction evidence، تصمیم preserve/reset، نتیجه mutation و residual limitation.
Failure Probe: evidence باید با شکستن contract تغییر کند
Mutation A — summary را duplicate کنید
برای summary state و Effect بسازید و عمداً یک dependency یا update path را ناقص کنید. قبل از اجرا mismatch مورد انتظار را بنویسید. هدف این نیست که «Effect بد است»؛ هدف این است که این value به source of truth دوم نیاز نداشت.
Mutation B — ownership selection را تقسیم کنید
selectedId را داخل هر دو pane جداگانه نگه دارید. selection یکی را تغییر دهید و ببینید دیگری میتواند profile قبلی را نشان دهد. repair صحیح بازگرداندن یک owner مشترک است، نه اضافه کردن synchronization دوم.
Mutation C — key هویت profile را حذف کنید
همان ProfileEditor را با prop جدید نگه دارید. برای A draft بنویسید، B را انتخاب کنید و بررسی کنید آیا draft A در B باقی میماند. چون requirement reset میخواهد، evidence باید fail شود. key درست را برگردانید و همان interaction را تکرار کنید.
Mutation D — render data را با Effect mirror کنید
یک derived value مستقیم را به state+Effect تبدیل کنید. update path را بررسی کنید. اینکه بعد از اجرای Effect ظاهر نهایی درست میشود، intermediate state و synchronization اضافی را حذف نمیکند. اگر external system وجود ندارد، derive during render راه سادهتر و دفاعپذیرتر است. [S-REACT-EFFECT]
مسیر experienced: state را data-model decision ببینید
اگر React را خوب میشناسید، پنج سؤال را مستقیم پاسخ دهید:
- کدام factها essential memory هستند و کدامها derive میشوند؟
- کدام fact مشترک است و نزدیکترین common owner آن کجاست؟
- کدام local state متعلق به workspace و کدام متعلق به entity انتخابشده است؟
- کدام مقدار در render محاسبه میشود و کدام واقعاً external synchronization میخواهد؟
- چه mutationای میتواند مدل فعلی را falsify کند؟
مدل خوب تعداد synchronization edgeها را کم میکند. اگر چند Effect فقط برای هماهنگ کردن stateهای React با یکدیگر دارید، ابتدا state shape را بررسی کنید. اگر siblingها دائماً پیام میفرستند تا selectionشان یکی بماند، ownership را بررسی کنید. اگر draft ناخواسته بعد از تغییر record باقی میماند، identity را بررسی کنید.
Alternative و Trade-off
Local draft یا controlled draft در parent. وقتی فقط editor به draft در حال ویرایش نیاز دارد، local state میتواند مناسب باشد. اگر sibling دیگری باید آن را هماهنگ یا persist کند، lift کردن ممکن است منطقی باشد. قانون universal نداریم؛ کوچکترین owner سازگار با requirement را انتخاب کنید.
reset با key یا reset صریح. key زمانی قوی است که product identity دقیقاً با component identity منطبق است و کل subtree محلی باید reset شود. reset صریح زمانی مناسب است که فقط بخشی از state باید تغییر کند یا migration لازم است. key را بهعنوان ترفند مبهم re-render استفاده نکنید.
Derived value یا memoization. derived بودن به معنی الزام useMemo نیست. اول redundant state را حذف کنید. فقط اگر measurement هزینه محاسبه را مهم نشان داد، optimization را جداگانه بررسی کنید.
Effect یا event handler. اگر action نتیجه کلیک کاربر است، event handler محل مستقیم آن است. اگر component به دلیل حضورش روی صفحه باید با external system هماهنگ شود، Effect ممکن است مناسب باشد. [S-REACT-EFFECT]
تمرین: repair هدایتشده و سپس transfer مستقل
Guided Repair
WEB011-LAB1 را باز کنید و starter خراب را قبل از هر edit بررسی کنید. ابتدا contract را بنویسید: «selection بین paneها مشترک است؛ filter با تغییر profile حفظ میشود؛ draft با identity profile reset میشود؛ summary derive میشود؛ Effect صرفاً برای mirror کردن render data استفاده نمیشود.»
فقط تغییرات لازم را انجام دهید. یک selectedId در common owner نگه دارید. selectedProfile را derive کنید. filter را خارج از editor keyشده نگه دارید. draft را به profile identity متصل کنید. summary را مستقیم از draft بسازید. public checkها را اجرا کنید، سپس interaction sequence را در runtime React ناشر انجام دهید و evidence واقعی را ثبت کنید.
یک mutation را قبل از دیدن نتیجه انتخاب کنید. failure مورد انتظار را بنویسید، mutation را اعمال کنید، نتیجه را observe کنید، dimension شکستهشده را diagnose کنید، repair را برگردانید و همان interaction را دوباره اجرا کنید.
Transfer مستقل
fixture دوم به project editor دوپنجرهای تبدیل میشود. همچنان یک selected project مشترک، query/filter پایدار و draft وابسته به project identity لازم است. owner و identity را دوباره از requirement استخراج کنید. profile-specific ID یا component name را copy نکنید.
Transfer blocking است؛ تقلید کد guided بدون بازسازی requirement→owner→identity evidence کافی نیست.
Verification: Boundary Record چه چیزی را باید اثبات کند
React Component State Boundary Record v1 حداقل این موارد را دارد:
- React/runtime context و fixture identifier؛
- mapping requirement به source of truth؛
- owner component برای state ضروری؛
- classification هر value به props/state/derived render؛
- component identity و تصمیم preserve/reset؛
- interaction sequence و observation؛
- mutation prediction و mutation observation؛
- diagnosis متصل به یک dimension مالکیت درس؛
- repair و post-repair result؛
- transfer در context جدید؛
- یک residual limitation.
Browser/DOM behavior فقط وقتی باید claim شود که واقعاً در همان environment مشاهده شده باشد. این Macro-A بهجای learner browser run چیزی جعل نمیکند. hidden oracle فقط contract ساختاری reference fixture را بررسی میکند و نشان میدهد mutationهای عمدی قابل تشخیصاند. learner evidence تا زمان اجرای interaction در runtime ناشر کامل نیست.
screenshot نهایی evidence ضعیفی است، چون duplicated state ممکن است موقتاً برابر باشد. code review تنها هم کافی نیست، چون preserve/reset requirement رفتاری است. artifact باید implementation structure و interaction evidence را کنار هم نگه دارد.
Assessment و Completion
WEB-011-S1 — retrieval. essential state، derived data، shared ownership، component identity و Effect boundary را از هم جدا کنید و یک non-goal بگویید.
WEB-011-S2 — application (blocking). profile editor را repair کنید و Boundary Record را با ownership و interaction evidence تکمیل کنید.
WEB-011-S3 — diagnosis (blocking). یک mutation در state shape، ownership یا identity را پیشبینی و اجرا کنید، diagnose و repair کنید و post-repair evidence بدهید.
WEB-011-S4 — decision. یکی از انتخابها مثل local vs lifted draft یا key-based vs explicit reset را برای requirement محدود دفاع کنید.
WEB-011-S5 — transfer (blocking). روش را روی project editor جدید پیاده کنید، بدون copy کردن assumptionهای profile fixture.
Completion به contract correctness، evidence quality، scope discipline و transfer مستقل نیاز دارد. residual limitation واقعی قابل قبول است؛ مثلاً «این evidence روی fixture client-rendered ناشر انجام شده و server-state lifecycle و routing خارج از WEB-011 هستند.»
Source و Freshness Boundary
Bible فریزشده پنج مرجع رسمی React را تعیین کرده و در authoring pass تاریخ 2026-09-13 دوباره بررسی شدند:
- S-REACT-STATE — state بهعنوان component memory بین renderها.
- S-REACT-STRUCTURE — پرهیز از contradictory، redundant و duplicated state.
- S-REACT-SHARING — انتقال coordinated state به نزدیکترین common owner.
- S-REACT-IDENTITY — preserve/reset state بر اساس render-tree identity از جمله type/position/key.
- S-REACT-EFFECT — Effect برای external synchronization، نه محاسبه معمول render data یا event logic.
React 19.3 در 2026-09-09 منتشر شده است. این شماره نسخه فقط review context است؛ claim جدید برای این درس ایجاد نمیکند و WEB-011 را به APIهای جدید 19.3 گسترش نمیدهد. fundamentals این درس همچنان به guidance رسمی بالا محدود است و طبق Bible حداکثر تا 2026-10-17 دوباره review میشود. [S-REACT-STATE] [S-REACT-STRUCTURE] [S-REACT-SHARING] [S-REACT-IDENTITY] [S-REACT-EFFECT]
Boundary Record یک design آموزشی MAKNOW است، نه API رسمی React. این درس ادعا نمیکند همه valueها باید state باشند، derived valueها به Effect نیاز دارند، تغییر prop خودکار local state را reset میکند یا ظاهر درست بهتنهایی state model را اثبات میکند.
ارزیابی
پاسخنامهی این درس در بستهی آموزش وجود ندارد؛ خودت جواب بده و صادقانه علامت بزن. «نیاز به مرور» درس را به صف تکرار میفرستد.
WEB-011-S1 retrieval
essential state، derived value، shared ownership، component identity و Effect boundary را تفکیک کنید و یک non-goal صریح بنویسید.
WEB-011-S2 application
profile editor دوپنجرهای را repair کنید تا selection مشترک، filter پایدار، draft وابسته به identity و summary مشتقشده باشد و Boundary Record را تکمیل کنید.
WEB-011-S3 diagnosis
یک mutation مربوط به state shape، ownership یا identity را پیشبینی و اجرا کنید، dimension نقضشده را diagnose و repair کنید و همان interaction را دوباره اجرا کنید.
WEB-011-S4 decision
برای یک requirement محدود، local vs lifted draft یا key-based vs explicit reset را دفاع کنید.
WEB-011-S5 transfer
روش را به project editor منتقل کنید: selected project مشترک، query پایدار و draft وابسته به project identity. ownership و identity را دوباره استخراج کنید.
ملاکهای ارزیابی (rubric)
Critical dimensions:
- Contract Correctness — Implementation and explanation preserve the owned semantic/behavioral contract.
- Evidence Quality — Observed evidence, mutation and repair are traceable and do not rely on appearance alone.
- Scope Discipline — Adjacent lesson outcomes and unsupported generalizations are explicitly deferred.
- Transfer Independence — Changed-context task is solved without decisive scaffold.
Blocking assessments: S2, S3, S5. Non-blocking assessments still require contract and scope discipline. Browser/DOM evidence must come from learner observation; machine checks must not fabricate it.
منابع و تازگی
مطالعهٔ موارد (case studies)
فایلهای آزمایشگاه
هدف: profile editor دوپنجرهای را بدون ساختن source of truth اضافه repair کنید.
رفتار لازم:
- selected profile بین هر دو pane یک source of truth مشترک داشته باشد.
- filter text با تغییر profile حفظ شود.
- draft هنگام تغییر profile identity reset شود.
- summary از draft فعلی derive شود و با Effect mirror نشود.
- interaction واقعی، یک mutation/repair و transfer به project editor در evidence JSON ثبت شود.
برای public structural checks دستور python3 run_checks.py را اجرا کنید. هر claim مربوط به Browser/DOM باید از اجرای واقعی در runtime React ناشر بیاید.
قاعده evidence در submission: در هر observation، observation را از inference جدا کنید؛ prediction/change/observation/diagnosis مربوط به mutation، repair، transfer، residual limitation و attestation باید کامل باشند. Transfer باید مستقل برای project editor بازاستنتاج شود: selected project یک source of truth مشترک داشته باشد، query هنگام تعویض project حفظ شود، draft در مرز identity پروژه reset شود و display label بهجای mirror شدن در state derive شود. شناسهها و نامهای profile-specific را در راهحل project کپی نکنید.
رسانه و اسلایدهای درس
فایلهای آموزشی (دانلود)