Redux: Ich gebe den initialState in createStore an und dann zeige combineReducers einen meiner Reducer, der während der Initialisierung undefiniert zurückgegeben wurde

Erstellt am 23. Aug. 2017  ·  27Kommentare  ·  Quelle: reduxjs/redux

bei Reduzierstücken:

const todoBlog = combineReducers({
  blogTypeVisibilityFilter,
  blogs
})

in blogTypeVisibilityFilter:

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

export default blogTypeVisibilityFilter;

in Blogs:

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

im createStore:

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

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

und dann wird es falsch angezeigt:

Der Reducer "blogTypeVisibilityFilter" hat während der Initialisierung undefiniert zurückgegeben. Wenn der an den Reduzierer übergebene Zustand undefiniert ist, müssen Sie den Anfangszustand explizit zurückgeben. Der Anfangszustand darf nicht undefiniert sein. Wenn Sie keinen Wert für diesen Reduzierer festlegen möchten, können Sie null anstelle von undefined verwenden.

aber wenn ich mich nur verändere

const todoBlog = combineReducers({
  blogTypeVisibilityFilter,
  blogs
})

zu

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

in Reduzierstücken läuft es gut und ohne Fehler

Warum ich CombineReducers verwende geht es schief?

Hilfreichster Kommentar

Dies ist ein Bug-Tracker, kein Support-System. Bei Fragen zur Verwendung verwenden Sie bitte Stack Overflow oder Reactiflux. Danke!

Alle 27 Kommentare

Dies ist ein Bug-Tracker, kein Support-System. Bei Fragen zur Verwendung verwenden Sie bitte Stack Overflow oder Reactiflux. Danke!

Ich habe das gleiche Problem.

Schritte:

  1. Bild einfache Ladenform { foo, bar } .
  2. Erstellen Sie einen einfachen Reduzierer wie simpleReducer = state => state .
  3. Kombinieren Sie Reduzierstücke in reducers = combineReducers({ foo: simpleReducer, bar: simpleReducer }) .
  4. Erstellen Sie den Zustand mit dem Anfangswert mit createState(reducers, { foo: Obj1, bar: Obj2 }) .
  5. Fehler Reducer "foo" returned undefined during initialization. If the state passed to the reducer is undefined, you must explicitly return the initial state

Grund ist assertReducerShape , die alle Reduzierer mit undefiniertem Zustand ausführen , um Anfangszustandsteile zu erhalten. Mein einfacher Reduzierer gibt sein Argument zurück, das in diesem Fall nicht definiert ist. Warum wird Obj1 vom createState Aufruf nicht als Anfangszustand des foo-Teils betrachtet?

@timdorr können Sie das Problem erneut öffnen?

Abhilfe schafft simpleReducer = (state = null) => state . Aber ist es in Ordnung?

Mit combineReducers wird erwartet, dass jeder Slice-Reducer seinen Slice of State "besitzt". Das bedeutet, einen Anfangswert bereitzustellen, falls das aktuelle Slice nicht definiert ist, und combineReducers weist darauf hin, wenn Sie dies nicht tun.

'Anfangszustand' hat mehrere Bedeutungen. Sie sind der Standardrückgabewert von Reducer und das zweite Argument von createStore. Ich denke, es gibt ein Problem. Können Sie erklären, warum CombineReducers Reducer auf initialState überprüft und createState nicht?

@Vittly : combineReducers ist absichtlich rechthaberisch, createStore jedoch nicht. createStore ruft einfach die Root-Reducer-Funktion auf, die Sie ihm gegeben haben, und speichert das Ergebnis. combineReducers geht davon aus, dass Sie bei der Verwendung bestimmte Annahmen über die Organisation des Staates und das Verhalten der kombinierten Reduzierstücke übernehmen.

Vielleicht möchten Sie #191 und #1189 lesen, die in die Geschichte und Details eingehen, warum combineReducers eigensinnig ist und welche Meinungen es hat.

Das Problem ist also (in Kürze) "wenn Sie Reducer kombinieren, teilen Sie auch Ihren Store-Tree auf und Sie können etwas übersehen oder absichtlich nur einen Teil des Stores in createStore vorladen 2. Argument. Um den Store-Tree konsistenter zu machen, verwenden Sie assertReducerShape". Okay, danke für die Hinweise

