useReducer
useReducer
, bileşeninize bir reducer eklemenizi sağlayan bir React Hook’udur.
const [state, dispatch] = useReducer(reducer, initialArg, init?)
- Başvuru Dokümanı
- Kullanım
- Sorun giderme
- Bir işlem yaptım, ancak state’i yazdırdığımda eski değerini veriyor
- Bir işlem yaptım, ancak ekran güncellenmiyor
- Dispatch işleminden sonra reducer state’in bir kısmı tanımsız (undefined) oluyor.
- Dispatch işleminden sonra tüm reducer state’i tanımsız (undefined) oluyor.
- ”Too many re-renders” hatası alıyorum
- Reducer veya başlatıcı (initializer) fonksiyonlarım iki kez çalışıyor.
Başvuru Dokümanı
useReducer(reducer, initialArg, init?)
Bileşeninizin state’ini bir reducer ile yönetmek için bileşeninizin üst düzeyinde useReducer
çağrısı yapın.
import { useReducer } from 'react';
function reducer(state, action) {
// ...
}
function MyComponent() {
const [state, dispatch] = useReducer(reducer, { age: 42 });
// ...
Daha fazla örnek için aşağıya bakınız.
Parametreler
reducer
: State’in nasıl güncelleneceğini belirleyen reducer fonksiyonudur. Saf hâlde (pure) olmalı, state’i ve işlemi(action) argüman olarak almalı ve bir sonraki state’i döndürmelidir. State ve işlem herhangi bir tür olabilir.initialArg
: Başlangıç state’inin hesaplandığı değerdir. Herhangi bir türden bir değer olabilir. Başlangıç state’inin nasıl hesaplandığı, sonrakiinit
argümanına bağlıdır.- isteğe bağlı
init
: Başlangıç state’ini döndürmesi gereken başlatıcı fonksiyondur. Belirtilmezse, başlangıç state’iinitialArg
olarak ayarlanır. Aksi takdirde, başlangıç state’iinit(initialArg)
çağrısının sonucuna ayarlanır.
Dönüş değerleri
useReducer
, tam olarak iki değer içeren bir dizi döndürür:
- Mevcut state. İlk render sırasında,
init(initialArg)
veyainitialArg
(init
olmadığında) olarak ayarlanır. - State’i farklı bir değere güncellemenizi ve yeniden render tetiklemenizi sağlayan
dispatch
fonksiyonu.
Dikkat edilmesi gerekenler
useReducer
, bir Hook olduğundan, yalnızca bileşeninizin üst düzeyinde veya kendi Hook’larınızda çağırabilirsiniz. Döngüler veya koşullar içinde çağıramazsınız. Buna ihtiyacınız varsa, yeni bir bileşen oluşturun ve state’i taşıyın.- The
dispatch
function has a stable identity, so you will often see it omitted from Effect dependencies, but including it will not cause the Effect to fire. If the linter lets you omit a dependency without errors, it is safe to do. Learn more about removing Effect dependencies. - Strict Mode’da, React, tesadüfi karışıklıkları bulmanıza yardımcı olmak için reducer ve başlatıcı fonksiyonunuzu iki kez çağırır. Bu, yalnızca geliştirme amaçlı bir davranıştır ve canlı ortamı etkilemez. Reducer ve başlatıcı fonksiyonlarınız saf halde ise (olmaları gerektiği gibi), bu mantığınızı etkilememelidir. Çağrılardan birinin sonucu yoksayılır.
dispatch
fonksiyonu
useReducer
tarafından döndürülen dispatch
fonksiyonu, state’i farklı bir değere güncellemenizi ve yeniden render tetiklemenizi sağlar. dispatch
işlevine tek argüman olarak eylemi iletmelisiniz:
const [state, dispatch] = useReducer(reducer, { age: 42 });
function handleClick() {
dispatch({ type: 'incremented_age' });
// ...
React, dispatch
fonksiyonuna ilettiğiniz eylemi ve geçerli state
ile çağırdığınız reducer
işlevinin sonucunu kullanarak, bir sonraki state’i ayarlayacaktır.
Parametreler
action
: Kullanıcı tarafından gerçekleştirilen eylem. Herhangi bir türde bir değer olabilir. Genellikle bir eylem, kendisini tanımlayan birtype
özelliği ve isteğe bağlı olarak ek bilgi içeren diğer özellikler olan bir nesne olarak temsil edilir.
Dönüş değerleri
dispatch
fonksiyonları bir dönüş değeri içermez.
Dikkat edilmesi gereken noktalar
-
dispatch
fonksiyonu, sadece bir sonraki render işlemi için state değişkenini günceller. Eğerdispatch
fonksiyonunu çağırdıktan sonra state değişkenini okursanız, çağrı öncesinde ekranda olan eski değeri elde edersiniz. -
Eğer sağladığınız yeni değer, bir
Object.is
karşılaştırması ile belirlendiği gibi, mevcutstate
ile aynı ise React elemanı ve alt elemanlarının yeniden render edilmesini atlar. Bu bir optimizasyonudur. React, sonucu yok saymadan önce yine de bileşeninizi çağırması gerekebilir ancak bu kodunuzu etkilememelidir. -
React, state güncellemelerini toplu halde işler. Tüm olay yöneticileri çalıştırıldıktan ve kendi
set
fonksiyonlarını çağırdıktan sonra ekranı günceller. Bu, tek bir olay sırasında birden fazla yeniden render işlemini önler. React’ı ekranı güncellemeye zorlamanız gereken nadir durumlarda, örneğin DOM’a erişmek için,flushSync
kullanabilirsiniz.
Kullanım
Bir bileşene reducer eklemek
Bileşeninizin state’ini reducer ile yönetmek için, useReducer
’ı bileşeninizin en üst düzeyinde çağırın.
import { useReducer } from 'react';
function reducer(state, action) {
// ...
}
function MyComponent() {
const [state, dispatch] = useReducer(reducer, { age: 42 });
// ...
useReducer
, tam olarak iki öğe içeren bir dizi döndürür:
- Bu state değişkeninin mevcut state’i, başlangıçta sağladığınız başlangıç state’i.
- Etkileşime yanıt olarak değiştirmenize olanak tanıyan
dispatch
fonksiyonu.
Ekrandaki içeriği güncellemek için, kullanıcının yaptığı işlemi temsil eden bir nesne ile dispatch
fonksiyonunu çağırın. Bu nesne bir eylem olarak adlandırılır:
function handleClick() {
dispatch({ type: 'incremented_age' });
}
React, reducer fonksiyonunuzu çağırırken, mevcut state’i ve eylemi aktaracaktır. Reducer fonksiyonunuz, sonraki state’i hesaplayacak ve döndürecektir. React, bu sonraki state’i saklayacak, bileşeninizi bu state ile yeniden render edecek ve kullanıcı arayüzünü güncelleyecektir.
import { useReducer } from 'react'; function reducer(state, action) { if (action.type === 'incremented_age') { return { age: state.age + 1 }; } throw Error('Bilinmeyen eylem.'); } export default function Counter() { const [state, dispatch] = useReducer(reducer, { age: 42 }); return ( <> <button onClick={() => { dispatch({ type: 'incremented_age' }) }}> Yaşı artır </button> <p>Merhaba! {state.age} yaşındasın.</p> </> ); }
useReducer
, useState
ile çok benzerdir, ancak state güncelleme mantığını olay yöneticilerinden bileşeninizin dışındaki tek bir bir fonksiyona taşımanıza olanak tanır. useState
ve useReducer
arasında seçim yapma hakkında daha fazla bilgi edinin.
Reducer fonksiyonu yazmak
Reducer fonksiyonu şöyle tanımlanır:
function reducer(state, action) {
// ...
}
Ardından, sonraki state’i hesaplayacak ve döndürecek olan kodu yazmanız gerekiyor. Geleneksel olarak, bunu bir switch
ifadesi olarak yazmak yaygındır. switch
ifadesindeki her case
için, bir sonraki state’i hesaplayın ve döndürün.
function reducer(state, action) {
switch (action.type) {
case 'incremented_age': {
return {
name: state.name,
age: state.age + 1
};
}
case 'changed_name': {
return {
name: action.nextName,
age: state.age
};
}
}
throw Error('Bilinmeyen eylem: ' + action.type);
}
Eylemlerin herhangi bir şekli olabilir. Geleneksel olarak, eylemi tanımlayan bir type
özelliği olan nesnelerin geçirilmesi yaygındır. Bu özellik, reducer’ın bir sonraki state’i hesaplamak için ihtiyaç duyduğu minimal bilgiyi içermelidir.
function Form() {
const [state, dispatch] = useReducer(reducer, { name: 'Taylor', age: 42 });
function handleButtonClick() {
dispatch({ type: 'incremented_age' });
}
function handleInputChange(e) {
dispatch({
type: 'changed_name',
nextName: e.target.value
});
}
// ...
Eylem türü adları bileşeninizle ilgilidir. Her eylem, birden çok veri değişikliğine yol açsa bile, yalnızca bir etkileşimi tanımlar. State’in şekli rastgeledir, ancak genellikle bir nesne veya bir dizi olacaktır.
Daha fazla bilgi için state mantığını reducer’a çıkarma makalesini okuyun.
Örnek 1 / 3: Form (nesne)
Bu örnekte, reducer iki alanı olan bir state nesnesini yönetir: name
ve age
.
import { useReducer } from 'react'; function reducer(state, action) { switch (action.type) { case 'incremented_age': { return { name: state.name, age: state.age + 1 }; } case 'changed_name': { return { name: action.nextName, age: state.age }; } } throw Error('Bilinmeyen eylem: ' + action.type); } const initialState = { name: 'Taylor', age: 42 }; export default function Form() { const [state, dispatch] = useReducer(reducer, initialState); function handleButtonClick() { dispatch({ type: 'incremented_age' }); } function handleInputChange(e) { dispatch({ type: 'changed_name', nextName: e.target.value }); } return ( <> <input value={state.name} onChange={handleInputChange} /> <button onClick={handleButtonClick}> Yaşı artır </button> <p>Merhaba, {state.name}. {state.age} yaşındasın.</p> </> ); }
Başlangıç state’ini yeniden oluşturmayı önleme
React, başlangıç state’ini bir kez kaydeder ve sonraki render işlemlerinde bunu görmezden gelir.
function createInitialState(username) {
// ...
}
function TodoList({ username }) {
const [state, dispatch] = useReducer(reducer, createInitialState(username));
// ...
createInitialState(username)
’in sonucu sadece ilk render işlemi için kullanılmasına rağmen, hala her render işleminde bu fonksiyonu çağırıyorsunuz. Bu, büyük diziler oluşturuyorsa veya maliyetli hesaplamalar yapıyorsa israf olabilir.
Bunu çözmek için, bunu üçüncü argüman olarak useReducer
’a bir initializer fonksiyon olarak geçebilirsiniz:
function createInitialState(username) {
// ...
}
function TodoList({ username }) {
const [state, dispatch] = useReducer(reducer, username, createInitialState);
// ...
Yukarıdaki örnekte, createInitialState
bir username
argümanı alır. Başlatıcı fonksiyonunuz başlangıç state’ini hesaplamak için herhangi bir bilgiye ihtiyacı yoksa, null
’i useReducer
’ın ikinci argümanı olarak geçebilirsiniz.
Dikkat edin ki, createInitialState
kendisi olan fonksiyonu geçiriyorsunuz ve çağrı sonucu olan createInitialState()
’i değil. Bu şekilde, başlatma işleminden sonra başlangıç state’i yeniden oluşturulmaz.
Örnek 1 / 2: Başlatıcı fonksiyonunu geçirme
Bu örnek başlatıcı fonksiyonunu geçirir, bu nedenle createInitialState
fonksiyonu yalnızca başlatma sırasında çalışır. Girdiye yazdığınız gibi, bileşen yeniden render olduğunda çalışmaz.
import { useReducer } from 'react'; function createInitialState(username) { const initialTodos = []; for (let i = 0; i < 50; i++) { initialTodos.push({ id: i, text: username + "'nun görevi #" + (i + 1) }); } return { draft: '', todos: initialTodos, }; } function reducer(state, action) { switch (action.type) { case 'changed_draft': { return { draft: action.nextDraft, todos: state.todos, }; }; case 'added_todo': { return { draft: '', todos: [{ id: state.todos.length, text: state.draft }, ...state.todos] } } } throw Error('Bilinmeyen eylem: ' + action.type); } export default function TodoList({ username }) { const [state, dispatch] = useReducer( reducer, username, createInitialState ); return ( <> <input value={state.draft} onChange={e => { dispatch({ type: 'changed_draft', nextDraft: e.target.value }) }} /> <button onClick={() => { dispatch({ type: 'added_todo' }); }}>Ekle</button> <ul> {state.todos.map(item => ( <li key={item.id}> {item.text} </li> ))} </ul> </> ); }
Sorun giderme
Bir işlem yaptım, ancak state’i yazdırdığımda eski değerini veriyor
dispatch
fonksiyonunu çağırmak çalışan kodda state’i değiştirmez:
function handleClick() {
console.log(state.age); // 42
dispatch({ type: 'incremented_age' }); // 43 ile bir yeniden render isteği
console.log(state.age); // Hâlâ 42!
setTimeout(() => {
console.log(state.age); // Hâlâ 42!
}, 5000);
}
Bu, state’in bir anlık görüntü gibi davrandığı için böyle olur. State güncellendiğinde, yeni state değeriyle başka bir yeniden render isteği yapılır, ancak zaten çalışan olay yöneticinizdeki state
JavaScript değişkenini etkilemez.
Bir sonraki state değerini tahmin etmeniz gerekiyorsa, reducer’ı kendiniz çağırarak manuel olarak hesaplayabilirsiniz:
const action = { type: 'incremented_age' };
dispatch(action);
const nextState = reducer(state, action);
console.log(state); // { age: 42 }
console.log(nextState); // { age: 43 }
Bir işlem yaptım, ancak ekran güncellenmiyor
React, Object.is
karşılaştırması ile belirlendiği gibi bir sonraki state önceki state ile eşitse, güncellemenizi yok sayar. Bu genellikle doğrudan state içinde bir nesne veya bir dizi değiştirdiğinizde olur:
function reducer(state, action) {
switch (action.type) {
case 'incremented_age': {
// 🚩 Hatalı: mevcut nesneyi değiştirme
state.age++;
return state;
}
case 'changed_name': {
// 🚩 Hatalı: mevcut nesneyi değiştirme
state.name = action.nextName;
return state;
}
// ...
}
}
Mevcut bir state
nesnesini değiştirip geri döndürdüğünüz için React güncellemeyi görmezden geldi. Bunu düzeltmek için, onları mutasyona uğratmak yerine, her zaman state içindeki nesneleri güncelleyerek ve state içindeki dizileri güncelleyerek emin olmanız gerekir:
function reducer(state, action) {
switch (action.type) {
case 'incremented_age': {
// ✅ Doğru: yeni bir nesne oluşturmak
return {
...state,
age: state.age + 1
};
}
case 'changed_name': {
// ✅ Doğru: yeni bir nesne oluşturmak
return {
...state,
name: action.nextName
};
}
// ...
}
}
Dispatch işleminden sonra reducer state’in bir kısmı tanımsız (undefined) oluyor.
Yeni state’i döndürürken her case
dalının mevcut tüm alanları kopyaladığından emin olun:
function reducer(state, action) {
switch (action.type) {
case 'incremented_age': {
return {
...state, // Bunu unutma!
age: state.age + 1
};
}
// ...
Yukarıdaki ...state
olmadan, döndürülen yeni state yalnızca age
alanını ve başka hiçbir şeyi içermeyecektir.
Dispatch işleminden sonra tüm reducer state’i tanımsız (undefined) oluyor.
Eğer state beklenmedik şekilde undefined
olursa, muhtemelen case
state’lerinden birinde state döndürmeyi unutuyorsunuz veya eylem türünüz herhangi bir case
ifadesine uymuyor. Bunun sebebini bulmak için, anahtar kelime switch
’in dışında bir hata yaratın:
function reducer(state, action) {
switch (action.type) {
case 'incremented_age': {
// ...
}
case 'edited_name': {
// ...
}
}
throw Error('Bilinmeyen eylem:: ' + action.type);
}
Böyle hataları yakalamak için TypeScript gibi bir statik tip denetleyicisi de kullanabilirsiniz.
”Too many re-renders” hatası alıyorum
Too many re-renders. React limits the number of renders to prevent an infinite loop.
hatası alabilirsiniz. Bu genellikle, işlemi koşulsuz bir şekilde render
sırasında gönderdiğiniz anlamına gelir, böylece bileşeniniz döngüye girer: render, gönderim (bu da bir yeniden render yapar), render, gönderim (bu da bir yeniden render yapar) ve böyle devam eder. Bu sıklıkla, bir olay yöneticisi belirleme hatası nedeniyle oluşur:
// 🚩 Yanlış: yöneticiyi yeniden render sırasında çağırır.
return <button onClick={handleClick()}>Tıkla</button>
// ✅ Doğru: olay yöneticisini aşağıya aktarır.
return <button onClick={handleClick}>Tıkla</button>
// ✅ Doğru: iç içe bir fonksiyonu aktarır.
return <button onClick={(e) => handleClick(e)}>Tıkla</button>
Bu hatanın nedenini bulamazsanız, konsoldaki hatanın yanındaki ok’a tıklayın ve JavaScript yığınını (stack) tarayarak hatadan sorumlu belirli dispatch
fonksiyonu çağrısını bulun.
Reducer veya başlatıcı (initializer) fonksiyonlarım iki kez çalışıyor.
Strict Mode içinde, React reducer ve başlatıcı (initializer) fonksiyonlarınızı iki kez çağırır. Bu, kodunuzu bozmamalıdır.
Bu yalnızca geliştirme sırasında gerçekleşen davranış, bileşenleri saf olarak tutmanıza yardımcı olur. React, çağrılardan birinin sonucunu kullanır ve diğer çağrının sonucunu yoksayar. Bileşen, başlatıcı ve azaltıcı foknisyonlarınız saf halde olduğu sürece, bu mantığınızı etkilememelidir. Ancak yanlışlıkla saf halde olmayan foknisyonlarınız varsa, bu hataları fark etmenize yardımcı olur.
Örneğin, aşağıdaki saf halde olmayan reducer fonksiyonu, state’deki bir diziyi değiştirir:
function reducer(state, action) {
switch (action.type) {
case 'added_todo': {
// 🚩 Hata: state değiştirme (mutate)
state.todos.push({ id: nextId++, text: action.text });
return state;
}
// ...
}
}
React, reducer fonksiyonunuzu iki kez çağırdığı için, yapılacakların iki kez eklendiğini göreceksiniz, bu yüzden bir hatanın olduğunu bileceksiniz. Bu örnekte, hatayı düzeltmek için diziyi değiştirmek yerine ona yeni bir dizi atayabilirsiniz:
function reducer(state, action) {
switch (action.type) {
case 'added_todo': {
// ✅ Doğru: yeni bir state ile değiştirme
return {
...state,
todos: [
...state.todos,
{ id: nextId++, text: action.text }
]
};
}
// ...
}
}
Artık bu reducer fonksiyonu saf halde olduğuna göre, bir kez daha çağrılması davranışta fark yaratmaz. Bu, React’in iki kez aramakla hataları bulmanıza yardımcı olmasının sebebidir. Sadece bileşen, başlatıcı ve reducer fonksiyonlar saf halde olmalıdır. Olay yöneticisi saf halde olmak zorunda değildir, bu nedenle React asla olay yöneticinizi iki kez çağırmaz.
Daha fazla bilgi edinmek için bileşenleri saf olarak tutma konusunu okuyun.