برنامه نویسی

راهنمای قطعی برقراری تماس های API در React

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

هنگام کار با وانیلی جاوا اسکریپت، احتمالاً از کتابخانه ای مانند Fetch یا Axios برای درخواست های API استفاده می کنید. در React نیز می توانید از آنها استفاده کنید، و چالش این است که چگونه کد را در اطراف این کتابخانه ها سازماندهی کنیم تا آن را تا حد امکان خوانا، توسعه پذیر و جدا کنیم.

این کار خیلی شهودی نیست. برای توسعه دهندگان جدیدی که با React شروع به کار می کنند بسیار معمول است که درخواست های API مانند این را ارائه دهند:

// ❌ Don't do this

const UsersList = () => {
  const [users, setUsers] = useState([]);

  useEffect(() => {
    fetch("/users").then((data) => {
      setUsers(users);
    });
  }, []);

  return (
    <ul>
      {users.map(user => (
        <li>{user.name}<li>
      ))}
    </ul>
  );
};
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

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

  • داده ها در حالت محلی ذخیره می شوند

    • هر فراخوانی API در اجزای دیگر به یک محلی جدید نیاز دارد useState
  • کتابخانه درخواست (Fetch) مستقیماً در کامپوننت فراخوانی می شود

    • برای مثال، اگر کتابخانه را به Axios تغییر دهید، هر مؤلفه باید دوباره ساخته شود
    • همین امر در مورد نقطه پایانی نیز صدق می‌کند، اگر تغییر کند، باید آن را در بسیاری از مکان‌ها تغییر دهید
  • یک درخواست در سطح سرور در یک مؤلفه نمایشی در حال انجام است

    • کامپوننت ها برای ارائه داده ها در نظر گرفته شده اند، نه مدیریت منطق واکشی
    • داشتن یک مسئولیت واحد برای هر جزء، کلاس و عملکرد، تمرین خوبی است
  • معلوم نیست چه درخواستی برمی گردد

    • برای دانستن آنچه توسط API برگردانده می شود، به نام نقطه پایانی تکیه می کنید

راه های مختلفی برای حل این مشکلات وجود دارد. امروز ایده‌ها و رویکردهایم را برای ایجاد یک ساختار پوشه و فایل که قابل اعتماد و مقیاس‌پذیر باشد، به شما نشان خواهم داد، و می‌توانید آن را – یا ایده پشت آن – را حتی در چارچوب‌هایی مانند Next.js اعمال کنید.

سناریوی مثال ما

برای درک و چسباندن همه مفاهیم، ​​بیایید به تدریج یک برنامه لیست مواد غذایی بسازیم. این برنامه دارای ویژگی های زیر خواهد بود:

  • فهرست موارد موجود؛
  • مورد جدیدی اضافه کنید؛
  • حذف مورد؛
  • علامت گذاری مورد به عنوان انجام شده؛

برای استایل ها، از TailwindCSS استفاده خواهم کرد. برای شبیه سازی درخواست های API از Mirage JS استفاده خواهد شد که استفاده از آن بسیار آسان و مفید است. برای فراخوانی این API، از Fetch استفاده می کنیم.

همه نمونه ها در GitHub من هستند، بنابراین با خیال راحت مخزن را شبیه سازی کنید و با آن بازی کنید. جزئیات نحوه اجرای آن در فایل README توضیح داده شده است.

نتیجه نهایی به این صورت خواهد بود:

برنامه لیست مواد غذایی

ایجاد نقاط پایانی API

این برنامه به 4 نقطه پایانی API نیاز دارد:

  1. GET /api/grocery-list – بازیابی همه موارد
  2. POST /api/grocery-list – یک آیتم جدید ایجاد کنید
  3. PUT /api/grocery-list/:id/done – مورد را با id برابر با :id به عنوان انجام شده علامت گذاری کنید
  4. DELETE /api/grocery-list/:id – آیتم با شناسه برابر با :id را حذف می کند

مثال های زیر ابتدایی ترین مورد فراخوانی API ها هستند. این بهترین کد نیست، اما در حین حرکت، کد را اصلاح می کنیم، بنابراین همه مفاهیم را بهتر درک خواهید کرد. همچنین، ما روی لایه ارائه، یعنی JSX واقعی کامپوننت تمرکز نمی کنیم. مطمئناً می توان آن را بهبود بخشید، اما تمرکز این مقاله نیست.

