Redux: Я даю initialState в createStore, а затем combReducers показывает, что один из моих редукторов вернулся undefined во время инициализации.

Созданный на 23 авг. 2017  ·  27Комментарии  ·  Источник: reduxjs/redux

в редукторах:

const todoBlog = combineReducers({
  blogTypeVisibilityFilter,
  blogs
})

в blogTypeVisibilityFilter:

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

export default blogTypeVisibilityFilter;

в блогах:

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

в createStore:

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

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

а затем он показывает неправильно:

Редуктор "blogTypeVisibilityFilter" вернул undefined во время инициализации. Если состояние, переданное редуктору, не определено, вы должны явно вернуть начальное состояние. Начальное состояние не может быть неопределенным. Если вы не хотите устанавливать значение для этого редуктора, вы можете использовать null вместо undefined.

но когда я просто меняю

const todoBlog = combineReducers({
  blogTypeVisibilityFilter,
  blogs
})

к

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

в редукторах работает нормально и без ошибок

почему я использую CombinReducers, все идет не так?

Самый полезный комментарий

Это баг-трекер, а не система поддержки. По вопросам использования используйте Stack Overflow или Reactiflux. Спасибо!

Все 27 Комментарий

Это баг-трекер, а не система поддержки. По вопросам использования используйте Stack Overflow или Reactiflux. Спасибо!

У меня такая же проблема.

Шаги:

  1. Изображение простой формы магазина { foo, bar } .
  2. Создайте простой редуктор, например simpleReducer = state => state .
  3. Объедините редукторы в reducers = combineReducers({ foo: simpleReducer, bar: simpleReducer }) .
  4. Создайте состояние с начальным значением с помощью createState(reducers, { foo: Obj1, bar: Obj2 }) .
  5. Получить ошибку Reducer "foo" returned undefined during initialization. If the state passed to the reducer is undefined, you must explicitly return the initial state

Причина в том, что assertReducerShape запускают все редукторы с неопределенным состоянием для получения частей исходного состояния. Мой простой редуктор возвращает свой аргумент, который в этом случае не определен. Почему Obj1 из вызова createState не считается начальным состоянием foo-part?

@timdorr, можешь снова открыть проблему?

Обходной путь - сделать simpleReducer = (state = null) => state . Но это нормально?

С combineReducers ожидается, что каждый редуктор среза будет «владеть» своим срезом состояния. Это означает предоставление начального значения на случай, если текущий срез не определен, и combineReducers укажет, если вы этого не сделаете.

«Исходное состояние» имеет несколько значений. Они являются возвращаемым значением по умолчанию для reducer и вторым аргументом createStore. Думаю, есть проблема. Можете ли вы объяснить, почему combReducers проверяет редукторы для initialState, а createState - нет?

@Vittly : combineReducers намеренно самоуверен, а createStore - нет. createStore просто вызывает любую функцию корневого редуктора, которую вы ему дали, и сохраняет результат. combineReducers предполагает, что если вы его используете, вы принимаете определенный набор предположений о том, как должно быть организовано состояние и как должны себя вести комбинированные редукторы.

Возможно, вы захотите прочитать # 191 и # 1189, в которых подробно рассказывается о том, почему combineReducers является самоуверенным, и какие мнения у него есть.

Итак, проблема (вкратце) в том, что «если вы комбинируете редукторы, вы также разделяете свое дерево хранилища, и вы можете что-то пропустить или намеренно предварительно загрузить только часть хранилища во втором аргументе createStore. Чтобы сделать дерево хранилища более согласованным, используйте assertReducerShape». Хорошо спасибо за реф

Здесь та же проблема.
Поэтому я использую createStore и передаю начальное состояние. Но в файле reduct.js функция assertReducerShape redux будет проверять мои редукторы при передаче состояния undefined .

ИМХО это баг.

@JoseFMP : Я действительно гарантирую, что все, что вы видите, _не_ ошибка. Однако, если вы можете собрать репозиторий или CodeSandbox, демонстрирующий проблему, мы можем взглянуть на нее.

Конечно. Я зарегистрировал проблему в Stackoverflow:
https://stackoverflow.com/questions/53018766/why-redux-reducer-getting-undefined-instead-of-the-initial-state

