Redux: Saya memberikan initialState di createStore dan kemudian combineReducers menunjukkan salah satu reduksi saya dikembalikan tidak terdefinisi selama inisialisasi

Dibuat pada 23 Agu 2017  ·  27Komentar  ·  Sumber: reduxjs/redux

di reduksi:

const todoBlog = combineReducers({
  blogTypeVisibilityFilter,
  blogs
})

di blogTypeVisibilityFilter:

const blogTypeVisibilityFilter = (state, action)=>{
  switch (action.type) {
    case 'BLOG_TYPE_VISIBILITY_FILTER':
      return action.filter
    default:
      return state
  }
}

export default blogTypeVisibilityFilter;

di blog:

const blogs = (state,action)=>{
  return state
}

di createStore:

const initialState = {
  blogTypeVisibilityFilter:'SHOW_ALL_BLOG',
  blogs:data.data,
}

const store = createStore(reducer,initialState,window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__());

dan kemudian itu menunjukkan salah:

Peredam "blogTypeVisibilityFilter" kembali tidak terdefinisi selama inisialisasi. Jika status yang diteruskan ke peredam tidak ditentukan, Anda harus secara eksplisit mengembalikan status awal. Status awal mungkin tidak terdefinisi. Jika Anda tidak ingin menetapkan nilai untuk peredam ini, Anda dapat menggunakan null alih-alih undefined.

tapi saat aku baru saja berubah

const todoBlog = combineReducers({
  blogTypeVisibilityFilter,
  blogs
})

ke

const todoBlog = (state={},action)=>{
  return{
    blogTypeVisibilityFilter:blogTypeVisibilityFilter(state.blogTypeVisibilityFilter,action),
    blogs:blogs(state.blogs,action)
  }
}

di reduksi itu berjalan dengan baik dan tanpa kesalahan

mengapa saya menggunakan combineReducers itu salah?

Komentar yang paling membantu

Ini adalah pelacak bug, bukan sistem pendukung. Untuk pertanyaan penggunaan, silakan gunakan Stack Overflow atau Reactiflux. Terima kasih!

Semua 27 komentar

Ini adalah pelacak bug, bukan sistem pendukung. Untuk pertanyaan penggunaan, silakan gunakan Stack Overflow atau Reactiflux. Terima kasih!

Saya memiliki masalah yang sama.

Langkah:

  1. Gambar bentuk toko sederhana { foo, bar } .
  2. Buat peredam sederhana seperti simpleReducer = state => state .
  3. Gabungkan reduksi di reducers = combineReducers({ foo: simpleReducer, bar: simpleReducer }) .
  4. Buat status dengan nilai awal dengan createState(reducers, { foo: Obj1, bar: Obj2 }) .
  5. Dapatkan kesalahan Reducer "foo" returned undefined during initialization. If the state passed to the reducer is undefined, you must explicitly return the initial state

Alasannya adalah assertReducerShape yang menjalankan semua reduksi dengan status tidak terdefinisi untuk mendapatkan bagian status awal. Peredam sederhana saya mengembalikan argumennya yang tidak terdefinisi dalam kasus itu. Mengapa panggilan Obj1 dari createState tidak dianggap sebagai status awal bagian foo?

@timdorr dapatkah Anda membuka kembali masalah?

Solusinya adalah membuat simpleReducer = (state = null) => state . Tapi apakah itu baik-baik saja?

Dengan combineReducers , setiap peredam irisan diharapkan "memiliki" status irisannya. Itu berarti memberikan nilai awal jika irisan saat ini tidak ditentukan, dan combineReducers akan menunjukkan jika Anda tidak melakukannya.

'Keadaan awal' memiliki banyak arti. Mereka adalah nilai pengembalian default peredam dan argumen kedua createStore. Saya pikir ada masalah. Bisakah Anda menjelaskan mengapa combineReducers memeriksa reduksi untuk initialState dan createState tidak?