1. بازیابی همه موارد

یک مکان خوب برای افزودن اولین تماس در این است useEffect از جزء، و اضافه کنید refresh حالت را به عنوان پارامتر تعیین کنید، بنابراین هر بار که این حالت تغییر می کند، موارد زیر را دوباره واکشی می کنیم:

// src/App.jsx

const App = () => {
  const [items, setItems] = useState([]);
  const [refetch, setRefetch] = useState(false);

  useEffect(() => {
    fetch("/api/grocery-list")
      .then((data) => data.json())
      .then((data) => {
        setItems(data.items);
      });
  }, [refresh]);

  return (
    <ul>
      {items.map((item) => (
        <li key={item.id}>{item.title}</li>
      ))}
    </ul>
  );
};
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

2. ایجاد یک آیتم جدید

هنگامی که کاربر عنوان مورد را وارد می‌کند و روی دکمه «افزودن» کلیک می‌کند، برنامه باید یک تماس با API برای ایجاد یک مورد جدید ارسال کند، سپس همه موارد را دوباره برای نمایش آیتم جدید واکشی کند:

// src/App.jsx

const App = () => {
  // ...
  const [title, setTitle] = useEffect("");

  const handleAdd = (event) => {
    event.preventDefault();

    fetch("/api/grocery-list", {
      method: "POST",
      body: JSON.stringify({ title }),
    }).then(() => {
      setTitle(""); // Empty the title input
      setRefresh(!refresh); // Force refetch to update the list
    });
  };

  return (
    // ...

    <form onSubmit={handleAdd}>
      <input
        required
        type="text"
        onChange={(event) => setTitle(event.target.value)}
        value={title}
      />
      <button type="submit">Add</button>
    </form>

    // ...
  );
};
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

3. علامت گذاری یک مورد به عنوان انجام شده

هنگامی که کاربر بر روی کادر انتخاب کلیک می کند تا مورد را به عنوان تمام شده علامت گذاری کند، برنامه باید یک درخواست PUT ارسال کند. item.id به عنوان یک پارامتر در نقطه پایانی. اگر مورد قبلاً به‌عنوان «تمام» علامت‌گذاری شده باشد، نیازی به ارائه درخواست نداریم.

این بسیار شبیه به ایجاد یک آیتم جدید است، فقط روش درخواست تغییر می کند:

// src/App.jsx

const App = () => {
  // ...

  const handleMarkAsDone = (item) => {
    if (item.isDone) {
      return;
    }

    fetch(`/api/grocery-list/${item.id}/done`, {
      method: "PUT",
    }).then(() => {
      setRefresh(!refresh); // Force refetch to update the list
    });
  };

  return (
    // ...

    <ul>
      {items.map((item) => (
        <li key={item.id}>
          <label>
            {/* Checkbox to mark the item as done */}
            <input
              type="checkbox"
              checked={item.isDone}
              onChange={() => handleMarkAsDone(item)}
            />
            {item.title}
          </label>
        </li>
      ))}
    </ul>

    // ...
  );
};
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

4. حذف یک مورد

این تقریباً همان کاری است که در علامت گذاری یک مورد به عنوان انجام شده انجام دادیم، اما با روش DELETE. هنگام کلیک بر روی دکمه “حذف”، برنامه باید تابعی را فراخوانی کند که تماس API را ارسال می کند:

// src/App.jsx

const App = () => {
  // ...

  const handleDelete = (item) => {
    fetch(`/api/grocery-list/${item.id}`, {
      method: "DELETE",
    }).then(() => {
      setRefresh(!refresh); // Force refetch to update the list
    });
  };

  return (
    // ...

    <ul>
      {items.map((item) => (
        <li key={item.id}>
          <label>
            {/* Checkbox to mark the item as done */}
            <input type="checkbox" onChange={() => handleMarkAsDone(item)} />
            {item.title}
          </label>

          {/* Delete button */}
          <button onClick={() => handleDelete(item)}>Delete</button>
        </li>
      ))}
    </ul>

    // ...
  );
};
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

کد نهایی قسمت اول مثال

کد نهایی باید به شکل زیر باشد:

// src/App.jsx

const App = () => {
  const [items, setItems] = useState([]);
  const [title, setTitle] = useState("");
  const [refresh, setRefresh] = useState(false);

  // Retrieve all the items
  useEffect(() => {
    fetch("/api/grocery-list")
      .then((data) => data.json())
      .then(({ items }) => setItems(items));
  }, [refresh]);

  // Adds a new item
  const handleAdd = (event) => {
    event.preventDefault();

    fetch("/api/grocery-list", {
      method: "POST",
      body: JSON.stringify({ title }),
    }).then(() => {
      setRefresh(!refresh);
      setTitle("");
    });
  };

  // Mark an item as done
  const handleMarkAsDone = (item) => {
    if (item.isDone) {
      return;
    }

    fetch(`/api/grocery-list/${item.id}/done`, {
      method: "PUT",
    }).then(() => {
      setRefresh(!refresh);
    });
  };

  // Deletes an item
  const handleDelete = (item) => {
    fetch(`/api/grocery-list/${item.id}`, {
      method: "DELETE",
    }).then(() => {
      setRefresh(!refresh);
    });
  };

  return (
    <>
      <form onSubmit={handleAdd}>
        <input
          required
          type="text"
          onChange={(event) => setTitle(event.target.value)}
          value={title}
        />
        <button type="submit">Add</button>
      </form>
      <ul>
        {items.map((item) => (
          <li key={item.id}>
            <label>
              <input
                type="checkbox"
                checked={item.isDone}
                onChange={() => handleMarkAsDone(item)}
              />
              {item.title}
            </label>
            <button onClick={() => handleDelete(item)}>delete</button>
          </li>
        ))}
      </ul>
    </>
  );
};
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

اولین Refactor: ایجاد خدمات

اکنون که همه چیز را در جای خود داریم و کار می کنیم، بیایید کد را بازسازی کنیم.

اولین کاری که می‌توانیم برای بهتر کردن کد انجام دهیم، ایجاد یک سرویس برای تماس‌های API است. خدمات اساساً توابع جاوا اسکریپت هستند که مسئول فراخوانی APIها هستند.

این مفید است زیرا اگر نیاز به تماس با API در مکان های دیگر دارید، به جای کپی پیست کل، فقط با سرویس تماس بگیرید. fetch زنگ زدن.

// src/services/grocery-list.js

const basePath = "/api/grocery-list";

export const getItems = () => fetch(basePath).then((data) => data.json());

export const createItem = (title) =>
  fetch(basePath, {
    method: "POST",
    body: JSON.stringify({ title }),
  });

export const markItemAsDone = (itemId) =>
  fetch(`${basePath}/${itemId}/done`, {
    method: "PUT",
  });

export const deleteItem = (itemId) =>
  fetch(`${basePath}/${itemId}`, {
    method: "DELETE",
  });
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

توجه داشته باشید که سرویس‌ها یک Promise را برمی‌گردانند و همه تماس‌های دولتی حذف شدند. همچنین مسیر پایه تکراری نقاط انتهایی API را با یک ثابت جایگزین کردیم.

حالا بیایید قدیمی را جایگزین کنیم fetch با سرویس های جدید کامپوننت را فراخوانی می کند:

// src/App.jsx

// Importing the services
import {
  createItem,
  deleteItem,
  getItems,
  markItemAsDone,
} from "./services/grocery-list";

const App = () => {
  // ...

  useEffect(() => {
    // Service call
    getItems().then(({ items }) => {
      setItems(items);
    });
  }, [refresh]);

  const handleAdd = (event) => {
    event.preventDefault();

    // Service call
    createItem(title).then(() => {
      setRefresh(!refresh);
      setTitle("");
    });
  };

  const handleMarkAsDone = (item) => {
    if (item.isDone) {
      return;
    }
    // Service call
    markItemAsDone(item.id).then(() => {
      setRefresh(!refresh);
    });
  };

  const handleDelete = (item) => {
    // Service call
    deleteItem(item.id).then(() => {
      setRefresh(!refresh);
    });
  };

  // ...
};
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