Selbes Problem hier.
Also verwende ich createStore und übergebe einen Anfangszustand. Aber in reduct.js Datei, Funktion assertReducerShape redux wird meine Reduzierungen überprüfen , wenn ein Übergang undefined Zustand.

IMHO ist das ein Bug.

@JoseFMP : Ich würde effektiv garantieren, dass alles, was Sie sehen, _kein_ ein Fehler ist. Wenn Sie jedoch ein Repo oder eine CodeSandbox zusammenstellen können, die das Problem demonstriert, können wir einen Blick darauf werfen.

Sicher. Ich habe ein Problem in Stackoverflow eingereicht:
https://stackoverflow.com/questions/53018766/why-redux-reducer-getting-undefined-anstatt-des-initialen-Zustands

Das Problem ist ziemlich einfach. Wenn ich also beim Erstellen des Speichers einen Anfangszustand festlege ... warum sollte Redux den Reduzierer mit einem undefined Zustand auswerten wollen? Das macht in der Tat alle Reduzierer dazu, einen neuen Zustand undefined .

Ist aber Unsinn, da wir einen Anfangszustand passieren.
Das schlägt also fehl:

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, ich glaube ich weiß woran es liegt. Dies ist spezifisch für die Funktionsweise von combineReducers() .

combineReducers erwartet, dass alle "Slice Reducer", die Sie ihm geben, ein paar Regeln befolgen. Insbesondere erwartet es, dass _wenn_ Ihr Reducer mit state === undefined aufgerufen wird, es einen geeigneten Standardwert zurückgibt. Um dies zu überprüfen, ruft combineReducers() Ihren Reduzierer mit (undefined, {type : "SOME_RANDOMIZED_ACTION_TYPE"}) auf, um zu sehen, ob er einen undefinierten oder einen "echten" Wert zurückgibt.

Ihre Reduzierer tun derzeit nichts, außer alles zurückzugeben, was übergeben wurde. Das bedeutet, dass, wenn state undefiniert ist, sie _zurück_ undefiniert werden. combineReducers() sagt Ihnen also, dass Sie das erwartete Ergebnis nicht erreichen.

Ändern Sie einfach die Deklarationen in etwas wie configReducer = (config = {}, action) usw., und das behebt die Warnungen.

Um es noch einmal klarzustellen: Dies ist _kein_ ein Fehler. Dies ist eine Verhaltensvalidierung.

Hej @markerikson danke für deine Antwort.
Ein paar Kommentare:

1) Nein, es ist NICHT spezifisch für combineReducers . Wenn ich combineReducers und meinen eigenen Root-Reducer implementiere, passiert dasselbe.

2) Die Inkonsistenz hier besteht darin, dass in der Redux-Dokumentation erwähnt wird, dass der Reducer, wenn er mit einem unbekannten oder unerwarteten Status aufgerufen wird, den Status nicht ändern und den als Argument gelieferten Wert zurückgeben sollte. Dh ... wenn es undefined empfängt, sollte es dann undefined . Aber eine andere Regel besagt, dass der Reducer niemals undefined daher sind diese beiden Regeln, wie sie derzeit in der Redux-Dokumentation stehen, inkonsistent. Und im Widerspruch zur Umsetzung.

@JoseFMP : Wie ich bereits sagte, wenn Sie eine CodeSandbox bereitstellen können, die das Problem speziell demonstriert, mit Kommentaren, die genau darauf hinweisen, was Sie erwarten und was tatsächlich passiert, kann ich vielleicht einen Blick darauf werfen. (Bitte weisen Sie auch auf die Dokumentationsabschnitte hin, die Ihrer Meinung nach inkonsistent sind.) Bis dahin kann ich dies nur auf ein Missverständnis der internen Funktionsweise von Redux zurückführen.

Danke @markerikson .
Über die Dokumentation:
image

Wenn also der Reducer mit einer unbekannten Aktion aufgerufen wird, sollte ich denselben Zustand zurückgeben, da der Reducer nicht weiß, was die Aktion in diesem Reducer tun soll.

Beim Erstellen des Stores überprüft Redux die Reducer, indem es ihnen eine unbekannte Aktion und den vorherigen Zustand undefined sendet. Wenn der vorherige Zustand also undefined der Reducer laut Dokumentation undefined (da der Reducer die Aktion nicht kennt). Aber wenn der Reducer undefined zurückgibt, garantiere ich Ihnen, dass keine Redux-App funktionieren könnte.

