ساختار پروژه‌های React

آفلاین
user-avatar
عرفان حشمتی
22 فروردین 1400, خواندن در 7 دقیقه

امروزه به دلیل کنترل بسیار بهتر روی قسمت فرانت-اند و داشتن آزادی عمل در فناوری‌های جدید، من درک بهتر و عمیق‌تری از چگونگی ساختار یک پروژه React برای برنامه‌های پیچیده مانند پروژه‌ای که در اینجا می‌سازیم، دارم.

و از آنجا که در حال حاضر جستجوهای زیادی مانند "Doing X in React" یا "Using React with technology X" انجام می‌شود و بحث آن بسیار داغ است، بنابراین فکر کردم که در مورد این موضوع مقاله‌ای بنویسم که به درک عمیق‌تری از React و استفاده گسترده آن در تولید نرم‌افزار بپردازد.

معرفی

به طور خلاصه یک پروژه پیچیده React باید به این شکل باشد. اگرچه من از NextJS در تولید استفاده می‌کنم، اما این ساختار فایل در هر تنظیم React کاملا مفید و کاربردی است.

src
|---adapters
|---contexts
|---components
|---styles
|---pages

توجه: در ساختار فایل فوق، assetها یا فایل‌های استاتیک باید در هر نوع پوشه public برای فریمورک شما قرار بگیرند.

برای پوشه‌های بالا بیایید هر یک از آنها را به ترتیب اولویت مورد بحث قرار دهیم.

1. آداپتورها (Adapters)

آداپتورها اتصالات برنامه شما با دنیای خارج هستند. هر نوع فراخوانی API یا تعامل وب که برای به اشتراک گذاشتن داده‌ها با یک سرور یا سرویس گیرنده خارجی انجام می‌شود، باید در آداپتور اتفاق بیفتد.

به عنوان مثال برخی از داده‌ها همیشه بین همه آداپتورها به اشتراک گذاشته می‌شود. مانند به اشتراک گذاری کوکی‌ها، URL پایه و هدرها در میان آداپتورهای AJAX (XHR) می‌تواند در پوشه xhr مقداردهی اولیه شود و سپس در داخل آداپتورهای دیگر وارد شود تا بیشتر مورد استفاده قرار گیرد.

این ساختار به صورت زیر خواهد بود:

adapters
|---xhr
|---page1Adapter
|---page2Adapter

در مورد axios می‌توانید با استفاده از axios.create یک آداپتور پایه ایجاد کنید و این نمونه اولیه را اکسپورت کرده یا توابع مختلفی را برای get، post، patch و delete ایجاد کنید. که اینگونه به نظر می‌رسد:

// adapters/xhr/index.tsx

import Axios from "axios";

function returnAxiosInstance() {
  return Axios.create(initializers);
}

export function get(url){
  const axios = returnAxiosInstance();
  return axios.get(url);
}

export function post(url, requestData){
  const axios = returnAxiosInstance();
  return axios.post(url, requestData);
}

... and so on ...

بعد از آماده شدن فایل (فایل‌های) پایه، بسته به پیچیده بودن برنامه برای هر صفحه یا هر مجموعه توابع یک فایل آداپتور جداگانه ایجاد کنید. تابع نوشته شده باید به راحتی درک کند که هر فراخوانی API چگونه انجام می‌شود و چه کاری باید انجام دهد.

// adapters/page1Adapter/index.tsx

import { get, post } from "adapters/xhr";
import socket from "socketio";

// well-named functions
export function getData(){
  return get(someUrl);
}

export function setData(requestData){
  return post(someUrl, requestData);
}

... and so on ...

اما این آداپتورها چگونه کاربردی خواهند داشت؟ بیایید در بخش بعدی بررسی کنیم.

2. کامپوننت‌ها (Components)

اگرچه در این بخش باید در مورد context صحبت کنیم، اما می‌خواهم ابتدا در مورد کامپوننت‌ها بحث کنیم. و برای درک این است که چرا context در برنامه‌های پیچیده مورد نیاز (و ضروری) است.

