Menu

React useReducer Hook'u: Sözdizimi, Örnekler, useState Farkı

useReducer state saklar ve her güncellemeyi tek bir reducer fonksiyonuna taşır: bir action dispatch edersin, reducer da sonraki state'i döndürür. Sözdizimini, saf bir reducer yazmayı, bir todo listesi örneğini, lazy başlatmayı ve ne zaman useState yerine seçileceğini öğren.

Bu sayfada çalıştırılabilir editörler var - düzenle, çalıştır ve sonucu anında gör.

useReducer, useState gibi state tutan ama onu değiştirmenin her yolunu tek bir fonksiyona, reducer'a taşıyan bir React hook'udur. Bileşenin { type: 'increment' } gibi bir action nesnesiyle dispatch çağırır, React da mevcut state'i ve o action'ı reducer'a aktarır; reducer sonraki state'i döndürür.

Düğmeler sayının nasıl değiştiğini söylemez, yalnızca ne olduğunu söyler. Tüm aritmetik reducer'da yaşar. { count: state.count * 2 } döndüren bir case 'double' ve onu dispatch eden bir düğme ekle; yeni bir güncelleme türünün nasıl yerleştiğini gör.

Sözdizimi

const [state, dispatch] = useReducer(reducer, initialArg, init); // init is optional

function reducer(state, action) {
    // return the next state
}
  • reducer, (state, action) => nextState biçiminde bir fonksiyondur. Onu bileşenin dışında tanımla; bileşenin kapsamından hiçbir şeye ihtiyacı yoktur.
  • initialArg başlangıç state'idir, yalnızca ilk render'da kullanılır.
  • init isteğe bağlıdır. Onu aktarırsan başlangıç state'i bunun yerine init(initialArg) olur.
  • state bu render için mevcut state'tir.
  • dispatch(action) reducer'a bir action gönderir ve sonuçla bir render planlar. Sabittir: her render'da aynı fonksiyondur, bu yüzden aşağı aktarmak ya da bağımlılık olarak listelemek güvenlidir.

Bir action herhangi bir değer olabilir, ama kurala göre ne olduğunu tarif eden bir type metni ve reducer'ın ihtiyaç duyduğu veriyi içeren bir nesnedir: { type: 'added', text: 'Buy milk' }.

Bir reducer yazmak

Çoğu reducer action.type üzerinde bir switch'tir ve her olay türü için bir case içerir. Her case yepyeni bir state döndürür; eskisini asla değiştirmez. default case hata fırlatır, böylece dispatch({ type: 'incremnet' }) gibi bir yazım hatası hiçbir şey yapmamak yerine yüksek sesle başarısız olur.

Action'ları aklındaki state değişikliğine göre (setTodos) değil, kullanıcının yaptığı şeye göre (added, toggled, deleted) adlandır. Böylece bileşen bir olay listesi gibi okunur ve her olayın ne anlama geldiğine karar veren tek yer reducer olur.

Reducer'lar saf olmalıdır

Bir reducer saf bir fonksiyondur: aynı state ve action verildiğinde aynı sonucu döndürür ve başka hiçbir şey yapmaz. Bu, içinde mutation, istek, zamanlayıcı, Math.random() ya da Date.now() olmaması demektir. React buna güvenir. Geliştirmede StrictMode altında React, saf olmayan bir reducer'ı fark etmene yardım etmek için reducer'ını her action için iki kez çağırır ve bir sonucu tutar. Buradaki önizleme bir production derlemesi gibi çalışır, bu yüzden bu sayfadaki örnekler onu bir kez çağırır.

İnsanların en çok çiğnediği kural mutation kuralıdır. state.todos'a push edip state döndürmek aynı nesneyi döndürür, bu yüzden React hiçbir değişiklik görmez ve render'ı atlar; bu, dizileri ve nesneleri güncellemek sayfasında anlatılan tuzağın aynısıdır. Yeni dizileri ve nesneleri spread, map ve filter ile oluştur.