Проблема довольно проста. Итак, если я установил начальное состояние при создании магазина ... зачем redux нужно оценивать редуктор с состоянием undefined ? Это действительно заставляет все редукторы возвращать новое состояние undefined .

Но это ерунда, т. К. Мы передаем начальное состояние.
Итак, это не удается:

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 :

Ах, думаю, я знаю, в чем проблема. Это зависит от того, как работает combineReducers() .

combineReducers ожидает, что все «редукторы срезов», которые вы ему дадите, будут следовать нескольким правилам. В частности, ожидается, что _ если_ ваш редуктор будет вызван с state === undefined , он вернет подходящее значение по умолчанию. Чтобы проверить это, combineReducers() фактически вызовет ваш редуктор с (undefined, {type : "SOME_RANDOMIZED_ACTION_TYPE"}) чтобы узнать, возвращает ли он неопределенное или «реальное» значение.

Ваши редукторы в настоящее время ничего не делают, кроме как возвращают все, что было передано. Это означает, что если state не определено, они будут _ return_ undefined. Итак, combineReducers() сообщает вам, что вы нарушаете ожидаемый результат.

Просто измените объявления на что-то вроде configReducer = (config = {}, action) и т. Д., И это устранит предупреждения.

Опять же, чтобы было ясно: это _не_ ошибка. Это проверка поведения.

Hej @markerikson, спасибо за ответ.
Несколько комментариев:

1) Нет, это НЕ относится к combineReducers . Если я не использую combineReducers и реализую свой собственный корневой редуктор, произойдет то же самое.

2) Несоответствие здесь заключается в том, что в документации Redux упоминается, что если редуктор вызывается с неизвестным или неожиданным статусом, он не должен изменять статус и возвращать значение, которое было передано в качестве аргумента. То есть ... если он получает undefined он должен вернуть undefined . Но другое правило гласит, что редуктор никогда не должен возвращать undefined поэтому эти два правила, как в настоящее время в документации по редуктору, несовместимы. И несовместимо с реализацией.

@JoseFMP : как я уже сказал ранее, если вы можете предоставить CodeSandbox, который конкретно демонстрирует проблему, с комментариями, указывающими именно то, что вы ожидаете, по сравнению с тем, что на самом деле происходит, я могу взглянуть. (Также, пожалуйста, укажите разделы документации, которые, по вашему мнению, противоречивы.) До тех пор я могу списать это только на непонимание того, как Redux работает внутри.

Спасибо @markerikson .
О документации:
image

Поэтому, если редуктор вызывается с неизвестным действием, я должен вернуть то же состояние, поскольку редуктор не знает, что действие должно делать в этом редукторе.

При создании хранилища Redux проверяет редукторы, отправляя им неизвестное действие и предыдущее состояние undefined . Итак, если предыдущее состояние было undefined редуктор должен вернуть undefined соответствии с документацией (потому что редуктор не знает действия). Но если редуктор вернет undefined , я гарантирую, что ни одно приложение Redux не сможет работать.

Для примера кода:

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 : Я думаю, что ключевое отличие, которое вам здесь не хватает, заключается в том, что в этом примере функция редуктора _already_ обработала случай, когда state равно undefined , с использованием синтаксиса аргументов по умолчанию ES6:

function todoApp(state = initialState, action) {

Так что совет в руководстве верен - редуктор _ должен_ всегда возвращать существующее состояние, _ предполагая, что неопределенный случай уже обработан_.

@markerikson
Спасибо за ваш ответ.
Для меня это ясно. Просто отсутствует в учебнике. В учебнике не говорится, что возвращаемый случай - это тот, который указан в качестве значения параметра по умолчанию в редукторе. Итак, предложение, которое вы сделали курсивом, правильное. Вот и все. Меня беспокоит то, что он отсутствует в учебнике. Или я не нашел, или это неоднозначно.

Теперь, независимо от учебника и / или документации, я считаю, что эта проверка undefined лично не имеет смысла для конкретного случая, в котором указано начальное состояние. Если начальное состояние не указано, я считаю, что это нормально. Теперь это не обсуждение, просто мое мнение: если указано начальное состояние, я считаю бессмысленным делать эту проверку. Но я все равно могу (и должен) жить с этим;)

Спасибо за поддержку, Марк.

