0から1まで。Reduxについて

Reduxのバージョン1.0がリリースされたとき、私は自分の経験に関する一連のストーリーに少し時間を費やすことにしました。 最近、クライアントアプリケーションに「Flux implementation」を選択する必要がありましたが、それでもReduxでの作業を楽しんでいます。



Reduxを選ぶ理由



Reduxは、JavaScriptアプリケーションの予測可能な状態コンテナーとしての地位を確立しています。 エディターはFluxElmに触発されています。 以前にFluxを使用したことがある場合は、新しい(優れた)ドキュメントの「 先行版 」セクションでReduxに共通点があることを読むことをお勧めします。



Reduxは、一連のアクションによって変更可能な初期状態としてアプリケーションを考えることを提案します。これは、多くの可能性を開く複雑なWebアプリケーションにとって本当に良いアプローチだと思います。



もちろん、 ドキュメントで Redux、そのアーキテクチャ、および各コンポーネントの役割に関する詳細情報を見つけることができます。



ReactとReduxで友達リストを作成する



本日は、Editors and Reactを使用した最初のアプリケーションの段階的な作成に焦点を当てます。簡単な友達リストをゼロから作成します。



完成したコードはこちらで見つけることができます。



画像



誰のため?



この記事は、Reduxの経験がない人を対象としています。 フラックス開発の経験もオプションです。 新しい概念に出会ったら、ドキュメントへのリンクを提供します。



1.インストール


Reduxの著者Daniil Abramovは、React、Webpack、ES6 / 7およびReact Hot Loaderを使用した開発用の優れたビルドを作成しました。



Reduxがインストールされたビルドは既にありますが、各ライブラリの役割を理解することが重要だと思います。



$ git clone https://github.com/gaearon/react-hot-boilerplate.git friendlist $ cd friendlist && npm install $ npm start
      
      





これで、 http:// localhost:3000でアプリケーションを開くことができます。 ご覧のとおり、「hello world」の準備ができました!



1.1 redux、react-redux、redux-devtoolsを追加します


次の3つのパッケージをインストールする必要があります。





 $ npm install --save redux@1.0.0-rc react-redux $ npm install --save-dev redux-devtools
      
      





1.2ディレクトリ構造


これから行うことは非常に簡単ですが、実際のアプリケーションのようにディレクトリ構造を作成しましょう。



 +-- src | +-- actions | +-- index.js | +-- components | +-- index.js | +-- constants | +-- ActionTypes.js | +-- containers | +-- App.js | +-- FriendListApp.js | +-- reducers | +-- index.js | +-- friendlist.js | +-- utils | +-- index.js +-- index.html +-- app.css
      
      





アプリケーションを作成するときに、各ディレクトリの役割を詳細に確認します。 App.jsをcontainersディレクトリに移動したため、index.jsでimportステートメントを構成する必要があります。



1.3 Reduxの接続


開発ツールを有効にする



開発ツールに対してのみdevtoolsを有効にする必要があるため、次のようにwebpack.config.jsを変更します。



 /* webpack.config.js */ var devFlagPlugin = new webpack.DefinePlugin({ __DEV__: JSON.stringify(JSON.parse(process.env.DEBUG || 'false')) }); [...] plugins: [ new webpack.HotModuleReplacementPlugin(), new webpack.NoErrorsPlugin(), devFlagPlugin ]
      
      





DEBUG=true npm start



でアプリケーションを実行すると、アプリケーションで使用できる__DEV__



フラグがオンになります。 次のようにdevtoolsを接続できます:



 /* src/utils/devTools.js */ import React from 'react'; import { createStore as initialCreateStore, compose } from 'redux'; export let createStore = initialCreateStore; if (__DEV__) { createStore = compose( require('redux-devtools').devTools(), require('redux-devtools').persistState( window.location.href.match(/[?&]debug_session=([^&]+)\b/) ), createStore ); } export function renderDevTools(store) { if (__DEV__) { let {DevTools, DebugPanel, LogMonitor} = require('redux-devtools/lib/react'); return ( <DebugPanel top right bottom> <DevTools store={store} monitor={LogMonitor} /> </DebugPanel> ); } return null; }
      
      