// Wrong: mutates and returns the same object
case 'added':
    state.todos.push(action.todo);
    return state;

// Right: returns a new object with a new array
case 'added':
    return { ...state, todos: [...state.todos, action.todo] };

Bir olaya ait yan etkiler, örneğin bir sunucuya kaydetmek, dispatch'in yanında olay işleyicisine gider. State'e ait yan etkiler, örneğin onu localStorage'a senkronize etmek, bir efekte gider.

useReducer ile bir todo listesi

İşte gerçek iş yapan reducer: todo eklemek, işaretlemek ve silmek. Reducer'ın en üstündeki console.log her action'ı yazdırır; bu, hata ayıklarken state'in nasıl değiştiğini izlemenin kullanışlı bir yoludur. Hiçbir şeyi değiştirmediği için bir reducer'da yaygın olarak hoş görülen tek yan etkidir; yayına almadan önce çıkar.

Bir todo ekle, bir kutuyu işaretle ve birini sil, sonra konsolu oku: her değişiklik action'ı ve verisini adlandıran tek bir satırdır. Input'un metni useState'te kalır, çünkü alana özeldir ve başka hiçbir olay ona dokunmaz. İki hook'u tek bir bileşende karıştırmak normaldir.

Yeni todo'nun id'si tıklama işleyicisinde oluşturulur ve action içinde taşınır, bu yüzden reducer onu yalnızca kopyalar. Reducer'ın içine id: nextId++ yazmak onu saf olmaktan çıkarırdı: geliştirmede StrictMode altında ikinci çağrı bir id'yi atlardı.

dispatch state'i hemen değiştirmez

dispatch bir useState setter'ı gibi çalışır: bir render planlar ve mevcut işleyicindeki state değişkeni eski değeri korur. Yeni state'i hemen kullanmak için reducer'ı çağırarak onu kendin hesapla.

Birkaç kez tıkla. İlk kayıt satırı her zaman bir adım geridedir, ikincisi ise render'dan sonra düğmedeki sayıyla eşleşir. Reducer düz bir fonksiyon olduğu için onu kendin çağırmak güvenlidir; bu da onu saf tutmanın bir başka faydasıdır.

useState ve useReducer

İki hook da state saklar ve biriyle yazdığın her şeyi diğeriyle de yazabilirsin. Fark, güncelleme mantığının nerede yaşadığıdır.

useStateuseReducer
Güncelleme mantığıHer olay işleyicisindeTek bir reducer fonksiyonunda
Uygun olduğu yerBirkaç bağımsız değerBirçok olayın birlikte değiştirdiği birkaç alan
Kod miktarıBasit state için daha azBaşta daha fazla, olaylar arttıkça daha az
TestBileşen üzerinden test etReducer'ı düz bir fonksiyon olarak test et
Hata ayıklamaOnu hangi işleyicinin ayarladığını bulHer action'ı tek bir yerde kaydet

Aynı state'in birçok işleyicide güncellendiğini fark ettiğinde, bir olayın tutarlı kalması gereken birkaç state parçasını değiştirmesi gerektiğinde ya da bir işleyici çoğunlukla sonraki state hakkındaki mantıktan oluştuğunda useReducer'a başvur. Bir toggle ya da bir metin alanı için useState daha kısa ve daha açıktır. Sonradan geçiş yapmak zor değildir: setter'ları dispatch'lerle değiştir ve mantığı case'lere taşı.

Lazy başlatma

Başlangıç state'ini useReducer(reducer, createInitialState('Ada')) gibi bir fonksiyon çağrısı olarak yazarsan, React sonucunu yalnızca ilk seferde kullansa bile bu çağrı her render'da çalışır. Başlangıç state'ini oluşturmak pahalıysa ya da bir prop'tan türetilmesi gerekiyorsa üçüncü bir argüman aktar: bir init fonksiyonu. React init(initialArg)'ı bir kez, ilk render'da çağırır.

