هایدرشن - Hydration چیست؟ چگونگی حل مشکلات مربوط به آن در React

هایدریشن یک ایده مهم در توسعه وب است که این پتانسیل را دارد که عملکرد وب سایت را تا حد زیادی افزایش دهد.

ایده ی کلی هایدرشن، به فرآیند گرفتن یک صفحه HTML ارائه شده در سمت سرور، و اتصال Event Listener ها و داده ها به آن در سمت کلاینت می پردازد. این روش، سرعت رندر سریعتر، و تجربه کاربری بهتری را فراهم می کند.

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

هایدریشن دقیقا چیست؟

هایدریشن فرآیند انتقال HTML ارائه شده توسط سرور به سمت کلاینت است، جایی که می تواند به یک صفحه وب کاملاً تعاملی تبدیل شود.

مزیت اصلی هایدریشن این است که سرعت بارگذاری سریعتر صفحات وب را امکان پذیر می کند. رندر سمت سرور می‌تواند با پیش رندر کردن HTML پایه، به کاهش زمان بارگذاری صفحه کمک کند، اما فاقد رفتار سمت مشتری و تعامل است. این زمانی است که هایدریشن وارد می شود و عملکرد مورد نیاز سمت کلاینت را به محتوای از پیش رندر شده اضافه می کند. این کار باعث می شود که در بررسی های SEO سایت هم نمره هایی بالاتری بگیرید.

هایدریشن در React

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

برای به دست آوردن HTML اولیه برای هایدریشن در React، ابتدا باید کامپوننت ،سمت سرور رندر شود. سپس، در سمت کلاینت، از متدReactDOM.hydrate برای اضافه کردن Event Listener ها و داده‌ها به HTML استفاده کنید. توجه کنید که طبیعتا فقط وقتی SSR داریم چنین چیزی معنا پیدا می کند.

اما علت اصلی نوشتن این مقاله مشکلی است که هر از چند گاه ممکن اس در console اپلیکیشن خود ببینید. که یک warning مربوط به hydration است. که خیلی نامفهموم است ولی به عنوان برنامه نویس واقعا وقتی در console یک سایت خطا یا warning می بینم حس خوبی ندارم و واقعا روی اعصابم می رود (البته شاید من وسواس دارم 😁)

برخی از خطاهایی که من برخورد کردم:

  • Warning: Text content did not match. Server: "Pre-rendered server content" Client: "Client app content" at div.
  • Warning: An error occurred during hydration. The server HTML was replaced with client content in div.
  • Text content does not match server-rendered HTML.
  • Hydration failed because the initial UI does not match what was rendered on the server.
  • There was an error while hydrating. Because the error happened outside of a Suspense boundary, the entire root will switch to client rendering.

دلیل پشت این خطاها عدم تطابق بین HTML ارائه شده توسط سرور و آنچه توسط برنامه فرانت تولید می شود، است. برای اینکه هایدرشن به درستی کار کند، HTML باید دقیقاً یکسان باشد.

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

function PracticalHydrationError({ theDate }) {
	const formatted_date = new Date(theDate).toLocaleDateString();
	return <div>{formatted_date}</div>;
}

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

راه حل ها

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

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

export default function NoFirstRender({ theDate }) {
const [hydrated, setHydrated] = React.useState(false);
React.useEffect(() => {
setHydrated(true);
}, []);
if (!hydrated) {
return null;
}
const formatted_date = new Date(theDate).toLocaleDateString();
return <div>{formatted_date}</div>;
}

در واقع ایده ی کلی این است که یا چیزی سمت سرور برگردانده نشود یا دیتای متفاوتی سمت سرور و کلایت برگردانده شود.

اما اگر برنامه نویس حرفه ای باشید می دانید که این راه حل خیلی راه جالبی نیست. اما چرا؟

هدف useEffect ربطی به هایدرشن ندارد. ارسال آرایه وابستگی خالی ([]) باعث می شود تا زمانی که کامپوننت برای اولین بار mount می شود، تابع اجرا شود.اما ممکن است که یک کامپوننت بارها mount و unmount شود.

برای حل این مسئله ، ما می توانیم React Context و Provider استفاده کنیم تا اطمینان حاصل کنیم که بررسی Hydration فقط یک بار انجام می شود:

const HydrationContext = React.createContext(false);
function HydrationProvider({ children }) {
const [hydrated, setHydrated] = React.useState(false);
React.useEffect(() => {
setHydrated(true);
}, []);
return <HydrationContext.Provider value={hydrated}>{children}</HydrationContext.Provider>;
}
function MyDateComponent({ theDate }) {
// Retrieve the hydration state from the context
const hydrated = React.useContext(HydrationContext);
const date = new Date(theDate);
const formatted_date = hydrated ? date.toLocaleDateString() : date.toUTCString();
return <div>{formatted_date}</div>;
}
export default function OnlyRerenderAfterHydration() {
return (
<HydrationProvider>
<MyDateComponent />
</HydrationProvider>
);
}

با انتقال بررسی هایدرشن به یک Provider، نیازی به فراخوانی آن هر بار که کامپوننت ما نصب می‌شود، نیست.

اگر <HydrationProvider> را در بالاترین سطح برنامه خود قرار دهیم، بررسی هایدرشن فقط یک بار در زمانی که برنامه واقعاً Hydrate شده است امجام می شود، نه هر بار که یک کامپوننت نصب می شود.

بنابراین، اگر بارها از <MyDateComponent> در برنامه خود استفاده کنید، تعداد رندرها به میزان قابل توجهی کاهش می یابد.