برنامه نویسی

چند اشتباه رایج React که ممکن است مرتکب شوید

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

اگر می‌خواهید برنامه React خوبی بسازید، باید از بسیاری از خطاهای رایج در این راه جلوگیری کنید.

در این مقاله، ما نه تنها نحوه رفع سریع اشتباهات را شرح خواهیم داد، بلکه برخی از الگوهای طراحی عالی را به شما ارائه خواهیم داد تا کد شما بهتر و قابل اعتمادتر شود.

1. در React متغیرهای وضعیت (state) را با setState ارسال نکنید

در کد زیر ما یک برنامه todo داریم که آرایه‌ای از todo ها را نمایش می‌دهد (در TodoList).

ما می‌توانیم todoهای جدید را در کامپوننت AddTodo اضافه کنیم، که آرایه todos را در App آپدیت می‌کند.

مشکل props که ما به AddTodo ارسال کردیم چیست؟

export default function App() {
  const [todos, setTodos] = React.useState([]);

  return (
    <div>
      <h1>Todo List</h1>
      <TodoList todos={todos} />
      <AddTodo setTodos={setTodos} todos={todos} />
    </div>
  );
}

function AddTodo({ setTodos, todos }) {
  function handleAddTodo(event) {
    event.preventDefault();
    const text = event.target.elements.addTodo.value;
    const todo = {
      id: 4,
      text,
      done: false
    };
    const newTodos = todos.concat(todo);
    setTodos(newTodos);
  }

  return (
    <form onSubmit={handleAddTodo}>
      <input name="addTodo" placeholder="Add todo" />
      <button type="submit">Submit</button>
    </form>
  );
}

ما یک todo جدید به آرایه todos اضافه می‌کنیم و سپس state را همانطور که باید تنظیم می‌کنیم. با این کار todoهای نمایش داده شده در کامپوننت TodoList آپدیت می شوند.

با این حال، از آنجا که state جدید خارج از state قبلی است، نیازی به ارسال به آرایه todos نداریم.

در عوض، با نوشتن یک تابع در تابع setState، می‌توانیم به state ی todos قبلی دسترسی پیدا کنیم. هر چه از این تابع برگردانیم به عنوان state جدید تنظیم می‌شود.

به عبارت دیگر، برای آپدیت صحیح state، فقط باید تابع setTodos را ارسال کنیم:

export default function App() {
  const [todos, setTodos] = React.useState([]);

  return (
    <div>
      <h1>Todo List</h1>
      <TodoList todos={todos} />
      <AddTodo setTodos={setTodos} />
    </div>
  );
}

function AddTodo({ setTodos }) {
  function handleAddTodo(event) {
    event.preventDefault();
    const text = event.target.elements.addTodo.value;
    const todo = {
      id: 4,
      text,
      done: false
    };
    setTodos(prevTodos => prevTodos.concat(todo));
  }

  return (
    <form onSubmit={handleAddTodo}>
      <input name="addTodo" placeholder="Add todo" />
      <button type="submit">Submit</button>
    </form>
  );
}

2. کامپوننت‌های React را تک وظیفه‌ای بسازید

در برنامه زیر، ما در حال واکشی تعدادی از کاربران از یک API درون کامپوننت app خود هستیم، داده‌های کاربر را در یک state قرار می‌دهیم، و سپس آن‌ها را در رابط کاربری خود نمایش می‌دهیم.

مشکل App component چیست؟

export default function App() {
  const [users, setUsers] = React.useState([]);

  React.useEffect(() => {
    fetch("https://jsonplaceholder.typicode.com/users")
      .then((res) => res.json())
      .then((data) => {
        setUsers(data);
      });
  }, []);

  return (
    <>
      <h1>Users</h1>
      {users.map((user) => (
        <div key={user.id}>
          <div>{user.name}</div>
        </div>
      ))}
    </>
  );
}

ما در کامپوننت‌مان، کارهای مختلفی را انجام می‌دهیم.

ما نه تنها واکشی داده‌ها از راه دور از سرور را انجام می‌دهیم، بلکه state را مدیریت کرده و همچنین آن state را با JSX نمایش می‌دهیم.

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

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

S در SOLID به منظور “اصل تک وظیفه‌ای” است، یک اصول اساسی که هنگام نوشتن کامپوننت‌های React باید از آن استفاده کرد.

ما می‌توانیم کامپوننت App خود را به hookها و کامپوننت‌های جداگانه تقسیم کنیم که هر کدام مسئولیت خاص خود را دارند. ابتدا واکشی داده‌های از راه دور را برای یک React hook سفارشی استخراج می‌کنیم.

این hook، که ما آن را useUserData می‌نامیم، از واکشی داده‌ها و قرار دادن آن در state لوکال مراقبت خواهد کرد.

function useUserData() {
  const [users, setUsers] = React.useState([]);

  React.useEffect(() => {
    fetch("https://jsonplaceholder.typicode.com/users")
      .then((res) => res.json())
      .then((json) => {
        setUsers(json);
      });
  }, []);

  return users;
}

