Server va Client Component birga ishlatish — composition pattern
Muammodan boshlaylik
Oldingi ikki darsda Server va Client Component ni alohida o'rgandik. Lekin haqiqiy loyihada ular doim birga ishlaydi.
Tasavvur qiling — e-commerce saytning mahsulot sahifasi:
┌─────────────────────────────────────┐
│ Mahsulot nomi, narxi, tavsifi │ ← statik, serverdan keladi
│ Rasm galereyasi │ ← slayder, brauzerda ishlaydi
│ "Savatga qo'shish" tugmasi │ ← onClick, brauzerda ishlaydi
│ O'xshash mahsulotlar │ ← statik, serverdan keladi
└─────────────────────────────────────┘
Bir sahifada ham Server, ham Client Component kerak. Ularni qanday to'g'ri tashkil qilish — bu darsning mavzusi.
Asosiy qoida — daraxt
Server va Client Component larni daraxt sifatida tasavvur qiling. Daraxtning ildizi — Server Component. Barglar — Client Component.
Page (Server)
├── ProductInfo (Server) ← DB dan ma'lumot
├── ImageGallery (Client) ← slayder, onClick
├── AddToCart (Client) ← savat, useState
└── RelatedProducts (Server) ← DB dan ma'lumot
Server Component Client Component ni import qila oladi — bu ishlaydi. Client Component Server Component ni import qila olmaydi — bu ishlamaydi.
To'g'ri tashkil qilish — amaliy misol
Mahsulot sahifasini quramiz:
// app/products/[id]/page.tsx — Server Component
import { ImageGallery } from '@/components/ImageGallery'
import { AddToCart } from '@/components/AddToCart'
export default async function ProductPage({ params }) {
const { id } = await params
const product = await db.getProduct(id) // serverda
return (
<div>
{/* Statik kontent — serverda render */}
<h1>{product.name}</h1>
<p>{product.price} so'm</p>
<p>{product.description}</p>
{/* Interaktiv qismlar — Client Component */}
<ImageGallery images={product.images} />
<AddToCart productId={product.id} price={product.price} />
</div>
)
}
// components/ImageGallery.tsx — Client Component
'use client'
export function ImageGallery({ images }: { images: string[] }) {
const [current, setCurrent] = useState(0)
return (
<div>
<img src={images[current]} alt="Mahsulot" />
<div>
{images.map((_, i) => (
<button key={i} onClick={() => setCurrent(i)}>
{i + 1}
</button>
))}
</div>
</div>
)
}
// components/AddToCart.tsx — Client Component
'use client'
export function AddToCart({ productId, price }) {
const [added, setAdded] = useState(false)
function handleClick() {
// savatga qo'shish logikasi
setAdded(true)
}
return (
<button onClick={handleClick}>
{added ? '✓ Savatda' : 'Savatga qo\'shish'}
</button>
)
}
product ma'lumotlari serverda olinadi. Interaktiv qismlar faqat kerakli ma'lumotni props orqali oladi.
children pattern — Client ichida Server
Ba'zan Client Component ni Server Component bilan "to'ldirish" kerak. Masalan, modal oyna — ochiq/yopiq holati Client da, lekin ichidagi kontent Server da render bo'lishi kerak.
// app/users/page.tsx — Server Component
import { Modal } from '@/components/Modal'
export default async function UsersPage() {
const users = await db.getUsers() // serverda
return (
<Modal title="Foydalanuvchilar">
{/* Bu kontent serverda render bo'ladi */}
<ul>
{users.map(u => <li key={u.id}>{u.name}</li>)}
</ul>
</Modal>
)
}
// components/Modal.tsx — Client Component
'use client'
export function Modal({ title, children }) {
const [isOpen, setIsOpen] = useState(true)
if (!isOpen) return null
return (
<div className="modal-overlay">
<div className="modal">
<div className="modal-header">
<h2>{title}</h2>
<button onClick={() => setIsOpen(false)}>✕</button>
</div>
<div className="modal-body">
{children} {/* Server Component shu yerda */}
</div>
</div>
</div>
)
}
children — Server Component da render bo'ladi, Modal ga tayyor HTML sifatida uzatiladi. Modal faqat ochiq/yopiq holatini boshqaradi.
Context — Client Component daraxtida
React.createContext faqat Client Component da ishlaydi. Lekin Provider ni Server Component da ishlatish mumkin — children orqali:
// components/ThemeProvider.tsx — Client Component
'use client'
import { createContext, useContext, useState } from 'react'
const ThemeContext = createContext('light')
export function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light')
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
{children}
</ThemeContext.Provider>
)
}
export function useTheme() {
return useContext(ThemeContext)
}
// app/layout.tsx — Server Component
import { ThemeProvider } from '@/components/ThemeProvider'
export default function RootLayout({ children }) {
return (
<html>
<body>
<ThemeProvider>
{children} {/* Server Component lar ham shu yerda */}
</ThemeProvider>
</body>
</html>
)
}
ThemeProvider — Client Component. Lekin children — Server Component bo'lishi mumkin. Context faqat Client Component larga ta'sir qiladi.
Noto'g'ri pattern — Server Component ni Client dan import qilish
// ❌ — bu ishlamaydi
'use client'
import { UserList } from './UserList' // Server Component
export function Dashboard() {
const [tab, setTab] = useState('users')
return (
<div>
<button onClick={() => setTab('users')}>Foydalanuvchilar</button>
{tab === 'users' && <UserList />} {/* Server Component — ishlamaydi */}
</div>
)
}
Nima uchun ishlamaydi? Dashboard — Client Component. U brauzerda ishlaydi. UserList — Server Component, u faqat serverda ishlaydi. Brauzerda Server Component bo'lmaydi.
Yechim — UserList ni children orqali uzatish:
// app/dashboard/page.tsx — Server Component
import { Dashboard } from './Dashboard'
import { UserList } from './UserList'
export default async function Page() {
return (
<Dashboard userList={<UserList />} />
)
}
// Dashboard.tsx — Client Component
'use client'
export function Dashboard({ userList }) {
const [tab, setTab] = useState('users')
return (
<div>
<button onClick={() => setTab('users')}>Foydalanuvchilar</button>
{tab === 'users' && userList} {/* tayyor HTML */}
</div>
)
}
Tez-tez uchraydigan xatolar
1. Hamma narsani Client Component qilish
// ❌ — sahifaning 90% statik, lekin hammasi Client
'use client'
export default function Page() {
const [count, setCount] = useState(0)
return (
<div>
<h1>Sarlavha</h1> {/* statik */}
<p>Uzoq matn...</p> {/* statik */}
<button onClick={...}>{count}</button> {/* faqat shu Client kerak */}
</div>
)
}
// ✅ — faqat interaktiv qism Client
export default function Page() {
return (
<div>
<h1>Sarlavha</h1>
<p>Uzoq matn...</p>
<Counter /> {/* faqat shu Client Component */}
</div>
)
}
2. Server Component ga serialize qilib bo'lmaydigan props uzatish
// ❌ — funksiya props orqali Server → Client uzatilmaydi
export default function Page() {
const handleClick = () => console.log('bosildi')
return <ClientComp onClick={handleClick} /> // xato!
}
Server Component dan Client Component ga faqat serialize qilinadigan qiymatlar uzatiladi: string, number, array, object, JSX. Funksiya uzatilmaydi.
// ✅ — funksiya Client Component ning o'zida bo'lishi kerak
// ClientComp.tsx
'use client'
export function ClientComp() {
const handleClick = () => console.log('bosildi')
return <button onClick={handleClick}>Bosing</button>
}
Xulosa
Composition pattern — Server va Client Component larni to'g'ri tashkil qilish.
Server Component → ma'lumot olish, statik kontent
Client Component → interaktivlik, holat, brauzer API
Server → Client: props orqali ma'lumot uzatish ✅
Client → Server: to'g'ridan-to'g'ri import ❌, children orqali ✅
Asosiy qoida: daraxtning ildizi Server Component, barglar Client Component. 'use client' ni imkon qadar pastga suring.
Keyingi dars
13-dars: Static Rendering — build vaqtida generatsiya, qachon mos
Rendering strategiyalarining birinchisi — Static Rendering. Qachon ishlaydi, qanday sozlanadi, qachon mos emas.