Мы определенно можем попытаться перефразировать некоторые формулировки в рамках нашей более крупной модернизации документации (№ 2590).

Тем не менее, одна из исходных идей Redux заключалась в том, что каждый «редуктор среза» отвечает за «владение» своей частью состояния, которая включает в себя как обновления, так и предоставление начального значения. Вы, кажется, застряли в аспекте «ну, я предоставляю начальное значение для createStore », но вы сбрасываете со счетов ожидания относительно того, как Redux ожидает, что он должен себя вести, даже если вы _не_ указание стоимости отдельно.

Несколько удивлен, что я еще не связал это, но вы можете прочитать страницу состояния инициализации в документации.

@markerikson
Да вы правы. Я уже знаю документацию о начальном состоянии. Я имею в виду (и я действительно считаю, что причина, по которой эта проблема была создана, и люди также имели в виду в этом направлении), заключается в том, что нелогично указывать «начальное состояние» при создании магазина и при этом определять «состояние по умолчанию» в редукторы срезов (или корневые редукторы). Т.е. из-за того, что очень часто, но не обязательно, они одинаковы или очень связаны, кажется нелогичным определять их дважды. См., Например, сообщения @ElonXun или @Vittly, которые запутались так же, как и я. Т.е. мое замечание не связано с API Redux, а о том, насколько интуитивно понятно использование API Redux в этом конкретном сценарии с чисто человеческой точки зрения.

Обратите внимание, что последний абзац посвящен человеческим ощущениям при использовании API-интерфейса redux. Внедрение механизма или причины, стоящие за этим, могут быть полностью законными. Но как потребитель API это сбивает с толку.

Например, у меня при разработке очень часто есть начальное состояние в моем приложении. Так что обычно мне нужно набирать его дважды. Один раз, чтобы подключить его, когда я создаю магазин, и другой раз, чтобы распространять его как значение по умолчанию в редукторах срезов. Конечно, много решений для этого. Но принцип, который для меня как человека сбивает с толку, заключается в том, что я должен набирать дважды одно и то же.

Однако я считаю, что наличие особого случая, когда начальное состояние установлено, и не делать обязательным, чтобы редукторы срезов или корневой редуктор имели «состояние по умолчанию», - это больше проблем, чем все еще делать его обязательным.

Так что единственный вклад здесь - упомянуть, что это кажется немного нелогичным. Но только это.

Я не помню, чтобы combineReducers вызывал assertReducerShape в более ранних версиях. В моем коде undefined - недопустимое состояние моих редукторов. Мне не нужны combReducers, чтобы обеспечить это для меня, мне нужно «комбинировать редукторы» и воссоздавать корневой объект, если что-то изменится. Это выходит за рамки того, что я ожидал. ИМО, сейчас это слишком самоуверенно.

По общему признанию, я какое-то время не использовал combReducers, так как он мне не нужен в основном приложении, которое я разрабатываю. Но сейчас я пытаюсь обернуть это приложение и решил, что combReducers можно использовать для разделения приложения на части. Поведение assertReducerShape вызывает удивление.

@lukescott : этот чек существует с сентября 2015 года:

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

И да, combineReducers() _намеренно_ самоуверен. Если вам не нужны эти предупреждения, достаточно просто написать собственную аналогичную функцию без этих проверок.

В качестве конкретного примера рассмотрим суть Redux Дэна

Я понял. Старая версия проходила начальное состояние и проверялась на неопределенность при каждом запуске (если я помню). Что удивительно, начальное состояние не передается при первом запуске. Передача undefined - это ошибка в моем приложении. Как я уже сказал, я какое-то время работаю над этим проектом и какое-то время не использую combReducers. Я только сейчас возвращаюсь к его использованию, помещая наше приложение в оболочку.

Я также понимаю, что это самоуверенное мнение. Но это мнение было «редуктор не должен возвращать undefined» - это правило, которому я подчиняюсь. Он изменился на «мы передадим undefined, и вы должны создать состояние самостоятельно из ничего». combReducers больше не работает с «всегда есть начальное состояние», что прискорбно. Это коренным образом меняет правила, действовавшие 3 года назад.

Я действительно не уверен, о каких изменениях поведения вы говорите. Вы можете привести конкретные примеры?