کامپوننت‌ها لازمه شکل گیری برنامه شما هستند. آنها رابط کاربری را برای برنامه به ارمغان می‌آورند و گاهی اوقات می‌توانند منطق تجاری و همچنین هر state را که باید حفظ شود نگه دارند.

در صورتی که یک کامپوننت برای بیان منطق تجاری با رابط کاربری شما بیش از حد پیچیده شود، خوب است که بتوانید آن را به یک فایل جداگانه bl.tsx تقسیم کنید، به طوری که index.tsx ریشه شما تمام توابع و مدیریت کننده‌ها را از آن ایمورت کند.

این ساختار به صورت زیر است:

components
|---page1Components
        |--Component1
        |--Component2
|---page2Component
        |--Component1
               |---index.tsx
               |---bl.tsx

در این ساختار هر صفحه پوشه مربوط به خود را در داخل کامپوننت‌های سازنده به دست می‌آورد، بنابراین به راحتی می‌توان فهمید کدام کامپوننت چه چیزی را تحت تأثیر قرار می‌دهد.

همچنین محدود کردن دامنه یک کامپوننت مهم است. از این رو کامپوننت فقط باید از آداپتورها برای واکشی داده‌ها استفاده کند، دارای یک فایل جداگانه برای منطق تجاری پیچیده باشد و فقط روی قسمت رابط کاربری تمرکز کند.

// components/page1Components/Component1/index.tsx


import businessLogic from "./bl.tsx";

export default function Component2() {
  
  const { state and functions } = businessLogic();

  return {
    // JSX
  }
}

در حالی که فایل BL فقط داده‌ها را وارد می‌کند و آنها را برمی‌گرداند.

// components/page1Components/Component1/bl.tsx

import React, {useState, useEffect} from "react";
import { adapters } from "adapters/path_to_adapter";

export default function Component1Bl(){
  const [state, setState] = useState(initialState);

  useEffect(() => {
    fetchDataFromAdapter().then(updateState);
  }, [])
}

با این حال مشکلی وجود دارد که در همه برنامه‌های پیچیده مشترک است: مدیریت state و نحوه به اشتراک گذاری آنها از طریق کامپوننت‌های از راه دور. به عنوان مثال ساختار فایل زیر را در نظر بگیرید:

components
|---page1Components
        |--Component1
               |---ComponentA
|---page2Component
        |--ComponentB

اگر برخی از stateها در کامپوننت‌های A و B در مثال بالا به اشتراک گذاشته شود، باید از طریق تمام کامپوننت‌های میانی و همچنین به سایر کامپوننت‌هایی که می‌خواهند با state تعامل داشته باشند، منتقل شود.

برای حل این مشکل چندین راه حل وجود دارد که می‌توانند مانندRedux ، Easy-Peasy و React Context مورد استفاده قرار گیرند و هر یک از آنها دارای مزایا و معایب خاص خود هستند. به طور کلی React Context باید به اندازه کافی خوب باشد تا بتواند این مشکل را حل کند. چرا که ما همه فایل‌های مربوطه را در contextها ذخیره می‌کنیم.

3. Contexts

contexts پوشه‌ای است که فقط شامل state است که باید در میان این کامپوننت‌ها به اشتراک گذاشته شود. هر صفحه می‌تواند چندین context تو در تو داشته باشد که هر یک از آنها فقط داده‌ها را به سمت پایین منتقل می‌کند، اما برای جلوگیری از پیچیدگی بهتر است فقط یک فایل context داشته باشید. این ساختار به صورت زیر خواهد بود:

contexts
|---page1Context
        |---index.tsx (Exports consumers, providers, ...)
        |---Context1.tsx (Contains part of the state)
        |---Context2.tsx (Contains part of the state)
|---page2Context
        |---index.tsx (Simple enough to also have state)

در مثال فوق از آنجا که صفحه 1 ممکن است کمی پیچیده‌تر باشد، با انتقال context فرزند به والد، ​​برخی از contextهای تو در تو را مجاز می‌دانیم. با این حال به طور کلی یک فایل index.tsx منفرد حاوی state و اکسپورت‌های فایل‌های مربوطه باید کافی باشد.

