Redux は、JavaScriptアプリの予測可能な状態コンテナーであり、アプリケーションの状態を整理するための非常に価値のあるツールです。 これはReactアプリで状態を管理するための人気のあるライブラリですが、Angular、Vue.js、または単なる古いバニラJavaScriptでも同様に使用できます。

ほとんどの人がReduxについて難しいと感じることの1つは、いつそれを使用するかを知ることです。 アプリが大きく複雑になるほど、Reduxを使用することでメリットが得られる可能性が高くなります。 アプリの開発を開始していて、アプリが大幅に成長すると予想される場合は、アプリの変更や拡張に応じて、それらの変更を簡単に実装できるように、すぐにReduxから始めることをお勧めします。既存のコードの多くをリファクタリングします。

このReduxの簡単な紹介では、主な概念であるリデューサーアクションアクションクリエーターストアについて説明します。 一見複雑なトピックのように見えるかもしれませんが、コアコンセプトは実際にはかなり単純です。

レデューサーとは何ですか?

レデューサーは、前の状態とアクションを引数として取り、新しい状態を返す純粋関数です。 アクションは、タイプとオプションのペイロードを持つオブジェクトです。

function myReducer(previousState, action) => {
  // use the action type and payload to create a new state based on
  // the previous state.
  return newState;
}

レデューサーは、ストアにディスパッチされたアクションに応じてアプリケーションの状態がどのように変化するかを指定します。

レデューサーは純粋関数であるため、与えられた引数を変更したり、API呼び出し、ルーティング遷移を実行したり、Math.random()やDate.now()などの非純粋関数を呼び出したりすることはしません

アプリに複数の状態がある場合は、複数のレデューサーを使用できます。 たとえば、アプリ内の主要な機能ごとに独自のレデューサーを設定できます。 レデューサーは、状態の値のみに関係します。

アクションとは何ですか?

アクションは、アプリケーションからストアにデータを送信する情報のペイロードを表すプレーンなJavaScriptオブジェクトです。 アクションには、typeとオプションのpayloadがあります。

Reduxを使用するアプリケーションでのほとんどの変更は、ユーザーによって直接または間接的にトリガーされるイベントから始まります。 ボタンのクリック、ドロップダウンメニューからの項目の選択、特定の要素へのホバー、またはデータを返したばかりのAJAXリクエストなどのイベント。 ページの最初のロードでさえ、アクションをディスパッチする機会になる可能性があります。 多くの場合、アクションはアクションクリエーターを使用してディスパッチされます。

アクションクリエーターとは何ですか?

Reduxでは、アクションクリエーターはアクションオブジェクトを返す関数です。 アクションクリエーターは余分なステップのように見えるかもしれませんが、それらは物事をよりポータブルでテストしやすくします。 アクション作成者から返されたアクションオブジェクトは、アプリ内のさまざまなレデューサーすべてに送信されます。

アクションが何であるかに応じて、レデューサーは状態の新しいバージョンを返すことを選択できます。 次に、新しく返された状態がアプリケーション状態にパイプされ、次にReactアプリにパイプで戻されます。これにより、すべてのコンポーネントが再レンダリングされます。

たとえば、ユーザーがボタンをクリックした場合、アクションを返す関数であるアクションクリエーターを呼び出します。 そのアクションには、トリガーされたばかりのアクションのタイプを説明するtypeがあります。

アクションクリエーターの例を次に示します。

export function addTodo({ task }) {
  return {
    type: 'ADD_TODO',
    payload: {
      task,
      completed: false
    },
  }
}

// example returned value:
// {
//   type: 'ADD_TODO',
//   todo: { task: '🛒 get some milk', completed: false },
// }

そして、これがタイプADD_TODOのアクションを処理する単純なレデューサーです。

export default function(state = initialState, action) {
  switch (action.type) {
    case 'ADD_TODO':
      const newState = [...state, action.payload];
      return newState;

    // Deal with more cases like 'TOGGLE_TODO', 'DELETE_TODO',...

    default:
      return state;
  }
}

