Design patternها یا الگوهای طراحی، در اصل روش‌های اثبات شده‌ای برای حل مشکلات رایج در توسعه نرم‌افزار هستند. در این مقاله قصد داریم تا با انواع design pattern در React آشنا شویم.

بررسی انواع design pattern در کامپوننت‌های React

در این بخش قصد داریم تا انواع design pattern در کامپوننت‌های React را بررسی کنیم. این فهرست شامل برخی از محبوب‌ترین design patternهای React است که برای cross-cutting concernها، به اشتراک‌گذاری داده‌های سراسری (بدون prop drilling)، جداسازی concernهایی مانند منطق پیچیده state از سایر قسمت‌های کامپوننت و موارد دیگر کارآمد هستند.

الگوی higher-order component

higher-order component یا الگوی HOC، یک الگوی React پیشرفته است که برای استفاده مجدد از منطق کامپوننت در برنامه‌ای که داریم، استفاده می‌شود. الگوی HOC برای cross-cutting concernها مفید است؛ ویژگی‌هایی که به اشتراک منطق کامپوننت در برنامه ما نیاز دارند، مانند authorization، logging و بازیابی داده‌ها.

HOCها بخشی از هسته React API نیستند، اما از ماهیت ترکیبی کامپوننت‌های فانکشنال React که توابع جاوااسکریپت هستند، ناشی می‌شوند.

یک higher-order component در React شبیه به یک higher-order function در جاوااسکریپت است. آن‌ها توابع pure با side effect صفر هستند. همچنین مانند higher-order function در جاوااسکریپت، HOCها مانند یک تابع decorator عمل می‌کنند.

در React، یک higher-order component به شکل زیر ساخته می‌شود:

import React, {Component} from 'react';

const higherOrderComponent = (DecoratedComponent) => {
  class HOC extends Component {
    render() {
      return <DecoratedComponent />;
    }
  }
  return HOC;
};

الگوی Render props

در بخش قبل، دیدیم که چگونه الگوی HOC می‌تواند props مفیدی ایجاد کرده و منطق مشترک را راحت‌تر کند. در این بخش، راه دیگری را بررسی خواهیم کرد تا بتوانیم با اجرای الگوی render props، از کامپوننت‌های React به صورت مجدد استفاده نماییم.

تصور کنید که ما یک کامپوننت Paragraph داریم که هر چیزی را که به آن ارسال می‌کنیم، رندر می‌کند. هدف اصلی کامپوننت رندر کردن مقداری است که به آن ارسال می‌نماییم. برای رسیدن به این هدف می‌توانیم کد زیر را مورد استفاده قرار دهیم:

<Paragraph render={() => <p>This is a rendered prop.</p>}>

برای بدست آوردن مقدار prop render، می‌توانیم آن را به صورت زیر فراخوانی کنیم:

const Paragraph = props => props.render()

در حالت ایده‌آل، این بدان معناست که Paragraph کامپوننتی است که یک prop render دریافت کرده و یک کامپوننت JSX را return می‌کند.

اکنون سناریو دیگری را بررسی می‌کنیم. تصور کنید که یک TextInput داریم که می‌خواهیم مقدار آن را با دو کامپوننت به اشتراک بگذاریم. ما می‌توانیم از render propها برای مدیریت آن استفاده کنیم.

سوالی که مطرح می‌شود این است که آیا قرار نیست state مربوط به TextInput در کامپوننت parent باشد؟ پاسخ این سوال بله است، اما در برنامه‌های بزرگ‌تر، انجام لیفت state اغلب دشوار می‌باشد:

import { useState } from "react";
const TextInput = (props) => {
  const [value, setValue] = useState("");
  return (
    <>
      <input
        type="text"
        value={value}
        onChange={(e) => setValue(e.target.value)}
        placeholder="Type text..."
      />
      {props.children(value)}
    </>
  );
};
export default TextInput;

کامپوننت input، مانند هر کامپوننت دیگر React، دارای children prop است، بنابراین در اینجا ما از آن استفاده می‌کنیم تا به هر دو کامپوننت اجازه دسترسی به مقدار آن را بدهیم. می‌توانیم این دو کامپوننت را به صورت زیر ایجاد کنیم:

