ساختار بهینه برای فایل ها و فولدرهای پروژه ری اکت

علیرغم اینکه در پروژه های ریکت ساختار استاندارد و مشخصی برای فایل ها و فولدرها وجود ندارد اما بهتر است طبق یک ساختار مناسب و بهینه کدنویسی پروژه را شروع کنیم. زیرا اگر در آینده بخواهیم ساختار و مسیر فایل ها و فولدرهای پروژه ریکتی را تغییر دهیم، نیاز است کدهای نوشته شده را مجددا بازبینی کرده و اصلاحات لازم را انجام دهیم.
پس پیشنهاد می شود قبل از شروع کدنویسی پروژه، به یک ساختار بهینه برای منابع اپلیکیشن ریکت خود برسیم تا در زمان و انرژی صرفه جویی کرده باشیم.
اگر file structure در پروژه ریکت بهینه باشد، فرقی ندارد که توسط ReactJS یک اپلیکیشن تحت وب تولید می کنیم یا توسط React-Native یک اپلیکیشن موبایلی. در یک تیم دو نفره کار می کنیم یا در یک تیم 10 نفره. با نسخه های قدیمی ریکت کد می زنیم یا با نسخه های جدید.
در این مقاله می خواهیم یک ساختار مناسب و بهینه برای ساختارمند کردن فایل ها و فولدرها در پروژه های ریکت معرفی کنیم که بعد از سال ها تجربه و سعی و خطای بسیار به آن رسیده ایم.
از شما کاربران گرامی و توسعه دهندگان ریکت خواهشمندیم تجربه ها و دانش خود را در زمینه ساختار فایل ها و پروژه های ریکت در بخش دیدگاه های همین مقاله اعلام فرمایید.
استفاده از دستور create-react-app:
با اجرای دستور npx create-react-app app-name می توانید یک پروژه ریکت جی اس راه اندازی کنید و نیازی به هیچ کار دیگری ندارید!
ساختار پیش فرض پروژه در این حالت بصورت زیر است:
my-app ├── build ├── node_modules ├── public │ ├── favicon.ico │ ├── index.html │ └── manifest.json ├── src │ ├── App.css │ ├── App.js │ ├── App.test.js │ ├── index.css │ ├── index.js │ ├── logo.svg │ └── serviceWorker.js ├── .gitignore ├── package.json └── README.md
پوشه build: شامل فایل های نهایی و به اصطلاح production پروژه شما می باشد. این فولدر تا وقتی که دستور npm build یا yarn build را اجرا نکنید وجود ندارد. پس از بیلد کردن پروژه، این فولدر بطور اتوماتیک ساخته می شود. فایل ها و فولدرهایی که در پوشه build قرار دارند آماده پابلیش بر روی هاست هستند.
پوشه node_modules: شامل تمام پکیج های نصب شده توسط دستور npm install یا yarn add می باشد.
پوشه public: محل قرارگیری فایل های استاتیک پروژه است. فایل هایی که در این فولدر هستند با همین نام در پوشه build یعنی نسخه production قرار خواهند داشت. معمولا فایل های داخل public در مرورگر کش می شوند و نیازی نیست هر بار توسط مرورگر کاربر دانلود شوند. فایل هایی که مانند index.html و manifest.json و robots.txt نام آنها مهم است و نباید تغییر کند باید در فولدر src درج شوند.
پوشه src: در این پوشه فایل های داینامیک قرار می گیرند. اگر فایلی توسط کدهای جاوا اسکریپت import می شود یا توسط کدهای js محتوایش تغییر می کند در این پوشه قرار دهید. فایل های داینامیک معمولا نباید کش cache شوند. به این منظور می توان از ابزارهایی مانند وب پک webpack استفاده کرد. وب پک می تواند یک نام رندوم را به انتهای نام هر فایل اضافه کند تا از کش خوانده نشوند. مانند banner-hash-code.jpg بجای hash-code یک رشته رندوم قرار خواهد گرفت.
بررسی ساختار پروژه در create-react-app:
دو موردی که می توانیم در استفاده از دستور create-react-app بیان کنیم:
- فایل ها باید در دایرکتوری های پروژه بر اساس نوع فایل دسته بندی شوند.
- اسامی فایل ها باید lowercase یا حروف کوچک باشند.
یکی از مواردی که معمولا در ساختار پروژه های ریکت رعایت می شود، تعریف کامپوننت های اپلیکیشن در فولدری به نام components می باشد. البته برخی از توسعه دهندگان ری اکت از دو فولدر برای کامپوننت های برنامه استفاده می کنند: یک فولدر برای کامپوننت های stateful و فولدری برای stateless
اما پیشنهاد ما استفاده از یک فولدر کلی بنام components می باشد که تمام اجزا یا کامپوننت های برنامه داخل آن تعریف شود. در ساختار پسشنهادی زیر یک کامپوننت داریم بنام App که شامل فایل های css و js مربوطه می باشد:
src ├── components │ └── app │ │ ├── app.css │ │ ├── app.js │ │ └── app.test.js │ └── index.js ├── images │ └── logo.svg ├── index.css ├── index.js └── service-worker.js
در ساختار درختی فوق مشاهده می کنید در روت فولدر components یک فایل بنام index.js داریم که شامل لیست تمام کامپوننت های exportشده پروژه می باشد.
منابع assets پروژه مانند تصاویر، فونت ها یا فایل های sass می توانند در فولدر مجزای خودشان تعریف شوند. مثلا همانطور که در ساختار فوق مشاهده می کنید، تصاویر در فولدر images درج خواهند شد. و به همین ترتیب فایل های sass می توانند در فولدر styles تعریف شوند. یعنی اپلیکیشن فقط یک فولدر برای تصاویر دارد و هر زمان که نیاز به تغییر عکس یا درج یک عکس جدید باشد، فقط کافیست به این فولدر مراجعه شود. یا اگر وب اپلیکیشن شما چند زبانه است می توانید فایل های مربوط به ترجمه زبان ها را در یک فولدر بنام locales تعریف کنید.
یک پروژه ری اکت به یک دایرکتوری مهم دیگر نیز احتیاج دارد. دایرکتوری utility
مانند src/utils یا src/helpers این پوشه ای است که شامل توابع helper می باشد که بصورت گلوبال در پروژه مورد استفاده قرار می گیرند.
در این صورت، بخشی از اپلیکیشن شما که کد آن در جاهای مختلف برنامه مورد استفاده قرار می گیرد نیاز به کپی پیست نخواهد داشت و از یک فولدر مثلا بنام utility تمامی کدها خوانده می شود.
نام این پوشه دلخواه است و بنا به نیازتان می توانید اسامی مناسب انتخاب کنید. اما چیزی که مهم است ایجاد یک فایل بنام index.js در این فولدر، مثلا utility است که شامل دستور export تمام فایل های js موجود در فولدر باشد.
در نهایت پیشنهاد می کنیم ماژول های مستقل و مجزای پروژه خود را در یک فولدر مثلا با نام src/packages یا src/shared تعریف کنید تا بتوانید در سایر پروژه های خود یا سایر تیم ها از آن استفاده کنید.
بر خلاف فولدرهای components و utils در این فولدر، مثلا packages یا shared نیازی به فایل index.js در روت پوشه نیست. زیرا معمولا نمی خواهیم از تمام ماژول های export شده استفاده کنیم. بلکه بر اساس نیاز می خواهیم یک کامپوننت را به شکل زیر ایمپورت کنیم:
import Component from 'src/packages/package-name';
با این شرایط، ساختار پروژه ریکتی ما به صورت زیر خواهد بود:
my-app ├── build ├── node_modules ├── public │ ├── favicon.ico │ ├── index.html │ └── manifest.json ├── src │ ├── components │ │ ├── app │ │ │ ├── app.css │ │ │ ├── app.js │ │ │ └── app.test.js │ │ └── index.js │ ├── images │ │ └── logo.svg │ ├── packages │ │ └── ... │ ├── utils │ │ ├── ... │ │ └── index.js │ ├── index.css │ ├── index.js │ └── service-worker.js ├── .gitignore ├── package.json └── README.md
دایرکتوری components در پروژه ریکت:
ساختار پوشه components شبیه سایر پوشه های پروژه ریکت می باشد. همانطور که می دانید یک اپلیکیشن react از چندین کامپوننت تشکیل شده است که بهتر است در مسیر src/components/component-name تعریف شوند.
اما مرحله بعدی برای ساختارمند کردن فایل ها و فولدرهای کامپوننت در ریکت چیست؟
اگر نام فایل js اصلی هر کامپوننت را index.js تعریف کنیم، فرض کنید چند کامپوننت را در نرم افزار ویژوال استودیو باز کردید و در حال ویرایش هستید، تصویر زیر را خواهیم داشت.