Страница документации состояния инициализации описывает взаимодействия между аргументом preloadedState для createStore , обработкой state = initialState для редуктора и combineReducers() . Это основано на ответе на переполнение стека, который Дэн написал в начале 2016 года, и, насколько мне известно, в этом отношении ничего значимого не изменилось.

@markerikson Вы правы. Я оглянулся до версии 2.0, и, похоже, так было всегда. Возможно, сложность моего приложения только что сделала это более очевидным.

У меня проблема в том, что мой редуктор определяется как:

reducer(state: State, action: Action)

Это означает, что это состояние не может быть неопределенным. Это означает, что должно быть начальное состояние. Но поскольку combReducers вызывает reducer (undefined, INIT) для его проверки, это вызывает ошибку в моем коде (в случае успеха он позже вызывает reducer (initState, INIT) - вызывая INIT дважды).

Это означает, что любой редуктор, используемый в combReducers, ДОЛЖЕН быть определен как:

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

Так что мое утверждение, что раньше этого не было, неверно. Но проблема у меня и у OP, вероятно, одна и та же: она заставляет вас объявлять редукторы с необязательным состоянием. На самом деле я не получаю предупреждений, это приводит к сбою моего кода, потому что он ожидает состояние, отличное от неопределенного.

Если вы говорите, что так должно быть, и мне нужно самому катить, это нормально. Это просто прискорбно, потому что помимо утверждения формы, он уже проверяет неопределенность во время выполнения. Утверждение формы кажется излишним и контрпродуктивным.

да. Как описано на этой странице документации, когда вы используете combineReducers , ожидается, что каждый редуктор среза отвечает за возврат своего собственного начального состояния, и он должен делать это в ответ на sliceReducer(undefined, action) . С точки зрения combineReducers , ваш код _is_ глючит, потому что он не соответствует ожидаемому контракту и, следовательно, сообщает вам, что код неправильный.

Вы на самом деле не предоставляете начальное состояние? Как на самом деле выглядит код?

Я предоставляю начальное состояние через createStore. Но это начальное состояние не передается моему редуктору, потому что assertReducerShape явно вызывает мой редуктор с помощью undefined , несмотря на то, что я передаю начальное состояние:

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

Мой код не содержит ошибок. Это просто требует, чтобы undefined _ никогда_ не передавался в него. Требуется начальное состояние, оно генерируется сервером. combReducers просто разрывает этот контракт и делает невозможным строгую типизацию редуктора с требуемым состоянием. Я могу сделать это без combReducers, и он отлично работает. Я предполагаю, что это то, что мне нужно сделать, но IMO - принудительное использование необязательного состояния нежелательно, и в моем случае делает combReducers бесполезным, потому что это нарушает мое строго типизированное приложение.

Я не понимаю, зачем нужен assertReducerShape. Он уже проверяет undefined здесь во время выполнения:

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

assertReducerShape кажется излишним. Но в моем случае нарушаются гарантии типа ... что он пытается утверждать?

Значит, это действительно проблема взаимодействия TypeScript?

Честно говоря, похоже, что в конечном итоге это разница в подходах.

combineReducers() был написан на JS и пытается выполнять проверки во время выполнения.

Вы пытаетесь выполнить статические проверки в TS, и написанное вами объявление не соответствует тому, как combineReducers() самом деле работает undefined при использовании с combineReducers() .

Конкретная строка, которую вы вызвали, проверяет, что возвращаемое значение вашего редуктора среза не равно undefined при вызове с (предположительно значимым) существующим значением среза состояния, тогда как assertReducerShape() проверяет, что оно не возвращает undefined при заданном начальном значении состояния undefined (т. е. о том, что редуктор самостоятельно инициализирует свое состояние), а также при вызове с неизвестным типом действия (т. е. что редуктор по умолчанию всегда возвращает существующее состояние).

если вы получаете undefined, то замените его ... в моем случае, когда мой сервер отвечает без данных по какой-либо причине, возможно, из-за того, что нет активного сеанса, у меня возникла эта проблема, поэтому, делая это в редукторе, работало как оберег для меня:

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

Если редуктор получает значение undefined, замените undefined на пустой массив, и вы больше не получите эту ошибку.

Ваше здоровье!

Была ли эта страница полезной?
0 / 5 - 0 рейтинги