Доступ до стану Redux у створенні дії?


296

Скажіть, у мене є таке:

export const SOME_ACTION = 'SOME_ACTION';
export function someAction() {
  return {
    type: SOME_ACTION,
  }
}

І в цьому творцю дії я хочу отримати доступ до глобального стану магазину (усі редуктори). Чи краще це зробити:

import store from '../store';

export const SOME_ACTION = 'SOME_ACTION';
export function someAction() {
  return {
    type: SOME_ACTION,
    items: store.getState().otherReducer.items,
  }
}

або це:

export const SOME_ACTION = 'SOME_ACTION';
export function someAction() {
  return (dispatch, getState) => {
    const {items} = getState().otherReducer;

    dispatch(anotherAction(items));
  }
}

Відповіді:


522

Існують різні думки щодо того, чи є доступ до штату в розробниках дій гарною ідеєю:

  • Творець Redux Дан Абрамов вважає, що це слід обмежити: "Мало випадків використання, де я вважаю, що це прийнятно, - це перевірити кешовані дані перед тим, як подати запит, або перевірити, чи справді ви автентифіковані (іншими словами, виконуючи умовну відправлення). Я думаю, що передача даних, таких як state.something.itemsу розробника дій, безумовно, є антидіаграмою, і це відстороняє, оскільки це затьмарює історію змін: якщо є помилка і itemsє невірними, важко простежити, звідки беруться ці невірні значення, оскільки вони є це вже частина дії, а не безпосередньо обчислюється редуктором у відповідь на дію. Тому робіть це обережно ".
  • Поточний супровідник Redux Марк Еріксон каже, що це добре і навіть рекомендується використовувати getStateв гронах - тому воно існує . Він обговорює плюси та мінуси доступу до держави у створенні дій у своєму блозі Idiomatic Redux: Думки про грози, саги, абстракцію та повторне використання .

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

import store from '../store';

export const SOME_ACTION = 'SOME_ACTION';
export function someAction() {
  return {
    type: SOME_ACTION,
    items: store.getState().otherReducer.items,
  }
}

Однак ви можете бачити, що це покладається на storeте, що сингл експортується з якогось модуля. Ми не рекомендуємо це робити, оскільки це набагато складніше додати рендеринг сервера до вашої програми, оскільки в більшості випадків на сервері ви хочете мати окремий магазин за запитом . Тож як технічно цей підхід працює, ми не рекомендуємо експортувати магазин із модуля.

Ось чому ми рекомендуємо другий підхід:

export const SOME_ACTION = 'SOME_ACTION';
export function someAction() {
  return (dispatch, getState) => {
    const {items} = getState().otherReducer;

    dispatch(anotherAction(items));
  }
}

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

В ідеалі ваші дії не повинні бути «жирними» і повинні містити якомога менше інформації, але ви повинні сміливо робити те, що найкраще підходить вам у власній заяві. Поширені запитання про Redux містять інформацію про логіку розбиття між творцями дій та редукторами та час, коли це може бути корисно використовувати getStateу творці дії .


5
У мене є одна ситуація, коли вибір чогось компонента може викликати PUT або POST, залежно від того, чи магазин містить дані, пов'язані з компонентом чи ні. Чи краще розмістити бізнес-логіку для вибору PUT / POST в компоненті замість творця, який базується на громах?
vicusbass

3
Яка найкраща практика? Зараз я стикаюся з подібною проблемою, коли я використовую getState у своєму створенні дій. У моєму випадку я використовую його, щоб визначити, чи є значення, якщо форма очікує на зміну (і якщо так, я відправлю дію, що показує діалогове вікно).
Арн Х. Бітубек

42
Чудово читати з магазину у творця дії. Я б закликав вас використовувати селектор, щоб не залежати від точної форми стану.
Дан Абрамов

2
Я використовую проміжне програмне забезпечення для надсилання даних у mixpanel. Тож у мене всередині дії мета-ключ. Мені потрібно передати різні змінні від стану до мікспанелі. Встановлення їх на дію творців, здається, є анти-зразком. Який був би найкращий підхід для розгляду таких випадків використання?
Аакаш Зігдел

