ReduxbindActionCreators のドキュメントは次のように述べています:
bindActionCreators
の唯一のユースケースは、一部のアクションクリエーターをReduxを認識しないコンポーネントに渡し、ディスパッチまたはReduxストアをそのコンポーネントに渡したくない場合です。
bindActionCreators
を使用/必要とする例は何ですか?
どの種類のコンポーネントがReduxを認識しないでしょうか?
両方のオプションの利点/欠点は何ですか?
//actionCreator
import * as actionCreators from './actionCreators'
function mapStateToProps(state) {
return {
posts: state.posts,
comments: state.comments
}
}
function mapDispatchToProps(dispatch) {
return bindActionCreators(actionCreators, dispatch)
}
function mapStateToProps(state) {
return {
posts: state.posts,
comments: state.comments
}
}
function mapDispatchToProps(dispatch) {
return {
someCallback: (postId, index) => {
dispatch({
type: 'REMOVE_COMMENT',
postId,
index
})
}
}
}
99%の時間、mapDispatchToProps
パラメーターの一部として、React-Redux connect()
関数で使用されます。指定したmapDispatch
関数内で明示的に使用するか、オブジェクトの短縮構文を使用し、アクション作成者でいっぱいのオブジェクトをconnect
に渡すと自動的に使用できます。
考えは、アクションクリエーターを事前にバインドすることにより、connect()
に渡すコンポーネントは、接続されていることを技術的に「知らない」-this.props.someCallback()
を実行する必要があることを知っているだけです。一方、アクションクリエーターをバインドせずにthis.props.dispatch(someActionCreator())
を呼び出した場合、コンポーネントはprops.dispatch
が存在することを期待しているため、接続されていることを「認識」します。
ブログの投稿でこのトピックに関するいくつかの考えを書きました 慣用句:アクションクリエーターを使用する理由 。
最も一般的な答えは、実際に質問に対処するとは思わない。
以下のすべての例は基本的に同じことを行い、「事前バインディング」の概念はありません。
// option 1
const mapDispatchToProps = (dispatch) => ({
action: () => dispatch(action())
})
// option 2
const mapDispatchToProps = (dispatch) => ({
action: bindActionCreators(action, dispatch)
})
// option 3
const mapDispatchToProps = {
action: action
}
オプション#3
は、オプション#1
の単なる省略形なので、オプション#1
とオプション#2
を使用する理由の本当の疑問です。それらの両方がreact-reduxコードベースで使用されているのを見てきましたが、かなり混乱していることがわかりました。
混乱は、react-redux
docのすべての examples がbindActionCreators
を使用しているのに対し、 bindActionCreators のdocは(質問自体に引用されているように)使用しないと言っているという事実に起因すると思います反応還元を使用。
答えはコードベースの一貫性だと思いますが、個人的には必要に応じて明示的にアクションをdispatchでラップすることを好みます。
より完全な例では、アクションクリエーターでいっぱいのオブジェクトを渡して接続します。
import * as ProductActions from './ProductActions';
// component part
export function Product({ name, description }) {
return <div>
<button onClick={this.props.addProduct}>Add a product</button>
</div>
}
// container part
function mapStateToProps(state) {
return {...state};
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({
...ProductActions,
}, dispatch);
}
export default connect(mapStateToProps, mapDispatchToProps)(Product);
元の質問に答えてみます...
最初の質問では、最初にbindActionCreators
が必要な理由と、Reduxを認識してはならないコンポーネントの種類を基本的に尋ねます。
要するに、ここでの考え方は、コンポーネントをsmart(コンテナ)およびdumb(プレゼンテーション)コンポーネントに分割することです。 ダムコンポーネントは、知る必要性に基づいて動作します。彼らの魂の仕事は、与えられたデータをHTMLにレンダリングすることです。彼らは、アプリケーションの内部動作を認識すべきではありません。それらは、アプリケーションのスキンディープフロントレイヤーとして見ることができます。
一方、スマートコンポーネントは一種の接着剤であり、dumbコンポーネントのデータを準備し、できればHTMLレンダリングを行いません。
この種のアーキテクチャは、UI層とその下のデータ層の間の疎結合を促進します。これにより、2つのレイヤーのいずれかを他のレイヤー(つまり、UIの新しいデザイン)に簡単に置き換えることができ、他のレイヤーが壊れることはありません。
あなたの質問に答えるために:ダムコンポーネントはRedux(または、その問題のためのデータ層の不必要な実装詳細)を認識すべきではありません。将来、それを別のものに置き換える可能性があるからです。
この概念の詳細については、Dan Abramovの Reduxマニュアル および記事 プレゼンテーションおよびコンテナコンポーネント を参照してください。
2番目の質問は、与えられた例の利点/欠点についてでした。
最初の例では、アクション作成者は別のactionCreators
ファイル/モジュールで定義されています。つまり、他の場所で再利用できます。それはほとんどアクションを定義する標準的な方法です。私はこれにどんなデメリットもありません。
2番目の例はインラインでアクション作成者を定義しますが、これには複数の欠点があります:
consts
として個別に定義することが望ましいです-タイプミスの可能性を減らします2番目の例には、1つ目の利点よりも1つの利点があります-書く方が速いです!したがって、コードの計画がこれ以上ない場合は、問題ないでしょう。
私は物事を少し明確にしたことを願っています...
bindActionCreators()
の使用方法の1つは、複数のアクションを1つの小道具として「マッピング」することです。
通常のディスパッチは次のようになります:
いくつかの一般的なユーザーアクションを小道具にマッピングします。
const mapStateToProps = (state: IAppState) => {
return {
// map state here
}
}
const mapDispatchToProps = (dispatch: Dispatch) => {
return {
userLogin: () => {
dispatch(login());
},
userEditEmail: () => {
dispatch(editEmail());
},
};
};
export default connect(mapStateToProps, mapDispatchToProps)(MyComponent);
大規模なプロジェクトでは、各ディスパッチを別々にマッピングすると、扱いにくいと感じる場合があります。相互に関連する一連のアクションがある場合は、これらのアクションを組み合わせてを実行できます。たとえば、すべての種類の異なるユーザー関連アクションを実行したユーザーアクションファイル。各アクションを個別のディスパッチとして呼び出す代わりに、dispatch
の代わりにbindActionCreators()
を使用できます。
bindActionCreators()を使用した複数のディスパッチ
関連するすべてのアクションをインポートします。それらはすべて、reduxストアの同じファイルにある可能性が高い
import * as allUserActions from "./store/actions/user";
そして今、ディスパッチを使用する代わりにbindActionCreators()を使用してください
const mapDispatchToProps = (dispatch: Dispatch) => {
return {
...bindActionCreators(allUserActions, dispatch);
},
};
};
export default connect(mapStateToProps, mapDispatchToProps,
(stateProps, dispatchProps, ownProps) => {
return {
...stateProps,
userAction: dispatchProps
ownProps,
}
})(MyComponent);
これで、prop userAction
を使用して、コンポーネントのすべてのアクションを呼び出すことができます。
IE:userAction.login()
userAction.editEmail()
またはthis.props.userAction.login()
this.props.userAction.editEmail()
。
注:bindActionCreators()を単一のプロップにマップする必要はありません。 (userAction
にマップされる追加の=> {return {}}
)。 bindActionCreators()
を使用して、単一ファイルのすべてのアクションを個別の小道具としてマップすることもできます。しかし、それを行うことは混乱を招く可能性があります。各アクションまたは「アクショングループ」に明示的な名前を付けることをお勧めします。また、ownProps
に名前を付けて、これらの「子の小道具」が何であるか、どこから来たのかをよりわかりやすくします。 Redux + Reactを使用する場合、すべての小道具が提供されている場所が少し混乱する可能性があるため、説明が多いほど良いでしょう。
bindActionCreators
の素敵なユースケースの1つは、 redux-saga-routines を使用して redux-saga と統合することです。例えば:
// routines.js
import { createRoutine } from "redux-saga-routines";
export const fetchPosts = createRoutine("FETCH_POSTS");
// Posts.js
import React from "react";
import { bindActionCreators } from "redux";
import { connect } from "react-redux";
import { fetchPosts } from "routines";
class Posts extends React.Component {
componentDidMount() {
const { fetchPosts } = this.props;
fetchPosts();
}
render() {
const { posts } = this.props;
return (
<ul>
{posts.map((post, i) => (
<li key={i}>{post}</li>
))}
</ul>
);
}
}
const mapStateToProps = ({ posts }) => ({ posts });
const mapDispatchToProps = dispatch => ({
...bindActionCreators({ fetchPosts }, dispatch)
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(Posts);
// reducers.js
import { fetchPosts } from "routines";
const initialState = [];
export const posts = (state = initialState, { type, payload }) => {
switch (type) {
case fetchPosts.SUCCESS:
return payload.data;
default:
return state;
}
};
// api.js
import axios from "axios";
export const JSON_OPTS = { headers: { Accept: "application/json" } };
export const GET = (url, opts) =>
axios.get(url, opts).then(({ data, headers }) => ({ data, headers }));
// sagas.js
import { GET, JSON_OPTS } from "api";
import { fetchPosts } from "routines";
import { call, put, takeLatest } from "redux-saga/effects";
export function* fetchPostsSaga() {
try {
yield put(fetchPosts.request());
const { data } = yield call(GET, "/api/posts", JSON_OPTS);
yield put(fetchPosts.success(data));
} catch (error) {
if (error.response) {
const { status, data } = error.response;
yield put(fetchPosts.failure({ status, data }));
} else {
yield put(fetchPosts.failure(error.message));
}
} finally {
yield put(fetchPosts.fulfill());
}
}
export function* fetchPostsRequestSaga() {
yield takeLatest(fetchPosts.TRIGGER, fetchPostsSaga);
}
このパターンは React Hooks (React 16.8以降)を使用して実装できることに注意してください。
bindActionsCreatorsについてもっと知りたいと思っていましたが、ここに私のプロジェクトでの実装方法を示します。
// Actions.js
// Action Creator
const loginRequest = (username, password) => {
return {
type: 'LOGIN_REQUEST',
username,
password,
}
}
const logoutRequest = () => {
return {
type: 'LOGOUT_REQUEST'
}
}
export default { loginRequest, logoutRequest };
Reactコンポーネントで
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import ActionCreators from './actions'
class App extends Component {
componentDidMount() {
// now you can access your action creators from props.
this.props.loginRequest('username', 'password');
}
render() {
return null;
}
}
const mapStateToProps = () => null;
const mapDispatchToProps = dispatch => ({ ...bindActionCreators(ActionCreators, dispatch) });
export default connect(
mapStateToProps,
mapDispatchToProps,
)(App);