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

با این حال ، همانطور که React فقط از لایه نمایش یک برنامه مراقبت می کند ، هیچ معماری خاصی را اعمال نمی کند (مانند MVC یا MVVM). این امر می تواند با رشد پروژه React ، سازماندهی پایگاه کد شما را دشوار کند.

در 9 عنصر، یکی از محصولات شاخص ما است PhotoEditorSDK – یک ویرایشگر عکس کاملاً قابل تنظیم که به راحتی در برنامه HTML5 ، iOS یا Android شما ادغام می شود. PhotoEditorSDK یک برنامه React در مقیاس بزرگ است که هدف توسعه دهندگان است. این مورد نیاز به عملکرد بالا ، ساختارهای کوچک دارد و از نظر سبک و به ویژه طرح زمینه بسیار انعطاف پذیر است.

در طول تکرارهای متعدد PhotoEditorSDK ، من و تیم من تعدادی از بهترین روش ها را برای سازماندهی یک برنامه بزرگ React انتخاب کرده ایم که می خواهیم برخی از آنها را در این مقاله با شما به اشتراک بگذاریم.

1. طرح فهرست

در ابتدا ، سبک و کد اجزای سازنده ما از هم جدا شده بودند. همه سبک ها در یک پرونده مشترک CSS زندگی می کردند (ما برای پیش پردازش از SCSS استفاده می کنیم). جز component واقعی (در این مورد FilterSlider) ، از سبک ها جدا شد:

├── components
│   └── FilterSlider
│       ├──  __tests__
│       │   └── FilterSlider-test.js
│       └── FilterSlider.jsx
└── styles
    └── photo-editor-sdk.scss

در طول چندین refactorings ، ما متوجه شدیم که این روش مقیاس خیلی خوبی ندارد. در آینده ، اجزای سازنده ما باید بین چندین پروژه داخلی مانند SDK و یک ابزار متنی تجربی که در حال حاضر در دست توسعه داریم ، به اشتراک گذاشته شود. بنابراین ما به یک طرح فایل م componentلفه محور تبدیل شدیم:

components
    └── FilterSlider
        ├── __tests__
        │   └── FilterSlider-test.js
        ├── FilterSlider.jsx
        └── FilterSlider.scss

ایده این بود که همه کدهایی که به یک م componentلفه تعلق دارند (مانند JavaScript ، CSS ، دارایی ها ، آزمایش ها) در یک پوشه قرار دارند. این کار استخراج کد را به یک ماژول npm یا در صورت عجله خیلی ساده می کند پوشه را با یک پروژه دیگر به اشتراک بگذارید بسیار آسان است.

وارد کردن ملفه ها

یکی از اشکالاتی که در این ساختار دایرکتوری وجود دارد این است که برای وارد کردن اجزای سازنده لازم است مسیر کاملاً واجد شرایط را وارد کنید:

import FilterSlider from 'components/FilterSlider/FilterSlider'

اما آنچه واقعاً دوست داریم بنویسیم این است:

import FilterSlider from 'components/FilterSlider'

برای حل این مشکل ، می توانید یک ایجاد کنید index.js و بلافاصله صادرات پیش فرض:

export { default } from './FilterSlider';

راه حل دیگر کمی گسترده تر است ، اما از مکانیزم حل استاندارد Node.js استفاده می کند و آن را مستحکم و مقاوم در برابر آینده می کند. تمام کاری که ما انجام می دهیم افزودن a است package.json پرونده به ساختار پرونده:

components
    └── FilterSlider
        ├── __tests__
        │   └── FilterSlider-test.js
        ├── FilterSlider.jsx
        ├── FilterSlider.scss
        └── package.json

و درون package.json، ما از اموال اصلی برای تنظیم نقطه ورود به م toلفه ، مانند این موارد:

{
  "main": "FilterSlider.jsx"
}

با استفاده از آن ، می توانیم م componentلفه ای مانند این را وارد کنیم:

import FilterSlider from 'components/FilterSlider'

2. CSS در JavaScript

استایل دهی و به ویژه مضمون سازی همیشه کمی مشکل ساز شده است. همانطور که در بالا ذکر شد ، در اولین تکرار برنامه ، ما یک پرونده بزرگ CSS (SCSS) داشتیم که تمام کلاس های ما در آن زندگی می کردند. برای جلوگیری از برخورد نام ها ، ما از پیشوند جهانی استفاده کردیم و برای ساختن نام های قوانین CSS از قراردادهای BEM پیروی کردیم. وقتی برنامه ما رشد کرد ، این روش مقیاس خیلی خوبی نداشت ، بنابراین ما به دنبال یک جایگزین بودیم. ابتدا ماژول های CSS را ارزیابی کردیم ، اما در آن زمان برخی از مشکلات عملکردی داشتند. همچنین ، استخراج CSS از طریق webpack استخراج پلاگین متن به این خوبی کار نکرد (اگرچه در زمان نوشتن مقاله باید درست باشد). علاوه بر این ، این روش وابستگی زیادی به بسته وب ایجاد کرده و آزمایش را بسیار دشوار می کند.