2

33

Коли ваш сценарій простий, ви можете використовувати

import store from '../store';

export const SOME_ACTION = 'SOME_ACTION';
export function someAction() {
  return {
    type: SOME_ACTION,
    items: store.getState().otherReducer.items,
  }
}

Але іноді вам action creatorпотрібно запустити кілька дій

наприклад, запит на асинхронізацію, тому вам потрібні REQUEST_LOAD REQUEST_LOAD_SUCCESS REQUEST_LOAD_FAILдії

export const [REQUEST_LOAD, REQUEST_LOAD_SUCCESS, REQUEST_LOAD_FAIL] = [`REQUEST_LOAD`
    `REQUEST_LOAD_SUCCESS`
    `REQUEST_LOAD_FAIL`
]
export function someAction() {
    return (dispatch, getState) => {
        const {
            items
        } = getState().otherReducer;
        dispatch({
            type: REQUEST_LOAD,
            loading: true
        });
        $.ajax('url', {
            success: (data) => {
                dispatch({
                    type: REQUEST_LOAD_SUCCESS,
                    loading: false,
                    data: data
                });
            },
            error: (error) => {
                dispatch({
                    type: REQUEST_LOAD_FAIL,
                    loading: false,
                    error: error
                });
            }
        })
    }
}

Примітка: для повернення функції у створенні дій вам потрібен redux-thunk


3
Чи можу я просто запитати, чи має бути перевірка стану "завантаження", щоб інший запит на ajax не робився, поки перший закінчується?
JoeTidee

@JoeTidee У прикладі виконується відправка стану завантаження. Якщо ви зробите цю дію, наприклад, кнопкою, ви перевірите, чи loading === trueіснує та відключите кнопку.
croraf

4

Я згоден з @Bloomca. Передати необхідне значення з магазину у функцію відправки як аргумент здається простішим, ніж експорт магазину. Я зробив тут приклад:

import React from "react";
import {connect} from "react-redux";
import * as actions from '../actions';

class App extends React.Component {

  handleClick(){
    const data = this.props.someStateObject.data;
    this.props.someDispatchFunction(data);
  }

  render(){
    return (
      <div>       
      <div onClick={ this.handleClick.bind(this)}>Click Me!</div>      
      </div>
    );
  }
}


const mapStateToProps = (state) => {
  return { someStateObject: state.someStateObject };
};

const mapDispatchToProps = (dispatch) => {
  return {
    someDispatchFunction:(data) => { dispatch(actions.someDispatchFunction(data))},

  };
}


export default connect(mapStateToProps, mapDispatchToProps)(App);

Цей метод цілком логічний.
Ахмет Шімшек

Це правильний спосіб зробити це і як я це роблю. Ваш творець дій не повинен знати весь стан, лише той біт, який стосується його.
Еш

3

Я хотів би зазначити, що читати з магазину не так уже й погано - може бути набагато зручніше вирішити, що робити на основі магазину, ніж передати все на компонент, а потім як параметр функція. Я повністю погоджуюся з Даном, що набагато краще не використовувати магазин як єдиний тон, якщо тільки ви не на 100% впевнені, що будете використовувати лише для візуалізації на стороні клієнта (інакше помилки можуть з’явитися).

Нещодавно я створив бібліотеку, щоб розібратися з багатозначністю скорочення, і я думаю, що це гарна ідея вкласти все в проміжне програмне забезпечення, так що у вас все є як ін'єкція залежності.

Отже, ваш приклад буде виглядати так:

import { createSyncTile } from 'redux-tiles';

const someTile = createSyncTile({
  type: ['some', 'tile'],
  fn: ({ params, selectors, getState }) => {
    return {
      data: params.data,
      items: selectors.another.tile(getState())
    };
  },
});

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


1

Представлення альтернативного способу вирішення цього питання. Це може бути кращим чи гіршим, ніж рішення Дана, залежно від вашої заявки.

