В моем проекте next.js 14 мне нужно передать состояние из дочернего компонента в родительский компонент.
На данный момент я объявил состояние в родительском компоненте и передал его дочерним компонентам. (таким образом делая все на стороне клиента родительского компонента)
Но я хочу, когда это возможно, сохранить серверный компонент. Вот минимальный воспроизводимый пример того, что я пытаюсь сделать:
У меня состояние isFocused
. На панели навигации есть кнопка для ее переключения. Когда он включен. Весь сайт просто,
<Navbar/>
<FocusedPage />
Если не включено,
<Navbar/>
<Hero />
<FocusedPage />
...
Теперь, визуализировать компонент <Hero />
или нет, мне нужно знать, является ли isFocused
true
или false
.
Итак, я храню его в родительском компоненте клиента. Таким образом, <Hero />
также становится клиентским компонентом.
Во-первых, вы должны знать, что использование клиентских компонентов не является плохой практикой, они существуют по какой-то причине, и эти причины упомянуты в Документах NextJS:
- Интерактивность: клиентские компоненты могут использовать прослушиватели состояний, эффектов и событий, то есть они могут предоставлять немедленную обратную связь пользователю и обновлять пользовательский интерфейс.
- API-интерфейсы браузера. Клиентские компоненты имеют доступ к API-интерфейсам браузера, таким как геолокация или localStorage.
Также, по словам Ли Робинсона из Vercel:
Клиентские компоненты не являются ни отключением, ни отговоркой. Вы можете рассматривать серверные компоненты как дополнение к существующей модели. Я понимаю желание использовать серверные компоненты для многих целей (неплохо!), но также совершенно нормально использовать клиентские компоненты для определенных целей.
Однако иногда (как в вашей ситуации) вам необходимо сохранить все серверные компоненты, кроме интерактивной части, в качестве клиентского компонента.
Подъем состояния из дочернего клиентского компонента (из-за прослушивателя событий onClick
) на родительский серверный компонент невозможен, и он выдаст ошибку времени выполнения:
Необработанная ошибка времени выполнения
Ошибка: функции нельзя передать непосредственно клиентским компонентам, если вы явно не предоставите их, пометив «использовать сервер».
Самый распространенный способ «повысить состояние» в NextJS — это использовать URLSearchParams вы будете использовать его во многих ситуациях, особенно когда хотите передать данные с клиента на сервер, но это не единственный вариант использования.
Чтобы воспользоваться преимуществами URLSearchParams, вам придется немного изменить свой код.
В настоящее время у вас есть четыре компонента и один page.tsx
, все компоненты будут серверными, кроме Navbar.tsx
, вы поместите свое состояние в Navbar.tsx
вместо Home.tsx
, потому что этот компонент отвечает за изменение состояния, а также будет иметь прослушиватель событий для изменения состояния и добавьте параметр запроса в URL-адрес, чтобы использовать его позже в компонентах сервера.
Навбар.tsx
'use client'; // make sure it's a client component
import { useRouter } from 'next/navigation';
import { useState, useEffect } from 'react';
const Navbar = () => {
// bring the state here
const [focusMode, setFocusMode] = useState(false);
const router = useRouter();
// useEffect will append the query param whenever the state changes
useEffect(() => {
router.push(`?focusMode=${focusMode}`);
}, [focusMode]);
return (
<nav className = "flex items-center justify-between border-b-2 border-gray-400 px-5 py-5">
<a className = "text-2xl font-bold tracking-tighter" href = "/">
Next.js
</a>
<div>
<a href = "/" className = "mr-5 underline">
Home
</a>
<button
className = "rounded-md border bg-gray-700 p-2 font-semibold text-gray-100 hover:bg-gray-500"
onClick = {() => setFocusMode((prevIsFocused) => !prevIsFocused)}
>
Toggle Focus Button
</button>
</div>
</nav>
);
};
export default Navbar;
По умолчанию page.tsx получает searchParams
, который представляет собой объект, содержащий все параметры запроса, вы можете передать его home.tsx
:
страница.tsx
import Home from '@/components/Home';
interface IProps {
params: {};
searchParams: {
focusMode: string;
};
}
export default function Root({ searchParams }: IProps) {
return (
<>
<Home focusMode = {JSON.parse(searchParams?.focusMode ?? false)} />
</>
);
}
JSON.parse
необходимо преобразовать true/false из string
в boolean
, поскольку все, что вы получаете из URL-адреса, — это string
.
searchParams?.focusMode ?? false
необходим, потому что при первом рендере у вас его не будет searchParams
, поэтому он вернется undefined
.
Наконец, вот как вы Home.tsx
будете выглядеть:
Home.tsx
import Navbar from '@/components/Navbar';
import Hero from './Hero';
import Focused from '@/components/Focused';
interface IProps {
focusMode: boolean;
}
function Home({ focusMode }: IProps) {
return (
<main className = "text-center">
<Navbar />
{!focusMode && <Hero />}
<Focused focusMode = {focusMode} />
</main>
);
}
export default Home;
все остальные файлы остались как были.
Бонус
Если вы хотите убедиться, что все компоненты являются серверными, попробуйте console.info(window)
в любом из них, и вы увидите ошибку (кроме Navbar.tsx
, потому что это клиентский компонент):
Все работает нормально. Спасибо за совет.