ここでは2つのことを行っています。 作成された関数を使用してcreateStoreをオーバーライドします。これにより、devToolsなどの複数のストアエンハンサーを使用できます。 DebugPanelをレンダリングするrenderDevTools関数も含まれています。



アプリケーションコンテナ



次に、App.jsを変更してreduxに接続する必要があります。 このために、react-reduxのProvider



を使用しProvider



。 これにより、プロバイダーコンポーネントに存在するすべてのコンポーネントがストレージインスタンスを利用できるようになります。 奇妙な外観の関数を心配する必要はありません。その目的は、React関数の「コンテキスト」を使用して、すべての子(コンポーネント)がアクセスできるリポジトリを作成することです。



 /* src/containers/App.js */ import React, { Component } from 'react'; import { combineReducers } from 'redux'; import { Provider } from 'react-redux'; import { createStore, renderDevTools } from '../utils/devTools'; import FriendListApp from './FriendListApp'; import * as reducers from '../reducers'; const reducer = combineReducers(reducers); const store = createStore(reducer);
      
      





リポジトリを作成するには、すべてのレデューサーのマップとして、devToolsファイルで定義したcreateStore



関数を使用します。



ES6構文import * as reducers



使用すると、{key:fn(state、action)、...}の形式でオブジェクトを取得できます。 これはcombineReducers



引数を設定するのにcombineReducers



です。



 export default class App extends Component { render() { return ( <div> <Provider store={store}> {() => <FriendListApp /> } </Provider> {renderDevTools(store)} </div> ); } }
      
      





このアプリケーションでは、App.jsはReduxの外部ラッパーであり、FriendListAppはアプリケーションのルートコンポーネントです。 FriendListApp.jsで単純な「Hello world」を作成したら、reduxとdevToolsを使用してアプリケーションを最終的に起動できます。 これを取得する必要があります(スタイルはありません)。



画像



これは単なる「Hello world」アプリケーションですが、ホットリロードが有効になっています。 テキストを変更して、画面で自動更新を受け取ることができます。 ご覧のとおり、右側のdevtoolsは空のリポジトリを示しています。 それらを記入してください!



2.アプリケーションを作成する


すべての設定が完了したので、アプリケーション自体に集中できます。



2.1アクションとアクションジェネレーター


アクションは、アプリケーションからリポジトリにデータを転送する構造です。 慣例により、アクションには、実行するアクションのタイプを示すtype



文字列フィールドが必要です。 別のモジュールでこのタイプを定義することは良い習慣であり、アプリケーションで何をするかを事前に考えさせてくれます。



 /* src/constants/ActionTypes.js */ export const ADD_FRIEND = 'ADD_FRIEND'; export const STAR_FRIEND = 'STAR_FRIEND'; export const DELETE_FRIEND = 'DELETE_FRIEND';
      
      





ご覧のとおり、これはアプリケーションの範囲を定義する非常に表現力のある方法です。これにより、友人を追加したり、「お気に入り」としてマークしたり、リストから削除したりできます。



アクションジェネレーターは、 アクションを作成する関数です。 Reduxでは、アクションジェネレーターは純粋な関数であるため、ポータブルでテストが容易です。 副作用はありません。



アクションフォルダーに配置しますが、これらは異なる概念であることを忘れないでください。



 /* src/actions/FriendsActions.js */ import * as types from '../constants/ActionTypes'; export function addFriend(name) { return { type: types.ADD_FRIEND, name }; } export function deleteFriend(id) { return { type: types.DELETE_FRIEND, id }; } export function starFriend(id) { return { type: types.STAR_FRIEND, id }; }
      
      





ご覧のとおり、アクションは非常にミニマルです。 要素を追加するために、すべてのプロパティをレポートし(ここでは名前のみを扱います)、他のプロパティについてはidを参照します。 より複雑なアプリケーションでは、非同期アクションを扱うかもしれませんが、これは別の記事のトピックです...



2.2レデューサー


レデューサーは、アプリケーションの状態を変更する責任があります。 これらは、次の形式(previousState, action) => newState



