Прочитайте початковий стан магазину в Redux Reducer


85

Початковий стан у програмі Redux можна встановити двома способами:

Якщо ви передаєте початковий стан своєму магазину, як ви зчитуєте цей стан із магазину і робите це першим аргументом у ваших редукторах?

Відповіді:


185

TL; DR

Без combineReducers()або подібного ручного коду initialStateзавжди перемагає state = ...в редукторі, тому що stateпереданий редуктору є initialState і ні undefined , тому синтаксис аргументу ES6 у цьому випадку не застосовується.

З combineReducers()поведінкою більш тонко. Ті редуктори, стан яких вказаний у initialState, отримають це state. Інші редуктори отримають undefined і через це повернуться до state = ...аргументу за замовчуванням, який вони вказали.

Загалом, initialStateвиграє держава, визначена редуктором. Це дозволяє редукторам вказувати початкові дані, які мають сенс для них, як аргументи за замовчуванням, але також дозволяє завантажувати наявні дані (повністю або частково), коли ви зволожуєте магазин із постійного сховища або сервера.

Спочатку розглянемо випадок, коли у вас є один редуктор.
Скажімо, ви не використовуєте combineReducers().

Тоді ваш редуктор може виглядати так:

function counter(state = 0, action) {
  switch (action.type) {
  case 'INCREMENT': return state + 1;
  case 'DECREMENT': return state - 1;
  default: return state;
  }
}

Тепер, припустимо, ви створюєте з ним магазин.

import { createStore } from 'redux';
let store = createStore(counter);
console.log(store.getState()); // 0

Початковий стан дорівнює нулю. Чому? Тому що другим аргументом createStoreбуло undefined. Це stateпередано вашому редуктору вперше. Коли Redux ініціалізується, він відправляє "фіктивну" дію для заповнення стану. Отже, ваш counterредуктор викликали з stateрівним undefined. Це саме той випадок, який «активує» аргумент за замовчуванням. Отже, stateзараз 0відповідає stateзначенням за замовчуванням ( state = 0). Цей стан ( 0) буде повернено.

Давайте розглянемо інший сценарій:

import { createStore } from 'redux';
let store = createStore(counter, 42);
console.log(store.getState()); // 42

Чому це 42, а ні 0, цього разу? Тому що createStoreбув викликаний 42як другий аргумент. Цей аргумент стає stateпереданим вашому редуктору разом із фіктивною дією. Цього разу stateне визначено (це 42!), Тому синтаксис аргументу ES6 за замовчуванням не впливає. stateЦе 42, і 42повертається з редуктора.


Тепер давайте розглянемо випадок, коли ви використовуєте combineReducers().
У вас є два редуктори:

function a(state = 'lol', action) {
  return state;
}

function b(state = 'wat', action) {
  return state;
}

Знижений редуктор combineReducers({ a, b })виглядає так:

// const combined = combineReducers({ a, b })
function combined(state = {}, action) {
  return {
    a: a(state.a, action),
    b: b(state.b, action)
  };
}

Якщо ми називаємо createStoreбез initialState, це буде форматувати stateв {}. Тому state.aі state.bбуде undefinedдо того моменту, коли він зателефонує aі bредукторам. І аргументи, aі bредуктори отримають undefinedяк свої state аргументи, і якщо вони вкажуть stateзначення за замовчуванням , вони будуть повернуті. Ось як комбінований редуктор повертає об’єкт { a: 'lol', b: 'wat' }стану при першому виклику.

import { createStore } from 'redux';
let store = createStore(combined);
console.log(store.getState()); // { a: 'lol', b: 'wat' }

Давайте розглянемо інший сценарій:

import { createStore } from 'redux';
let store = createStore(combined, { a: 'horse' });
console.log(store.getState()); // { a: 'horse', b: 'wat' }

Тепер я вказав initialStateаргумент як аргумент createStore(). Стан, повернутий із комбінованого редуктора, поєднує початковий стан, який я вказав для aредуктора, із 'wat'аргументом за замовчуванням, вказаним тим, що bредуктор вибрав сам.

Давайте згадаємо, що робить комбінований редуктор:

// const combined = combineReducers({ a, b })
function combined(state = {}, action) {
  return {
    a: a(state.a, action),
    b: b(state.b, action)
  };
}

У цьому випадку stateбуло вказано, щоб не повертатися до {}. Це був об’єкт із aполем, рівним 'horse', але без bполя. Ось чому aредуктор отримав 'horse'як свій stateі із задоволенням повернув його, але bредуктор отримав undefinedяк свій stateі таким чином повернув свою ідею за замовчуванням state(у нашому прикладі, 'wat'). Отак ми отримуємо { a: 'horse', b: 'wat' }взамін.