Für den Beispielcode:

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 : Ich denke, der Hauptunterschied, den Sie hier vermissen, besteht darin, dass die Reducer-Funktion in diesem Beispiel _bereits_ den Fall behandelt hat, in dem state undefined , indem sie die ES6-Standardargumentensyntax verwendet:

function todoApp(state = initialState, action) {

Der Rat im Tutorial ist also richtig - ein Reducer _sollte_ immer den bestehenden Zustand zurückgeben, _vorausgesetzt, der undefinierte Fall wurde bereits behandelt_.

@markerikson
Vielen Dank für Ihre Antwort.
Das ist für mich klar. Fehlt nur noch im Tutorial. Im Tutorial heißt es nicht, dass der zurückzugebende Fall derjenige ist, der als Standardparameterwert im Reducer angegeben ist. Der Satz, den Sie kursiv gesetzt haben, ist also richtig. Das ist das. Meine Sorge ist, dass es im Tutorial fehlt. Oder ich habe es nicht gefunden, oder ist mehrdeutig.

Nun, unabhängig von Tutorial und/oder Dokumentation finde ich diese undefined Prüfung persönlich für den speziellen Fall, in dem ein Anfangszustand angegeben wird, nicht sinnvoll. Wenn kein Anfangszustand angegeben ist, finde ich es in Ordnung. Das ist jetzt keine Diskussion, sondern nur meine Meinung: Wenn ein Anfangszustand angegeben ist, finde ich diese Überprüfung sinnlos. Aber damit kann (und muss) ich sowieso leben ;)

Vielen Dank für Ihre Unterstützung Markus.