const Comp1 = ({ value }) => <p>{value}</p>;
const Comp2 = ({ value }) => <p>{value}</p>;

سپس، می‌توانیم از آن‌ها به صورت زیر استفاده کنیم:

<TextInput>
         {(value) => (
           <>
             <Comp1 value={value} />
             <Comp2 value={value} />
           </>
         )}
       </TextInput>

در ادامه، Comp1 و Comp2 همان مقدار TextInput را حفظ خواهند کرد.

الگوی State reducer

پس از انتشار هوک‌های React، محبوبیت الگوی state reducer بین توسعه‌دهندگان بسیار افزایش پیدا کرد. state reducer به یک الگوی مرسوم برای codebaseهای مختلف در تولید تبدیل شده است، به خصوص به دلیل انتزاع آن از گردش کار Redux با استفاده از هوک useReducer.

در این بخش، نحوه استفاده از الگوی state reducer برای ساخت برنامه‌های React با قابلیت استفاده مجدد را بررسی خواهیم کرد. ساده‌ترین راه برای از بین بردن استفاده از الگوی state reducer، ایجاد یک هوک کمکی سفارشی است. بنابراین، یک هوک useToggle برای تغییر state کامپوننت‌ها در برنامه خود ایجاد می‌کنیم.

برای شروع، یک تایپ برای reducer خود می‌سازیم:

const toggleActionTypes = {
  toggle: "TOGGLE",
};

سپس، toggleReducer را می‌سازیم:

const toggleReducer = (state, action) => {
  switch (action.type) {
    case toggleActionTypes.toggle:
      return { on: !state.on };
    default:
      throw new Error(`Undefined type: ${action.type}`);
  }
};

پس از آن، هوک useToggle را می‌سازیم:

const useToggle = ({ reducer = toggleReducer } = {}) => {
  const [{ on }, dispatch] = useReducer(reducer, { on: false });
  const toggle = () => dispatch({ type: toggleActionTypes.toggle });
  return [on, toggle];
};

سپس، می‌توانیم از آن در یک کامپوننت مانند زیر استفاده کنیم:

const Toggle = () => {
  const [on, toggle] = useToggle({
    reducer(currentState, action) {
      const updates = toggleReducer(currentState, action);
      return updates;
    },
  });
  return (
    <div>
      <button onClick={toggle}>{on ? "Off" : "On"}</button>
    </div>
  );
};
export default Toggle;

با کلیک بر روی دکمه، state On و Off آن تغییر می‌کند.

الگوی provider

یکی دیگر از انواع design pattern در React، الگوی provider است که برای به اشتراک گذاشتن داده‌های سراسری در چندین کامپوننت در درخت کامپوننت React استفاده می‌شود. این الگو شامل یک کامپوننت Provider است که داده‌های سراسری را در خود نگه می‌دارد و این داده‌ها را در درخت کامپوننت برنامه، با استفاده از کامپوننت Consumer یا هوک سفارشی به اشتراک می‌گذارد.

باید به این نکته توجه داشته باشیم که الگوی provider منحصر به React نیست. کتابخانه‌هایی مانند Redux و MobX نیز الگوی provider را پیاده‌سازی می‌کنند.

کد زیر تنظیمات الگوی provider در Redux را نشان می‌دهد:

import React from 'react'
import ReactDOM from 'react-dom'

import { Provider } from 'react-redux'
import store from './store'

import App from './App'

const rootElement = document.getElementById('root')
ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  rootElement
)

در React، الگوی provider در React Context API پیاده‌سازی می‌شود.

به طور پیش‌فرض، React از یک جریان داده رو به پایین یک‌طرفه از یک کامپوننت parent به کامپوننت‌های child پشتیبانی می‌کند. در نتیجه، برای ارسال داده به یک کامپوننت child که در اعماق درخت کامپوننت قرار دارد، باید به صراحت از هر سطح از درخت کامپوننت عبور کنیم. این فرآیند prop drilling نام دارد.

React Context API از الگوی provider برای حل این مشکل استفاده می‌کند. بنابراین، ما را قادر می‌سازد تا داده‌ها را در درخت کامپوننت‌های React بدون استفاده از prop drilling به اشتراک بگذاریم.

