post header image

State kezelés Next.js-el IV.

Helló mindenkinek!

Folytatjuk sorozatunkat: state kezelés a Next.js React keretrendszer esetében, immáron sorozatunknak ez már a negyedik része. 😊 Hogyha kíváncsi vagy az újdonságokra és hogyan használd őket, akkor ez neked szól! Ez a cikk az eddigiekre épül, valamint kiegészíti azokat, amelyeket itt érheted el (első rész, második rész, harmadik rész). 2023 sok izgalmas dolgot hozott a Next.js és ezáltal a mi életünkbe, csak kapkodjuk a fejünket, hogy követni tudjuk az események gyűrűdző láncolatát. A cikk írása időpontjában a 14-es szám az, amely leírja a Next.js utolsó stabil kiadott verzióját. Az app router (amely az új csodák létrejöttét lehetővé teszi) használata esetében azonban az eddigi state kezelési technikák bizonyos változtatásokat, fejlesztéseket igényelnek. (Az app routerről annak megjelenésekor itt írtunk.) Nincs ez máshogyan a state kezelés esetében sem.

Beépített megoldások

Nézzük is meg a beépített megoldásokat: a useState, useReducer és a React Context API használatát.

Pages router használata

Amennyiben egy applikáció a pages router-t használja, akkor az összes eddigi javaslatunk (első rész, második rész, harmadik rész) továbbra is megállja a helyét. Azonban mielőtt belevágnánk - mivel témánkat igen erősen befolyásolja - gyorsan ejtsünk pár szót a szerver és a kliens komponensekről.

Az újonnan bevezetett Szerver komponensek és azok előnyeiről

A Next.js számára ez az alap, minden komponens szerver komponensként kezdi az életét. Ennek a megoldásnak az előnyei az alábbiak:

  • Adat lekérés: Mivel ez a szerver oldalon, az adat forrásánál (async functional komponensekkel) történik meg, ezért ez gyorsabb lesz, mint más adat lekérési stratégiák.
  • Biztonság: Az érzékeny adatok, mint API kulcsok, tokenek szerver oldalon léteznek, ezért sosem jutnak el a klienshez.
  • Gyorsítótárazás: A szerveren az eredményt gyorsítótárazni lehet és újrahasznosítani a későbbi kérések gyorsítása érdekében. Ez automatikusan történik, mivel a Next.js elvégzi nekünk.
  • Csomag méret: Mivel a JS module-ok a szerveren kerülnek importálásra és használatra, ezért a kliensnek nem kell letöltenie, feldolgoznia és végrehajtania a JavaScript-et. Ez a SEO optimalizálásban is nagy érdem, hiszen előbb jelenítődik meg használható tartalom a felhasználóknak. Ez nagy hatással van természetesen az első oldalbetöltésre, az FCP-re (First Contentful Paint).
  • Streamelhetőség: A szerver komponensek lehetővé teszik számodra, hogy a renderelési munkát részekre bontsd, és ahogyan azok elkészülnek, azokat streameld a kliensnek. Ez lehetővé teszi a felhasználó számára, hogy az oldal egyes részeit korábban lássa, anélkül, hogy várnia kellene az egész oldal szerveroldali renderelésére.

source: https://nextjs.org/blog/next-13

Ez mind nagyon szép és jó, egyenesen csodálatos, de hol a bökkenő, tehetjük fel a kérdést. Ott, hogy a szerver komponensek esetében semmiféle felhasználói interakció nem lehetséges, valamint nem használhatók hook-ok.

Itt jönnek a kliens komponensek a képbe.

A megváltozott kliens komponensek és azok előnyeiről

A komponens, ahol elvárjuk az interaktivitás lehetőségét ott a file tetején 'use client' direktíva használata indokolt, még az importok felett. Ezzel létrehozunk egy bűvös határt. Az adott komponens file-ban, az ide behúzott egyéb modulok csak kliens komponensek lehetnek, minden az „app router era” előtt is megszokott formában fog működni.

