Reduxを選ぶ理由
Reduxは、JavaScriptアプリケーションの予測可能な状態コンテナーとしての地位を確立しています。 エディターはFluxとElmに触発されています。 以前に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つのパッケージをインストールする必要があります。
- Redux:ライブラリ自体
- React-redux:Reactの束
- Redux-devtools:オプションで、いくつかの便利な開発ツールを提供します。
$ 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つの単純なコンポーネントがあります。
- 友達リスト
- friends:友人の配列を配列します
FriendListItem 1つのフレンド要素
- name:友達の文字列名
-
starred: boolean
友人がお気に入りとしてマークされている場合、starred: boolean
はアスタリスクを表示します -
starFriend: function
ユーザーがアスタリスクをクリックしたときにトリガーされるstarFriend: function
-
deleteFriend: function
ユーザーがゴミ箱をクリックしたときにトリガーされるdeleteFriend: function
新しい名前を入力するためのAddFriendInputフィールド
-
addFriend: function
押すとトリガーされるaddFriend: function
呼び出し
Reduxでは、可能な限りほとんどのコンポーネントを「ダム」にすることをお勧めします。 つまり Reduxに関連付けられているコンポーネントが少ないほど良い。
ここでは、 FriendListAppが唯一の「スマート」コンポーネントになります。
import { bindActionCreators } from 'redux'; import { connect } from 'react-redux'; import * as FriendsActions from '../actions/FriendsActions'; import { FriendList, AddFriendInput } from '../components'; @connect(state => ({ friendlist: state.friendlist })) export default class FriendListApp extends Component {
これは、decoratorと呼ばれる新しいES7構文の一部です。 これは、高階関数を呼び出す便利な方法です。connect(select)(FriendListApp);
と同等connect(select)(FriendListApp);
select
は、ここで行ったことを返す関数です。
static propTypes = { friendsById: PropTypes.object.isRequired, dispatch: PropTypes.func.isRequired } render () { const { friendlist: { friendsById }, dispatch } = this.props; const actions = bindActionCreators(FriendsActions, dispatch); return ( <div className={styles.friendListApp}> <h1>The FriendList</h1> <AddFriendInput addFriend={actions.addFriend} /> <FriendList friends={friendsById} actions={actions} /> </div> ); } }
bindActionCreators
を使用して、アクションジェネレーターをディスパッチでラップします。 目標は、ディスパッチオブジェクトを提供せずにアクションジェネレーターを他のコンポーネントに渡すことです(それらを愚かに保ちます)。
次に起こるのは、Reactの標準的なアプローチです。 関数をonClick、onChange、またはonKeyDownプロパティにバインドして、ユーザーアクションを処理します。
これを行う方法に興味がある場合は、コード全体を見ることができます 。
これで、redux / reactアプリケーションの魔法を感じることができます。 GIFに示されているように、すべてのアクションを記録します。
いくつかのアクションを実行し、バグを見つけ、それらを修正し、修正し、すでに修正されたシーケンスを繰り返すことができる場合、開発する方が便利です...
翻訳
オリジナル記事http://www.jchapron.com/2015/08/14/getting-started-with-redux/ - friends:友人の配列を配列します