ترکیب React و Typescript: استفاده صحیح از useState

Combining React and Typescript: Proper use of useState

25 مرداد 1399
ترکیب React و Typescript: استفاده ی صحیح از useState

در قسمت قبل توانستیم با صدا زدن useState در فایل App.tsx اولین state برنامه خود را راه بیندازیم:

const App: React.FC = () => {
  const [todos, setTodos] = useState([]);
// بقیه کدها //

اما این به تنهایی کافی نیست. ما باید بتوانیم از setTodos استفاده کنیم تا با هر بار دریافت داده جدید از سمت کاربر آن را به آرایه Todos که همان state ما است، اضافه نماییم. احتمالا در ابتدا بخواهید چنین کاری را انجام بدهید:

  const todoAddHandler = (text: string) => {
    setTodos({id: Math.random().toString(), text: text});
  };

یعنی با تابع random عددی تصادفی را برای id تعیین می کنیم (فقط جهت اینکه کارمان راه بیفتد) و text را نیز برابر همان text ای قرار می دهیم که به صورت آرگومان دریافت کرده ایم اما با نوشتن کد بالا سریعا به خطا برمی خورید. خطا به ما می گوید که پاس دادن چنین شیء ای ممکن نیست چرا که انتظار می رود مقدار نهایی آرایه ای از هیچی باشد! قطعا ما چنین چیزی را نمی خواهیم. اگر قرار است state برنامه ما آرایه ای خالی باشد، پس چرا state را تعریف کردیم؟ مشکل در کد زیر است:

  const [todos, setTodos] = useState([]);

زمانی که به useState یک آرایه خالی پاس می دهیم، تایپ اسکریپت سعی می کند خودش حدس بزند که state ما چه شکلی خواهد داشت. مثلا اگر کد بالا را به شکل زیر می نوشتیم:

  const [todos, setTodos] = useState('');

تایپ اسکریپت حدس می زند که state ما قرار است یک رشته باشد، بنابراین کد مثالی زیر صحیح می بود:

  const todoAddHandler = (text: string) => {
    setTodos('some random text');
  };

اما حالا که آرایه ای خالی را پاس داده ایم تایپ اسکریپت تصور می کند که state ما قرار است یک آرایه خالی باشد. برای حل این مشکل باید به react بگوییم که state ما چه شکلی خواهد داشت. از آنجایی که این کار را در چندین قسمت از برنامه تکرار خواهیم کرد، من یک فایل جداگانه در پوشه src به نام todo.model.ts می سازم و یک interface از state خودمان را در آن تعریف و Export می کنم:

export interface Todo {
  id: string;
  text: string;
}

حالا به فایل app.tsx برگشته این interface را import می کنیم:

import React, { useState } from 'react';

import TodoList from './components/TodoList';
import NewTodo from './components/NewTodo';
import { Todo } from './todo.model';

با این حساب می توانیم دستور useState را به شکل زیر بنویسیم:

  const [todos, setTodos] = useState<Todo[]>([]);

چرا؟ به دلیل اینکه useState یک generic type است و می توانیم خودمان مشخص کنیم که جزئیات آن به چه شکلی باشد. کد بالا یعنی آرایه ای از todo ها را خواهیم داشت. با این کار خطای ما از بین می رود اما از نظر منطقی مشکل داریم. به کد زیر نگاه کنید:

  const todoAddHandler = (text: string) => {
    setTodos({id: Math.random().toString(), text: text});
  };

همانطور که می دانید در react تغییر دادن state به شکل بالا باعث overwrite شدن state می شود. یعنی تمام state قبل حذف شده و این یک شیء جایگزین آن می شود. مثلا اگر کاربر «خرید از فروشگاه» را به برنامه ما اضافه کند مشکلی نخواهد داشت اما اگر هر آیتم دیگری را اضافه کند (مثلا «عوض کردن روغن ماشین»)، آیتم قبلی (خرید) حذف شده و فقط یک آیتم به نام «عوض کردن روغن ماشین» باقی می ماند. قطعا ما چنین چیزی را نمی خواهیم چرا که برنامه ما قرار است انواع و اقسام کارهای روزمره کاربر را در خود نگه دارد. با این حساب راه حل چیست؟

راه حل اول استفاده از spread operator (علامت سه نقطه) است که از state قبلی یک کپی گرفته و شیء جدید را نیز به آن اضافه می کند:

  const todoAddHandler = (text: string) => {
    setTodos([...todos, {id: Math.random().toString(), text: text}]);
  };

اگر با react آشنا باشید حتما با این روش هم آشنا هستید. این روش بدون مشکل کار می کند اما بهترین روش انجام کار نیست. در برنامه کوچک ما این روش حتما کار خواهد کرد اما در برنامه های بزرگ تر ممکن است باعث مشکلاتی شود. چرا؟ به دلیل اینکه react تغییرات state را در زمان بندی خاصی انجام می دهد (ممکن است آن ها را به تعویق بیندازد) و به همین خاطر ممکن است مقداری که درون todo ما می باشد، آخرین نسخه از state نباشد (در دوره آموزش react در این باره مفصلا صحبت کرده ایم).

راه حل بهتر، پاس دادن یک تابع به setTodos می باشد. این تابع به صورت خودکار todo های قبلی ما را دریافت می کند و نهایتا state را به روز رسانی می کند:

const App: React.FC = () => {
  const [todos, setTodos] = useState<Todo[]>([]);

  const todoAddHandler = (text: string) => {
    setTodos(prevTodos => [
      ...prevTodos,
      { id: Math.random().toString(), text: text }
    ]);
  };
// بقیه کدها //

حالا prevTodos (نام آن به سلیقه ما انتخاب می شود اما به صورت خودکار توسط react پاس داده می شود) آخرین نسخه state را خواهد داشت. به همین دلیل به جای پاس دادن todos، مقدار prevTodos را پاس می دهیم تا مطمئن شویم به روز رسانی ها همیشه بر اساس آخرین نسخه state ما انجام می شوند.

اگر کدهای بالا را ذخیره کرده و در مرورگر اجرا کنید، با اضافه کردن هر مقداری از طریق input باید همان مقدار را به صورت یک <li> در صفحه مشاهده کنید. در قسمت بعد، یاد خواهیم گرفت که چطور این آیتم های اضافه شده را از state خود حذف کنیم.

دانلود کدهای این جلسه

تمام فصل‌های سری ترتیبی که روکسو برای مطالعه‌ی دروس سری آموزش جامع تایپ اسکریپت توصیه می‌کند:
نویسنده شوید
دیدگاه‌های شما

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

مقالات مرتبط
ما را دنبال کنید
اینستاگرام روکسو تلگرام روکسو ایمیل و خبرنامه روکسو