Ahogyan a kép bal oldali részén is láthatjuk, a szerver komponensek esetében a feltüntetett hibákkal szembesülnénk. Ezzel ellentétben a jobb oldalon létrehoztuk a bűvös határt a 'use client' direktívával, így már van interaktivitás, használhatóak a hookok és nincs is hiba.

source: https://nextjs.org/docs/app/building-your-application/rendering/client-components

Mint láthatjuk ezek nem rosszabbak vagy jobbak a szerver komponenseknél, csupán más célt szolgáltak és eddig végig „csak” a kliens komponenseket használtuk.

Konyhanyelven leírva, hogyha akarsz hookot, böngésző API-t használni akkor használd 'use client'-et.

  • Interaktvitás: A kliens komponensek képesek elérni state változást, effekteket, eseményekre hallgatni, más szóval, megteremteni a kapcsolatot a felhasználó és a UI között.
  • Böngésző API: A kliens komponenseknek van hozzáférése a böngésző API-jaihoz, mint a geolokáció vagy a localStorage.

A kliens komponens és useState példa a használatban:

page.tsx

import Counter from "@/app/components/Counter"

export default function Home() {
    return (
        <main className="flex min-h-screen flex-col items-center justify-center gap-8">
            <h1 className="text-3xl font-bold">My really precious Counter has arrived!</h1>
            <Counter/>
        </main>
    )
}

Counter.tsx

'use client'

import { useState } from 'react'

export default function Counter() {
    const [count, setCount] = useState(0)

    return (
        <div>
            <p>You clicked {count} times</p>
            <button onClick={(prev) => setCount(prev + 1)}>Click me</button>
        </div>
    )
}

Szolgáltatók (provider-ek a továbbiakban)

Mi a helyzet a provider-ekkel?

Tesszük is fel gyorsan a kérdést. A provider-ek a state kezelés esszenciális részeit képezik, mindegy is, hogy most a Context API-hoz kapcsolódóan vagy pedig akár egy Redux Toolkit Provider-hez, tudatában annak, hogy nem is lehetséges a használatuk szerver komponensekben.

Az eddigiek alapján is kitűnik, valóban csak kliens komponensként használhatjuk fel őket helyesen.

De hát akkor deoptimalizáltuk az egész komponens fánkat (provider-ek esetén az egész appunkat) csak azért, mert provider segítségével akarunk state kezelést használni???!?444

A válasz a NEM, szerencsére. Amennyiben a kliens komponens kap prop-ot egy szerver komponenstől, pl.: a children-t, akkor az ugyanúgy lehet még szerver komponens. Ez az, amit a provider-einknél ki tudunk használni.

Itt hozunk is egy példát, hogy hogyan is lehet ezt elérni.

Példa a Szolgáltatókra (provider):

layout.tsx

import {ReactNode} from "react"
import {Inter} from 'next/font/google'

import './globals.css'
import MainProvider from "@/providers/MainProvider"

const inter = Inter({subsets: ['latin']})

export default function RootLayout({
                                       children,
                                   }: {
    children: ReactNode
}) {
    return (
        <html lang="en">
        <body className={inter.className}>
        <MainProvider>
            {children}
        </MainProvider>
        </body>
        </html>
    )
}

MainProvider.tsx

'use client'

import {ReactNode} from "react"
import {Provider} from 'react-redux'

import {store} from "@/app/store"

const MainProvider = ({children} : {children: ReactNode}) => {
    return (
        <Provider store={store}>
            {children}
        </Provider>
    )
}

export default MainProvider

Ahogy azt látjuk, a MainProvider-ben használjuk a 'use client' direktívát, itt akár több provider-em is lehetne. A MainProvider fogná össze azokat, rendereli a children propját, ami alapvetően ismét szerver komponens lesz, hiába kliens komponens a szülője. Az új app router ökoszisztémában a children prop a page.tsx tartalma lesz.

