مرجع Hooks API

این مطلب توضیحاتی در مورد API های هوک های قرار گرفته در React آورده است.

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

  • Basic Hooks
    • useState
    • useEffect
    • useContext
  • Additional Hooks
    • useReducer
    • useCallback
    • useMemo
    • useRef
    • useImperativeHandle
    • useLayoutEffect
    • useDebugValue

Hook های پایه

useState

const [state, setState] = useState(initialState);

یک مقدار حالت‌دار و یک تابع جهت آپدیت آن، برمی‌گرداند.

در حین رندر اولیه، state برگردانده شده همان مقداری است که به عنوان اولین آرگومان منتقل شده است (initialState).

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

setState(newState);

در حین رندرهای بعدی، اولین مقدار برگردانده شده توسط useState همیشه آخرین حالت بعد از اعمال آپدیت خواهد بود.

آپدیت های تابعی

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

function Counter({initialCount}) {
  const [count, setCount] = useState(initialCount);
  return (
    <>
      Count: {count}
      <button onClick={() => setCount(initialCount)}>Reset</button>
      <button onClick={() => setCount(prevCount => prevCount + 1)}>+</button>
      <button onClick={() => setCount(prevCount => prevCount - 1)}>-</button>
    </>
  );
}

دکمه‌های + و – از فرم تابعی استفاده می‌کنند چون مقدار آپدیت شده بر مبنای مقدار قبلی است. اما دکمۀ Reset از فرم معمولی استفاده می‌کند زیرا همیشه شمارنده را به مقدار صفر می‌گرداند.

نکته: بر خلاف متد setState موجود در کامپوننت های تابعی، useState به طور خودکار آبجکت‌های آپدیت را ادغام نمی‌کند. شما می‌توانید با ترکیب فرم تابع آپدیت کننده با سینتکس انتشار آبجکت، این رفتار را تکرار کنید:

setState(prevState => {
  // Object.assign would also work
  return {...prevState, ...updatedValues};
});

گزینۀ دیگر استفاده از useReducer است که برای مدیریت آبجکت‌های حالتی که شامل زیرمقادیر چندگانه هستند مناسب‌تر است.

مقداردهی اولیۀ سست

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

const [state, setState] = useState(() => {
  const initialState = someExpensiveComputation(props);
  return initialState;
});

useEffect

useEffect(didUpdate);

یک تابع شامل کد دستوری و احتمالا اثرگذار را می‌پذیرد.

تغییرات، subscriptions، تایمرها، logging و اثرات جانبی دیگر در داخل بدنۀ اصلی کامپوننت های تابعی مجاز نیستند. انجام این کار باعث به وجود آمدن مشکلات و ناسازگاری‌هایی در رابط کاربری می‌گردد.

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

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

پاکسازی یک effect

اغلب، effect ها منابعی را می‌سازند که نیاز است قبل از ترک کردن کامپوننت از روی صفحه، پاکسازی شوند، مانند subscription و آی‌دی تایمر. برای انجام این کار، تابع منتقل شده به useEffect می‌تواند یک تابع پاکسازی را برگرداند. برای مثال جهت ساخت یک subscription:

useEffect(() => {
  const subscription = props.source.subscribe();
  return () => {
    // Clean up the subscription
    subscription.unsubscribe();
  };
});

تابع پاکسازی قبل از اینکه کامپوننت از رابط کاربری حذف شود اجرا می‌شود تا از نشت حافظه جلوگیری کند. به علاوه، اگر یک کامپوننت چند بار رندر کند (که معمولا هم اینگونه است)، effect قبلی پیش از اینکه effect بعدی اجرا شود پاکسازی شده است. در مثال ما، به این معنی است که در هر آپدیت یک subscription جدید ساخته شده است. جهت جلوگیری از اجرای یک effect قبل از هر آپدیت، بخش بعدی را مطالعه کنید.

زمان‌بندی effect ها

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

هرچند، تمام effect ها قابل به تاخیر انداختن نیستند. برای مثال، یک تغییر DOM که در معرض دید کاربر است باید به طور همزمان و قبل از تصویر بعدی اجرا شود به طوری که کاربر در صفحه یک ناسازگاری را مشاهده نکند. برای این گونه  effectها، ری اکت یک Hook اضافی با نام useLayoutEffect فراهم کرده است. این Hook کاملا مشابه useEffect است و تنها تفاوت آن در هنگام اجرا شدن است.

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

اجرای شرطی یک effect

رفتار پیش‌فرض effect ها، اجرا شدن پس از اتمام هر رندر است. به این صورت، یک effect همیشه در صورتی که یکی از ورودی‌هایش تغییر کند بازسازی می‌شود.

هرچند در برخی موارد مانند مثال subscription از بخش قبلی، ممکن است بیش از حد باشد. اگر تغییری در prop های source  رخ ندهد، ما نیازی به ساخت یک subscription جدید در هر آپدیت نداریم.