すべてのレデューサーがアクションを処理しました。 この特定のアクションタイプに関心のないレデューサーは同じ状態を返し、関心のあるレデューサーは新しい状態を返します。 これで、すべてのコンポーネントに状態の変更が通知されます。 通知されると、すべてのコンポーネントが新しい小道具で再レンダリングされます。

{
  currentTask: { task: '🛒 get some milk', completed: false },
  todos: [
    { task: '🛒 get some milk', completed: false },
    { task: '🎷 Practice saxophone', completed: true }
  ],
}

レデューサーの組み合わせ

Reduxは、次の2つのタスクを実行するcombineReducersという関数を提供します。

  • キーに従って選択された状態のスライスを使用してレデューサーを呼び出す関数を生成します。
  • 次に、結果をもう一度1つのオブジェクトに結合します。

ストアとは何ですか?

とらえどころのないstoreについては言及し続けますが、実際のであるかについてはまだ話していません。

Reduxでは、storeは、アクション(何が起こったかを表す)とレデューサー(それらのアクションに従って状態を更新する)をまとめるオブジェクトを指します。 Reduxアプリケーションにはsingleストアしかありません。

ストアにはいくつかの義務があります。

  • getState()を介して状態へのアクセスを許可します。
  • dispatch(action)を介して状態を更新できるようにします。
  • アプリケーション全体の状態を保持します。
  • subscribe(listener)を使用してリスナーを登録します。
  • subscribe(listener)によって返される関数を介してリスナーの登録を解除します。

基本的に、ストアを作成するために必要なのはレデューサーだけです。 combineReducersは、複数のレデューサーを1つにまとめることに言及しました。 ここで、ストアを作成するために、combineReducersをインポートし、それをcreateStoreに渡します。

import { createStore } from 'redux';
import todoReducer from './reducers';

const store = createStore(todoReducer);

次に、ストアのdispatchメソッドを使用して、次のようにアプリでアクションをディスパッチします。

store.dispatch(addTodo({ task: '📖 Read about Redux'}));
store.dispatch(addTodo({ task: '🤔 Think about meaning of life' }));
// ...

Reduxのデータフロー

Reduxの多くの利点の1つは、アプリケーション内のすべてのデータが同じライフサイクルパターンに従うことです。 Reduxアーキテクチャは厳密な単方向データフローに従うため、アプリのロジックはより予測可能で理解しやすくなります。

Reduxのデータライフサイクルの4つの主要なステップ

  • アプリ内のイベントにより、store.dispatch(actionCreator(payload))への呼び出しがトリガーされます。
  • Reduxストアは、現在の状態とアクションを使用してルートレデューサーを呼び出します。
  • ルートレデューサーは、複数のレデューサーの出力を1つの状態ツリーに結合します。
export default const currentTask(state = {}, action){
  // deal with this piece of state
  return newState;
};

export default const todos(state = [], action){
  // deal with this piece of state
  return newState;
};

export default const todoApp = combineReducers({
  todos,
  currentTask,
});

アクションが発行されると、todoAppは両方のレデューサーを呼び出し、両方の結果セットを1つの状態ツリーに結合します。

return {
  todos: nextTodos,
  currentTask: nextCurrentTask,
};
  • Reduxストアは、ルートレデューサーから返された完全な状態ツリーを保存します。 これで、新しい状態ツリーがアプリのnextStateになります。

結論

ほんの少しの言葉でそれをやり直すのは大変だったので、すべての要素がどのように組み合わされているかがまだ完全にわからなくても、恐れることはありません。 Reduxは、アプリケーションの状態を管理するための非常に強力なパターンを提供するため、概念に慣れるのに少し練習が必要なのは当然です。

詳細については、次のリソースをご覧ください。

🎩今後の投稿では、Redux-SagaまたはReduxThunkを使用した非同期イベントの処理などのより高度なトピックについて説明します。