Підводячи підсумок, якщо ви дотримуєтеся конвенцій Redux і повертаєте початковий стан від редукторів, коли вони викликаються undefinedяк stateаргумент (найпростіший спосіб реалізувати це вказати stateзначення аргументу за замовчуванням ES6), приємна корисна поведінка для комбінованих редукторів. Вони віддадуть перевагу відповідному значенню в initialStateоб'єкті, який ви createStore()передаєте функції, але якщо ви не передали жодного або якщо відповідне поле не встановлено, stateзамість цього вибирається аргумент за замовчуванням, вказаний редуктором.Цей підхід працює добре, оскільки забезпечує як ініціалізацію, так і гідратацію існуючих даних, але дозволяє окремим редукторам скинути свій стан, якщо їх дані не збереглися. Звичайно, ви можете застосовувати цей шаблон рекурсивно, оскільки ви можете використовувати його combineReducers()на багатьох рівнях, або навіть складати редуктори вручну, викликаючи редуктори та надаючи їм відповідну частину дерева стану.


3
Дякую за чудові деталі - саме те, що я шукав. Гнучкість вашого API тут чудова. Частина, яку я не бачив, полягає в тому, що "дочірній" редуктор зрозуміє, яка частина початкового стану належить їй, виходячи з ключа, що використовується в combineReducers. Дякую ще раз.
кантера

Дякую за цю відповідь, Ден. Якщо я правильно перетравлюю вашу відповідь, ви говорите, що найкраще встановити початковий стан за допомогою типу дії редуктора? Наприклад, GET_INITIAL_STATE і викликати диспетчеру до цього типу дії, скажімо, зворотний виклик componentDidMount?
Con Antonakos,

@ConAntonakos Ні, я не маю на увазі це. Я маю на увазі вибір початкового стану саме там, де ви визначаєте редуктор, наприклад function counter(state = 0, action)або function visibleIds(state = [], action).
Дан Абрамов

1
@DanAbramov: використовуючи другий аргумент у createstore (), як я можу регідратати стан при перезавантаженні сторінки SPA останнім збереженим станом у redux замість початкових значень. Я думаю, я запитую, як я можу кешувати весь магазин та зберігати його при перезавантаженні та передавати його у функцію createstore.
jasan 02

1
@jasan: Використовуйте localStorage. egghead.io/lessons/…
Ден Абрамов

5

У двох словах: саме Redux передає початковий стан редукторам, вам нічого не потрібно робити.

Коли ви телефонуєте, createStore(reducer, [initialState])ви повідомляєте Redux, який початковий стан передається редуктору при першій дії.

Другий варіант, про який ви згадали, застосовується лише в тому випадку, якщо ви не пройшли початковий стан при створенні магазину. тобто

function todoApp(state = initialState, action)

state буде ініціалізовано лише в тому випадку, якщо Redux не передав штат


1
Дякуємо за відповідь - у випадку складу редуктора, якщо ви застосували початковий стан до магазину, як сказати дочірньому / допоміжному редуктору, яка частина дерева початкового стану йому належить? Наприклад, якщо у вас є menuState, як я можу сказати редуктору меню прочитати значення state.menuз магазину і використовувати це як початковий стан?
кантера

Дякую, але моє запитання повинно бути незрозумілим. Зачекаю, чи інші відгукнуться перед редагуванням.
кантера

1

як ви читаєте цей стан із магазину і робите це першим аргументом у своїх редукторах?

combReducers () виконують роботу за вас. Перший спосіб його написання насправді не є корисним:

const rootReducer = combineReducers({ todos, users })

Але інший, що є еквівалентом, є більш зрозумілим:

function rootReducer(state, action) {
   todos: todos(state.todos, action),
   users: users(state.users, action)
}

0

Сподіваюся, це відповідає на ваш запит (який я зрозумів як ініціалізацію редукторів під час передачі intialState і повернення цього стану)

Ось як ми це робимо (попередження: скопійовано з коду Typescript).

Суть цього - if(!state)тест у функції mainReducer (завод)

function getInitialState(): MainState {

     return {
         prop1:                 'value1',
         prop1:                 'value2',
         ...        
     }
}



const reducer = combineReducers(
    {
        main:     mainReducer( getInitialState() ),
        ...
    }
)



const mainReducer = ( initialState: MainState ): Reducer => {

    return ( state: MainState, action: Action ): MainState => {

        if ( !state ) {
            return initialState
        }

        console.log( 'Main reducer action: ', action ) 

        switch ( action.type ) {
            ....
        }
    }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.