پیش نوشت: من معمولا سعی میکنم به زبون محاورهای بنویسم تا توضیح و فهم مطالب راحتتر بشه. اما این متن رو برای مشارکت در پروژه ترجمه مستندات ریاکت جی اس ترجمه کردم و مجبور بودم که از قواعد نوشتاری این پروژه تبعیت کنم.
به عقیده ما، ریاکت بهترین راه برای ساخت وب اپلیکیشن هایی سریع و بزرگ، با استفاده از جاوااسکریپت است و برای ما در فیسبوک و اینستاگرام خیلی خوب جواب داده است.
ریاکت بخشهای خیلی خوب زیادی دارد، اما یکی از بهترین آنها، چگونگی نگرش به اپهایی است که مشغول به ساخت آنها هستید. در این سند (Document)، به فرایند تفکر در ساخت یک جدول محصولات با قابلیت جستجو خواهیم پرداخت، و برای ساخت این جدول از ریاکت استفاده میکنیم.
تصور کنید که ما از قبل یکJSON API و یک مدل، که توسط طراح آماده شده، داریم. مدل آماده شده، چیزی شبیه به این است:
و JSON API هم دادههایی را باز میگرداند که به صورت زیر است:
اولین کاری که باید کنید، این است که دور هر کدام از کامپوننتهای موجود خط بکشید و تمامی کامپوننتها را به همراه زیرمجموعههای آن (Subcomponents) مشخص کنید و برای هر کدام یک نام در نظر بگیرید. (هر کامپوننت ممکن است یک یا چند کامپوننت زیرمجموعه داشته باشد)
اگر به همراه یک طراح (دیزاینر) کار میکنید، ممکن است او قبلا همین کار را انجام داده باشد. نامگذاری آنها برای لایههای مختلف در فایل فتوشاپی که تهیه کردهاند، میتواند نام کامپوننتها در کد شما هم باشد!
اما چطور بفهمیم که یک بخش باید تبدیل به یک کامپوننت جداگانه شود؟
در این موارد بهتر است از همان تکنیکهایی استفاده کنیم که زمان تصمیمگیری برای تعریف یک تابع Function یا یک شیء Object جدید به کمکمان می آمدند.
یکی از این تکنیکها اصل مسئولیت واحد (single responsibility principle) است. این اصل بیان میکند که هر کامپوننت، باید در حالت ایدهآل فقط یک کار را انجام دهد و اگر وظایف آن گسترش یافت، باید برای آن کامپوننتهای زیرمجموعه تعریف کرد و وظایف اضافی را به آنها سپرد.
از آنجایی که اغلب یک مدل داده JSON به کاربر نشان داده میشود، متوجه خواهید شد که اگر این مدل درست ساخته شده باشد، رابط کاربری و ساختار کامپوننتهای شما هم به درستی قابل ترسیم و تعیین خواهد بود. این بدان دلیل است که مدل داده(Data Model) و رابط کاربری معمولا از یک معماری اطلاعات (information architecture) یکسان تبعیت میکنند. رابط کاربری را طوری به کامپوننتهای مختلف تقسیم کنید که هر کامپوننت با بخش خاصی از مدل داده مرتبط باشد.
در این تصویر می بینید که ما پنج کامپوننت در برنامه خود داریم و اطلاعاتی که هر کامپوننت نمایش میدهد را با حروف ایتالیک مشخص کرده ایم:
کامپوننت FilterableProductTable به رنگ نارنجی: شامل تمام برنامه
کامپوننت SearchBar به رنگ آبی: ورودیهای کاربر را دریافت میکند
کامپوننت ProductTableبه رنگ سبز: تمامی مجموعه اطلاعات را با توجه به ورودی کاربر نمایش داده و اطلاعات را فیلتر میکند
کامپوننت ProductCategoryRow به رنگ فیروزه ای: یک تیتر را برای هر دسته نمایش میدهد
کامپوننت ProductRow به رنگ قرمز: یک ردیف را برای هر محصول نمایش میدهد
اگر نگاهی به کامپوننت ProductTable بیندازید، متوجه میشوید که تیترهای جدول (شامل “Name” و “Price” ) کامپوننت جداگانهای ندارند که بیشتر یک موضوع سلیقهای است و دلایل و استدلالهایی برای استفاده یا عدم استفاده از یک کامپوننت جداگانه برای آنها وجود دارد.
در این مثال، آن را بخشی ازProductTable قرار دادیم، چرا که جزئی از مجموعه داده ها data collection بوده و رندر کردن آن وظیفه ProductTable است. با این حال، اگر هدر جدول پیچیدهتر شود (مثلا اگر گزینهای برای مرتب کردن لیست محصولات اضافه میکردیم)، مطمئنا منطقیتر بود که آنها در یک کامپوننت جداگانه با نام ProductTableHeader بگذاریم
حالا که کامپوننتهای موجود در پروژه را مشخص کردیم، وقتش رسیده که سلسله مراتب آنها را نیز تعیین کنیم. کامپوننتهایی که با توجه به مدل، درون یک کامپوننت دیگر قرار میگیرند، فرزند آن محسوب میشوند:
FilterableProductTable
SearchBar
ProductTable
ProductCategoryRow
ProductRow
بعد از اینکه سلسله مراتب را مشخص کردید، نوبت به پیادهسازی اپ میرسد. راحتترین راه این است که نسخهای بسازید که مدل داده ها را میگیرد و رابط کاربری را تولید میکند اما هیچ تعاملی با کاربر ندارد. بهتر است که این دو فرایند (نوشتن نسخه تعاملی و ایستا) را از هم جدا کنیم، به این دلیل که ساخت نسخه ایستا، به مقدار زیادی نوشتن و مقدار کمی فکر کردن نیاز دارد. اما اضافه کردن امکان تعامل و پویایی، احتیاج به مقدار زیادی تفکر دارد و نیاز آن به نوشتن بسیار کمتر است. دلیلش را در ادامه خواهیم دید.
برای ساخت یک نسخه ایستا که مدل داده را رندر کند و نمایش دهد، باید کامپوننتهایی بسازید که از بقیه کامپوننتها استفاده میکنند و داده ها را از طریق props انتقال میدهند. Props امکانی است که بوسیله آن، دادهها از کامپوننت والدparent به کامپوننت فرزندchild منتقل میشوند. اگر که با مفهومstate آشنایی دارید، هرگز از آن برای ساخت نسخه ایستا استفاده نکنید! State برای ایجاد تعامل طراحی شده و دادهای است که در طول زمان تغییر میکند و از آنجایی که فعلا روی نسخه ایستا کار میکنیم، نیازی به آن نخواهیم داشت.
میتوانید روند ساخت را از بالا به پایین یا از پایین به بالا شروع کنید. به این معنا که هم میتوانید از بالاترین کامپوننت در سلسله مراتب آغاز کنید ،مثلا (FilterableProductTable) یا از کامپوننتهایی که در سطوح پایینتری قرار دارند مثل (ProductRow) در مثالهای سادهتر، معمولا شروع از بالا به پایین راحتتر است. در حالیکه در پروژههای بزرگتر، بهتر است که از پایین به بالا پیش بروید و همزمان با ساخت اپ، برای آن تست نیز بنویسید.
در پایان این گام، شما کتابخانهای از کامپوننتها خواهید داشت که مدل داده را رندر میکند. از آنجایی که با نسخه ایستا سر و کار داریم، کامپوننتها تنها متد ()render خواهند داشت. کامپوننتی که در راس سلسله مراتب قرار دارد، یعنی (FilterableProductTable) مدل دادهها را بعنوان یک prop دریافت میکند. اگر تغییراتی را در مدل دادههای زیربنایی پروژه ایجاد کنید و دوباره ()ReactDOM.render را صدا بزنید، رابط کاربری بروز رسانی (update) خواهد شد و میتوانید ببینید که رابط کاربری چگونه و در کجا تغییر میکند. جریان یکطرفه داده one-way data flow در ریاکت (که با نام binding یکطرفه نیز شناخته میشود) همه چیز را ماژولار و سریع نگه میدارد.
برای تعاملی کردن رابط کاربری، باید بتوانید در مدل داده زیربنایی پروژه [همان دادههای اولیه] تغییر ایجاد کنید. اینکار در ریاکت از طریق state انجام میشود. برای ساخت اپ به صورت صحیح، ابتدا باید به مجموعهای حداقلی از دادههای قابل تغییر فکر کنید که در پروژه شما نیاز است. کلید این موضوع، اصل خودت را تکرار نکن است. تعیین کنید که در حال حاضر، مختصرترین مجموعه state که برنامه شما نیاز دارد چیست و بقیه موارد را به مرور، و در زمان لزوم محاسبه و تعیین کنید.
برای مثال، اگر یک برنامه برای لیست کارها (TODO List) طراحی میکنید، یک آرایه از آیتمهای موجود در لیست کارها را در دسترس نگه دارید. اما دیگر نیازی به تعیین یک state برای شمارش آیتمها ندارید. به جای آن، زمان رندر کردن تعداد، میتوانید از طول آرایه آیتمها (Array.length) استفاده کنید.
تمام بخش های داده در مثال خودمان را در نظر بگیرید:
به طور جداگانه سراغ هر یک از این موارد میرویم تا ببینیم که کدامشان state هستند. درمورد هر بخش، این 3 سوال را از خودتان بپرسید:
لیست اولیه و اصلی محصولات، بعنوان یک props منتقل میشود، بنابراینstate نیست. اما به نظر میرسد که متن جستجو و مقدار چک باکس state هستند چرا که در طول زمان تغییر میکنند و نمیتوان آنها را بوسیله داده دیگری محاسبه کرد. در نهایت، لیست محصولات فیلتر شده پس از جستجو همstate نیست، چرا که میتوان آن را از ترکیب لیست اصلی و اولیه با متن سرچ کاربر و مقدار چک باکس بدست آورد.
پس در مجموع، state این اپ عبارتند از:
بعد از اینکه ما حداقل state مورد نیاز در پروژه را مشخص کردیم، نوبت این است که بدانیم هر state باید در کدام کامپوننت قرار بگیرد.
به یاد داشته باشید: ریاکت بر مبنای جریان یک طرفه و رو به پایین داده در سلسله مراتب کامپوننتها کار میکند. ممکن است این موضوع که کدام کامپوننت، کدام state را در خود جا میدهد، فورا مشخص و واضح نباشد. اغلب این مساله، چالش برانگیزترین بخش برای افرادیست که تازه با ریاکت آشنا شده اند.
پس برای اینکه متوجه موضوع بشوید، این قدم ها را برای هر state در برنامه خود دنبال کنید:
کامپوننتهایی را که چیزی را براساس آن state رندر میکنند، مشخص کنید.
حالا این استراتژی را برای برنامه خودمان اجرا کنیم:
حالا میتوانید ببینید که برنامه شما چطور عمل میکند: filterText را به "ball" تغییر بدهید و برنامه را دوباره بارگذاری (refresh) کنید. خواهید دید که جدول دادهها به درستی تغییر میکند.
تا به اینجای کار، ما برنامهای ساختیم که به طور صحیح و به شکل تابعی از props و state، رندر میشد و جریان داده در آن از بالا به پایین بود. اما حالا زمان آن است که برنامه، جریان داده را به شکل معکوس و رو به بالا پشتیبانی کند: فرمهای موجود در پایینترین بخش سلسله مراتب کامپوننتها، باید بتوانند state درون کامپوننت FilterableProductTable را تغییر دهند.
ریاکت، این جریان داده را ساده و شفاف میکند تا به کمک آن، بتوانید طرز کار برنامهتان را درک کنید، اما این مساله احتیاج به نوشتن و تایپ بیشتری نسبت به data binding دو طرفه و مرسوم دارد.
اگه در فیلد جستجو تایپ کنید یا مقدار چک باکس را تغییر دهید [تیک آن را فعال کنید] خواهید دید که ریاکت، این تغییرات را نادیده میگیرد. این موضوع عمدی است، چرا که ما تعیین کردیم، value در input همواره با مقدار state که از کامپوننت FilterableProductTable منتقل میشود، برابر باشد.
بیایید به این فکر کنیم که میخواهیم چه اتفاقی بیفتد؟ میخواهیم مطمئن شویم که هر زمان کاربر، تغییراتی را در فرم (فیلد جستجو یا چک باکس) اعمال میکند، state بروز رسانی شده و تغییرات را منعکس کند. از آنجایی که کامپوننتها فقط مجاز به تغییر state موجود در خودشان هستند، کامپوننت FilterableProductTable کالبکهایی (callback) را به کامپوننت SearchBar انتقال میدهد تا هر وقت نیاز به تغییر state وجود داشت، اجرا شوند. میتوانیم از رویداد برای ورودیها استفاده کنیم تا به این طریق، کامپوننتFilterableProductTable از لزوم اجرای تغییر مطلع شود. آن کالبک که از کامپوننت FilterableProductTable انتقال مییابد، ()setState است و باعث تغییر و بروز رسانی اپ میشود.
همین!
امیدواریم که این مطلب، به شما ایده داده باشد که چگونه باید درباره ساختن کامپوننتها و برنامهها در ریاکت فکر کنید. البته ممکن است که میزان نوشتن، بیشتر از حدی باشد که به آن عادت دارید، اما به یاد داشته باشید که کد، بیشتر از اینکه نوشته شود، خوانده میشود و خواندن این کد که به شکل ماژولار، ساده و شفاف نوشته شده است، بسیار راحتتر خواهد بود.
به محض اینکه ساخت کتابخانههای بزرگ متشکل از کامپوننتها را شروع کنید، بابت این شفافیت و ماژولار بودن سپاسگزار خواهید شد، و با وجود امکان استفاده مجدد از کدها، تعداد خطهای کد شما به مرور کمتر خواهد شد! :)
پ.ن: برای خوندن مطالب بیشتر، میتونید به سایت من مراجعه کنید.