アクションクリエーターで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の作成者であるDan Abramovは、それを制限すべきだと感じています。「許容できると思ういくつかのユースケースは、リクエストを行う前にキャッシュされたデータを確認するか、認証されているかどうかを確認する(つまり、条件付きディスパッチを行う)場合です。アクションクリエーターなどでデータを渡すことstate.something.itemsは間違いなくアンチパターンであり、変更履歴が不明瞭になるためお勧めしません。バグitemsがあり、正しくない場合それらの誤った値がどこから来ているのかを追跡することは困難です。アクションに応じてレデューサーによって直接計算されるのではなく、すでにアクションの一部です。そのため、これは慎重に行ってください。」
  • 現在のReduxメンテナであるMark Eriksonは、それgetStateは問題なく、サンクで使用することさえ奨励されている-それが存在する理由です。彼は、彼のブログ投稿「イディオマティックRedux:サンクス、サガ、抽象化、および再利用可能性についての考え」で、アクションクリエイターのステートにアクセスすることの長所と短所について説明しています。

これが必要であることがわかった場合は、提案した両方の方法で問題ありません。最初のアプローチはミドルウェアを必要としません:

import store from '../store';

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

ただしstore、一部のモジュールからエクスポートされたシングルトンであることに依存していることがわかります。ほとんどの場合、サーバー上ではリクエストごとに別個のストアが必要になるため、サーバーレンダリングをアプリ追加することが非常に難しくなるため、お勧めしません。したがって、技術的にはこのアプローチは機能しますが、モジュールからストアをエクスポートすることはお勧めしません。

これが、2番目のアプローチをお勧めする理由です。

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がトリガーされる可能性がある状況が1つあります。サンクベースのアクションクリエーターではなく、コンポーネントにPUT / POST選択のビジネスロジックを配置する方が良いですか?
vicusbass 2016年

3
ベストプラクティスは何ですか?アクションクリエーターでgetStateを使用している場合と同じ問題に直面しています。私の場合、これを使用して、フォームに保留中の変更があるかどうかを判断します(そうである場合は、ダイアログを表示するアクションをディスパッチします)。
Arne H. Bitubekk

42
アクションクリエーターでストアから読み取るのは問題ありません。正確な状態の形状に依存しないように、セレクター使用することをお勧めします。
ダンアブラモフ2016

2
ミドルウェアを使用してデータをミックスパネルに送信しています。そのため、アクション内にメタキーがあります。状態からミックスパネルにさまざまな変数を渡す必要があります。アクションクリエーターにそれらを設定することはアンチパターンのようです。このような使用例を処理するための最良のアプローチは何でしょうか?
Aakash Sigdel

2
ちょっと、あなた!getState2番目のパラメーターとしてredux thunk receiveを知らなかったので、頭を割っていました。どうもありがとうございました
Lai32290

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 2017年

@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);

この方法は非常に論理的です。
AhmetŞimşek18年

これは、それを行うための正しい方法と、それを行う方法です。アクション作成者は状態全体を知る必要はなく、それに関連するビットだけを知る必要があります。
アッシュ

3

ストアから読み取るのはそれほど悪いことではないことを指摘しておきます。すべてをコンポーネントに渡してからパラメータとして渡すよりも、ストアに基づいて実行する必要があることを決定する方がはるかに便利です。機能。クライアント側のレンダリングにのみ使用することを100%確信していない限り、ストアをシングルトーンとして使用しない方がはるかに良いとDanに完全に同意します(そうしないと、バグの追跡が困難になる場合があります)。

私は最近、reduxの冗長性に対処するためにライブラリを作成しました。すべてをミドルウェアに配置することをお勧めします。これにより、依存関係のインジェクションとしてすべてのことを行うことができます。

したがって、例は次のようになります。

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

これを解決する別の方法を提示します。これは、アプリケーションによっては、Danのソリューションよりも良い場合も悪い場合もあります。

アクションを2つの別個の関数に分割することにより、レデューサーからアクションに状態を取得できます。最初にデータを要求し、2番目にデータを操作します。これは、を使用して行うことができますredux-loop

最初に「親切にデータを要求する」

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

レデューサーで、askをインターセプトし、を使用して第2ステージアクションにデータを提供します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);

あなたはこれを再利用したい場合には、特定の抽出する必要がありますmapStatemapDispatchそしてmergeProps関数に他の場所で再利用するが、これは依存関係が完全に透明になります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.