برای پیاده سازی این عمل، آرگومان دومی را به useEffect منتقل کنید که آرایه‌ای از مقادیری است که effect به آن بستگی دارد. مثال آپدیت شدۀ ما به این صورت است:

useEffect(
  () => {
    const subscription = props.source.subscribe();
    return () => {
      subscription.unsubscribe();
    };
  },
  [props.source],
);

اکنون فقط زمانی subscription بازسازی می‌شود که props.source تغییر کند.

انتقال یک آرایۀ خالی [] به ری اکت می‌گوید که effect تو به هیچ مقداری از کامپوننت وابسته نیست در نتیجه آن effect فقط هنگام mount اجرا شده و هنگام unmounts پاکسازی می‌شود؛ هنگام آپدیت‌ها اجرا نخواهد شد.

نکته: آرایۀ ورودی‌ها به عنوان آرگومان‌ها به تابع effect منتقل نشده است. به طور مفهومی، هرچند آن چیزی است که آنها نمایندگی می‌کنند: هر مقدار ارجاع داده شده داخل تابع effect باید در آرایۀ ورودی‌ها نیز ظاهر باشد. در آینده، یک کامپایلرِ به اندازۀ کافی پیشرفته می‌تواند به طور خودکار این آرایه را بسازد.

useContext

const context = useContext(Context);

یک آبجکت متنی (context) را پذیرفته (مقداری که از React.createContext برگشت داده شده است) و مقدار آن را برمی‌گرداند، همانطور که توسط نزدیک ترین فراهم کنندۀ context برای context داده شده، آماده شده است.

وقتی فراهم کننده (provider) آپدیت شود، این Hook یک رندر کننده را با آخرین مقدار context اجرا می‌کند.

Hook های دیگر

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

useReducer

const [state, dispatch] = useReducer(reducer, initialState);

گزینۀ دیگری برای useState است. یک کاهش دهنده (reducer) از نوع (state, action) => newState را پذیرفته و حالت کنونی را به همراه متد dispatch برمی‌گرداند. (اگر با Redux آشنا هستید، همین الان هم می‌دانید که این Hook چگونه کار می‌کند.)

در اینجا همان مثال شمارنده که در بخش useState آورده شده بود، با یک کاهش دهنده بازنویسی شده است:

const initialState = {count: 0};

function reducer(state, action) {
  switch (action.type) {
    case 'reset':
      return initialState;
    case 'increment':
      return {count: state.count + 1};
    case 'decrement':
      return {count: state.count - 1};
    default:
      // A reducer must always return a valid state.
      // Alternatively you can throw an error if an invalid action is dispatched.
      return state;
  }
}

function Counter({initialCount}) {
  const [state, dispatch] = useReducer(reducer, {count: initialCount});
  return (
    <>
      Count: {state.count}
      <button onClick={() => dispatch({type: 'reset'})}>
        Reset
      </button>
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
    </>
  );
}

مقداردهی اولیۀ سست

useReducer یک آرگومان سوم اختیاری یعنی initialAction را می‌پذیرد. اگر فراهم باشد، initialAction حین رندر اولیه اعمال می‌شود. این کار در محاسبۀ یک حالت اولیه که شامل مقادیر منتقل شده توسط prop ها می‌باشد مفید است:

const initialState = {count: 0};

function reducer(state, action) {
  switch (action.type) {
    case 'reset':
      return {count: action.payload};
    case 'increment':
      return {count: state.count + 1};
    case 'decrement':
      return {count: state.count - 1};
    default:
      // A reducer must always return a valid state.
      // Alternatively you can throw an error if an invalid action is dispatched.
      return state;
  }
}

function Counter({initialCount}) {
  const [state, dispatch] = useReducer(
    reducer,
    initialState,
    {type: 'reset', payload: initialCount},
  );

  return (
    <>
      Count: {state.count}
      <button
        onClick={() => dispatch({type: 'reset', payload: initialCount})}>
        Reset
      </button>
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
    </>
  );
}

useReducer معمولا هنگامی که شما منطق پیچیده‌ای دارید که شامل چند زیرمقدار است بر useState ارجحیت دارد. همچنین به شما اجازه می‌دهد عملکرد کامپوننت‌هایی را که آپدیت‌هایی در سطوح پایین را اجرا می‌کنند بهینه سازی کنید چون می‌توانید dispatch را به پایین منتقل کنید به جای اینکه بازخوانی‌ها را انجام دهید.

useCallback

const memoizedCallback = useCallback(
  () => {
    doSomething(a, b);
  },
  [a, b],
);

یک memoized callback را برمی‌گرداند.