پس از آن، ما برای دسترسی به آرایه hook، users درون App را فراخوانی خواهیم کرد.

با این حال، به جای نمایش داده‌های کاربر مستقیما در عبارت return در App، ما یک مولفه جداگانه User ایجاد خواهیم کرد که شامل تمام JSXهای لازم برای نمایش هر المنت در این آرایه، به علاوه استایل‌های مرتبط (در صورت وجود) است.

function User({ user }) {
  const styles = {
    container: {
      margin: '0 auto',
      textAlign: 'center' 
    }
  };  
    
  return (
    <div style={styles.container}>
      <div>{user.name}</div>
    </div>
  );
}

export default function App() {
  const users = useUserData();

  return (
    <>
      <h1>Users</h1>
      {users.map((user) => (
        <User key={user.id} user={user} />
      ))}
    </>
  );
}

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

3. اثرات جانبی (side effect) خود را تک وظیفه ای بسازید

در کامپوننت زیر، هم داده‌های user و هم post را واکشی می‌کنیم.

وقتی که لوکیشن (URL) برنامه ما تغییر کند، ما هم داده‌های user و هم post را واکشی می‌کنیم.

export default function App() {
  const location = useLocation();

  function getAuthUser() {
    // fetches authenticated user
  }
    
  function getPostData() {
    // fetches post data
  }

  React.useEffect(() => {
    getAuthUser();
    getPostData();
  }, [location.pathname]);

  return (
    <main>
      <Navbar />
      <Post />
    </main>
  );
}

در صورت تغییر URL، پست جدیدی را نشان می‌دهیم، اما آیا لازم است هر بار که لوکیشن تغییر می‌کند آن را واکشی کنیم؟

ما این کار را انجام نمی‌دهیم.

در بیشتر کد React، ممکن است وسوسه شوید همه اثرات جانبی خود را در یک تابع use effect قرار دهید. اما انجام این کار اصل تک وظیفه‌ای که گفتیم را نقض می‌کند.

این کار می‌تواند منجر به مشکلاتی از جمله انجام اثرات جانبی در صورت عدم نیاز شود. به یاد داشته باشید که اثرات جانبی خود را نیز به یک مسئولیت اختصاص دهید.

برای اصلاح کدمان، تنها کاری که باید انجام دهیم این است که getAuthUser را در یک use effect hook جداگانه فراخوانی کنیم. با این کار اطمینان حاصل می‌شود که هر زمان که نام مسیر (pathname) لوکیشن تغییر کند، اما فقط یک بار کامپوننت app ما سوار می‌شود.

export default function App() {
  const location = useLocation();

  React.useEffect(() => {
    getAuthUser();
  }, []);

  React.useEffect(() => {
    getPostData();
  }, [location.pathname]);

  return (
    <main>
      <Navbar />
      <Post />
    </main>
  );
}

4. استفاده از عبارت سه تایی به جای && در JSX

بیاید بگوییم ما در حال نمایش لیستی از پست‌ها در یک کامپوننت اختصاصی، PostList، هستیم.

منطقی است که بررسی کنیم ببینیم آیا قبل از تکرار آن‌ها، پستی داریم یا خیر.

از آنجا که لیست پست‌های ما یک آرایه است، می توانیم از ویژگی length. برای بررسی و اینکه آیا یک مقدار بزرگ‌تر از صفر است استفاده کنیم. در این صورت ما می‌توانیم این آرایه را با JSX خود مپ کنیم.

ما می‌توانیم همه این ها را با عملگر && بیان کنیم:

export default function PostList({ posts }) {
  return (
    <div>
      <ul>
        {posts.length &amp;amp;&amp;amp;
          posts.map((post) => <PostItem key={post.id} post={post} />)}
      </ul>
    </div>
  );
}

با این حال، اگر چنین کدی را اجرا کنید، ممکن است با آنچه که می‌بینید متعجب شوید. اگر آرایه ما خالی باشد، ما چیزی نمی‌بینیم، ما عدد 0 را می‌بینیم!

چی؟ چرا اینطور است؟

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

بهترین راه برای رفع این مشکل و جلوگیری از چنین خطاهایی در آینده چیست؟

در بسیاری از موارد ما نباید از عملگر and استفاده کنیم اما در عوض از یک عبارت سه گانه برای تعریف صریح آنچه در صورت عدم تحقق شرط نمایش داده می‌شود استفاده می‌کنیم.

اگر بخواهیم کد خود را با این حالت سه تایی بنویسیم، مقدار null را در شرط دیگری وارد می‌کنیم تا مطمئن شویم که چیزی نمایش داده نمی‌شود.

export default function PostList({ posts }) {
  return (
    <div>
      <ul>
        {posts.length
          ? posts.map((post) => <PostItem key={post.id} post={post} />)
          : null}
      </ul>
    </div>
  );
}

با استفاده از حالت سه تایی به جای &&، می‌توانید از بسیاری از باگ‌های آزاردهنده مانند این مورد جلوگیری کنید.

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

نوشته های مشابه

دکمه بازگشت به بالا