Input'a yaz: bileşen her tuş vuruşunda render olur, ama createInitialState yalnızca bir kez kayıt yazar. Şimdi çağrıyı useReducer(reducer, createInitialState('Ada')) olarak değiştir ve yeniden yaz. Fonksiyon her render'da çalışır ve React ilkinden sonra her seferinde sonucu atar.

Fonksiyonun kendisini aktar, çağırmanın sonucunu değil. useReducer(reducer, 'Ada', createInitialState) lazy'dir; useReducer(reducer, createInitialState('Ada')) değildir.

Context ile useReducer

Derin bir ağacın state'e ihtiyacı olduğunda bir reducer context ile iyi eşleşir. State'i ve dispatch'i en üstte context'e koy; aşağıdaki herhangi bir bileşen her katmandan props geçirmeden state'i okuyabilir ya da action gönderebilir. dispatch hiç değişmediği için yalnızca action gönderen bileşenler ayrı bir dispatch context'ini okuyabilir ve state değiştiğinde yeniden render'ı atlayabilir.

import { createContext, useContext, useReducer } from 'react';

const TodosContext = createContext(null);
const TodosDispatchContext = createContext(null);

export function TodosProvider({ children }) {
    const [todos, dispatch] = useReducer(todosReducer, []);
    return (
        <TodosContext value={todos}>
            <TodosDispatchContext value={dispatch}>{children}</TodosDispatchContext>
        </TodosContext>
    );
}

function AddTodo() {
    const dispatch = useContext(TodosDispatchContext);
    return <button onClick={() => dispatch({ type: 'added', text: 'New' })}>Add</button>;
}

React 19'da bir context nesnesi kendi provider'ı olarak çalışır (<TodosContext value={...}>); <TodosContext.Provider> da hâlâ çalışır. useContext sayfası context'in bileşenlere nasıl ulaştığını ve onları ne zaman yeniden render ettiğini anlatıyor.

Sıkça Sorulan Sorular

React'te useReducer nedir?

Güncellemeleri action olarak tarif edilen state için bir hook'tur. const [state, dispatch] = useReducer(reducer, initialState) çağırırsın, sonra olay işleyicilerinden dispatch({ type: 'added' }) çağırırsın. React mevcut state'i ve action'ı reducer'ına aktarır; onun döndürdüğü şey sonraki state olur.

useState yerine ne zaman useReducer kullanmalıyım?

Birkaç olay aynı state'i birbiriyle ilişkili şekillerde güncellediğinde, sonraki state aynı anda birkaç alana bağlı olduğunda ya da güncelleme mantığı onu bileşenin dışında, test edilebilir tek bir fonksiyonda istemeni gerektirecek kadar uzun olduğunda. Bir iki bağımsız değer için useState daha basittir.

Bir reducer neden saf olmalı?

React aynı action için reducer'ını birden fazla kez çağırabilir (StrictMode geliştirmede bunu bilerek yapar) ve her seferinde aynı sonucu bekler. Bu yüzden bir reducer state'i değiştirmemeli, veri çekmemeli, zamanlayıcı kurmamalı ya da rastgele değerler okumamalıdır. Bunları olay işleyicilerinde ya da efektlerde yap.

dispatch state'i hemen günceller mi?

Hayır. Bir useState setter'ı gibi dispatch bir render planlar. Mevcut olay işleyicisinin içinde state hâlâ eski değeri tutar; yeni state sonraki render'da görünür.

useReducer'ın üçüncü argümanı nedir?

İsteğe bağlı bir init fonksiyonu. Onu aktardığında React başlangıç state'ini init(initialArg) olarak ve yalnızca ilk render'da hesaplar. Başlangıç state'ini oluşturmak pahalı olduğunda ya da bir prop'a bağlı olduğunda kullanışlıdır.

Coddy programlama dilleri çizimi

Coddy ile kodlamayı öğren

BAŞLA