اشتباهات رایج در هنگام کار با React

Top Mistakes to Avoid When Using React

26 خرداد 1400
اشتباهات رایج در هنگام کار با React

کتابخانه react در حال حاضر یکی از بزرگترین کتابخانه های طراحی front-end است و به سرعت به رشد خود ادامه می دهد. کمپانی های بزرگی مانند فیسبوک و نتفلیکس نیز از این کتابخانه استفاده می کنند اما توسعه دهندگان در هنگام استفاده از این کتابخانه اشتباهات زیادی دارند. در این مقاله با ۱۰ مورد از بزرگترین اشتباهات توسعه دهندگان در هنگام استفاده از react آشنا می شویم.

۱. تعداد کامپوننت ناکافی

در react می توانید کامپوننت های بزرگی داشته باشید که کارهای مختلفی را انجام می دهند و به همین دلیل بسیاری از توسعه دهندگان react کامپوننت های بسیار بزرگی می سازند که عملیات های زیادی را انجام می دهند. این کار یک اشتباه بزرگ است. شما باید تا حد ممکن کامپوننت ها را کوچک نگه دارید. چرا؟ کوچک نگه داشتن کامپوننت ها باعث بالا رفتن تعداد کل کامپوننت ها می شود اما مزایای مهمی دارد:

  • نگهداری از کامپوننت های کوچک تر بسیار ساده تر است چرا که حجم کمتری دارند و خواندن کدهایشان نیز آسان می شود.
  • مشکل زدایی یا debug کردن کد در کامپوننت های کوچکتر بسیار ساده تر است چرا که هر خطا دامنه کوچک تری دارد بنابراین برای شناسایی عامل خطا نیاز به بررسی محیط کوچکتری خواهیم داشت.
  • به روز رسانی کامپوننت های کوچکتر یا ایجاد تغییرات در سایت بسیار ساده تر خواهد بود. فرض کنید بعد از چند سال می خواهید بخشی از سیستم خود را تغییر بدهید. در این حالت اگر کامپوننت های کوچکی داشته باشید به راحتی می توانید هر تغییری را ایجاد کنید. حتی اگر مجبور به حذف کامپوننت ها شوید، فقط چند کامپوننت کوچک را حذف می کنید نه اینکه قسمت بزرگی از برنامه را نابود کنید.

مثال زیر یک مثال ساده از یک کامپوننت ToDo است:

// ./components/TodoList.js




import React from 'react';




import { useTodoList } from '../hooks/useTodoList';

import { useQuery } from '../hooks/useQuery';

import TodoItem from './TodoItem';

import NewTodo from './NewTodo';




const TodoList = () => {

  const { getQuery, setQuery } = useQuery();

  const todos = useTodoList();

  return (

    <div>

      <ul>

        {todos.map(({ id, title, completed }) => (

          <TodoItem key={id} id={id} title={title} completed={completed} />

        ))}

        <NewTodo />

      </ul>

      <div>

        Highlight Query for incomplete items:

        <input value={getQuery()} onChange={e => setQuery(e.target.value)} />

      </div>

    </div>

  );

};




export default TodoList;

همانطور که می بینید این کامپوننت ساده خودش به بخش های مختلفی تقسیم شده است بنابراین مدیریت آن بسیار ساده تر خواهد بود.

۲. ویرایش مستقیم state

ویرایش مستقیم state در react آنچنان اشتباه بزرگی است که در documentation رسمی react نیز آمده است. احتمالا شنیده باشید که state در react باید immutable یا غیر قابل ویرایش باشد. یعنی پس از آنکه ساخته شد نباید به صورت مستقیم مورد ویرایش قرار بگیرد. پس چطور می توانیم state را ویرایش کنیم؟ به مثال زیر توجه کنید:

const modifyPetsList = (element, id) => {

  petsList[id].checked = element.target.checked;

  setPetsList(petsList);

};

در اینجا ما قصد داریم کلید checked از یک شیء را بر اساس وضعیت checkbox به روز رسانی کنیم اما به مشکل برخورد کرده ایم. react این تغییر را نمایش نخواهد داد (re-rendering انجام نخواهد شد) چرا که reference شیء مورد نظر تغییر نکرده است و react از همین reference ها برای تشخیص تغییر استفاده می کند.

برای ویرایش صحیح state باید از متد setState یا از hook ای به نام useState استفاده کنید. استفاده از این متدها باعث می شود react متوجه تغییر جدید شما بشود و محتوا را به صورت صحیح نمایش بدهد. اگر بخواهیم مثال بالا را به شکل صحیح بنویسیم می گوییم:

const modifyPetsList = (element, id) => {

  const { checked } = element.target;

  setpetsList((pets) => {

    return pets.map((pet, index) => {

      if (id === index) {

        pet = { ...pet, checked };

      }

      return pet;

    });

  });

};

۳. پاس دادن اعداد به شکل رشته

یکی دیگر از اشتباهات رایج کار با react در هنگام پاس دادن اعداد به صورت prop است. بسیاری از توسعه دهندگان به صورت ندانسته یک رشته عددی (مانند “34”) را به عنوان props پاس می دهند در حالی که تصور می کنند مقدار پاس داده شده یک عدد است. به مثال زیر توجه کنید:

class Arrival extends React.Component {

  render() {

    return (

      <h1>

        Hi! You arrived {this.props.position === 1 ? "first!" : "last!"} .

      </h1>

    );

  }

}

در این کد کامپوننت ما انتظار دارد position را به عنوان prop داشته باشد و همچنین انتظار دارد که این prop یک عدد نیز باشد. در کد بالا مشخص است که ما از یک اپراتور ternary استفاده کرده ایم و اگر به جای عدد ۱ رشته ای به این prop پاس داده شده باشد، بخش دوم (عبارت !last) انتخاب خواهد شد و کدهایتان بهم خواهد ریخت. برای حل این مشکل در react باید از curly bracket ها (علامت های {}) در هنگام پاس دادن یک prop استفاده کنید:

const element = <Arrival position={1} />;

۴. عدم استفاده از key در کامپوننت های لیست

اگر از توسعه دهندگان react باشید می دانید که کامپوننت های لیست نیاز به یک key دارند. فرض کنید شما لیستی از عناصر را به شکل زیر چاپ کرده اید:

const lists = ['cat', 'dog', 'fish’];




render() {

  return (

    <ul>

      {lists.map(listNo =>

        <li>{listNo}</li>)}

    </ul>

  );

}

آیتم های لیست بالا هیچ خصوصیتی به نام key ندارند. react از key به عنوان روشی برای تشخیص این آیتم ها استفاده می کند بنابراین اگر key نداشته باشید، در هنگام ویرایش یا حذف عناصر مختلف از یک لیست دچار مشکل می شوید. همچنین اگر عنصری را به صورت دستی حذف کنید react توانایی تشخیص این موضوع را نداشته و نمی فهمد که عنصری از لیست حذف شده است.

برای حل این مشکل همیشه خصوصیت key را به لیست ها پاس بدهید. key ها باید مقداری یکتا داشته باشند و معمولا مقدارشان را از یک پایگاه داده می گیریم اما برای نمایش نحوه کار آن ها فعلا از همان مقدارشان استفاده می کنم:

<ul>

  {lists.map(listNo =>

    <li key={listNo}>{listNo}</li>)}

</ul>

۵. فراموش کردن ناهمگامی setState

بسیاری از توسعه دهندگان react فراموش می کنند که setState ناهمگام یا async است. حالا این ناهمگام بودن setState در عمل به چه معناست؟ این ناهمگامی بدین معنی است که تغییرات ایجاد شده با استفاده از setState در لحظه ثبت نمی شوند و بنا به شرایط کد شما ممکن است در render های بعدی انجام شوند. چرا؟ به دلیل اینکه react برای بالا بردن سرعت خود، دستورات به روز رسانی را جمع کرده و به صورت یکجا اجرا می کند. با این حساب اگر دقیقا پس از مقدار دهی به state سعی در دسترسی به آن و دریافت مقدارش داشته باشید ممکن است نتیجه ای خلاف انتظارتان را دریافت کنید:

handlePetsUpdate = (petCount) => {

  this.setState({ petCount });




  // در این بخش مقدار قبلی دریافت می شود

  this.props.callback(this.state.petCount);

};

برای حل این مشکل باید یک پارامتر دیگر را به setState بدهید که به عنوان یک callback عمل می کرده و مقدار به روز رسانی شده را به شما می دهد. این callback فقط زمانی اجرا می شود که فرآیند به روز رسانی به طور کامل تمام شده باشد بنابراین:

handlePetsUpdate = (petCount) => {

  this.setState({ petCount }, () => {




    // در این بخش مقدار به روز شده و جدید دریافت می شود

    this.props.callback(this.state.petCount);

  });

};

این مسئله برای useState نیز صادق می باشد با این تفاوت که useState آرگومان دومی به عنوان callback ندارد. برای حل مشکل useState باید از هوکی (hook) به نام useEffect استفاده کنید.  زمانی که کامپوننتی دوباره render شود، هوک useEffect اجرا می شود. این هوک زمانی که کاربرد که بخواهیم کاری را پس از اعمال تمام تغییرات انجام بدهیم. مثلا:

function BooksList () {

   const [books, updateBooks] = React.useState([]);

   const [counter, updateCounter] = React.useState(0);

  

   React.useEffect(function effectFunction() {

       if (books) {

           updateBooks([...books, { name: 'A new Book', id: '...'}]);

       }

   }, [counter]);

  

   const incrementCounter = () => {

       updateCounter(counter + 1);

   }

   ...

}

قرار دادن آرایه به عنوان آرگومان آخر و پاس دادن counter به آن یعنی useEffect به آن وابسته است.

۶. استفاده بیش از حد از Redux

بسیاری از توسعه دهندگان برنامه های بزرگ react از Redux برای مدیریت state سراسری برنامه خود استفاده می کنند. با اینکه redux برای برنامه های بزرگ بسیار کارآمد است اما شما نباید از آن برای مدیریت state تمام برنامه های خود استفاده کنید. به طور مثال اگر برنامه شما کامپوننت های موازی ندارد که نیازی به رد و بدل کردن داده ها داشته باشند، هیچ نیازی به اضافه کردن یک کتابخانه دیگر به پروژه خود ندارید. یادتان باشد که کتابخانه های بیشتر می توانند سرعت برنامه شما را کم کنند.

۷. استفاده از God component ها

God component ها یا کامپوننت های خدا که بعضا به سوپر کامپوننت نیز مشهور هستند معمولا قابلیت استفاده مجدد را ندارند و بسیار بزرگ هستند. برنامه نویسان تازه کار react برای راحتی کار خودشان همه چیز را درون یک کامپوننت بسیار بزرگ قرار می دهند که آن را تبدیل به سوپر کامپوننت می کند. به جای این کار باید وقت بگذارید و قسمت های مختلف برنامه تان را به صورت منطقی جدا کنید و برای هر بخش یک کامپوننت جداگانه قرار بدهید. قبلا در رابطه با مزایای کامپوننت های کوچکتر صحبت کردیم.

۸. نداشتن ساختار برای پوشه ها

یادتان باشد که پروژه هایی که روی ‌آن ها کار می کنید کاری دفعه ای نیستند که پس از توسعه تمام شوند بلکه در بسیاری از اوقات نیاز به به روز رسانی و ویرایش دارند (مثلا نیاز است قابلیت جدیدی را به آن اضافه کنید). به همین منظور ساختار پوشه ها در برنامه های react اهمیت بسیار زیادی پیدا می کند. تصویر زیر یک مثال ساده از ساختار پوشه ها در یک برنامه react است:

یک ساختار نمونه از پوشه بندی در برنامه های react
یک ساختار نمونه از پوشه بندی در برنامه های react

توجه داشته باشید که این فقط یکی از ده ها روش تعیین ساختار پوشه ها در react است و نباید آن را کپی کنید. نکته مهم این است که شما باید بر اساس نیاز پروژه خود و در نظر گرفتن احتمالات آینده ساختار پوشه مناسبی برای خود داشته باشید.

۹. عدم استفاده از حروف بزرگ در نام کامپوننت

یکی از قرارداد های رایج بین برنامه نویسان react این است که حرف اول در نام کامپوننت ها را با حروف بزرگ انگلیسی (مثلا Book به جای book) می نویسند. برخی از افراد تصور می کنند این مسئله به دلیل خوانایی بیشتر کدها است در حالی که اینطور نیست! در JSX کامپوننت هایی که نامشان با حروف کوچک نمایش داده شود به عناصر HTML کامپایل می شوند! یعنی چه؟ یعنی JSX اصلا آن ها را کامپوننت در نظر نمی گیرد و تصور می کند <book> یک عنصر HTML مانند <a> است. طبیعتا چنین مسئله ای باعث حذف کامپوننت هایتان می شود. به مثال زیر توجه کنید:

class demoComponentName extends React.Component {

}

این کامپوننت در هنگام کامپایل شدن کدها باعث خطا می شود. نسخه تصحیح شده آن بدین شکل است:

class DemoComponentName extends React.Component {

}

۱۰. نوشتن منطق کاری درون کامپوننت ها

معمولا زمانی که صحبت از پیاده سازی صحیح کامپوننت هایی با قابلیت استفاده چند باره می شود، الگویی آشنا به ذهن توسعه دهندگان react خطور می کند: کامپوننت های presentational (نمایشی) و کامپوننت های container (نگهدارنده یا منطقی). این الگو یک الگوی خوب برای شروع کار است. اشتباهی که معمولا رخ می دهد، ادغام مکانیسم نمایش و منطق کاری در یک کامپوننت است. زمانی که منطق کاری به همراه مکانیسم نمایش درون یک کامپوننت قرار بگیرد، استفاده مجدد از آن کامپوننت بدون کپی و پیست کردن کد بسیار سخت می شود.

به طور مثال کامپوننت زیر یک کامپوننت نمایشی است:

const Books = props => (

  <ul>

    {props.books.map(book => (

      <li>{book}</li>

    ))}

  </ul>

)

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

class BooksContainer extends React.Component {

  constructor() {

    this.state = {

      books: []

    }

  }




  componentDidMount() {

    axios.get('/books').then(books =>

      this.setState({ books: books }))

    )

  }




  render() {

    return <Books books={this.state.books} />

  }

}