برای استفاده از Context API، ابتدا باید با استفاده از React.createContext یک آبجکت context ایجاد کنیم. آبجکت context با یک کامپوننت Provider ارائه می‌شود که یک مقدار را می‌پذیرد: داده‌های سراسری. همچنین یک کامپوننت Consumer دارد که برای تغییرات context در کامپوننت Provider مشترک می‌شود و سپس آخرین context value props را برای childها فراهم می‌کند.

در ادامه یک مثال از استفاده معمولی از React Context API را بررسی می‌کنیم:

import { createContext } from "react";
const LanguageContext = createContext({});
function GreetUser() {
  return (
    <LanguageContext.Consumer>
      {({ lang }) => (
        <p>Hello, Kindly select your language. Default is {lang}</p>
      )}
    </LanguageContext.Consumer>
  );
}
export default function App() {
  return (
    <LanguageContext.Provider value={{ lang: "EN-US" }}>
      <h1>Welcome</h1>
      <GreetUser />
    </LanguageContext.Provider>
  );
}

باید به این نکته توجه داشته باشیم که React همچن یک API مستقیم‌تر، یعنی هوک useContext را برای به اشتراک‌گذاری مقدار context فعلی به جای استفاده از کامپوننت Consumer ارائه می‌دهد.

الگوی Compound components

Compound Compounds یک الگوی کانتینر React پیشرفته است که روشی ساده و کارآمد را برای چندین کامپوننتی که باهم کار می‌کنند، برای اشتراک‌گذاریstateها و مدیریت منطق ارائه می‌دهد.

الگوی Compound Compounds یک API رسا و منعطف برای ارتباط بین یک کامپوننت parent و کامپوننت‌های child را فراهم می‌کند. همچنین، این الگو، یک کامپوننت parent را قادر می‌سازد تا به طور ضمنی با کامپوننت‌های child خود تعامل داشته باشد و state را با آن‌ها به اشتراک بگذارد، که آن را برای ایجاد یک declarative UI مناسب می‌کند.

دو مثال خوب در این مورد عبارتند از المنت‌های HTML select و options. هر دو select و options به صورت پشت سر هم کار می‌کنند تا یک فیلد فرم dropdown را ارائه دهند. به عنوان مثال:

<select>
  <option value="javaScript">JavaScript</option>
  <option value="python">Python</option>
  <option value="java">Java</option>
</select>

در کد بالا، المنت select state خود را به طور ضمنی مدیریت کرده و با المنت‌های options به اشتراک می‌گذارد. در نتیجه، اگرچه هیچ تعریف صریحی از state وجود ندارد، اما المنت select می‌داند که کاربر چه گزینه‌ای را انتخاب می‌کند.

الگوی compound component در ساخت کامپوننت‌های پیچیده React مانند switch، tab switcher، accordion، dropdown، tag list و موارد دیگر مفید است. می‌توانیم این الگو را با استفاده از Context API یا تابع React.cloneElement پیاده‌سازی کنیم.

در این قسمت با ساخت کامپوننت Accordion با استفاده از الگوی compound component بیشتر آشنا می‌شویم. ما الگوی compound component خود را با Context API پیاده‌سازی خواهیم کرد.

مراحل پیاده‌سازی مثال مربوط به الگوی compound component

ابتدا یک برنامه جدید React می‌سازیم:

yarn create react-app Accordion

cd Accordion

yarn start

این کار را می‌توانیم با استفاده از vite نیز انجام دهیم.

سپس، dependencyها را نصب می‌کنیم:

yarn add styled-components

پس از آن، داده‌های خود را به برنامه اضافه می‌کنیم. در پوشه src، یک پوشه data ایجاد کرده و کد زیر را به آن می‌افزاییم:

const faqData = [

{

id: 1,

header: "What is LogRocket?",

body:

"Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book."

},

{

id: 2,

header: "LogRocket pricing?",

body:

"Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book."

},

{

id: 3,

header: "Where can I Find the Doc?",

body:

"Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book."

},

{

id: 4,

header: "How do I cancel my subscription?",

body:

"Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book."

},

{

id: 5,

header: "What are LogRocket features?",

body:

"Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book."

}

];