@Vittly : combineReducers sengaja beropini, sedangkan createStore tidak. createStore cukup memanggil fungsi peredam akar apa pun yang Anda berikan padanya, dan menyimpan hasilnya. combineReducers mengasumsikan bahwa jika Anda menggunakannya, Anda menerima serangkaian asumsi spesifik tentang bagaimana keadaan harus diatur dan bagaimana reduksi gabungan harus berperilaku.

Anda mungkin ingin membaca #191 dan #1189, yang membahas sejarah dan detail mengapa combineReducers dikritik dan pendapat apa yang dimilikinya.

Jadi masalahnya adalah (singkatnya) "jika Anda menggabungkan reduksi, Anda juga membagi pohon toko Anda dan Anda mungkin melewatkan sesuatu atau dengan sengaja memuat hanya sebagian dari toko di createStore 2nd arg. Untuk membuat pohon toko lebih konsisten gunakan assertReducerShape". Oke, terima kasih atas referensinya

Masalah yang sama disini.
Jadi saya menggunakan createStore dan memberikan status awal. Tetapi dalam file reduct.js , fungsi assertReducerShape redux akan memeriksa reduksi saya ketika melewati status undefined .

IMHO ini adalah bug.

@JoseFMP : Saya akan secara efektif menjamin bahwa apa pun yang Anda lihat adalah _bukan_ bug. Namun, jika Anda dapat mengumpulkan repo atau CodeSandbox yang menunjukkan masalah tersebut, kita dapat melihatnya.

Tentu. Saya mengajukan masalah dalam masalah di Stackoverflow:
https://stackoverflow.com/questions/53018766/why-redux-reducer-getting-undefined-instead-of-the-initial-state

Masalahnya cukup sederhana. Jadi jika saya menetapkan status awal saat membuat toko... mengapa redux ingin mengevaluasi peredam dengan status undefined ? Itu memang membuat semua reduksi mengembalikan status baru undefined .

Tapi omong kosong, karena kita melewati keadaan awal.
Jadi ini gagal:

import { combineReducers, createStore } from 'redux';

const configReducer = (config: any, action: any): any =>{
        return config;
}

const customData = (customData: any, action: any): any =>  {
        return customData;
}
const reducers = combineReducers({config: configReducer, customData: customDataReducer})

const defaultConfig = "cool config";
const data = "yieaah some data";

var initialState = {config: defaultConfig, customData: data};
const store = createStore(reducers, initialState) // at this point redux calls all the reducers with 'undefined' state. Why?

@JoseFMP :

Ah, sepertinya aku tahu apa masalahnya. Ini khusus untuk cara kerja combineReducers() .

combineReducers mengharapkan bahwa semua "pengecil irisan" yang Anda berikan akan mengikuti beberapa aturan. Secara khusus, ia mengharapkan bahwa _if_ peredam Anda dipanggil dengan state === undefined , itu akan mengembalikan nilai default yang sesuai. Untuk memverifikasi ini, combineReducers() benar-benar akan memanggil peredam Anda dengan (undefined, {type : "SOME_RANDOMIZED_ACTION_TYPE"}) untuk melihat apakah itu mengembalikan nilai yang tidak ditentukan atau "nyata".

Pereduksi Anda saat ini tidak melakukan apa pun kecuali mengembalikan apa pun yang diteruskan. Itu berarti bahwa jika state tidak ditentukan, mereka akan _mengembalikan_ tidak ditentukan. Jadi, combineReducers() memberi tahu Anda bahwa Anda melanggar hasil yang diharapkan.

Ubah saja deklarasi menjadi sesuatu seperti configReducer = (config = {}, action) , dll, dan itu akan memperbaiki peringatan.

Sekali lagi, untuk lebih jelasnya: ini _bukan_ bug. Ini adalah validasi perilaku.

Hej @markerikson terima kasih atas tanggapan Anda.
Beberapa komentar:

1) Tidak, ini TIDAK spesifik dari combineReducers . Jika saya tidak menggunakan combineReducers dan mengimplementasikan peredam root saya sendiri, hal yang sama akan terjadi.