در مرحله بعد ، ما برخی دیگر از راه حل های CSS-in-JS را که اخیراً در صحنه آمده اند ارزیابی کردیم:

انتخاب یکی از این کتابخانه ها به میزان استفاده شما بستگی دارد:

  • آیا برای تف کردن یک فایل CSS کامپایل شده برای تولید به کتابخانه نیاز دارید؟ EmotionJS و Linaria می توانند این کار را انجام دهند! Linaria حتی به زمان اجرا هم نیاز ندارد. این برنامه از طریق متغیرهای CSS به برنامه های CSS نگاشت می کند که پشتیبانی از IE11 را رد می کند – اما به هر حال چه کسی به IE11 نیاز دارد؟
  • آیا لازم است روی سرور اجرا شود؟ این برای نسخه های اخیر همه کتابخانه ها مشکلی ندارد!

برای ساختار دایرکتوری ، ما می خواهیم همه سبک ها را در a قرار دهیم styles.js:

export const Section = styled.section`
  padding: 4em;
  background: papayawhip;
`;

به این ترتیب ، افراد جلویی خالص همچنین قادر به ویرایش برخی از سبک ها هستند بدون اینکه با React برخورد کنند ، اما آنها باید حداقل JavaScript و نحوه نقشه برداری از ویژگی ها را به ویژگی های CSS بیاموزند:

components
    └── FilterSlider
        ├── __tests__
        │   └── FilterSlider-test.js
        ├── styles.js
        ├── FilterSlider.jsx
        └── index.js

پاک کردن پرونده جز component اصلی خود از HTML یک روش خوب است.

تلاش برای مسئولیت منفرد اجزای واکنش دهنده

وقتی م componentsلفه های UI بسیار انتزاعی ایجاد می کنید ، جدا کردن نگرانی ها گاهی اوقات دشوار است. در بعضی از مواقع ، مولفه شما به یک منطق دامنه خاص از مدل شما احتیاج دارد و سپس اوضاع بهم ریخته است. در بخش های بعدی ، ما می خواهیم روش های خاصی را برای خشک کردن اجزای خود به شما نشان دهیم. تکنیک های زیر از لحاظ عملکرد با یکدیگر همپوشانی دارند و انتخاب یک روش مناسب برای معماری شما بیشتر از اینکه مبتنی بر واقعیت های سخت باشد اولویت در سبک است. اما بگذارید ابتدا موارد استفاده را معرفی کنم:

  • ما مجبور شدیم مکانیزمی برای مقابله با م componentsلفه هایی که از کاربر ورود به سیستم آگاه هستند ، معرفی کنیم.
  • ما باید یک جدول با چندین تاشو ارائه دهیم <tbody> عناصر.
  • ما مجبور بودیم اجزای مختلفی را بسته به حالت های مختلف نمایش دهیم.

در بخش زیر ، راه حل های مختلفی برای مشکلات شرح داده شده در بالا نشان خواهم داد.

3. قلاب های سفارشی

بعضی اوقات باید اطمینان حاصل کنید که یک م componentلفه React تنها زمانی نمایش داده می شود که کاربر وارد برنامه شما شده باشد. در ابتدا ، هنگام رندر کردن ، مقداری بررسی عقلانی انجام می دهید تا زمانی که متوجه شوید که زیاد تکرار می کنید. در مأموریت خود برای خشک کردن آن کد ، دیر یا زود باید قلاب های سفارشی بنویسید. نترس: اینقدر سخت نیست. به مثال زیر نگاهی بیندازید:

import { useEffect } from 'react';
import { useAuth } from './use-auth-from-context-or-state-management.js';
import { useHistory } from 'react-router-dom';

function useRequireAuth(redirectUrl = "/signup") {
  const auth = useAuth();
  const history = useHistory();

  
  
  useEffect(() => {
    if (auth.user === false) {
      history.push(redirectUrl);
    }
  }, [auth, history]);
  return auth;
}

useRequireAuth قلاب بررسی می کند که آیا یک کاربر وارد سیستم شده است یا در غیر این صورت به صفحه دیگری هدایت می شود. منطق موجود در useAuth قلاب را می توان از طریق زمینه یا سیستم مدیریت دولتی مانند MobX یا Redux تهیه کرد.

4. عملکرد در دوران کودکی

ایجاد یک ردیف جدول جمع شونده کار خیلی ساده ای نیست. چگونه دکمه سقوط را ارائه می دهید؟ چگونه میزها بهم نریزد چگونه نمایش دهیم؟ من می دانم که با JSX 2.0 کار بسیار ساده تر شده است ، زیرا شما می توانید به جای یک برچسب آرایه ای را برگردانید ، اما این مثال را توضیح می دهم ، زیرا این یک مورد استفاده خوب برای در کودکی فعالیت می کنند الگو. جدول زیر را تصور کنید:

export default function Table({ children }) {
  return (
    <table>
      <thead>
        <tr>
          <th>Just a table</th>
        </tr>
      </thead>
      {children}
    </table>
  );
}