export default faqData;

در مرحله بعد، کامپوننت‌ها را می‌سازیم و استایل‌ها را اضافه می‌کنیم. داخل پوشه src، یک پوشه components، یک فایل Accordion.js و یک فایل Accordion.styles.js ایجاد می‌کنیم. اکنون استایل‌های خود را با استفاده از styled-components می‌سازیم. کد زیر را به فایل Accordion.styles.js اضافه می‌کنیم:

import styled from "styled-components";

export const Container = styled.div
display: flex;
background: #6867ac;
border-bottom: 8px solid #ffbcd1;
font-family: "Inter", sans-serif;
; export const Wrapper = styled.div
margin-bottom: 40px;
; export const Inner = styled.div
display: flex;
padding: 70px 45px;
flex-direction: column;
max-width: 815px;
margin: auto;
; export const Title = styled.h1
font-size: 33px;
line-height: 1.1;
margin-top: 0;
margin-bottom: 8px;
color: white;
text-align: center;
; export const Item = styled.div
color: white;
margin: auto;
margin-bottom: 10px;
max-width: 728px;
width: 100%;
&:first-of-type {
margin-top: 3em;
}
&:last-of-type {
margin-bottom: 0;
}
; export const Header = styled.div
display: flex;
flex-direction: space-between;
cursor: pointer;
border: 1px solid #ce7bb0;
border-radius: 8px;
box-shadow: #ce7bb0;
margin-bottom: 1px;
font-size: 22px;
font-weight: normal;
background: #ce7bb0;
padding: 0.8em 1.2em 0.8em 1.2em;
user-select: none;
align-items: center;
; export const Body = styled.div
font-size: 18px;
font-weight: normal;
line-height: normal;
background: #ce7bb0;
margin: 0.5rem;
border-radius: 8px;
box-shadow: #ce7bb0;
white-space: pre-wrap;
user-select: none;
overflow: hidden;
&.open {
max-height: 0;
overflow: hidden;
}
span {
display: block;
padding: 0.8em 2.2em 0.8em 1.2em;
}
;

سپس در مرحله بعد، کد زیر را به فایل Accordion.js اضافه می‌کنیم:

import React, { useState, useContext, createContext } from "react";
import { Container, Inner, Item, Body, Wrapper, Title, Header
} from "./Accordion.styles";

const ToggleContext = createContext();
export default function Accordion({ children, ...restProps }) {
  return (
    <Container {...restProps}>
      <Inner>{children}</Inner>
    </Container>
  );
}

Accordion.Title = function AccordionTitle({ children, ...restProps }) {
  return <Title {...restProps}>{children}</Title>;
};

Accordion.Wrapper = function AccordionWrapper({ children, ...restProps }) {
  return <Wrapper {...restProps}>{children}</Wrapper>;
};

Accordion.Item = function AccordionItem({ children, ...restProps }) {
  const [toggleShow, setToggleShow] = useState(true);
  const toggleIsShown = (isShown) => setToggleShow(!isShown);
  return (
    <ToggleContext.Provider value={{ toggleShow, toggleIsShown }}>
      <Item {...restProps}>{children}</Item>
    </ToggleContext.Provider>
  );
};

Accordion.ItemHeader = function AccordionHeader({ children, ...restProps }) {
  const { toggleShow, toggleIsShown } = useContext(ToggleContext);
  return (
    <Header onClick={() => toggleIsShown(toggleShow)} {...restProps}>
      {children}
    </Header>
  );
};

Accordion.Body = function AccordionBody({ children, ...restProps }) {
  const { toggleShow } = useContext(ToggleContext);
  return (
    <Body className={toggleShow ? "open" : ""} {...restProps}>
      <span>{children}</span>
    </Body>
  );
};

در کد بالا، آبجکت context ToggleContext وظیفه دارد state toggleShow را نگه دارد و این state را از طریق ToggleContext.Provider برای همه Accordion children فراهم می‌کند.

همچنین، ما کامپوننت‌های جدیدی را با استفاده از  JSX dot notation ایجاد و به کامپوننت Accordion متصل کردیم.

در نهایت App.js را با کد زیر به‌روزرسانی می‌کنیم:

import React from "react";
import Accordion from "./components/Accordion";
import faqData from "./data";
export default function App() {
  return (
    <Accordion>
      <Accordion.Title>LogRocket FAQ</Accordion.Title>
      <Accordion.Wrapper>
        {faqData.map((item) => (
          <Accordion.Item key={item.id}
            <Accordion.ItemHeader>{item.header}</Accordion.ItemHeader>
            <Accordion.Body>{item.body}</Accordion.Body>
          </Accordion.Item>
        ))}
      </Accordion.Wrapper>
    </Accordion>
  );
}

نتیجه کدی که نوشتیم در این لینک قابل مشاهده می‌باشد.

الگوهای کامپوننت presentational و container

هر دو الگوی presentational و container از الگوهای مفید در design pattern های React هستند، زیرا به ما کمک می‌کنند تا concernها، به عنوان مثال منطق پیچیده state، را از سایر جنبه‌های یک کامپوننت جدا کنیم.

با این حال، از آنجایی که هوک‌های React ما را قادر می‌سازند تا concernها را با تقسیم دلخواه از هم جدا کنیم، بنابراین استفاده از الگوی Hooks به جای الگوی کامپوننت‌های presentational و container توصیه می‌شود. اما بسته به مورد استفاده ما، استفاده از این الگوها ممکن است همچنان مفید باشند.

هدف این الگوها جداسازی concernها و ساختار کدهای ما به گونه‌ای است که به راحتی قابل درک باشد.

کامپوننت‌های presentational، کامپوننت‌های فانکشنال stateless هستند که فقط به ارائه داده‌ها به view مربوط می‌شوند، و هیچ وابستگی به قسمت‌های دیگر برنامه ندارند. در برخی موارد که نیاز به نگه داشتن یک state مربوط به view دارند، می‌توانیم آن‌ها را با کلاس کامپوننت‌های React پیاده‌سازی کنیم.

در ادامه مثالی از یک کامپوننت presentational داریم. این کامپوننت یک لیست را برای ما ارائه می‌دهد:

const usersList = ({users}) => {
  return (
  <ul>
      {users.map((user) => (
      <li key={user.id}>
          {user.username}
      </li>
      ))}
  </ul>
  );
};

کامپوننت‌های container، کلاس کامپوننت‌های مفیدی هستند که state داخلی و lifecycle آن‌ها را پیگیری می‌کنند. آن‌ها همچنین حاوی کامپوننت‌های presentational و منطق دریافت داده‌ها نیز هستند.

نمونه‌ای از کامپوننت‌های container را در ادامه داریم:

class Users extends React.Component {
  state = {
    users: []
  };

  componentDidMount() {
    this.fetchUsers();
  }

  render() {
    return (); // ... jsx code with presentation component
  }
}

الگوی Hooks

APIهای مربوط به هوک‌های React در نسخه React 16.8 معرفی شدند و نحوه ساخت کامپوننت‌های React را متحول کردند.

هوک‌های React راهی ساده و مستقیم را در اختیار کامپوننت‌های فانکشنال React قرار می‌دهند تا به ویژگی‌های رایج React مانند props، state، context، refs و lifecycle دسترسی داشته باشند.

اگرچه الگوهایی مانند الگوهای کامپوننت‌های presentational و container ما را قادر می‌سازند تا concernها را از هم جدا کنیم، اما containerها اغلب منجر به ایجاد کامپوننت‌های غول‌پیکر می‌شوند. کامپوننت‌های با منطق بزرگ، در چندین متد lifecycle تقسیم می‌گردند. خواندن و نگه‌داری این کامپوننت‌های غول‌پیکر دشوار است.

علاوه بر این، با توجه به این که containerها کلاس هستند، به راحتی ترکیب نمی‌شوند. همچنین، هنگام کار با containerها، ما با مشکلات دیگر مرتبط با کلاس مانند autobinding و کار کردن با کلمه کلیدی this روبه‌رو هستیم.

با سوپرشارژ کردن کامپوننت‌های فانکشنال با قابلیت ردیابی state داخلی، دسترسی به lifecycle کامپوننت‌ها و سایر ویژگی‌های مرتبط با کلاس، الگوهای Hooks مشکلات مربوط به کلاس را که در بالا ذکر شد حل می‌کنند.