純粋な関数(previousState, action) => newState



。 リデューサーの初期状態を決して変更しないでください。 代わりに、previousStateプロパティに基づいて新しいオブジェクトを作成できます。 そうしないと、これにより望ましくない結果が生じる可能性があります。 また、これはルーティングや非同期呼び出しなどの副作用を処理する場所ではありません。



まず、 initialState



アプリケーションの状態を決定します。



 /* src/reducers/friends.js */ const initialState = { friends: [1, 2, 3], friendsById: { 1: { id: 1, name: 'Theodore Roosevelt' }, 2: { id: 2, name: 'Abraham Lincoln' }, 3: { id: 3, name: 'George Washington' } } };
      
      





状態は私たちが望むものであれば何でもかまいません。友人の配列を保存するだけです。 しかし、このソリューションはうまくスケールしないため、友人のidおよびmap配列を使用します。 それについてはnormalizrで読むことができます。



次に、現在のレデューサーを作成する必要があります。 ES6の機能を利用して、状態が未定義の場合を処理するデフォルト引数を設定します。 これは、リデューサーの作成方法を理解するのに役立ちます。この場合、スイッチを使用します。



 export default function friends(state = initialState, action) { switch (action.type) { case types.ADD_FRIEND: const newId = state.friends[state.friends.length-1] + 1; return { friends: state.friends.concat(newId), friendsById: { ...state.friendsById, [newId]: { id: newId, name: action.name } } } default: return state; } }
      
      





ES6 / 7の構文に精通していない場合、読みにくいかもしれません。 なぜなら ルールとしてObject.assignまたはSpread operatorを使用して、オブジェクトの新しい状態を返す必要があります。



ここで何が起こるか:新しいIDを定義します。 実際のアプリケーションでは、サーバーから取得するか、少なくとも一意であることを確認します。 次に、 concat



を使用して、この新しいIDをIDリストに追加します。 Concatは新しい配列を追加し、元の配列を変更しません。



計算されたプロパティは、 [newId]



を使用してfriendsByIdオブジェクトに動的キーを簡単に作成できるようにする便利なES6機能です。



ご覧のとおり、最初は混乱するかもしれない構文にもかかわらず、ロジックは単純です。 状態を設定して、新しい状態に戻します。 重要:このプロセスのどの時点でも、以前の状態を変更しないでください。



さて、戻って他の2つのアクションのレデューサーを作成しましょう。



 import omit from 'lodash/object/omit'; import assign from 'lodash/object/assign'; import mapValues from 'lodash/object/mapValues'; /* ... */ case types.DELETE_FRIEND: return { ...state, friends: state.friends.filter(id => id !== action.id), friendsById: omit(state.friendsById, action.id) } case types.STAR_FRIEND: return { ...state, friendsById: mapValues(state.friendsById, (friend) => { return friend.id === action.id ? assign({}, friend, { starred: !friend.starred }) : friend }) }
      
      





オブジェクト管理を簡素化するためにlodashを追加しました。 通常、これら2つの例では、前の状態を変更しないことが重要です。そのため、新しいオブジェクトを返す関数を使用します。 たとえば、 delete state.friendsById[action.id]



delete state.friendsById[action.id]



代わりに、 _.omit



_.omit



関数を使用します。



また、スプレッド演算子を使用すると、変更する必要がある状態のみを操作できることに気付くかもしれません。



Reduxはデータの保存方法を問わないため、 Immutable.jsを使用できます。



App.jsで手動でディスパッチを呼び出すことで、インターフェイスをバイパスしてストレージを操作できます。



 /* src/containers/App.js */ import { addFriend, deleteFriend, starFriend } from '../actions/FriendsActions'; store.dispatch(addFriend('Barack Obama')); store.dispatch(deleteFriend(1)); store.dispatch(starFriend(4));
      
      





devToolsにアクションが表示され、リアルタイムでそれらを操作できます。



画像



3.インターフェースを作成する


なぜなら このレッスンでは、Reactコンポーネントの作成をスキップし、Redaxのみに焦点を当てました。 3つの単純なコンポーネントがあります。






All Articles