و بدنه میز تاشو:

import { useState } from 'react';

export default function CollapsibleTableBody({ children }) {
  const [collapsed, setCollapsed] = useState(false);

  const toggleCollapse = () => {
    setCollapsed(!collapsed);
  };

  return (
    <tbody>
      {children(collapsed, toggleCollapse)}
    </tbody>
  );
}

شما می توانید از این جز component به روش زیر استفاده کنید:

<Table>
  <CollapsibleTableBody>
    {(collapsed, toggleCollapse) => {
      if (collapsed) {
        return (
          <tr>
            <td>
              <button onClick={toggleCollapse}>Open</button>
            </td>
          </tr>
        );
      } else {
        return (
          <tr>
            <td>
              <button onClick={toggleCollapse}>Closed</button>
            </td>
            <td>CollapsedContent</td>
          </tr>
        );
      }
    }}
  </CollapsibleTableBody>
</Table>

شما به سادگی یک عملکرد را به عنوان کودکان منتقل می کنید ، که در اجزای والد فراخوانی می شود. همچنین ممکن است دیده باشید که از این تکنیک به عنوان “render callback” یا در موارد خاص به عنوان “prop render” یاد می شود.

5. رندرها را رندر کنید

اصطلاح “ارائه رندر” توسط مایکل جکسون ، که این پیشنهاد را مطرح کرد ، ابداع شد الگوی م componentلفه مرتبه بالاتر می تواند 100٪ از زمان را با یک م componentلفه معمولی با “رندر پروپ” جایگزین کند. ایده اصلی در اینجا این است که همه اجزای React توابع هستند و عملکردها می توانند به عنوان غرفه منتقل شوند. پس چرا اجزای React را از طریق لوازم جانبی رد نمی کنیم ؟! آسان!

کد زیر سعی می کند نحوه واکشی داده ها از API را تعمیم دهد. (لطفاً توجه داشته باشید که این مثال فقط برای نمایش است. در پروژه های واقعی ، شما حتی می توانید این منطق واکشی را در a خلاصه کنید useFetch قلاب برای جدا کردن آن حتی از UI.) کد اینجاست:

import { useEffect, useState } from "react";

export default function Fetch({ render, url }) {

  const [state, setState] = useState({
    data: {},
    isLoading: false
  });

  useEffect(() => {
    setState({ data: {}, isLoading: true });

    const _fetch = async () => {
      const res = await fetch(url);
      const json = await res.json();

      setState({
        data: json,
        isLoading: false,
      });
    }

    _fetch();
  }, https%3A%2F%2Feditor.sitepoint.com);

  return render(state);
}

همانطور که می بینید ، ملکی به نام وجود دارد render، که تابعی است که در طی فرآیند رندر فراخوانی می شود. تابعی که درون آن فراخوانی می شود ، حالت کامل را به عنوان پارامتر دریافت می کند و JSX را برمی گرداند. اکنون به موارد زیر توجه کنید:

<Fetch
  url="https://api.github.com/users/imgly/repos"
  render={({ data, isLoading }) => (
    <div>
      <h2>img.ly repos</h2>
      {isLoading && <h2>Loading...</h2>}

      <ul>
        {data.length > 0 && data.map(repo => (
          <li key={repo.id}>
            {repo.full_name}
          </li>
        ))}
      </ul>
    </div>
  )} />

همانطور که می بینید ، data و isLoading ساختار پارامترها از جسم حالت تجزیه می شود و می توان از آنها برای هدایت پاسخ JSX استفاده کرد. در این حالت ، تا زمانی که وعده تحقق نیافته است ، یک عنوان “در حال بارگیری” نشان داده می شود. این به شما بستگی دارد که کدام قسمت از ایالت را به پایه رندر منتقل می کنید و چگونه از آنها در رابط کاربری خود استفاده می کنید. به طور کلی ، این یک مکانیسم بسیار قدرتمند برای استخراج رفتار مشترک رابط کاربر است. در کودکی فعالیت می کنند الگویی که در بالا توضیح داده شد در واقع همان الگویی است که خاصیت در آن وجود دارد children.

Protip: از آنجا که ارائه پروپ الگوی تعمیم است در کودکی فعالیت می کنند الگوی ، هیچ چیز مانع شما نمی شود که چندین رندر را روی یک م componentلفه داشته باشید. به عنوان مثال ، الف Table کامپوننت می تواند یک پایه رندر برای هدر و سپس دیگری برای بدن بدست آورد.

بیایید بحث را ادامه دهیم

امیدوارم از این پست درباره الگوهای React معماری لذت برده باشید. اگر چیزی را در این مقاله از دست داده اید (مطمئناً بهترین روش های بیشتر وجود دارد) ، یا اگر فقط می خواهید با شما تماس بگیریم ، لطفا مرا بخوانید توییتر.

PS: 9element ها همیشه به دنبال توسعه دهندگان با استعداد هستند ، بنابراین در صورت تمایل درخواست دهید.