2) Ketidakkonsistenan di sini adalah, dalam dokumentasi Redux disebutkan bahwa jika peredam dipanggil dengan status yang tidak diketahui atau tidak terduga, seharusnya tidak mengubah status dan mengembalikan nilai yang datang sebagai argumen. Yaitu ... jika menerima undefined maka harus mengembalikan undefined . Tetapi aturan lain mengatakan peredam tidak boleh mengembalikan undefined sehingga kedua aturan ini, seperti yang saat ini ada dalam dokumentasi redux tidak konsisten. Dan tidak sesuai dengan pelaksanaannya.

@JoseFMP : seperti yang saya katakan sebelumnya, jika Anda dapat memberikan CodeSandbox yang secara khusus menunjukkan masalah tersebut, dengan komentar yang menunjukkan dengan tepat apa yang Anda harapkan terjadi vs apa yang sebenarnya terjadi, saya mungkin dapat melihatnya. (Juga, harap tunjukkan bagian dokumen yang menurut Anda tidak konsisten.) Sampai saat itu, saya hanya bisa menganggap ini sebagai kesalahpahaman tentang bagaimana Redux bekerja secara internal.

Terima kasih @markerikson .
Tentang dokumentasi:
image

Jadi jika peredam dipanggil dengan tindakan yang tidak diketahui, saya harus mengembalikan keadaan yang sama karena peredam tidak tahu tindakan apa yang harus dilakukan di peredam ini.

Saat membuat toko, Redux memeriksa reduksi dengan mengirimkan tindakan yang tidak diketahui dan status sebelumnya undefined . Jadi jika keadaan sebelumnya adalah undefined , peredam harus mengembalikan undefined sesuai dengan dokumentasi (karena peredam tidak mengetahui tindakannya). Tetapi Jika peredam mengembalikan undefined , saya jamin Anda tidak ada aplikasi Redux yang bisa berfungsi.

Untuk kode contoh:

import { createStore } from 'redux';

const configReducer = (config: any, action: any): any =>{
        return config;
}

const customReducer = (customData: any, action: any): any =>  {
        return customData;
}

const reducers = (currentState: IAppState, action: any): IAppState => {

    var appStateToEvaluate: any;
    if (currentState) { //needs to add this to pass the `undefined` check of redux
        appStateToEvaluate = currentState;
    }
    else{
        //why redux is doing this ?!
        appStateToEvaluate = {}
    }
    const newState: IAppState = {
        cvConfig: configReducer(appStateToEvaluate.config, action),
        personalData: customReducer(appStateToEvaluate.customData, action)
    }

    return newState;
}

const defaultConfig = "cool config";
const data = "yieaah some data";

var initialState = {config: defaultConfig, customData: data};
const store = createStore(reducers, initialState) // at this point redux calls all the reducers with 'undefined' state. Why?

@JoseFMP : Saya pikir perbedaan utama yang Anda lewatkan di sini adalah bahwa dalam contoh itu, fungsi peredam telah _sudah_ menangani kasus di mana state adalah undefined , dengan menggunakan sintaks argumen default ES6:

function todoApp(state = initialState, action) {

Jadi saran dalam tutorial ini benar - peredam _harus_ selalu mengembalikan status yang ada, _dengan asumsi kasus yang tidak ditentukan telah ditangani_.

@markerikson
Terima kasih atas jawaban Anda.
Itu jelas bagi saya. Cuma ketinggalan tutorialnya. Dalam tutorial tidak disebutkan bahwa case yang akan dikembalikan adalah yang ditentukan sebagai nilai parameter default di peredam. Jadi kalimat yang Anda buat miring itu benar. Itu adalah itu. Kekhawatiran saya adalah bahwa itu hilang dalam tutorial. Atau saya tidak menemukannya, atau ambigu.

Sekarang, terlepas dari tutorial dan/atau dokumentasi, saya menemukan cek undefined secara pribadi tidak masuk akal untuk kasus spesifik di mana status awal ditentukan. Jika tidak ada status awal yang ditentukan, saya rasa itu baik-baik saja. Sekarang ini bukan diskusi, hanya pendapat saya: Jika keadaan awal ditentukan, saya merasa tidak ada gunanya melakukan pemeriksaan ini. Tapi saya tetap bisa (dan harus) menjalaninya ;)