به عنوان توابع pure جاوااسکریپت، کامپوننت‌های فانکشنال React قابل ترکیب هستند و دردسر کار با کلمه کلیدی this را از بین می‌برند. به عنوان مثال:

import React, { Component } from "react";
class Profile extends Component {
  constructor(props) {
    super(props);
    this.state = {
      loading: false,
      user: {}
    };
  }
  componentDidMount() {
    this.subscribeToOnlineStatus(this.props.id);
    this.updateProfile(this.props.id);
  }
  componentDidUpdate(prevProps) {
    // compariation hell.
    if (prevProps.id !== this.props.id) {
      this.updateProfile(this.props.id);
    }
  }
  componentWillUnmount() {
    this.unSubscribeToOnlineStatus(this.props.id);
  }
  subscribeToOnlineStatus() {
    // subscribe logic
  }
  unSubscribeToOnlineStatus() {
    // unscubscribe logic
  }
  fetchUser(id) {
    // fetch users logic here
  }
  async updateProfile(id) {
    this.setState({ loading: true });
    // fetch users data
    await this.fetchUser(id);
    this.setState({ loading: false });
  }
  render() {
     // ... some jsx
  }
}
export default Profile;

از container بالا می‌توانیم به سه چالش اشاره کنیم:

Hooks این مشکلات را با ارائه یک API تمیزتر و ساده‎‌تر حل می‌کند. اکنون می‌توانیم کامپوننت Profile خود را مطابق شکل زیر تغییر دهیم:

import React, { useState, useEffect } from "react";
function Profile({ id }) {
  const [loading, setLoading] = useState(false);
  const [user, setUser] = useState({});

  // Similar to componentDidMount and componentDidUpdate:
  useEffect(() => {
    updateProfile(id);
    subscribeToOnlineStatus(id);
    return () => {
      unSubscribeToOnlineStatus(id);
    };
  }, [id]);

  const subscribeToOnlineStatus = () => {
    // subscribe logic
  };

  const unSubscribeToOnlineStatus = () => {
    // unsubscribe logic
  };

  const fetchUser = (id) => {
    // fetch user logic here
  };

  const updateProfile = async (id) => {
    setLoading(true);
    // fetch user data
    await fetchUser(id);
    setLoading(false);
  };

  return; // ... jsx logic
}
export default Profile;

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

الگوی Prop combination

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

برخی از مزایای این الگو شامل کاهش کد boilerplate، بهبود خوانایی کد و قابلیت نگه‌داری آن است.

در ادامه بررسی می‌کنیم که چگونه می‌توانیم این الگو را پیاده‌سازی کنیم:

import React from 'react';

function Button({ style, onClick, children }) {
  const buttonStyle = {
    backgroundColor: style.color || 'blue',
    fontSize: style.size || '16px',
    fontWeight: style.weight || 'bold'
  };

  return (
    <button style={buttonStyle} onClick={onClick}>
      {children}
    </button>
  );
}

export default Button;

در بلاک کد بالا، یک کامپوننت Button داریم. اکنون، می‌توانیم برای size، color و font weight دکمه، propهای جداگانه داشته باشیم. اما با استفاده از الگوی props combination می‌توانیم این props را در یک آبجکت واحد به نام style ترکیب کنیم.

سپس می‌توانیم آبجکت prop style واحد خود را به هر کامپوننتی که از کامپوننت Button استفاده می‌کند، ارسال کنیم و به آن اجازه دهیم تا کامپوننت Button را سفارشی کند، همانطور که در مثال زیر داریم:

import React from 'react';
import Button from './Button';

function App() {
  return (
    <div>
      <Button style={{ color: 'red', weight: 'light', size: '20px' }} onClick={() => console.log('Button clicked')}>
        Submit
      </Button>
    </div>
  );
}

export default App;

الگوی Controlled component

یکی دیگر از انواع design pattern در React، الگوی controlled component است. این الگو با ایجاد یک جریان داده یک طرفه واضح بین input فرم و state آن، به مدیریت inputهای فرم کمک می‌کند. input فرم state خود را از طریق props دریافت می‌کند و سپس از یک callback مانند onChange استفاده می‌کند تا در صورت بروز هرگونه تغییری، آن‌ها را به state اطلاع‌رسانی نماید.

