همانطور که در قسمت قبل گفتم ما می توانیم ingredients را در firebase ثبت کنیم اما هنوز کدهای دریافت آن را ننوشته ایم. در واقع هدف این است که به محض render شدن کامپوننت ingredients.js، محتویات را از سرور firebase دریافت کنیم. حتما یادتان است که برای چنین کاری از lifecycle hook هایی مثل componentDidMount استفاده می کردیم اما این کامپوننت از نوع کاربردی است و lifecycle hook ها فقط در کامپوننت های کلاس محور قابل دسترسی بود. React برای حل این مشکل useEffect را معرفی می کند که یکی دیگر از react hook ها می باشد. ما این hook را وارد فایل ingredients.js می کنیم:
import React, { useState, useEffect } from 'react';
برای استفاده از useEffect باید آن را درون کامپوننت کاربردی خود صدا زده و یک تابع را به آن پاس بدهید. نکته مهم اینجاست که useEffect بعد از هر بار render شدن کامپوننت شما اجرا خواهد شد. یعنی هر بار که کامپوننت Ingredients ما render شود، پس از آن useEffect و تابع درونش نیز اجرا خواهد شد. بنابراین به طور خلاصه مهم ترین نکات در مورد useEffect دو نکته هستند:
چرا به صورت عادی از fetch برای دریافت اطلاعاتمان استفاده نمی کنیم و عملیات دریافت اطلاعات از firebase را از درون useEffect انجام می دهیم؟ به طور مثال ابتدا fetch را صدا می زنیم:
fetch('https://react-hooks-update.firebaseio.com/ingredients.json')
سپس مثل جلسه قبل با دستور then بدنه پاسخ را دریافت می کنیم:
fetch('https://react-hooks-update.firebaseio.com/ingredients.json') .then(response => response.json())
در مرحله بعد محتویات را از بدنه پاسخ را دریافت می کنیم که یک یک شیء است و باید آن را تبدیل به یک آرایه کنیم، بنابراین:
fetch('https://react-hooks-update.firebaseio.com/ingredients.json') .then(response => response.json()) .then(responseData => { const loadedIngredients = []; for (const key in responseData) { loadedIngredients.push({ id: key, title: responseData[key].title, amount: responseData[key].amount }); } setUserIngredients(loadedIngredients); });
در کد بالا یک آرایه خالی ساخته ام و سپس با یک حلقه For تک تک key های پاسخ را دریافت می کنم. اگر یادتان باشد key ها همان id های خاص ساخته شده توسط firebase بود:
بنابراین key های responseData همین id های طولانی و خاص هستند و مقدار این key ها نیز ingredients ما می باشد، یعنی شیء های تو در تو. با این کار یک شیء جدید را به آرایه loadedIngredients اضافه می کنم که حاوی مقادیر ما است. در نهایت با استفاده از setUserIngredients نیز محتویات را به state وارد می کنیم. سوال اصلی اینجاست که چرا این کار ها را در useEffect انجام بدهیم؟
اگر کد بالا را درون کامپوننت خود قرار دهید (بدون اینکه از useEffect استفاده کنید) و در مرورگر اجرا کنید، از سربرگ network متوجه می شوید که در حال ارسال بی نهایت درخواست هستید بنابراین در یک حلقه گیر افتاده ایم! چرا؟ به دلیل اینکه کدها را مستقیما درون کامپوننت قرار داده ایم بنابراین هر بار که کامپوننت ما render شود، این کدها (درخواست HTTP به firebase) نیز ارسال می شود. مشکل اینجاست که درون کدهای درخواست به Firebase از setUserIngredients استفاده کرده ایم و بدین صورت State به روز رسانی می شود. ما از فصل های اول این دوره آموزشی یاد گرفتیم که تغییر State باعث render شدن دوباره کامپوننت می شود و render شدن کامپوننت ما باعث ارسال دوباره درخواست به Firebase شده و بدین صورت در یک حلقه بی نهایت گیر می کنیم.
راه حل این است که تمام این کد را درون یک تابع در useEffect قرار بدهیم:
useEffect(() => { fetch('https://react-hooks-update.firebaseio.com/ingredients.json') .then(response => response.json()) .then(responseData => { const loadedIngredients = []; for (const key in responseData) { loadedIngredients.push({ id: key, title: responseData[key].title, amount: responseData[key].amount }); } setUserIngredients(loadedIngredients); }); });
آیا کد بالا مشکل را حل می کند؟ خیر! هنوز هم در یک حلقه بی نهایت هستیم. در حالت فعلی، یعنی زمانی که useEffect فقط یک پارامتر ورودی داشته باشد، معادل componentDidUpdate است و با هر به روز رسانی در کامپوننت دوباره اجرا می شود. مسئله اینجاست که useEffect می تواند دو پارامتر ورودی بگیرد، اولین پارامتر همین تابعی است که پس از هر بار render شدن کامپوننت اجرا می شود و پارامتر دوم یک آرایه است که وابستگی های تابع (پارامتر اول) را درون خود دارد. اگر این پارامتر دوم را تعریف کنید فقط زمانی که آن وابستگی خاص تغییر کند، تابع اول اجرا خواهد شد.
در کد بالا هیچ وابستگی خارجی نداریم. منظور از وابستگی خارجی (External dependency) داده ها یا متغیرهای مورد استفاده ما، خارج از useEffect است، به زبان ساده تر وابستگی هایی که خارج از useEffect هستند. شاید بگویید تابع setUserIngredients یک وابستگی خارجی است که تا حدی درست است اما این تابع یک تابع خاص است که توسط useState در React ساخته می شود و نحوه کار آن به گونه ای است که واقعا تغییری نمی کند بنابراین یک مورد استثناء است. از آنجایی که useEffect ما در این کد هیچ وابستگی خارجی ندارد می توانیم یک آرایه خالی را به عنوان پارامتر دوم آن تعیین کنیم:
const Ingredients = () => { const [userIngredients, setUserIngredients] = useState([]); useEffect(() => { fetch('https://react-hooks-update.firebaseio.com/ingredients.json') .then(response => response.json()) .then(responseData => { const loadedIngredients = []; for (const key in responseData) { loadedIngredients.push({ id: key, title: responseData[key].title, amount: responseData[key].amount }); } setUserIngredients(loadedIngredients); }); }, []); // بقیه کدها //
استفاده از useEffect به شکل بالا (با مشخص کردن پارامتر دوم) معادل componentDidMount است و فقط یک بار (آن هم بعد از اولین render) اجرا می شود. با انجام این کار از حلقه بی نهایت خارج می شویم. می توانید برای چک کردن این مسئله، کدهای بالا را در کامپوننت خود ذخیره کرده و به مرورگر بروید. سپس سربرگ network را باز کنید تا ببینید که به غیر از بار اول، درخواست دیگری ارسال نمی شود.
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.