این بسیار قابل خواندن و آزمایش تر است. شما می‌توانید هر سرویس را به‌جای آزمایش کامل مؤلفه به‌صورت جداگانه آزمایش کنید. همچنین، درک اینکه کد قرار است چه کاری انجام دهد بسیار ساده تر است، به عنوان مثال:

// Get the items, then set the items.
getItems().then(({ items }) => {
  setItems(items);
});
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

Refactor دوم: انتزاع تماس HTTP

این grocery-list این سرویس به شدت به کتابخانه Fetch متکی است. اگر تصمیم بگیریم آن را به Axios تغییر دهیم، همه تماس ها باید تغییر کنند. همچنین، لایه سرویس نیازی به دانستن ندارد چگونه برای فراخوانی API، اما فقط که API باید فراخوانی شود.

برای جلوگیری از اختلاط این مسئولیت ها، من دوست دارم یک API Adapter ایجاد کنم. نام در واقع مهم نیست –هدف در اینجا داشتن یک مکان واحد است که در آن تماس‌های HTTP API پیکربندی می‌شوند.

// src/adapters/api.js

const basePath = "/api";

const api = {
  get: (endpoint) => fetch(`${basePath}/${endpoint}`),
  post: (endpoint, body) =>
    fetch(`${basePath}/${endpoint}`, {
      method: "POST",
      body: body && JSON.stringify(body),
    }),
  put: (endpoint, body) =>
    fetch(`${basePath}/${endpoint}`, {
      method: "PUT",
      body: body && JSON.stringify(body),
    }),
  delete: (endpoint) =>
    fetch(`${basePath}/${endpoint}`, {
      method: "DELETE",
    }),
};

export { api };
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

این تنها فایل در کل برنامه است که با تماس های HTTP سروکار دارد. سایر فایل هایی که نیاز به فراخوانی API دارند، فقط باید این متدها را فراخوانی کنند.

حالا اگر تصمیم دارید که Fetch را با Axios جایگزین کنید، فقط این تک فایل را تغییر دهید و آماده هستید.

در سمت تست، اکنون می توان هر روش API را به صورت جداگانه و بدون اتکا به تماس سرویس آزمایش کرد.

در مورد خدمات صحبت می کنیم، بیایید قدیمی را جایگزین کنیم fetch تماس با جدید api. آنهایی که

// src/services/grocery-list

import { api } from "../adapters/api";

const resource = "grocery-list";

export const getItems = () => api.get(resource).then((data) => data.json());

export const createItem = (title) => api.post(resource, { title });

export const markItemAsDone = (itemId) => api.put(`${resource}/${itemId}/done`);

export const deleteItem = (itemId) => api.delete(`${resource}/${itemId}`);
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

وای، خیلی تمیزتر! توجه داشته باشید که برخی از مسئولیت‌هایی که در سطح درخواست هستند، دیگر اینجا نیستند، مانند تبدیل یک شی JSON به یک رشته. این مسئولیت سرویس ها نبود و اکنون لایه API این کار را انجام می دهد.

باز هم، کد خوانا و آزمایش پذیرتر شده است.

Refactor سوم: ایجاد قلاب

ما سرویس‌ها و لایه‌های API را داریم، حالا بیایید لایه ارائه، یعنی مؤلفه UI را بهبود ببخشیم.

مؤلفه ها در حال حاضر مستقیماً با خدمات تماس می گیرند. این به خوبی کار می کند، اما نگه داشتن وضعیت و فراخوانی سرویس بیشتر شبیه به یک ویژگی برنامه شما است به جای مسئولیت هر مؤلفه ای که نیاز به فراخوانی API دارد.

اولین قلابی که میخواهیم ایجاد کنیم قلاب است useGetGroceryListItems()، که شامل getItems() تماس API.

// src/hooks/grocery-list.js

// Default module import
import * as groceryListService from "../services/grocery-list";

export const useGetGroceryListItems = () => {
  const [items, setItems] = useState([]);
  const [refresh, setRefresh] = useState(false);

  useEffect(() => {
    groceryListService.getItems().then(({ items }) => {
      setItems(items);
    });
  }, [refresh]);

  const refreshItems = () => {
    setRefresh(!refresh);
  };

  return { items, refreshItems };
};
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