Wir können sicherlich versuchen, einige der Formulierungen im Rahmen unserer größeren Überarbeitung der Dokumente (#2590 ) umzuformulieren.

Eine der ursprünglichen Ideen von Redux war jedoch, dass jeder "Slice Reducer" dafür verantwortlich ist, seinen Teil des Zustands zu "besitzen", der sowohl Updates enthält als auch einen Anfangswert bereitstellt. Sie scheinen bei dem Aspekt "Nun, ich gebe einen Anfangswert für createStore " festzustecken, aber Sie ziehen die Erwartungen, wie Redux erwartet, dass es sich verhalten sollte, ab, selbst wenn Sie _nicht_ sind. den Wert separat angeben.

Etwas überrascht, dass ich dies noch nicht verlinkt habe, aber vielleicht möchten Sie die Seite Initialisierungsstatus in den Dokumenten durchlesen.

@markerikson
Ja du hast Recht. Die Dokumentation über den Ausgangszustand kenne ich schon. Was ich meine (und ich glaube, das ist der Grund, warum dieses Problem erstellt wurde und die Leute auch in diese Richtung meinten) ist, dass es nicht intuitiv ist, beim Erstellen des Shops "einen Anfangszustand" bereitzustellen und trotzdem einen "Standardzustand" zu definieren die Slice Reducer (oder Root Reducer). Das heißt, weil sie sehr oft, aber nicht notwendigerweise gleich oder sehr verwandt sind, fühlt es sich nicht intuitiv an, sie zweimal definieren zu müssen. Siehe als Beispiel die Beiträge von @ElonXun oder @Vittly , die genauso verwirrt waren wie ich. Dh meine Bemerkung bezieht sich nicht auf die API von Redux, sondern darauf, wie intuitiv es sich aus einer rein menschlichen Perspektive anfühlt, die API von Redux in diesem speziellen Szenario zu verwenden.

Beachten Sie, dass es im letzten Absatz um das menschliche Gefühl bei der Verwendung der API von Redux geht. Die Maschinenimplementierung oder die Gründe dafür können völlig legitim sein. Aber als API-Nutzer fühlt es sich verwirrend an.

Für mich zum Beispiel habe ich bei der Entwicklung sehr häufig einen Ausgangszustand in meiner Bewerbung. Normalerweise muss ich es zweimal eingeben. Einmal um es zu stecken, wenn ich den Store erstelle und ein anderes Mal, um es als Standardwert in den Slice-Reduzierern zu verteilen. Dafür natürlich viele Lösungen. Aber das Prinzip, das es für mich als Mensch verwirrend macht, ist, dass ich zweimal dasselbe tippen muss.

Ich schätze jedoch, dass es schwieriger ist, einen Sonderfall zu haben, in dem der Anfangszustand festgelegt ist und der nicht zwingend erforderlich ist, dass die Slice-Reduzierer oder der Root-Reduzierer einen "Standardzustand" haben, als ihn immer noch obligatorisch zu machen.

Der einzige Beitrag hier ist also zu erwähnen, dass es sich ein wenig kontraintuitiv anfühlt. Aber nur das.

Ich kann mich nicht erinnern, dass combineReducers assertReducerShape in früheren Versionen undefined ein ungültiger Zustand meiner Reduzierstücke. Um dies für mich sicherzustellen, benötige ich keine CombineReducers, ich brauche es, um Reducer zu "kombinieren" und das Root-Objekt neu zu erstellen, wenn sich etwas ändert. Es geht ein bisschen über das hinaus, was ich erwarten würde. IMO, es ist jetzt zu eigensinnig.

Zugegeben, ich habe die CombineReducers seit einiger Zeit nicht mehr verwendet, da ich sie in der Kernanwendung, die ich entwickelt habe, nicht benötigt habe. Aber ich versuche jetzt, diese Anwendung einzuschließen, und stellte fest, dass combineReducers in Ordnung war, um die Anwendung aufzuteilen. Das Verhalten von assertReducerShape ist überraschend.

@lukescott : dieser Scheck gibt es seit September 2015:

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

Und ja, combineReducers() ist _bewusst_ eigensinnig. Wenn Sie diese Warnungen nicht möchten, können Sie ganz einfach eine eigene ähnliche Funktion ohne diese Überprüfungen schreiben.

Als konkretes Beispiel sehen Sie sich Dans wesentliche Redux in 100 Zeilen ohne die Checks an .

Ich verstehe es. Die alte Version hat den Anfangszustand bestanden und bei jedem Lauf auf undefiniert überprüft (wenn ich mich erinnere). Überraschend ist, dass der Ausgangszustand beim ersten Durchlauf nicht überschritten wird. Das Übergeben von undefiniert ist ein Fehler in meiner App. Wie gesagt, ich arbeite schon seit einiger Zeit an diesem Projekt und habe CombineReducers eine Weile nicht mehr verwendet. Ich beginne gerade wieder damit, es zu verwenden, indem ich unsere Anwendung in einen Wrapper stecke.

Ich verstehe auch, dass es eigensinnig ist. Aber diese Meinung war "ein Reduzierstück darf nicht undefiniert zurückkommen" - das ist die Regel, an die ich mich halte. Es hat sich geändert in "Wir werden undefiniert passieren und Sie müssen den Zustand selbst aus dem Nichts erstellen". CombiReducers funktioniert nicht mehr mit "es gibt immer einen Anfangszustand" - was bedauerlich ist. Das ändert die Regeln, die vor 3 Jahren in Kraft waren, drastisch.

Ich bin mir wirklich nicht sicher, auf welche Verhaltensänderungen Sie sich beziehen. Können Sie konkrete Beispiele nennen?

Auf der Dokumentseite Initializing State werden die Interaktionen zwischen dem Argument preloadedState für createStore , der Handhabung von state = initialState für einen Reduzierer und combineReducers() . Das basiert auf einer Stack Overflow-Antwort, die Dan Anfang 2016 geschrieben hat, und in dieser Hinsicht hat sich nichts Wesentliches geändert, was mir bekannt ist.

@markerikson Du hast recht. Ich habe bis 2.0 zurückgeblickt und es sieht so aus, als ob es dies immer getan hat. Vielleicht hat die Komplexität meiner Bewerbung es nur noch deutlicher gemacht.

Das Problem, das ich habe, ist, dass mein Reduzierstück wie folgt definiert ist:

reducer(state: State, action: Action)

Das bedeutet, dass der Zustand nicht undefiniert sein darf. Das heißt, es muss einen Anfangszustand geben. Aber weil combineReducers Reducer(undefined, INIT) aufruft, um es zu überprüfen, verursacht es einen Fehler in meinem Code (wenn es erfolgreich ist, ruft es später Reducer(initState, INIT) auf - Aufruf von INIT zweimal).

Dies bedeutet, dass jedes Reduzierstück, das in CombineReducers verwendet wird, definiert werden muss als:

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

Daher ist meine Behauptung, dass dies vorher nicht der Fall war, falsch. Aber das Problem, das ich habe und das OP hat, ist wahrscheinlich das gleiche: Es zwingt Sie, Ihre Reduzierstücke mit einem optionalen Zustand zu deklarieren. Ich erhalte die Warnungen nicht wirklich, es führt zum Absturz meines Codes, weil er einen nicht-undefinierten Zustand erwartet.

Wenn Sie sagen, dass es so sein muss und ich es selbst rollen muss, ist das in Ordnung. Es ist nur bedauerlich, denn zusätzlich zur Bestätigung der Form wird bereits zur Laufzeit auf undefiniert geprüft. Die Durchsetzung der Form erscheint etwas übertrieben und kontraproduktiv.

Ja. Wie auf dieser Dokumentationsseite beschrieben, wird bei Verwendung von combineReducers speziell erwartet, dass jeder Slice-Reducer dafür verantwortlich ist, seinen eigenen Anfangszustand zurückzugeben, und dies sollte als Reaktion auf sliceReducer(undefined, action) tun. Ab combineReducers _ist_ Ihr Code fehlerhaft, da er nicht dem erwarteten Vertrag entspricht und Ihnen daher mitteilt, dass der Code falsch ist.

Liefern Sie eigentlich keinen Ausgangszustand? Wie sieht der Code eigentlich aus?

Ich stelle einen Anfangszustand über createStore bereit. Aber dieser Anfangszustand wird nicht an meinen Reduzierer übergeben, da assertReducerShape meinen Reduzierer explizit mit undefined aufruft, obwohl ich einen Anfangszustand übergebe:

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

Mein Code ist nicht fehlerhaft. Es erfordert nur, dass undefined _niemals_ übergeben wird. Ein Initialzustand ist erforderlich und wird vom Server generiert. combineReducers bricht nur diesen Vertrag und macht das strikte Eintippen des Reducers mit einem erforderlichen Zustand unmöglich. Ich kann dies ohne CombineReducers tun und es funktioniert gut. Ich denke, das ist, was ich tun muss - aber IMO - das Erzwingen eines optionalen Zustands ist unerwünscht und macht in meinem Fall CombineReducers nutzlos, da es meine streng typisierte Anwendung unterbricht.

Was ich nicht verstehe ist, warum assertReducerShape notwendig ist. Hier wird bereits zur Laufzeit nach undefined gesucht:

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

assertReducerShape scheint irgendwie überflüssig zu sein. Aber in meinem Fall bricht Typgarantien ... was ist die Sache, die es zu behaupten versucht?

Das ist also wirklich ein Interaktionsproblem mit TypeScript?

Um ehrlich zu sein, scheint dies letztendlich ein Unterschied in den Ansätzen zu sein.

combineReducers() wurde in JS geschrieben und versucht, Laufzeitüberprüfungen durchzuführen.

Sie versuchen, statische Prüfungen in TS durchzuführen, und die von Ihnen geschriebene Deklaration stimmt nicht combineReducers() tatsächlichen Funktionsweise von undefined aufgerufen werden, wenn er mit combineReducers() .

Die spezifische Zeile, die Sie aufgerufen haben, überprüft, ob der Rückgabewert Ihres Slice-Reducers nicht undefined wenn er mit einem (vermutlich aussagekräftigen) vorhandenen Slice-Wert des Zustands aufgerufen wird, während assertReducerShape() prüft, ob dies der Fall ist gibt undefined wenn ein anfänglicher Zustandswert von undefined (dh dass der Reducer seinen Zustand selbst initialisiert) und auch wenn er mit einem unbekannten Aktionstyp aufgerufen wird (dh dass der Reducer gibt standardmäßig immer den bestehenden Zustand zurück).

Wenn Sie undefiniert werden, ersetzen Sie es ... in meinem Fall, wenn mein Server aus irgendeinem Grund ohne die Daten antwortet, vielleicht weil keine aktive Sitzung vorhanden ist, habe ich dieses Problem ein Zauber für mich:

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

Wenn der Reducer undefiniert wird, ersetzen Sie undefined durch ein leeres Array, und Sie erhalten diesen Fehler nicht mehr.

Beifall!

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen

Verwandte Themen

ilearnio picture ilearnio  ·  3Kommentare

amorphius picture amorphius  ·  3Kommentare

captbaritone picture captbaritone  ·  3Kommentare

CellOcean picture CellOcean  ·  3Kommentare

caojinli picture caojinli  ·  3Kommentare