این الگو تضمین می‌کند که کامپوننت‌ها بر خلاف کامپوننت‌های uncontrolled، به طور قابل پیش‌بینی و قابل اطمینان عمل می‌کنند. نمونه‌ای از این الگو را در ادامه مشاهده می‌نماییم:

import React, { useState } from "react";

function MyForm() {
  const [inputValue, setInputValue] = useState("");

  const handleChange = (event) => {
    setInputValue(event.target.value);
  };

  return (
    <form>
      <input type="text" value={inputValue} onChange={handleChange} />
    </form>
  );
}

در بلاک کد بالا، یک المنت input ایجاد کردیم و یک مقدار state برای نظارت بر state ایجاد کردیم که به عنوان props به input ارسال نمودیم. بعد از آن، از callback onChange برای نظارت بر روی تغییرات و سپس به‌روزرسانی state مربوط به input استفاده کردیم. این الگو تضمین می‌کند که فیلد input هر بار به طور قابل پیش‌بینی کار خواهد کرد.

مدیریت کامپوننت‌های سفارشی با استفاده از متد forwardRef

prop ref معمولا برای به دست آوردن رفرنس به یک المنت DOM استفاده می‌شود. الگوی طراحی forwardRef زمانی بسیار کاربردی می‌شود که ما نیاز داریم یک رفرنس را به یک کامپوننت سفارشی یا از یک کامپوننت parent به کامپوننت child منتقل کنیم. این الگوی طراحی به کامپوننت parent اجازه می‌دهد تا به المنت DOM یا نمونه اصلی کامپوننت child دسترسی داشته و با آن تعامل داشته باشد. به عنوان مثال:

import React from 'react';

const ChildComponent = React.forwardRef((props, ref) => {
  <input ref={ref} {...props} />
});

const ParentComponent = () => {
  const childRef = React.useRef();

  return <ChildComponent ref={childRef} />;
};

بلاک کد بالا یک کامپوننت Child را نشان می‌دهد که درون forwardRef قرار گرفته است. این اجازه می‌دهد تا ref ارسال شده و توسط ParentComponent قابل دسترسی باشد.

الگوی Conditional rendering

Conditional rendering یا رندر شرطی شامل نمایش داینامیک المنت‌های مختلف UI بر اساس شرایط خاص است. این الگو برای ساخت برنامه‌هایی که اطلاعات متفاوتی را بسته به state برنامه، تعاملات کاربر و عوامل مختلف دیگر نمایش می‌دهند بسیار مفید است.

راه‌های مختلفی برای پیاده‌سازی رندر شرطی در React وجود دارد. اولین و بیسیک‌ترین راه استفاده از دستور if است که در ادامه آن را مشاهده می‌کنیم:

function showLoggenInUser(props) {
  if (props.isAuthenticated) {
    return <p>Welcome back, {props.username}!</p>;
  } else {
    return <p>Please sign in.</p>;
  }
}

راه دیگر برای رندر کردن کامپوننت‌ها به‌صورت شرطی، استفاده از عملگر ternary است. عملگر ternary مختصر دستور if است و می‌تواند مستقیماً در JSX مورد استفاده قرار بگیرد. به عنوان مثال:

functionshowLoggenInUser(props) {
  return props.isAuthenticated ? (
    <p>Welcome back, {props.username}!</p>
  ) : (
    <p>Please sign in.</p>
  );
}

روش دیگر، استفاده از عملگر منطقی AND مطابق شکل زیر است:

function showLoggenInUser(props) {
  return props.isAuthenticated && <p>Welcome back, {props.username}!</p>;
}

جمع‌بندی

در این مقاله با برخی از انواع design pattern یا الگوهای طراحی مفید React در سال ۲۰۲۴ آشنا شدیم. استفاده از الگوهای طراحی بسیار مفید است زیرا، ما را قادر می‌سازند تا از تخصص همه توسعه‌دهندگانی که این الگوها را ایجاد و بررسی کرده‌اند بهره‌مند شویم و در نتیجه، زمان توسعه کوتاه‌تری داشته باشیم.