توجه داشته باشید که ما اساساً رفتاری را که قبلاً روی مؤلفه بود در قلاب جدید کپی کردیم. ما همچنین نیاز به ایجاد refreshItems()، بنابراین می توانیم به جای تماس مستقیم دوباره با سرویس، داده ها را در زمانی که می خواهیم به روز نگه داریم.

ما همچنین ماژول سرویس را وارد می کنیم تا از آن استفاده کنیم groceryListService.getItems()، به جای اینکه فقط تماس بگیرید getItems(). این به این دلیل است که قلاب‌های ما نام‌های عملکرد مشابهی خواهند داشت، بنابراین برای جلوگیری از تداخل و همچنین بهبود خوانایی، کل ماژول سرویس وارد می‌شود.

حالا بیایید بقیه قلاب ها را برای سایر ویژگی ها (ایجاد، به روز رسانی و حذف) ایجاد کنیم.

// src/hooks/grocery-list.js

export const useCreateGroceryListItem = () => {
  const createItem = (title) => groceryListService.createItem(title);

  return { createItem };
};

export const useMarkGroceryListItemAsDone = () => {
  const markItemAsDone = (item) => {
    if (item.isDone) {
      return;
    }
    groceryListService.markItemAsDone(item.id);
  };

  return { markItemAsDone };
};

export const useDeleteGroceryListItem = () => {
  const deleteItem = (item) => groceryListService.deleteItem(item.id);

  return { deleteItem };
};
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

سپس باید تماس های سرویس را با هوک های موجود در کامپوننت جایگزین کنیم.

// src/App.jsx

// Hooks import
import {
  useGetGroceryListItems,
  useCreateGroceryListItem,
  useMarkGroceryListItemAsDone,
  useDeleteGroceryListItem,
} from "./hooks/grocery-list";

const App = () => {
  // ...
  const { items, refreshItems } = useGetGroceryListItems();
  const { createItem } = useCreateGroceryListItem();
  const { markItemAsDone } = useMarkGroceryListItemAsDone();
  const { deleteItem } = useDeleteGroceryListItem();

  // ...

  const handleMarkAsDone = (item) => {
    // Validation moved to hook and passing `item` instead of `item.id`
    markItemAsDone(item).then(() => refreshItems());
  };

  const handleDelete = (item) => {
    // Passing `item` instead of `item.id`
    deleteItem(item).then(() => refreshItems());
  };

  // ...
};
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

و بس. اکنون برنامه از قلاب ها استفاده می کند که بسیار مفید است زیرا اگر به همین ویژگی در سایر اجزا نیاز دارید، فقط آن را فراخوانی می کنید.

اگر از یک راه حل مدیریت حالت مانند Redux، Context API یا Zustand استفاده می کنید، می توانید به جای فراخوانی در سطح کامپوننت، تغییرات حالت را در داخل هوک ها انجام دهید. این کمک می کند تا همه چیز واضح تر شود و به خوبی بین مسئولیت ها تقسیم شود.

آخرین Refactor: اضافه کردن حالت بارگذاری

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

پس از اضافه کردن حالت بارگذاری به هر هوک، فایل به شکل زیر در می‌آید:

// src/hooks/grocery-list.js

export const useGetGroceryListItems = () => {
  const [isLoading, setIsLoading] = useState(false); // Creating loading state
  const [items, setItems] = useState([]);
  const [refresh, setRefresh] = useState(false);

  useEffect(() => {
    setIsLoading(true); // Adding loading state
    groceryListService.getItems().then(({ items }) => {
      setItems(items);
      setIsLoading(false); // Removing loading state
    });
  }, [refresh]);

  const refreshItems = () => {
    setRefresh(!refresh);
  };

  return { items, refreshItems, isLoading };
};

export const useCreateGroceryListItem = () => {
  const [isLoading, setIsLoading] = useState(false); // Creating loading state

  const createItem = (title) => {
    setIsLoading(true); // Adding loading state
    return groceryListService.createItem(title).then(() => {
      setIsLoading(false); // Removing loading state
    });
  };

  return { createItem, isLoading };
};

export const useMarkGroceryListItemAsDone = () => {
  const [isLoading, setIsLoading] = useState(false); // Creating loading state

  const markItemAsDone = (item) => {
    if (item.isDone) {
      return;
    }

    setIsLoading(true); // Adding loading state
    return groceryListService.markItemAsDone(item.id).then(() => {
      setIsLoading(false); // Removing loading state
    });
  };

  return { markItemAsDone, isLoading };
};