همانطور که مشاهده می کنید نام تمام کامپوننت ها برابر index.js است و مشخص نیست هر فایل مربوط به کدام کامپوننت است. البته نرم افزار visual studio code از این ایراد آگاه است و معمولا نام فولدر هر فایل را در کنار نام فایل نمایش می دهد اما سایر ادیتورها این قابلیت را ندارند و کار سخت می شود.
ما می توانیم از یک روش بهتر برای نام گذاری کامپوننت های اپلیکیشن reactjs استفاده کنیم.
بطور معمول یک کامپوننت بیش از یک فایل دارد. بعنوان مثال هر کامپوننت معمولا شامل فایل های SASS یا CSS می باشد. یک کامپوننت امکان دارد دارای کامپوننت فرزند یا Child Component باشد.
به همین دلیل است که فولدر components در ریکت در اغلب موارد بیش از یک فایل component-name.js دارد و احتمالا فایل ها و پوشه های مختلفی در هر فولدر کامپوننت وجود دارد.
استفاده از هوک ها در ری اکت:
در این حالت احتمالا یک کامپوننت root در مسیر src/components/component-name وجود دارد. توسط هوک hook در ری اکت می توانید local state و global state را مدیریت کنید. معمولا به HOC: High-Order-Component ها نیازی ندارید. اگر هم نیاز باشد بصورت زیر تعریف می شوند:
function MyComponent() {
const state = useHook();
return <div {...state} />;
}export default withHoc(MyComponent);مطالعه مقاله “راهنمای هوک های useState و useEffect در ری اکت” توصیه می شود.
به منظور رفع مشکل یکسان بودن نام کامپوننت ها و عدم خوانایی نام کامپوننت ها در ادیتور، نام هر فایل کامپوننت را بصورت component-name.js تعریف می کنیم و در یک فایل index.js این کامپوننت را import می کنیم.
برای راحتی تست پروژه پیشنهاد می کنیم هوک های مربوط به هر کامپوننت را در فایلی بنام useComponentName تعریف کنید. ساختار درختی پروژه در حالتی که از Hook استفاده می کنیم بصورت زیر است:
my-app └── src └── components ├── component-name │ ├── hooks │ │ ├── index.js │ │ └── use-component-name.js │ ├── component-name.css │ ├── component-name.scss │ ├── component-name-styles.js │ ├── component-name.js │ └── index.js └── index.js
درست مانند src/components و src/utils فولدر هوک هم می تواند شامل یک فایل index.js باشد که تمام هوک های کامپوننت در آن export شده اند.
جمع بندی ساختار پروژه ری اکت در صورت استفاده از Hook:
- hooks/index.js: یک فایل ورودی یا مدخل entryاست که تمام هوک ها در آن export شده اند.
- hooks/use-component-name.js: یک هوک واحد است که تمام هوک ها در آن فراخوانی شده اند که در کامپوننت مربوطه استفاده شده اند.
- component-name.css: یک فایل استایل cssاست که در کامپوننت مربوطه import شده است.
- component-name.scss: یک فایل SASSاست که در کامپوننت مربوطه import شده است.
- js: مدخل ورودی یا entry pointکامپوننت است که component-name در آن export شده است و هر جای برنامه که بخواهیم از این کامپوننت استفاده کنیم، این فایل را import می کنیم.
نحوه export کامپوننت در فایل index.js بصورت زیر است:
export { default as ComponentName } from './component-name';
عدم استفاده از هوک های ریکت:
در فرآیند توسعه اپلیکیشن، باید بررسی کنیم که آیا یک کامپوننت state خود را دارد یا state آن بصورت گلوبال در global store مدیریت می شود یا یک کامپوننت والد props و state ها را به این کامپوننت ارسال می کند؟
ممکن است یک کامپوننت به هریک از روش های مذکور کار کند. بدلیل خوانایی فایل ها و لزوم جداسازی بخش های مستقل از یکدیگر، هر یک از موارد مذکور باید در کامپوننت مجزای خودشان تعریف شوند و فایل مستقل داشته باشند اما ما فقط component-name/index.js را داریم. بنابراین به چه شکل باید تعریف شود؟
راه حل ریفکتور یا refactor کردن می باشد. state ها در یک کامپوننت ممکن است از طریق یک global state مدیریت شود. مانند ریداکس. همچنین ممکن است state های یک کامپوننت از طریق props از کامپوننت والد ارسال شوند. یا در حالت سوم ممکن است state ها به شکل لوکال و محلی در کامپوننت کنترل شوند.
در نهایت هم ممکن است یک کامپوننت اصلا state نداشته باشد. در حالتی که کامپوننت از یک global state استفاده می کند مجبور هستیم کل فایل را از index.js به component-name-view.js منتقل کنیم و entry point را به global state متصل کنیم.
مسلما جابجایی فایل ها در پروژه دشوار است و احتمال دارد دچار خطا در برنامه شویم. ما پس از انجام سعی و خطا و تغییرات زیاد در پروژه ها به این نتیجه رسیدیم که:
- در فایل index.js کامپوننت فقط کافیست دستور زیر را تعریف کنیم:
export { default } from './component-name-topmost.js';- کامپوننت stateless را نیز در فایل component-name-view.js تعریف کنید.
- کامپوننت container را در فایل component-name-container.js تعریف کنید.
- state گلوبال در component-name-redux.js تعریف شود.
- و در نهایت هر کدام از موارد فوق را که در کامپوننت نیاز داشتید می توانید export کنید. اگر کامپوننت شما فاقد state باشد فقط view.js را اکسپورت می کنید. اگر به state نیاز پیدا کردید container.js را خواهید داشت. قفط کافیست در دستور export کلمه view را با container جایگزین کنید. در صورتی که به یک global state نیاز داشتید فقط کافیست redux.js را در فایل index.js اکسپورت کنید. در این حالت فقط کافیست در دستور export کامپوننت، کلمه container.js را با redux.js جایگزین کنید. به همین راحتی!
به ساختار درختی زیر در این زمینه توجه کنید:
my-app └── src └── components ├── component-name │ ├── component-name.css │ ├── component-name.scss │ ├── component-name-container.js │ ├── component-name-redux.js │ ├── component-name-styles.js │ ├── component-name-view.js │ └── index.js └── index.js
ساختار سلسله مراتبی فوق خلاصه مواردیست که تا اینجا ذکر کردیم:
- component-name-container.js: شامل business logic کامپوننت و state management آن می باشد.
- component-name-redux.js: شامل mapStateToProps و mapDispatchToProps در ریداکس می باشد. اما ممکن است شما بجای Redux از یک state manager دیگر مثلا mobX استفاده کنید. در این حالت نام فایل را می توانید component-name-mobx.js در نظر بگیرید. با این روش نام گذاری این امکان را خواهید داشت که براحتی از چندین global state در اپلیکیشن خود بهره ببرید. و هر زمان که لازم باشد از mobx یا redux استفاده کنید.
- component-name-view.js: معادل کامپوننت بدون state یا stateless-component شما می باشد. در اغلب موارد این کامپوننت باید بصورت تابعی یا functional تعریف شود.
- index.js: مدخل ورودی یا entry point کامپوننت شما می باشد. محتوای آن چیزی نیست به جز export فایل های موردنیاز. که در بالا بطور مفصل توضیح دادیم.
دایرکتوری component-name می تواند شامل زیرفولدرهای مختص خود باشد. مانند utils. منظورمان فولدرهایی است که فقط مختص این کامپوننت هستند و در سایر بخش ها و کامپوننت های برنامه کاربردی ندارند. در اینصورت تست واحد یا unit test برنامه سریع تر و راحت تر خواهد شد.
دایرکتوری کامپوننت های زیرمجموعه یا SubComponent:
اگر کامپوننتی بعنوان کامپوننت فرزند یک کامپوننت دیگر باشد، بهتر است آن را بعنوان یک زیرشاخه در فولدر کامپوننت والد تعریف کنید.
نمودار درختی زیر را در نظر بگیرید:
my-app └── src └── components └── github-repo ├── components │ ├── icon │ │ ├── icon.scss │ │ ├── icon.js │ │ └── index.js │ ├── title │ │ ├── title.scss │ │ ├── title.js │ │ └── index.js │ └── index.js ├── github-repo.scss ├── github-repo.js └── index.js
زیرپوشه ها یا subdirectory ها را باید در سطح اول این ساختار درختی تعریف کرد. تا مرور کدها یا code review آسان تر باشد.
اگر کامپوننت GitHuboRepo شامل کامپوننتی بنام Icon باشد، که خود این آیکون یک کامپوننت فرزند بنام Image داشته باشد که این image هم یک کامپوننت فرزند بنام Svg داشته باشد، ساختار درختی فولدر کامپوننت ها به شکل زیر خواهد بود:
my-app └── src └── components └── github-repo ├── components │ ├── image │ │ ├── image.scss │ │ ├── image.js │ │ └── index.js │ ├── icon │ │ ├── icon.scss │ │ ├── icon.js │ │ └── index.js │ ├── svg │ │ ├── svg.scss │ │ ├── svg.js │ │ └── index.js │ ├── title │ │ ├── title.scss │ │ ├── title.js │ │ └── index.js │ └── index.js ├── github-repo.scss ├── github-repo.js └── index.js
نکته: در مثال بالا فرض شده است که کامپوننت های Icon, Image, Svg قابل استفاده مجدد یا reusable نمی باشند.
اگرچه Image و Svg فرزندان کامپوننت Icon هستند، بصورت هم سطح و sibling تعریف شده اند. زیرا سطوح تودرتو یا nested به حد زیاد ایجاد شده و بیش از این جایز نیست!
به بیان دیگر میزان تو در تو نویسی کامپوننت ها در دایرکتوری components نباید از مسیر زیر بیشتر شود:
src/components/component-name/SUBTYPE/name
یا در مورد util ها می توان به شکل زیر سلسله مراتب را تعریف کرد:
src/components/component-name/utils/utility-name
استفاده از ریکت روتر React Router:
یکی دیگر از دایرکتوری هایی که احتمالا در اغلب اپلیکیشن های ریکتی وجود دارد src/routes است. بعنوان مثال اگر یک صفحه در مسیر rahkarino.com/portfolio واقع باشد، کامپوننت مسیریابی یا routing در مسیر src/routes/portfolio/index.js قرار خواهد داشت.
قابلیت تست اپلیکیشن ری اکت:
در نهایت برای سادگی اجرای تست واحد یا unit test بهتر است فایل های مرتبط در کنار هم قرار داشته باشند. در اینصورت، جابجایی بین فایل های کامپوننت و فایل های تست ساده خواهد بود. همچنین import کردن فایل های تست و هماهنگی بین فایل های تست و فایل های کامپوننت نیز ساده تر خواهد بود.
به ساختار درختی کامپوننت دقت کنید:
my-app └── src └── components └── component-name ├── component-name-container.js ├── component-name-container.test.js ├── component-name-redux.js ├── component-name-redux.test.js ├── component-name-view.js └── component-name-view.test.js
همانطور که مشاهده می کنید، فایل های تست مربوط به هر کامپوننت با فرمت .test.js در کنار فایل اصلی قرار گرفته اند.
کاربران گرامی، پیشنهادها یا سوالات خود را در فرم ارسال دیدگاه مقاله بیان کنید تا پاسخگو باشیم.

دیدگاهتان را بنویسید