2

レデューサーとアクションに関する初心者の質問。redux のドキュメントから:

アクションは、何かが起こったという事実を記述しますが、それに応じてアプリケーションの状態がどのように変化するかは指定しません。

同じ引数を指定すると、リデューサーは次の状態を計算して返す必要があります。驚く様な事じゃない。副作用はありません。API 呼び出しはありません。突然変異なし。ただの計算。

したがって、次のシナリオを考えてみます。

  1. ユーザーは地図上にポイントを配置し、それらのポイント間のルートを取得できます。
  2. ユーザーが最初にマップをクリックすると、これが開始点になります。彼が 2 回目にクリックしたとき - これが彼の終点です。その後クリックすると、前のポイントと終了位置の間にポイントが追加されます。
  3. 各ポイントが追加された後 (最初のポイントを除く)、新しいポイントと前のポイントの間のルートを計算する必要があります。したがって、S -> A -> F があり、ポイント B を追加する場合 (S -> A -> B -> F)、2 つのルートを計算する必要があります A -> B および B -> F

したがって、3+ ポイントを追加すると、2 つの副作用があります。

  • リストの末尾以外に新しい点が配置されます
  • 新しいルートは、フィニッシュ ポイントまで計算する必要があります。

Point 構造を次のようにモデル化すると:

 // typescript
interface Point {
  coordinates;
  routeTo?;
}

アクションでアイテムの位置計算とルート検索を実行するのは正しいですか?

  // pseudo code
    export function addMapPoint(newPoint) {
      return (dispatch, getState) => {
        const {points} = getState();
        const position = getInsertPosition(points, newPoint)
        dispatch(addPoint(newPoint, position));
        if (points.length >= 2) {
          const previousPoint = getPreviousPoint(points, position);
          calculateRoute(newPoint, previousPoint).then(route => {
            dispatch(updateRoute(newPoint, route))
          })
        }
      }
    }

私にとって、これは「しかし、アプリケーションの状態がどのように変化するかを指定しないでください」と矛盾します-アクションから、新しいポイントを挿入する場所を指定しているためです。
レデューサーで挿入位置を計算できましたが、フィニッシュ ポイントのルート データを取得するにはどうすればよいですか?
ここで正しいアプローチは何ですか?

4

1 に答える 1

5

calculateRoute2 つのポイントを受け取り、それらの間のルートを解決する promise を返す関数があると仮定します。

まず、単純なアクション クリエーターを作成して、ポイントが正しく保存されていることを確認します。

let addPoint = (point, index) => {
    return {
        type: 'ADD_POINT',
        point: point,
        index: index
    }
}

次に、このアクションをレデューサーで処理しましょう。

let reducer = (state = { points: [] }, action) => {
    switch (action.type) {
        case 'ADD_POINT':
            return Object.assign({}, state, {
                points: [
                    ...state.points.slide(0, action.index),
                    action.point,
                    ...state.points.slide(action.index + 1)
                ]
            });
        default:
            return state;
    }
}

さて、ユーザーがポイントを追加した後、それを使用してアクションを作成し、addPointそれをディスパッチします。ここまでは良いことですが、これは簡単なことです。

私が目指している構造は、Reducer にもルート リストを含めることです。そのため、それをサポートするように拡張しましょう。

let reducer = (state = { points: [], routes: [] }, action) => {
    switch (action.type) {
        case 'ADD_POINT':
            return Object.assign({}, state, {
                points: [
                    ...state.points.slide(0, action.index),
                    action.point,
                    ...state.points.slide(action.index + 1)
                ]
            });
        case 'UPDATE_ROUTES':
            return Object.assign({}, state, {
                routes: action.routes
            });
        default:
            return state;
    }
}

アクション作成者は次のようになります。

let updateRoutes = (routes) => {
    return {
        type: 'UPDATE_ROUTES',
        routes: routes
    }
}

ルート コレクション全体をオーバーライドしていることに注意してください。今のところは問題ありませんが、本番システムでは少し最適化する必要があるでしょう。

ここで、実際にいくつかのロジックを記述する必要があります。ポイントのコレクションを取得し、それぞれのルートのリストを解決する promise を返す があるという便利な仮定を仮定しcalculateRoutesます。各ルートは、2 つのポイントと実際のルートを含むオブジェクトになります。そうは言っても、thunk今は次のようになります。

addPointAndUpdateRoutes = (point, index) => {
    return (dispatch, getState) => {
        // First, update the point list
        dispatch(addPoint(point, index));

        // Now, recalculate routes
        calculateRoutes(getState().points)
            .then(routes => dispatch(updateRoutes(routes));
    };
};

私の意見では、どちらがはるかに優れています。


もちろん、魔法のcalculateRoutes関数があると仮定することは深刻な仮定ではありませんが、この関数を最適化された方法で実装するのは非常に難しい作業ではありません (つまり、以前に計算しなかったルートのみを実際にサーバーに送信するなど)。ただし、これは単なるロジックであり、アプリケーションの状態ではないため、ストアとレデューサーによって定義された「コントラクト」を保持している限り、好きな方法で自由に実装できます。

これがお役に立てば幸いです。

于 2016-06-10T12:20:28.500 に答える