export const useDeleteGroceryListItem = () => {
  const [isLoading, setIsLoading] = useState(false); // Creating loading state

  const deleteItem = (item) => {
    setIsLoading(true); // Adding loading state
    return groceryListService.deleteItem(item.id).then(() => {
      setIsLoading(false); // Removing loading state
    });
  };

  return { deleteItem, isLoading };
};
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

اکنون باید وضعیت بارگذاری صفحه را به هر هوک متصل کنیم:

// src/App.jsx

const App = () => {
  // ...

  // Getting loading states and renaming to avoid conflicts
  const { items, refreshItems, isLoading: isFetchingItems } = useGetGroceryListItems();
  const { createItem, isLoading: isCreatingItem } = useCreateGroceryListItem();
  const { markItemAsDone, isLoading: isUpdatingItem } = useMarkGroceryListItemAsDone();
  const { deleteItem, isLoading: isDeletingItem } = useDeleteGroceryListItem();

  // Read each loading state and convert them to a component-level value
  const isLoading = isFetchingItems || isCreatingItem || isUpdatingItem || isDeletingItem;

  // ...

  return (
    <>
      <form onSubmit={handleAdd}>
        <input
          required
          type="text"
          onChange={(event) => setTitle(event.target.value)}
          value={title}
          disabled={isLoading} {/* Loading State */}
        />
        <button type="submit" disabled={isLoading}> {/* Loading State */}
          Add
        </button>
      </form>
      <ul>
        {items.map((item) => (
          <li key={item.id}>
            <label>
              <input
                type="checkbox"
                checked={item.isDone}
                onChange={() => handleMarkAsDone(item)}
                disabled={isLoading} {/* Loading State */}
              />
              {item.title}
            </label>
            <button onClick={() => handleDelete(item)} disabled={isLoading}> {/* Loading State */}
              delete
            </button>
          </li>
        ))}
      </ul>
    </>
  );
};
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

Bonus Refactor: یک ابزار کاربردی ایجاد کنید

توجه کنید که در useMarkGroceryListItemAsDone() hook ما منطقی داریم که می گوید آیا آیتم باید به روز شود یا نه:

// src/hooks/grocery-list.js

const markItemAsDone = (item) => {
  if (item.isDone) {
    return; // Don't call the service
  }

  // Call the service and update the item
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

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

یک راه حل ممکن این است که یک Util ایجاد کنید و این منطق را در آنجا اضافه کنید، بنابراین ما فقط تابع موجود در hook را فراخوانی می کنیم:

// src/utils/grocery-list.js

export const shouldUpdateItem = (item) => !item.isDone;
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

و سپس این ابزار را در قلاب صدا بزنید:

export const useMarkGroceryListItemAsDone = () => {
  // ...

  const markItemAsDone = (item) => {
    // Calling the util
    if (!shouldUpdateItem(item)) {
      return;
    }

    // ...
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

اکنون هوک ها به هیچ منطق مرتبط با کسب و کار وابسته نیستند: آنها فقط توابع را فراخوانی می کنند و مقادیر آن را برمی گردانند.

بسته بندی

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

ما اساساً برای بهتر کردن کد، اصل مسئولیت تک را اعمال کردیم. این کد می تواند به عنوان پایه ای برای ساخت سرویس های دیگر، اتصال با API های خارجی، ایجاد اجزای دیگر و غیره استفاده شود.

همانطور که گفته شد، شما همچنین می توانید راه حل مدیریت ایالت خود را در اینجا وصل کنید و وضعیت جهانی برنامه را در قلاب هایی که ما ایجاد کرده ایم مدیریت کنید.

برای بهبود بیشتر کد، ایده خوبی است که با React Query کار کنید تا از ویژگی‌های آن مانند کش کردن، واکشی مجدد و باطل کردن خودکار استفاده کنید.

خودشه! امیدوارم امروز چیز جدیدی یاد گرفته باشید تا سفر برنامه نویسی خود را حتی بهتر کنید!


اگر بازخورد یا پیشنهادی دارید، برای من ایمیل بفرستید

کد نویسی عالی!

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

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

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

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