Ви можете отримати стан від редукторів до дій, розділивши дію на 2 окремі функції: перший запитуйте дані, другий дійте на дані. Це можна зробити за допомогою redux-loop.

Спочатку "ласкаво запитайте дані"

export const SOME_ACTION = 'SOME_ACTION';
export function someAction() {
    return {
        type: SOME_ACTION,
    }
}

У редукторі перехопіть запит і надайте дані дії другої стадії, використовуючи redux-loop.

import { loop, Cmd } from 'redux-loop';
const initialState = { data: '' }
export default (state=initialState, action) => {
    switch(action.type) {
        case SOME_ACTION: {
            return loop(state, Cmd.action(anotherAction(state.data))
        }
    }
}

Маючи дані в руці, робіть все, що ви спочатку хотіли

export const ANOTHER_ACTION = 'ANOTHER_ACTION';
export function anotherAction(data) {
    return {
        type: ANOTHER_ACTION,
        payload: data,
    }
}

Сподіваюся, що це комусь допоможе.


0

Я знаю, що тут запізнююся на партію, але я прийшов сюди, щоб висловити думки про власне бажання використовувати державу в діях, а потім сформував своє, коли зрозумів, що я вважаю правильною поведінкою.

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

export const SOME_ACTION = 'SOME_ACTION';
export function someAction(items) {
  return (dispatch) => {
    dispatch(anotherAction(items));
  }
}

Можливо, це виглядає як витікання абстракцій, але ваш компонент очевидно повинен надіслати повідомлення, і корисне навантаження повідомлення повинно містити відповідний стан. На жаль, ваше запитання не має конкретного прикладу, тому що ми могли б працювати через «кращу модель» селекторів і дій таким чином.


0

Я хотів би запропонувати ще одну альтернативу, яку я вважаю найчистішою, але вона вимагає react-reduxабо щось подібне - також я використовую кілька інших фантазійних функцій по дорозі:

// actions.js
export const someAction = (items) => ({
    type: 'SOME_ACTION',
    payload: {items},
});
// Component.jsx
import {connect} from "react-redux";

const Component = ({boundSomeAction}) => (<div
    onClick={boundSomeAction}
/>);

const mapState = ({otherReducer: {items}}) => ({
    items,
});

const mapDispatch = (dispatch) => bindActionCreators({
    someAction,
}, dispatch);

const mergeProps = (mappedState, mappedDispatches) => {
    // you can only use what gets returned here, so you dont have access to `items` and 
    // `someAction` anymore
    return {
        boundSomeAction: () => mappedDispatches.someAction(mappedState.items),
    }
});

export const ConnectedComponent = connect(mapState, mapDispatch, mergeProps)(Component);
// (with  other mapped state or dispatches) Component.jsx
import {connect} from "react-redux";

const Component = ({boundSomeAction, otherAction, otherMappedState}) => (<div
    onClick={boundSomeAction}
    onSomeOtherEvent={otherAction}
>
    {JSON.stringify(otherMappedState)}
</div>);

const mapState = ({otherReducer: {items}, otherMappedState}) => ({
    items,
    otherMappedState,
});

const mapDispatch = (dispatch) => bindActionCreators({
    someAction,
    otherAction,
}, dispatch);

const mergeProps = (mappedState, mappedDispatches) => {
    const {items, ...remainingMappedState} = mappedState;
    const {someAction, ...remainingMappedDispatch} = mappedDispatch;
    // you can only use what gets returned here, so you dont have access to `items` and 
    // `someAction` anymore
    return {
        boundSomeAction: () => someAction(items),
        ...remainingMappedState,
        ...remainingMappedDispatch,
    }
});

export const ConnectedComponent = connect(mapState, mapDispatch, mergeProps)(Component);

Якщо ви хочете використовувати це , вам доведеться витягти специфічний mapState, mapDispatchі mergePropsв функції для повторного використання в інших місцях, але це робить залежностей абсолютно ясно.

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