چگونه فراخوانی‌های useEffect در HTTP را به سادگی و با استفاده از RxJS لغو کنیم؟

گردآوری و تالیف : عرفان کاکایی
تاریخ انتشار : 16 خرداد 1398
دسته بندی ها : react

حال که Hookهای React به طور رسمی منتشر شده‌اند، الگوهای بیشتری در حال پدیدار شدن بر روی اینترنت هستند.

useEffect

هوک useEffect در میان معروف‌ترین هوک‌ها است؛ زیرا می‌تواند جایگزین componentDidMount، componentDidUpdate و componentWillUnmount شود. اکثر راه‌اندازی‌ها، بروزرسانی‌ها، و منطق‌های تمیزکاری که یک کامپوننت می‌تواند نیاز داشته باشد، می‌توانند داخل useEffect قرار بگیرند.

یک تجربه کاربری زشت

من بر روی یکی از پروژه‌های اخیر خود با سناریو‌ای مواجه شدم که useEffect بر روی آن دسته از درخواست‌های HTTP فعالیت می‌کرد که من دیگر به آن‌ها علاقه‌ای نداشتم. ار نظر مفهومی، رابط کاربری آن به این صورت بود:

  • در اولین باری که برنامه بارگذاری می‌شود، لیست میوه‌ها را دریافت کن و یک دکمه (<button>) برای هر کدام رندر کن.
  • بر روی یک دکمه کلیک کنید، تا جزئیات میوه مورد نظر را ببینید.

اما ببینید وقتی که من به طور پشت سر هم بر روی چند میوه کلیک می‌کنم چه اتفاقی می‌افتد:

چرا پس از این که من دیگر کلیک نمی‌کردم، بخش جزئیات میوه باز هم به تغییر یافتن ادامه داد؟

کد

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

اگر می‌خواهید با این مقاله ادامه دهید، در اینجا لینک‌های Codesandbox و GitHub مربوطه را می‌توانید بیابید. این فایل useFruitDetail.js نام دارد.

import { useEffect, useState } from 'react';
import { getFruit } from './api';

export const useFruitDetail = fruitName => {
  const [fruitDetail, setFruitDetail] = useState(null);

  useEffect(
    () => {
      if (!fruitName) {
        return;
      }

      getFruit(fruitName).then(setFruitDetail);
    },
    [fruitName]
  );

  return fruitDetail;
};

هر زمان که fruitName تغییر کند، ما جزئیات آن را درخواست می‌کنیم. ما هیچ راهی برای لغو درخواست مورد نظر نداریم! پس این که این نتایج را به طور سریع مجددا اجرا کنیم، ممکن است باعث بروز تغییراتی در state شود که ما دیگر به آن‌ها علاقه‌ای نداریم.

اگر این را به یک رابط کاربری رندر کنید، یک تجربه کاربری به هم ریخته به دست خواهید آورد که در آن بخش جزئیات همینطور عوض می‌شود تا این که درخواست نهایی resolve شود.

وارد RxJS شوید

نادیده گرفتن درخواست‌های قدیمی در RxJS بدیهی است.

این بخش کد من، یعنی کد effect باید تغییر کند.

() => {
  if (!fruitName) {
    return;
  }

  getFruit(fruitName).then(setFruitDetail);
}

به جای یک promise، بیایید getFruit را با استفاده از تابع from در RxJS به یک Observable رندر کنیم. و به جای .then، ما .subscribe را فراخوانی خواهیم کرد.

import { from } from 'rxjs';

...

() => {
  if (!fruitName) {
    return;
  }

  from(getFruit(fruitName))
    .subscribe(setFruitDetail);
}

این کار هنوز مشکل را برطرف نمی‌کند. ما همچنان باید اگر fruitName تغییر کرد، از آن unsubscribe کنیم.

طبق اسناد React، ما می‌توانیم یک تابع را برگردانیم که در پایان effect ما اجرا خواهد شد. این به عنوان منطق تمیزکاری عمل می‌کند.

پس چیزی به مانند این کد:

() => {
  if (!fruitName) {
    return;
  }

  const subscription = from(getFruit(fruitName))
    .subscribe(setFruitDetail);

  return () => {
    subscription.unsubscribe();
  };
}

این کد کار می‌کند!

این تجربه بسیار تمیزتر است!

با کلیک کردن بر روی یک میوه دیگر، useEffect می‌بیند که fruitName تغییر می‌کند و منطق تمیزکاری قبلی effect را اجرا می‌کند. در نتیجه، ما از فراخوانی دریافت قبلی unsubscribe می‌کنیم و بر روی دریافت فعلی تمرکز می‌کنیم.

حال رابط کاربری ما صبر می‌کند تا کلیک کردن کاربر تمام شود و جزئیات آخرین میوه را برگرداند.

منبع

مقالات پیشنهادی