یک فراخوانی مرتب شدۀ خطی (inline) و آرایه‌ای از ورودی‌ها را منتقل می‌کند. useCallback یک فراخوانی memorize شده را برمی‌گرداند که فقط در صورتی که یکی از ورودی‌ها تغییر کنند، تغییر می‌کند. این هوک هنگام انتقال فراخوانی‌ها به کامپوننت های child بهینه سازی شده که روی برابری مرجع تکیه کرده و جهت جلوگیری از رندرهای غیرضروری (مانند shouldComponentUpdate) می‌باشند، مفید است.

useCallback(fn, inputs) معادل useMemo(() => fn, inputs) می‌باشد.

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

useMemo

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

یک مقدار memoize شده را برمی‌گرداند.

یک تابع create و آرایه‌ای از ورودی‌ها را منتقل می‌کند. useMemo فقط در صورتی که یکی از ورودی‌ها تغییر کنند، مقدار memoize شده را مجددا محاسبه می‌کند. این بهینه سازی از محاسبات زیاد در هر رندر جلوگیری می‌کند.

اگر آرایه‌ای فراهم نشده باشد، هر وقت یک شیء تابع جدید به عنوان اولین آرگومان منتقل شود، مقدار جدید محاسبه خواهد شد. (با یک تابع مرتب شدۀ خطی در هر رندر)

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

useRef

const refContainer = useRef(initialValue);

useRef یک آبجکت ref تغییرپذیر که مشخصۀ .current آن به آرگومانِ منتقل شده مقداردهی اولیه شده است (initialValue) را برمی‌گرداند. آبجکت برگشت داده شده در تمام طول عمر کامپوننت باقی خواهد ماند.

یک مورد پرکاربرد این Hook دسترسی به یک child به طور دستوری است:

function TextInputWithFocusButton() {
  const inputEl = useRef(null);
  const onButtonClick = () => {
    // `current` points to the mounted text input element
    inputEl.current.focus();
  };
  return (
    <>
      <input ref={inputEl} type="text" />
      <button onClick={onButtonClick}>Focus the input</button>
    </>
  );
}

دقت داشته باشید که useRef() بیشتر از قابلیت ref کاربردی است. همینطور برای نگهداری هر مقدار تغییرپذیری مناسب است.

useImperativeHandle

useImperativeHandle(ref, createHandle, [inputs])

useImperativeHandle مقدار شیء را که در معرض کامپوننت های والد قرار دارد هنگام استفاده از ref سفارشی می‌کند. مثل همیشه، کدهای دستوری‌ای که از ref ها استفاده می‌کنند در اکثر موارد باید اجتناب شوند. useImperativeHandle بهتر است همراه با forwardRef استفاده شود:

function FancyInput(props, ref) {
  const inputRef = useRef();
  useImperativeHandle(ref, () => ({
    focus: () => {
      inputRef.current.focus();
    }
  }));
  return <input ref={inputRef} ... />;
}
FancyInput = forwardRef(FancyInput);

در این مثال، یک کامپوننت والد که <FancyInput ref={fancyInputRef} /> را رندر می‌کند، می‌تواند fancyInputRef.current.focus() را فراخوانی کند.

useLayoutEffect

نوع عملکرد آن مشابه useEffect است با این تفاوت که useLayoutEffect به طور همزمان پس از تمام تغییرات DOM اجرا می‌شود. از آن برای خواندن layout از DOM و رندر مجدد همزمان استفاده کنید. آپدیت برنامه ریزی شده داخل useLayoutEffect به طور همزمان پاک می‌شود، قبل از اینکه مرورگر شانسی برای paint داشته باشد.

هنگامی که امکان جلوگیری از بلاک کردن آپدیت های بصری وجود دارد، useEffect استاندارد ارجحیت دارد.

نکته: اگر در حال هجرت از کامپوننت class هستید، useLayoutEffect در همان فاز componentDidMount و componentDidUpdate اجرا می‌شود، لذا اگر مطمئن نیستید که از کدام effect Hook استفاده کنید، useLayoutEffect کمترین ریسک را خواهد داشت.

useDebugValue

useDebugValue(value)

useDebugValue می‌تواند جهت نمایش یک لیبل برای Hook های سفارشی در DevTools استفاده شود.

برای مثال، Hook سفارشی useFriendStatus که در صفحۀ قبل (ساخت Hook های سفارشی خودتان) تعریف شد را در نظر بگیرید:

function useFriendStatus(friendID) {
  const [isOnline, setIsOnline] = useState(null);

  // ...

  // Show a label in DevTools next to this hook
  // e.g. "FriendStatus: Online"
  useDebugValue(isOnline ? 'Online' : 'Offline');

  return isOnline;
}

نکته: اضافه کردن مقدارهای debug را به هر Hook سفارشی توصیه نمی‌کنیم. این کار بیشتر در Hook های سفارشی‌ای که بخشی از کتابخانۀ به اشتراک گذاشته شده هستند مفید است.

1 Star2 Stars3 Stars4 Stars5 Stars (2 votes, average: 4٫50 out of 5)
Loading...
counter customizable free hit