page.tsx

import moment from "moment"

import Counter from "@/app/components/Counter"
import Test from "@/app/components/Test"

export default function Home() {
    return (
        <main className="flex min-h-screen flex-col items-center justify-center gap-8">
            <h1 className="text-3xl font-bold">My really precious Counter has arrived!</h1>
            <p>{moment().format('MMMM Do YYYY, h:mm:ss a')}</p>
            <Counter/>
            <Test/>
        </main>
    )
}

A page.tsx-ben láthatjuk, hogy ez egy szerver komponens. Hogyha akarnánk a 'moment' nevű, szép nagy JS lib-et használni, hogy megformázzuk a dátumot (ne tegyük amúgy, mert van helyette erre tökéletesen alkalmas alternatíva: date-fns vagy a beépített Intl), akkor ezzel nem terhelnénk a klienst, mert szerver oldalon kerül csak betöltésre és mire HTML-ként megérkezik a böngészőbe addigra már nem kell a könyvtárat is leküldeni, hogy a böngésző letöltse, feldolgozza és végrehajtsa.

Counter.tsx

'use client'

import {useDispatch, useSelector} from "react-redux"
import {RootState} from "@/app/store"
import {increment, decrement,} from "@/slices/counterSlice"

export default function Counter() {
    const count = useSelector((state: RootState) => state.counter.value)
    const dispatch = useDispatch()

    return (
        <>
            <p className="text-center">You clicked {count} times</p>
            <div className="flex p-2 bg-blue-200 w-96 justify-between">
                <button className="p-2 bg-amber-100" onClick={() => dispatch(decrement())}>Decrement</button>
                <button className="p-2 bg-amber-100" onClick={() => dispatch(increment())}>Increment</button>
            </div>
        </>
    )
}

A Counter file tetején láthatjuk a 'use client' direktívát, tehát boldogan használhatjuk gombokra kattintva a Redux adta state kezelés adta előnyöket.

App router, annak stabilitásáról, és a Next.js 14-ről pár szó plusz a saját vélemény

A cikkben végig az app router-rel történő state kezelés használatát tárgyaltuk, ami a 13-as verzióban került bevezetésre és csak a későbbiekben került (13.4) hivatalosan is stabil állapotba.

Több, mint meglepő módon ezt a bejelentését követően, 2 hónapra rá hivatalos blog post érkezett , hogy még tovább növelték a teljesítményét, STABILITÁSÁT, fejlesztői élményét.

Ahogyan a cikk elején is említésre került a Next.js legfrissebb verziója a 14, amiben talán a legnagyobb dolgot a már 13-ra beígért szerver akciók stabilizálása jelentette. Ehhez csak annyit szeretnénk hozzáfűzni (kissé savanyúan, hiszen sok értelme még nem nagyon volt), hogy az átállás 13-ról 14-re zökkenőmentes volt. Ha nem így lett volna, akkor igen csalódott lettem volna egy akár breaking changekkel járó keretrendszer főverziójának frissítésekor, ami cserébe ilyen kevés újdonságot nyújt. Természetesen, ahogyan azt már megszokhattuk, a Next.js 13-ban bejelentett Turbopack még mindig nem production ready.

Összegzés: Miről olvastál?

A Next.js 13-mal beköszöntő app router használatával járó kihívásokról. Hogyan lehet használni state kezelésre a React beépített hook-jait, valamint az egyik legismertebb, harmadik féltől származó state kezelési könyvtárat, mint a Redux-ot. Láthattuk a layout és page fájl-t használat közben, valamint kitértünk a szerver és kliens komponensek használatára.

Remélem a cikk olvasását követően könnyebben állsz majd te is a state kezeléshez a Next.js keretrendszert használva. Ne adj isten megszereted.

COPYRIGHT © 1999 - 2024 | SKYLINE-COMPUTER KFT.MINDEN JOG FENNTARTVA