03 / 09
Selector ve yeniden render
Selector'ın nasıl karşılaştırıldığı, atomik selector, useShallow, v5'teki sonsuz döngü hatası, türetilmiş değer ve yalnızca custom hook export etmek.
Selector Nasıl Karşılaştırılır
Store her değiştiğinde Zustand, component'teki selector'ı yeniden çalıştırır ve sonucu bir öncekiyle Object.is ile karşılaştırır. Sonuç farklıysa component render olur, aynıysa olmaz.12
const count = useCartStore((state) => state.items.length);Burada sonuç bir sayıdır. Sepet kapanıp açılsa da sayı aynı kaldığı sürece component render olmaz. Selector'ın döndürdüğü değer ne kadar küçükse component o kadar az render olur.
Bütün Store'a Abone Olmak
Selector vermezseniz hook bütün state'i döndürür. Bu, store'daki her değişiklikte render demektir:3
// ❌ Yalnızca items'ı kullanıyor ama isOpen değişince de render olur
const { items } = useCartStore();TkDodo bu yüzden selector'ları zorunlu sayar: store'da tek bir değer olsa bile bir selector yazın.3
Atomik Selector
Her değer için ayrı bir selector yazmak en basit ve en güvenli yoldur:3
const items = useCartStore((state) => state.items);
const isOpen = useCartStore((state) => state.isOpen);Her satır kendi değerine abone olur. TkDodo'nun tercihi budur: bir component iki değere ihtiyaç duyuyorsa iki selector kullanır.3
Nesne Döndüren Selector
Birden fazla değeri tek seferde almak için nesne döndürmek caziptir. Ama selector her çalıştığında yeni bir nesne üretir; Object.is iki nesneyi hiçbir zaman eşit saymaz.1
// ❌ Her seferinde yeni nesne
const { items, isOpen } = useCartStore((state) => ({
items: state.items,
isOpen: state.isOpen,
}));v4'te bu kod yalnızca gereksiz render'a yol açıyordu. v5 React'in davranışına uyduğu için sonuç daha ağırdır: React, değerin her okumada değiştiğini görür ve sayfa "Maximum update depth exceeded" hatasıyla durur.4 Konsolda öncesinde "The result of getSnapshot should be cached" uyarısı çıkar.
Çözüm useShallow'dur. Selector'ı sarar ve sonucu sığ karşılaştırır: nesnenin ya da dizinin her elemanı Object.is ile aynıysa önceki referansı döndürür.24
import { useShallow } from "zustand/react/shallow";
const { items, isOpen } = useCartStore(
useShallow((state) => ({ items: state.items, isOpen: state.isOpen })),
);
// Dizi döndürmek de aynı
const [items, isOpen] = useCartStore(useShallow((state) => [state.items, state.isOpen]));useShallow hem zustand/react/shallow'dan hem zustand/shallow'dan import edilebilir.4
Uyarı
Varsayılan değeri selector içinde üretmek de aynı hatayı verir. state.action ?? (() => {}) her seferinde yeni bir fonksiyon döndürür. Varsayılanı selector'ın dışında bir sabite koyun.4
Türetilmiş Değer
Hesaplanabilen bir değeri store'a ayrıca yazmayın; selector'da hesaplayın.5 Sonuç bir sayı ya da metinse ek bir şey gerekmez:
const total = useCartStore((state) =>
state.items.reduce((sum, item) => sum + item.price * item.quantity, 0),
);Sonuç yeni bir dizi ise (filter, map, Object.keys) her seferinde yeni referans üretir. useShallow ile sarın; dizi elemanları aynı kaldıkça component render olmaz:1
const ids = useCartStore(useShallow((state) => state.items.map((item) => item.id)));Bu kalıp listeler için kullanışlıdır. Liste component'i yalnızca id'leri okur; her satır kendi kaydını id ile seçer. Bir ürünün adedi değişince yalnızca o satır render olur.
Yalnızca Custom Hook Export Edin
TkDodo'nun bir numaralı önerisi: store hook'unu dışarı açmayın, her değer için bir custom hook export edin.3
// Export edilmiyor: kimse bütün store'a abone olamaz
const useCartStore = create<CartState>()((set) => ({
/* ... */
}));
export const useCartItems = () => useCartStore((state) => state.items);
export const useCartCount = () =>
useCartStore((state) => state.items.reduce((sum, item) => sum + item.quantity, 0));
export const useIsCartOpen = () => useCartStore((state) => state.isOpen);Kullanan component selector yazmak zorunda kalmaz, yanlışlıkla bütün store'a abone olamaz, store'un iç yapısı değişince yalnızca bu dosya değişir. Action'ları dışarı açmanın yolu Action ve güncelleme dersinde.
Kendin Dene
Üç component yazın: biri bütün store'u, biri atomik selector'ı, biri
useShallowile nesne okusun. Her birineconsole.log("render")koyup store'da ilgisiz bir alanı değiştirin.useShallow'u kaldırıp nesne döndüren selector'ı çalıştırın. Konsolda hangi uyarı ve hata çıkıyor?Sepet listesini önce
items'ı okuyarak, sonra yalnızcaid'leri okuyup satırlara böldükten sonra render edin. Bir ürünün adedini değiştirince kaç satır render oluyor?
Detaylı Öğrenmek İçin
Yazı: TkDodo, "Working with Zustand"; custom hook export etmek, atomik selector ve action'ları ayırmak.3
Resmi doküman: "Prevent rerenders with useShallow"; türetilmiş dizide
useShallowörneği.1Resmi doküman: "How to Migrate to v5 from v4"; kararlı selector çıktısı şartı ve sonsuz döngü örnekleri.4
Kaynaklar
`Object.is` karşılaştırması ve türetilmiş dizide gereksiz render.
Selector'ı sığ karşılaştırmayla memoize etmek.
: — 20 Kasım 2022. Yalnızca custom hook export etmek, atomik selector ve bütün store'a abone olmanın maliyeti.
Kararlı selector çıktısı şartı, "Maximum update depth exceeded" ve `useShallow` ile çözüm.
Birden fazla selector ve selector'da türetilmiş state.
Son güncelleme tarihi: 21 Ekim 2026
