リッチで複雑なJavaScriptアプリケーションの成長を続けるエコシステムでは、現在のユーザー、読み込まれた投稿のリストなど、これまで以上に多くの状態を管理する必要があります。
イベントの履歴を必要とするデータのセットは、ステートフルと見なすことができます。状態の管理は困難でエラーが発生しやすい場合がありますが、(可変ではなく)不変のデータと特定のサポートテクノロジー(この記事の目的ではRedux)を使用すると非常に役立ちます。
不変データには、作成後に変更できないという制限がありますが、特に参照と値の同等性において多くの利点があり、データの頻繁な比較(更新が必要かどうかの確認)に依存するアプリケーションを大幅に高速化できます。 、 例えば)。
不変の状態を使用すると、データを再帰的に比較する必要なしに、状態が変化したかどうかをすばやく判断できるコードを記述できます。これは通常、はるかに高速です。
この記事では、アクションクリエーター、純粋関数、構成されたレデューサー、Redux-sagaとRedux Thunkを使用した不純なアクション、そして最後にReactを使用したReduxの使用を通じて状態を管理する場合のReduxの実用的なアプリケーションについて説明します。そうは言っても、MobX、Relay、Fluxベースのライブラリなど、Reduxに代わるものはたくさんあります。
ReduxをMobX、Relay、その他のほとんどのFluxベースの実装など、他のほとんどの状態コンテナーから分離する重要な側面は、Reduxには、にディスパッチされる「アクション」(プレーンJavaScriptオブジェクト)を介してのみ変更できる単一の状態があることです。 Reduxストア。他のほとんどのデータストアには、Reactコンポーネント自体に含まれる状態があり、複数のストアを持つことや、可変状態を使用することができます。
これにより、ストアのレデューサー(不変データを操作する純粋関数)が実行され、状態が更新される可能性があります。このプロセスは、より理解しやすく、より決定論的な一方向のデータフローを実施します。
Reduxレデューサーは不変データを操作する純粋関数であるため、同じ入力に対して常に同じ出力を生成し、テストを容易にします。レデューサーの例を次に示します。
import Immutable from 'seamless-immutable' const initialState = Immutable([]) // create immutable array via seamless-immutable /** * a reducer takes a state (the current state) and an action object (a plain JavaScript object that was dispatched via dispatch(..) and potentially returns a new state. */ function addUserReducer(state = initialState, action) { if (action.type === 'USERS_ADD') { return state.concat(action.payload) } return state // note that a reducer MUST return a value } // somewhere else... store.dispatch({ type: 'USERS_ADD', payload: user }) // dispatch an action that causes the reducer to execute and add the user
純粋関数を扱うことで、Reduxは、次のような、一般的に変異状態では簡単に実行できない多くのユースケースを簡単にサポートできます。
Reduxのアクションクリエーターは、コードをクリーンでテスト可能な状態に保つのに役立ちます。 Reduxの「アクション」は、発生するはずのミューテーションを記述するプレーンなJavaScriptオブジェクトにすぎないことに注意してください。そうは言っても、同じオブジェクトを何度も書き出すことは繰り返しであり、エラーが発生しやすくなります。
諮問委員会の形成方法
Reduxのアクションクリエーターは、ミューテーションを説明するプレーンなJavaScriptオブジェクトを返す単なるヘルパー関数です。これにより、繰り返しのコードを減らし、すべてのアクションを1か所にまとめることができます。
export function usersFetched(users) { return { type: 'USERS_FETCHED', payload: users, } } export function usersFetchFailed(err) { return { type: 'USERS_FETCH_FAILED', payload: err, } } // reducer somewhere else... const initialState = Immutable([]) // create immutable array via seamless-immutable /** * a reducer takes a state (the current state) and an action object (a plain JavaScript object that was dispatched via dispatch(..) and potentially returns a new state. */ function usersFetchedReducer(state = initialState, action) { if (action.type === 'USERS_FETCHED') { return Immutable(action.payload) } return state // note that a reducer MUST return a value }
レデューサーとアクションの性質上、不変性ヘルパーライブラリがなくても簡単にテストできますが、オブジェクトの変更から保護するものは何もありません。つまり、すべてのレデューサーのテストは特に堅牢である必要があります。
あなたを保護するためのライブラリなしで遭遇する問題の次のコード例を考えてみてください。
const initialState = [] function addUserReducer(state = initialState, action) { if (action.type === 'USERS_ADD') { state.push(action.payload) // NOTE: mutating action!! return state } return state // note that a reducer MUST return a value }
このコード例では、前の状態が現在の状態と同じになるため、タイムトラベルが中断されます。状態への参照が変更されていないため、純粋コンポーネントが更新(または再レンダリング)されない可能性があります。含むが変更され、突然変異を推論するのははるかに困難です。
不変性ライブラリがないと、Reduxが提供するすべての利点が失われます。したがって、特に複数の手がコードに触れる大規模なチームで作業する場合は、immutable.jsやシームレス不変などの不変性ヘルパーライブラリを使用することを強くお勧めします。
使用するライブラリに関係なく、Reduxは同じように動作します。両方の長所と短所を比較して、ユースケースに最も適した方を選択できるようにしましょう。
Immutable.jsは、Facebookによって構築されたライブラリであり、マップ、リスト、セット、シーケンスなどのデータ構造をより機能的なスタイルで取り入れています。不変の永続データ構造のライブラリは、異なる状態間で可能な限り最小限のコピーを実行します。
長所:
短所:
シームレス不変は、ES5までの下位互換性がある不変データ用のライブラリです。
次のうち、html5、javascript、およびcssに精通しているのはどれですか?
これは、defineProperty(..)
などのES5プロパティ定義関数に基づいていますオブジェクトのミューテーションを無効にします。そのため、lodashやRamdaなどの既存のライブラリと完全に互換性があります。また、本番ビルドで無効にすることもでき、パフォーマンスが大幅に向上する可能性があります。
長所:
短所:
Reduxのもう1つの便利な機能は、レデューサーを一緒に構成する機能です。これにより、はるかに複雑なアプリケーションを作成でき、かなりのサイズのアプリケーションでは、必然的に複数のタイプの状態(現在のユーザー、読み込まれた投稿のリスト、等)。 Reduxは、関数combineReducers
を自然に提供することにより、このユースケースをサポート(および推奨)します。
import { combineReducers } from 'redux' import currentUserReducer from './currentUserReducer' import postsListReducer from './postsListReducer' export default combineReducers({ currentUser: currentUserReducer, postsList: postsListReducer, })
上記のコードを使用すると、currentUser
に依存するコンポーネントを作成できます。およびpostsList
に依存する別のコンポーネント。これにより、単一のコンポーネントがツリーのブランチに関係するものにのみサブスクライブするため、パフォーマンスも向上します。
デフォルトでは、プレーンJavaScriptオブジェクトのみをReduxにディスパッチできます。ただし、ミドルウェアを使用すると、Reduxは、現在時刻の取得、ネットワークリクエストの実行、ファイルのディスクへの書き込みなどの不純なアクションをサポートできます。
「ミドルウェア」は、ディスパッチされているアクションを傍受できる機能に使用される用語です。インターセプトされると、他のフレームワーク(Express.jsなど)のミドルウェアと同様に、アクションの変換や非同期アクションのディスパッチなどを実行できます。
2つの非常に一般的なミドルウェアライブラリは、ReduxThunkとRedux-sagaです。 Redux Thunkは命令型で書かれていますが、Redux-sagaは機能的なスタイルで書かれています。両方を比較してみましょう。
1099 vsw2時給計算機
Redux Thunkは、他のチェーン可能な関数を返す関数であるサンクを使用して、Redux内の不純なアクションをサポートします。 Redux-Thunkを使用するには、最初にReduxThunkミドルウェアをストアにマウントする必要があります。
import { createStore, applyMiddleware } from 'redux' import thunk from 'redux-thunk' const store = createStore( myRootReducer, applyMiddleware(thunk), // here, we apply the thunk middleware to R )
これで、サンクをReduxストアにディスパッチすることで、不純なアクション(API呼び出しの実行など)を実行できます。
store.dispatch( dispatch => { return api.fetchUsers() .then(users => dispatch(usersFetched(users)) // usersFetched is a function that returns a plain JavaScript object (Action) .catch(err => dispatch(usersFetchError(err)) // same with usersFetchError } )
サンクを使用すると、コードのテストが難しくなり、コードフローによる推論が難しくなる可能性があることに注意してください。
Redux-sagaは、 ES6(ES2015) ジェネレーターと呼ばれる機能と関数型/純粋ヘルパーのライブラリ。ジェネレーターの優れている点は、ジェネレーターを再開および一時停止できることです。また、APIコントラクトにより、ジェネレーターのテストが非常に簡単になります。
sagasを使用して以前のサンクメソッドの読みやすさとテスト容易性をどのように改善できるか見てみましょう!
まず、Redux-sagaミドルウェアをストアにマウントしましょう。
import { createStore, applyMiddleware } from 'redux' import createSagaMiddleware from 'redux-saga' import rootReducer from './rootReducer' import rootSaga from './rootSaga' // create the saga middleware const sagaMiddleware = createSagaMiddleware() // mount the middleware to the store const store = createStore( rootReducer, applyMiddleware(sagaMiddleware), ) // run our saga! sagaMiddleware.run(rootSaga)
run(..)
に注意してください関数の実行を開始するには、sagaで関数を呼び出す必要があります。
それでは、サガを作成しましょう。
import { call, put, takeEvery } from 'redux-saga/effects' // these are saga effects we'll use export function *fetchUsers(action) { try { const users = yield call(api.fetchUsers) yield put(usersFetched(users)) } catch (err) { yield put(usersFetchFailed(err)) } } export default function *rootSaga() { yield takeEvery('USERS_FETCH', fetchUsers) }
ユーザーリストとrootSaga
をフェッチする2つのジェネレーター関数を定義しました。 api.fetchUsers
を呼び出さなかったことに注意してください直接ですが、代わりに呼び出しオブジェクトでそれを生成しました。これは、Redux-sagaが呼び出しオブジェクトをインターセプトし、そこに含まれる関数を実行して純粋な環境を作成するためです(ジェネレーターに関する限り)。
rootSaga
takeEvery,
という関数への単一の呼び出しを生成しますUSERS_FETCH
のタイプでディスパッチされたすべてのアクションを実行しますそしてfetchUsers
を呼び出しますそれがとった行動を伴う佐賀。ご覧のとおり、これによりReduxの非常に予測可能な副作用モデルが作成され、テストが簡単になります。
ジェネレーターによってサガのテストが簡単になる方法を見てみましょう。使用します モカ この部分では、単体テストを実行し、 チャイ アサーション用。
sagasはプレーンなJavaScriptオブジェクトを生成し、ジェネレーター内で実行されるため、モックなしで正しい動作を実行することを簡単にテストできます。 call
に注意してください、take
、put
などは、Redux-sagaミドルウェアによってインターセプトされる単なるJavaScriptオブジェクトです。
import { take, call } from 'redux-saga/effects' import { expect } from 'chai' import { rootSaga, fetchUsers } from '../rootSaga' describe('saga unit test', () => { it('should take every USERS_FETCH action', () => { const gen = rootSaga() // create our generator iterable expect(gen.next().value).to.be.eql(take('USERS_FETCH')) // assert the yield block does have the expected value expect(gen.next().done).to.be.equal(false) // assert that the generator loops infinitely }) it('should fetch the users if successful', () => { const gen = fetchUsers() expect(gen.next().value).to.be.eql(call(api.fetchUsers)) // expect that the call effect was yielded const users = [ user1, user2 ] // some mock response expect(gen.next(users).value).to.be.eql(put(usersFetched(users)) }) it('should fail if API fails', () => { const gen = fetchUsers() expect(gen.next().value).to.be.eql(call(api.fetchUsers)) // expect that the call effect was yielded const err = { message: 'authentication failed' } // some mock error expect(gen.throw(err).value).to.be.eql(put(usersFetchFailed(err)) }) })
Reduxは特定のコンパニオンライブラリに関連付けられていませんが、特にうまく機能します React.js Reactコンポーネントは、状態を入力として受け取り、仮想DOMを出力として生成する純粋関数であるためです。
React-Reduxは、ReactとReduxのヘルパーライブラリであり、2つを接続するハードワークのほとんどを排除します。 React-Reduxを最も効果的に使用するために、プレゼンテーションコンポーネントとコンテナコンポーネントの概念を見ていきましょう。
プレゼンテーションコンポーネントは、レンダリングする小道具のみに応じて、物事が視覚的にどのように見えるかを記述します。小道具からコールバックを呼び出してアクションをディスパッチします。それらは手書きで完全に純粋であり、Reduxのような状態管理システムに結び付けられていません。
一方、コンテナコンポーネントは、物事がどのように機能するかを記述し、Reduxを認識し、Reduxアクションを直接ディスパッチしてミューテーションを実行し、通常はReact-Reduxによって生成されます。それらはしばしばプレゼンテーションコンポーネントとペアになって、その小道具を提供します。
ここに示されている知覚組織の原理
プレゼンテーションコンポーネントを作成し、React-Reduxを介してReduxに接続しましょう。
const HelloWorld = ({ count, onButtonClicked }) => ( Hello! You've clicked the button {count} times! Click me ) HelloWorld.propTypes = { count: PropTypes.number.isRequired, onButtonClicked: PropTypes.func.isRequired, }
これは、機能するためにその小道具に完全に依存する「ダム」コンポーネントであることに注意してください。これは素晴らしいです、なぜならそれは テストが簡単で構成が簡単なReactコンポーネント 。このコンポーネントをReduxに接続する方法を見てみましょう。最初に、高階コンポーネントとは何かについて説明します。
React-Reduxはconnect( .. )
と呼ばれるヘルパー関数を提供しますこれは、Reduxを認識する「ダム」Reactコンポーネントから高次のコンポーネントを作成します。
Reactは、コンポーネントを他のコンポーネントでラップするときの構成を通じて、拡張性と再利用性を強調します。これらのコンポーネントをラップすると、動作が変更されたり、新しい機能が追加されたりする可能性があります。 Reduxを認識しているプレゼンテーションコンポーネント(コンテナコンポーネント)から高次コンポーネントを作成する方法を見てみましょう。
方法は次のとおりです。
import { connect } from 'react-redux' const mapStateToProps = state => { // state is the state of our store // return the props that we want to use for our component return { count: state.count, } } const mapDispatchToProps = dispatch => { // dispatch is our store dispatch function // return the props that we want to use for our component return { onButtonClicked: () => { dispatch({ type: 'BUTTON_CLICKED' }) }, } } // create our enhancer function const enhancer = connect(mapStateToProps, mapDispatchToProps) // wrap our 'dumb' component with the enhancer const HelloWorldContainer = enhancer(HelloWorld) // and finally we export it export default HelloWorldContainer
2つの関数mapStateToProps
を定義したことに注意してください。およびmapDispatchToProps
。
mapStateToProps
Redux状態から計算されたオブジェクトを返す(状態:オブジェクト)の純粋関数です。このオブジェクトは、ラップされたコンポーネントに渡された小道具とマージされます。これは、コンポーネントの小道具にマージするRedux状態の一部を選択するため、セレクターとも呼ばれます。
mapDispatchToProps
も純粋関数ですが、Reduxディスパッチ関数から計算されたオブジェクトを返す(dispatch:(Action)=> void)の1つです。このオブジェクトは、同様に、ラップされたコンポーネントに渡された小道具とマージされます。
コンテナコンポーネントを使用するには、Provider
を使用する必要があります。 React-Reduxのコンポーネントは、コンテナコンポーネントに使用するストアを指示します。
import { Provider } from 'react-redux' import { render } from 'react-dom' import store from './store' // where ever your Redux store resides import HelloWorld from './HelloWorld' render( ( ), document.getElementById('container') )
Provider
コンポーネントは、Reduxストアにサブスクライブするすべての子コンポーネントにストアを伝播し、すべてを1つの場所に保持し、エラーや変更のポイントを減らします。
Redux、その多数のサポートライブラリ、およびReact.jsとのフレームワーク接続に関するこの新たに発見された知識を使用すると、状態制御を通じてアプリケーションのミューテーションの数を簡単に制限できます。強力な状態制御により、より速く移動し、より自信を持って堅実なコードベースを作成できます。