Пошук уроків, статей та іншого контенту
Навчитеся позначати клієнтські компоненти директивою "use client" і працювати з її наслідками для бандла.
"use client""use client" — це спеціальна директива, яка позначає файл як клієнтський компонент у середовищі, що підтримує React Server Components, наприклад у Next.js App Router.
Вона повідомляє фреймворку:
цей компонент може використовувати браузерні API;
у ньому можна використовувати клієнтські React Hooks;
у ньому можуть працювати обробники подій;
код компонента потрібно включити до клієнтського JavaScript-бандла.
Директива має бути першим оператором у файлі — до імпортів:
"use client";
import { useState } from "react";
export default function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
Натиснуто: {count}
</button>
);
}Рядок "use client" — це не імпорт і не виклик функції. Це директива, яку фреймворк розпізнає під час збирання застосунку.
"use client"Клієнтський компонент потрібен, якщо компонент використовує можливості, доступні лише в браузері або на клієнті.
Наприклад:
"use client";
import { useState } from "react";
export default function LikeButton() {
const [liked, setLiked] = useState(false);
return (
<button onClick={() => setLiked(!liked)}>
{liked ? "Вподобано" : "Вподобати"}
</button>
);
}Без "use client" фреймворк не зможе використовувати useState у цьому компоненті як у клієнтському компоненті.
Також директива потрібна для інших клієнтських Hooks, наприклад:
useEffect;
useReducer;
useRef у сценаріях, що залежать від браузера;
власних Hooks, які використовують клієнтські Hooks.
Обробники onClick, onChange, onSubmit та інші потребують виконання JavaScript у браузері:
"use client";
export default function LoginButton() {
function handleClick() {
alert("Кнопку натиснуто");
}
return <button onClick={handleClick}>Увійти</button>;
}Для роботи з window, document, localStorage та іншими браузерними API компонент також має бути клієнтським.
"use client";
import { useEffect, useState } from "react";
export default function ThemeInfo() {
const [theme, setTheme] = useState("невідома");
useEffect(() => {
const savedTheme = window.localStorage.getItem("theme");
if (savedTheme) {
setTheme(savedTheme);
}
}, []);
return <p>Збережена тема: {theme}</p>;
}Директива діє не лише на один експортований компонент. Вона створює межу клієнтського коду.
Розглянемо структуру:
app/
├── page.jsx
└── Counter.jsxФайл Counter.jsx:
"use client";
import { useState } from "react";
export default function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
Лічильник: {count}
</button>
);
}Файл page.jsx:
import Counter from "./Counter";
export default function Page() {
return (
<main>
<h1>Головна сторінка</h1>
<Counter />
</main>
);
}У цьому прикладі:
page.jsx залишається серверним компонентом;
Counter.jsx є клієнтським компонентом;
серверний компонент може відображати клієнтський компонент;
інтерактивність потрібна лише для Counter.
Це дає змогу залишити більшу частину сторінки серверною, а на клієнт передати тільки інтерактивну частину.
"use client" і клієнтський бандлКлієнтський бандл — це JavaScript-код, який браузер завантажує та виконує для роботи інтерактивних компонентів.
Якщо файл має "use client", до клієнтського графа залежностей потрапляють:
сам файл;
його імпорти;
імпорти цих залежностей;
інші модулі, необхідні для виконання клієнтського коду.
Наприклад:
InteractivePanel.jsx
└── formatDate.js
└── date-utils.jsЯкщо InteractivePanel.jsx позначений як клієнтський, його залежності можуть потрапити до клієнтського бандла також.
Тому не варто без потреби додавати "use client" до великих компонентів або до компонентів верхнього рівня. Це може збільшити кількість JavaScript-коду, який потрібно завантажити браузеру.
Неінтерактивну частину залишайте серверною:
// app/page.jsx
import SearchForm from "./SearchForm";
export default function Page() {
return (
<main>
<h1>Каталог товарів</h1>
<p>Цей опис не потребує JavaScript у браузері.</p>
<SearchForm />
</main>
);
}А "use client" додавайте лише до форми:
// app/SearchForm.jsx
"use client";
import { useState } from "react";
export default function SearchForm() {
const [query, setQuery] = useState("");
function handleSubmit(event) {
event.preventDefault();
alert(`Пошук: ${query}`);
}
return (
<form onSubmit={handleSubmit}>
<input
value={query}
onChange={(event) => setQuery(event.target.value)}
placeholder="Введіть запит"
/>
<button type="submit">Знайти</button>
</form>
);
}У такому варіанті клієнтський код потрібен лише для SearchForm, а не для всієї сторінки.
Серверний компонент може передавати клієнтському звичайні дані через props:
// app/page.jsx
import ProductCounter from "./ProductCounter";
export default function Page() {
return (
<ProductCounter
productName="Навушники"
initialQuantity={1}
/>
);
}// app/ProductCounter.jsx
"use client";
import { useState } from "react";
export default function ProductCounter({
productName,
initialQuantity,
}) {
const [quantity, setQuantity] = useState(initialQuantity);
return (
<section>
<h2>{productName}</h2>
<button onClick={() => setQuantity(quantity + 1)}>
Кількість: {quantity}
</button>
</section>
);
}Props, які проходять через межу серверного та клієнтського компонентів, мають бути серіалізованими — тобто такими, які можна безпечно передати у форматі даних.
Зазвичай без проблем передаються:
рядки;
числа;
логічні значення;
масиви;
прості об'єкти;
null.
Не слід передавати як props:
функції;
екземпляри класів;
складні об'єкти з несеріалізованим станом;
значення, які залежать від конкретного процесу або з'єднання.
Наприклад, функцію-обробник не можна передати із серверного компонента в клієнтський як звичайний prop:
// Такий підхід не підходить для межі серверного та клієнтського коду
<ClientButton onClick={handleClick} />Обробник має бути визначений усередині клієнтського компонента або реалізований за допомогою відповідного механізму фреймворку.
"use client" не означає «компонент ніколи не виконується на сервері»Клієнтський компонент призначений для роботи в браузері, але під час початкового завантаження сторінки його розмітка може бути підготовлена на сервері.
Важливо розрізняти:
клієнтський компонент — компонент, код якого доступний клієнту та може бути інтерактивним;
рендеринг у браузері — створення або оновлення інтерфейсу JavaScript-кодом після завантаження.
Тому "use client" не є перемикачем «рендерити тільки в браузері». Його основне призначення — визначити межу клієнтського JavaScript і дозволити клієнтські можливості React.
"use client" не потрібенДиректива не потрібна для компонента, який:
лише відображає отримані дані;
не використовує стан;
не використовує ефекти;
не має обробників подій;
не звертається до браузерних API.
Наприклад:
export default function ProductInfo({ name, price }) {
return (
<article>
<h2>{name}</h2>
<p>{price} грн</p>
</article>
);
}Якщо додати "use client" до такого компонента без потреби, він стане частиною клієнтського графа коду, хоча інтерактивність йому не потрібна.
Правильно:
"use client";
import { useState } from "react";Неправильно:
import { useState } from "react";
"use client";Розташовуйте директиву перед усіма імпортами та іншими операторами.
Не кожен компонент має бути клієнтським. Починайте без "use client" і додавайте його лише тоді, коли потрібні:
Hooks;
обробники подій;
браузерні API;
клієнтські бібліотеки.
Якщо одна кнопка потребує стану, не обов'язково позначати весь великий блок як клієнтський. Винесіть кнопку в окремий невеликий компонент із "use client".
Функція не є звичайними серіалізованими даними. Не передавайте серверні обробники подій у клієнтські компоненти через props без спеціального механізму, передбаченого фреймворком.
Такий код потребує клієнтського компонента:
"use client";
import { useEffect } from "react";
export default function WindowInfo() {
useEffect(() => {
console.log(window.innerWidth);
}, []);
return <p>Розмір вікна буде виведено в консоль.</p>;
}"use client" позначає файл як клієнтський компонент.
Директива має розташовуватися на початку файлу, до імпортів.
Вона потрібна для стану, ефектів, обробників подій і браузерних API.
Клієнтський компонент і його залежності можуть потрапити до клієнтського JavaScript-бандла.
Щоб зменшити бандл, створюйте якомога менші клієнтські компоненти.
Серверні компоненти можуть відображати клієнтські компоненти.
Дані через межу між сервером і клієнтом мають бути серіалізованими.
Не додавайте "use client" до компонентів, яким не потрібна інтерактивність.