من وارد قسمت پیاده سازی کتابخانه‌های مدیریت state React نخواهم شد؛ زیرا هر کدام از آنها مقاله جداگانه‌ای را می‌طلبند و جنبه‌های مثبت و منفی خود را دارند. بنابراین توصیه می‌کنم هر آنچه که تصمیم می‌گیرید برای یادگیری بهترین روش‌های آن استفاده کنید را مرور کنید.

Context مجاز است از آداپتورها برای واکشی داده‌ها و واکنش به اثرات خارجی ایمپورت شود. در مثال React Context، ارائه دهندگان در داخل صفحات وارد می‌شوند تا state را در همه کامپوننت‌ها به اشتراک بگذارند و چیزی مانند useContext در داخل این کامپوننت‌ها استفاده می‌شود تا بتواند از این داده‌ها بهره بگیرد.

به سراغ آخرین قطعه اصلی یعنی صفحات برویم.

4. صفحات (Pages)

من می‌خواهم از تعصب به فریمورک برای این قطعه جلوگیری کنم، اما به طور کلی داشتن یک پوشه خاص برای قرار دادن کامپوننت‌های route-level، یک روش خوب است. Gatsby و NextJS وجود همه مسیرها را در پوشه‌ای به نام pages اعمال می‌کنند. این یک روش مناسب برای تعریف کامپوننت‌های route-level است و تقلید از آن در برنامه تولید شده توسط CRA همچنین باعث خوانایی بهتر کد می‌شود.

یک مکان متمرکز برای مسیرها همچنین به شما کمک می‌کند از قابلیت "رفتن به فایل" بیشتر محیط‌های توسعه با استفاده از ctrl+click روی ایمپورت استفاده کنید تا با سرعت از طریق کد و با وضوح آنچه که به آن تعلق دارد، حرکت کنید. همچنین یک سلسله مراتب مشخص برای تمایز بین صفحات و کامپوننت‌ها تعیین می‌کند، جایی که یک صفحه می‌تواند کامپوننتی را برای نمایش آن ایمپورت کرده و کار دیگری انجام ندهد.

با این حال امکان ایمپورت کردن ارائه دهندگان context در داخل صفحه وجود دارد تا کامپوننت‌های فرزند بتوانند آن را مصرف کنند. برای مثال در NextJS می‌توان چند کد سمت سرور نوشت که می‌تواند داده‌ها را با استفاده از getServerSideProps یا getStaticProps به کامپوننت‌های شما منتقل کند.

5. استایل‌ها (Styles)

سرانجام به استایل‌ها می‌رسیم. اگرچه هدف اصلی من این است که اینها را با استفاده از یک راه حل CSS-in-JS مانند Styled-Components درون رابط کاربری تعبیه کنم، اما داشتن یک مجموعه استایل کلی در یک فایل CSS نیز گاهی مفید است. یک فایل ساده CSS قدیمی در بین پروژه‌ها به اشتراک گذاشته می‌شود و همچنین می‌تواند استایل کامپوننت‌هایی را که اجزای آن نمی‌توانند به آنها دسترسی داشته باشند، تحت تأثیر قرار دهد (به عنوان مثال کامپوننت‌های شخص ثالث).

بنابراین می‌توانید همه این فایل‌های CSS را در داخل پوشه styles ذخیره کنید و از هر کجا که بخواهید، آنها را آزادانه وارد یا لینک دهید.

در صورت تمایل می‌توانید نظرات خود را در بخش زیر با ما در میان بگذارید.

منبع

چه امتیازی به این مقاله می دید؟
خیلی بد
بد
متوسط
خوب
عالی

دیدگاه‌ها و پرسش‌ها

برای ارسال دیدگاه لازم است، ابتدا وارد سایت شوید.

در حال دریافت نظرات از سرور، لطفا منتظر بمانید

در حال دریافت نظرات از سرور، لطفا منتظر بمانید

آفلاین
user-avatar
عرفان حشمتی @heshmati74
مهندس معماری سیستم های کامپیوتری، طراح و توسعه دهنده وب سایت
دنبال کردن

گفتگو‌ برنامه نویسان

بخشی برای حل مشکلات برنامه‌نویسی و مباحث پیرامون آن وارد شو