Terima kasih atas dukungannya Markus.

Kami tentu saja dapat mencoba menyusun ulang beberapa frasa sebagai bagian dari perombakan dokumen kami yang lebih besar (#2590 ).

Yang mengatakan, salah satu ide orisinal di balik Redux adalah bahwa setiap "peredam irisan" bertanggung jawab untuk "memiliki" bagian dari statusnya, yang mencakup pembaruan dan memberikan nilai awal. Anda tampaknya agak terjebak pada aspek "baik, saya memberikan nilai awal ke createStore ", tetapi Anda mengabaikan harapan tentang bagaimana Redux mengharapkannya berperilaku bahkan jika Anda _aren't_ memberikan nilai secara terpisah.

Agak terkejut saya belum menautkan ini, tetapi Anda mungkin ingin membaca halaman Inisialisasi Status di dokumen.

@markerikson
Ya kamu benar. Saya sudah tahu dokumentasi tentang keadaan awal. Maksud saya (dan saya percaya adalah alasan mengapa masalah ini dibuat dan orang-orang juga bermaksud ke arah ini) adalah kontra-intuitif untuk memberikan "keadaan awal" saat membuat toko dan masih mendefinisikan "keadaan default" di pengurang irisan (atau peredam akar). Yaitu karena sangat sering, tetapi tidak harus, mereka sama, atau sangat terkait, rasanya kontra-intuitif harus mendefinisikannya dua kali. Lihat contohnya postingan @Vittly yang bingung sama saya. Yaitu komentar saya bukan API Redux, adalah tentang bagaimana intuitif rasanya menggunakan API Redux dalam skenario khusus ini, dari sudut pandang manusia murni.

Perhatikan bahwa paragraf terakhir adalah tentang perasaan manusia saat menggunakan API redux. Implementasi mesin atau alasan di baliknya mungkin benar-benar sah. Namun sebagai konsumen API rasanya membingungkan.

Misalnya, bagi saya, ketika mengembangkan, saya sangat sering memiliki status awal dalam aplikasi saya. Jadi Biasanya saya perlu mengetiknya dua kali. Sekali pasang ketika saya membuat toko dan lain waktu untuk mendistribusikannya sebagai nilai default di reduksi irisan. Tentu banyak solusi untuk itu. Tetapi prinsip yang bagi saya sebagai manusia membuatnya membingungkan adalah saya harus mengetik dua kali hal yang sama.

Namun saya rasa memiliki kasus khusus di mana keadaan awal diatur dan tidak membuat wajib bahwa pengurang irisan atau peredam akar memiliki "keadaan default" lebih banyak masalah daripada tetap membuatnya wajib.

Jadi satu-satunya kontribusi di sini adalah untuk menyebutkan bahwa itu terasa sedikit kontra-intuitif. Tapi hanya itu.

Saya tidak ingat combineReducers memanggil assertReducerShape di versi sebelumnya. Dalam kode saya undefined adalah status reduksi saya yang tidak valid. Saya tidak perlu combineReducers untuk memastikan ini untuk saya, saya membutuhkannya untuk "menggabungkan reduksi" dan membuat kembali objek root jika ada perubahan. Ini akan sedikit melampaui apa yang saya harapkan untuk dilakukan. IMO, itu terlalu berpendirian sekarang.

Memang saya belum pernah menggunakan combineReducers dalam beberapa waktu karena saya tidak membutuhkannya dalam aplikasi inti yang telah saya kembangkan. Tapi saya sekarang mencoba untuk membungkus aplikasi itu, dan saya pikir combineReducers baik-baik saja digunakan untuk mengiris aplikasi. Perilaku assertReducerShape mengejutkan.

@lukescott : cek itu sudah ada sejak September 2015:

https://github.com/reduxjs/redux/commit/a1485f0e30ea0ea5e023a6d0f5947bd56edff7dd

Dan ya, combineReducers() _disengaja_ beropini. Jika Anda tidak menginginkan peringatan tersebut, cukup mudah untuk menulis fungsi serupa Anda sendiri tanpa pemeriksaan tersebut.

Sebagai contoh spesifik, lihat Dan's Gist Redux dalam 100 baris tanpa tanda centang .

Saya mengerti. Versi lama melewati status awal dan memeriksa tidak terdefinisi pada setiap proses (jika saya ingat). Yang mengejutkan adalah keadaan awal tidak dilewati pada putaran pertama. Melewati undefined adalah kesalahan di aplikasi saya. Seperti yang saya katakan, saya telah mengerjakan proyek ini untuk sementara waktu, dan belum menggunakan combineReducers untuk sementara waktu. Saya baru saja kembali menggunakannya, menempatkan aplikasi kita di dalam pembungkus.

Saya juga mendapatkan bahwa itu berpendirian. Tetapi pendapat itu adalah "peredam tidak boleh mengembalikan tidak terdefinisi" - yang merupakan aturan yang saya patuhi. Itu telah berubah menjadi "kami akan melewati tidak terdefinisi dan Anda harus membuat status sendiri dari ketiadaan". combineReducers tidak lagi berfungsi dengan "selalu ada status awal" - yang sangat disayangkan. Itu secara drastis mengubah aturan yang ada 3 tahun lalu.

Saya benar-benar tidak yakin perubahan perilaku apa yang Anda maksud. Bisakah Anda menunjukkan contoh spesifik?

Halaman dokumen Status Inisialisasi menjabarkan interaksi antara argumen preloadedState untuk createStore , penanganan state = initialState untuk peredam, dan combineReducers() . Itu didasarkan pada jawaban Stack Overflow yang ditulis Dan pada awal 2016, dan tidak ada yang berarti yang berubah dalam hal itu yang saya ketahui.

@markerikson Anda benar. Saya melihat ke belakang sejauh 2.0, dan sepertinya selalu melakukan ini. Mungkin kerumitan aplikasi saya baru saja membuatnya lebih jelas.

Masalah yang saya miliki adalah bahwa peredam saya didefinisikan sebagai:

reducer(state: State, action: Action)

Artinya keadaan tidak boleh tidak terdefinisi. Itu berarti harus ada keadaan awal. Tetapi karena combineReducers memanggil peredam (tidak terdefinisi, INIT) untuk memeriksanya, itu menyebabkan kesalahan dalam kode saya (jika berhasil nanti memanggil peredam (initState, INIT) - memanggil INIT dua kali).

Ini berarti setiap peredam yang digunakan dalam combineReducers HARUS didefinisikan sebagai:

reducer(state: State | undefined, action: Action)

Jadi pernyataan saya itu tidak melakukan itu sebelumnya adalah salah. Tetapi masalah yang saya miliki, dan OP miliki, kemungkinan besar sama: Ini memaksa Anda untuk mendeklarasikan reduksi Anda dengan status opsional. Saya sebenarnya tidak mendapatkan peringatan, itu menyebabkan kode saya macet karena mengharapkan keadaan yang tidak ditentukan.

Jika Anda mengatakan itu harus seperti ini dan saya harus menggulungnya sendiri, tidak apa-apa. Sangat disayangkan karena selain menegaskan bentuk, itu sudah memeriksa tidak terdefinisi saat run-time. Menegaskan bentuk tampaknya sedikit berlebihan dan kontra-produktif.

Ya. Seperti yang dijelaskan di halaman dokumen itu, ketika Anda menggunakan combineReducers , harapannya secara khusus adalah bahwa setiap peredam irisan bertanggung jawab untuk mengembalikan status awalnya sendiri, dan ia harus melakukannya sebagai tanggapan terhadap sliceReducer(undefined, action) . Dari sudut combineReducers , kode Anda _is_ bermasalah karena tidak mengikuti kontrak yang diharapkan, dan karena itu memberi tahu Anda bahwa kode itu salah.

Apakah Anda tidak benar-benar memberikan status awal? Seperti apa sebenarnya kode itu?

Saya memberikan status awal melalui createStore. Tetapi status awal itu tidak diteruskan ke peredam saya karena assertReducerShape secara eksplisit memanggil peredam saya dengan undefined , meskipun saya melewati status awal:

https://github.com/reduxjs/redux/blob/231f0b32641059caab3f98a3e04d3afaad19a7d1/src/combineReducers.js#L68

Kode saya tidak bermasalah. Itu hanya mengharuskan undefined _never_ diteruskan ke dalamnya. Status awal diperlukan dan dihasilkan dari server. combineReducers baru saja memutuskan kontrak itu dan membuat pengetikan peredam secara ketat dengan status yang diperlukan menjadi tidak mungkin. Saya bisa melakukan ini tanpa combineReducers dan berfungsi dengan baik. Saya kira itulah yang harus saya lakukan - tetapi IMO - memaksa keadaan opsional tidak diinginkan, dan dalam kasus saya membuat combineReducers tidak berguna karena merusak aplikasi saya yang diketik dengan ketat.

Yang tidak saya mengerti adalah mengapa assertReducerShape diperlukan. Itu sudah memeriksa undefined di sini saat runtime:

https://github.com/reduxjs/redux/blob/231f0b32641059caab3f98a3e04d3afaad19a7d1/src/combineReducers.js#L169

assertReducerShape tampaknya agak berlebihan. Tetapi dalam kasus saya, jaminan tipe istirahat ... apa hal yang coba ditegaskan?

Jadi, ini benar-benar masalah interaksi TypeScript?

Sejujurnya, ini sepertinya pada akhirnya perbedaan pendekatan.

combineReducers() ditulis dalam JS, dan sedang mencoba melakukan pemeriksaan runtime.

Anda mencoba melakukan pemeriksaan statis di TS, dan deklarasi yang Anda tulis tidak cocok dengan cara kerja combineReducers() sebenarnya. Jadi, dalam hal ini, deklarasi tipe untuk peredam irisan Anda salah, karena _can_ dan _will_ dipanggil dengan undefined bila digunakan dengan combineReducers() .

Baris spesifik yang Anda panggil memeriksa bahwa nilai kembalian peredam irisan Anda bukan undefined saat dipanggil dengan nilai irisan status (yang mungkin berarti), sedangkan assertReducerShape() memeriksa apakah itu tidak mengembalikan undefined ketika diberi nilai keadaan awal undefined (yaitu, bahwa peredam menginisialisasi sendiri keadaannya) dan juga ketika dipanggil dengan jenis tindakan yang tidak diketahui (yaitu, bahwa peredam selalu mengembalikan status yang ada secara default).

jika Anda tidak terdefinisi, gantilah ... dalam kasus saya, ketika server saya membalas tanpa data karena alasan apa pun, mungkin karena tidak ada sesi aktif, saya mendapatkan masalah itu, jadi melakukan ini di peredam, berfungsi sebagai pesona bagi saya:

export default function itemReducer(state = initialState.items | undefined, action){ 
    switch(action.type) 
   { 
         case "LOAD_ITEMS_SUCCESS":
               if(action.items==undefined) { 
                        action.items=[]; 
               }
                return action.items

Jika peredam menjadi tidak terdefinisi, ganti tidak terdefinisi untuk array kosong, dan Anda tidak akan mendapatkan kesalahan itu lagi.

Bersulang!

Apakah halaman ini membantu?
0 / 5 - 0 peringkat

Masalah terkait

timdorr picture timdorr  ·  3Komentar

CellOcean picture CellOcean  ·  3Komentar

vraa picture vraa  ·  3Komentar

jbri7357 picture jbri7357  ·  3Komentar

captbaritone picture captbaritone  ·  3Komentar