۱۱. عدم استفاده از مسیرهای مطلق

این مورد شاید آنچنان شبیه به اشتباه نباشد اما قطعا در برنامه های بزرگ تر مشکل ایجاد می کند. در برنامه های بزرگ فایل های CSS و جاوا اسکریپت زیادی داریم که در پوشه های مختلفی قرار دارند. ما برای آدرس دهی به این فایل ها می توانیم از آدرس های نسبی استفاده کنیم که بدین شکل هستند:

../../../importone.js

../../../importtwo.js

همانطور که می بینید خوانایی کدها بین شکل پایین می آید و معلوم نیست فایل ها از کجا آمده اند. برای حل این مشکل create-react-app از نسخه سوم به بعد قابلیت مشخص کردن آدرس ها به صورت آدرس مطلق را نیز فراهم کرده است. برای استفاده از این قابلیت باید یک فایل jsconfig.json را در پوشه خود بسازید و تنظیمات زیر را به آن پاس بدهید:

// jsconfig.json

{

  "compilerOptions": {

    "baseUrl": "src"

  },

  "include": ["src"]

}

با این کار پایه تمام آدرس های شما پوشه src خواهد بود بنابراین باید از src به بعد را آدرس دهی کنید. یک مثال ساده:

import React from 'react';

import { LINKS } from 'helpers/constants';

import Button from 'components/Button/Button';




function App() {

  return (

    <>

      <Button>

        This is my button

      </Button>




      <a href={LINKS.ABOUT}>About Us</a>

    </>

  );

}

export default App;

نه تنها این روش باعث خوانایی بهتر آدرس ها می شود بلکه اگر محل بعضی از آن ها را عوض کنید نیاز به به روز رسانی آدرس نخواهید داشت.

۱۲. ننوشتن unit test

از آنجایی که برنامه های react بدون unit test نیز کار می کنند، بسیاری از توسعه دهندگان react اصلا unit test نمی نویسند. این مسئله از نظر من یک اشتباه است چرا که هیچ چیزی جای testing برنامه را نمی گیرد. unit test ها به شما اجازه می دهند قسمت های مختلف برنامه خود را به صورت جداگانه تست کنید. به طور مثال می توانید تستی بنویسید که مشخص می کند آیا یک prop پاس داده شده به یک کامپوننت در مرورگر نمایش داده می شود یا خیر. شاید بگویید چرا باید چنین تست کوچکی را بنویسیم؟ در برخی از اوقات یک دستور CSS که در قسمت دیگری از برنامه شما بوده است وارد عمل شده و نمایش آن prop را دچار مشکل می کند. unit test ها معمولا در این زمینه ها به شما کمک می کنند.

۱۳. عدم استفاده از react dev tool

Debugging کردن برنامه های react کار آسانی نیست چرا که معمولا چندین کامپوننت را در یک صفحه داریم بنابراین اگر در بخشی از صفحه خطایی رخ بدهد پیدا کردن این خطا ممکن است کار سختی باشد. react dev tools یک افزونه بسیار کاربردی برای مرورگرها است که به شما اجازه می دهد جزئیات برنامه خود را در پس زمینه مشاهده کنید. مثلا چه کامپوننتی در حال نمایش است یا یک state خاص چه مقدار و وضعیتی دارد و الی آخر. استفاده از این ابزار به شما کمک می کند که مشکل زدایی یا debug برنامه ها را سریع تر انجام بدهید.

 Redux dev tools نیز دقیقا مانند react dev tools است با این تفاوت که مستقیما برای redux طراحی شده است. این افزونه معمولا زمانی استفاده می شود که در حال کار با برنامه های بزرگ باشید و از پکیج redux استفاده کنید. این افزونه به شما اجازه می دهد که state خود را در زمان جا به جا کنید و ببینید هر عملیات در چه زمانی اتفاق افتاده است.


منابع: وب سایت های plainenglish و logrocket

نویسنده شوید
دیدگاه‌های شما (1 دیدگاه)

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.

تایماز
01 تیر 1400
ممنون عزیزم ///// خوب بود

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.