bindActionCreators的Redux文档指出:
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%的时间,它与React-Redux connect()
函数一起使用,作为mapDispatchToProps
参数的一部分。它可以在您提供的mapDispatch
函数中显式使用,或者如果您使用对象速记语法并将一个充满动作创建者的对象传递给connect
则会自动使用。
这个想法是通过预先绑定动作创建者,你传递给connect()
的组件在技术上“不知道”它是连接的 - 它只知道它需要运行this.props.someCallback()
。另一方面,如果你没有绑定动作创建者,并且调用了this.props.dispatch(someActionCreator())
,那么现在组件“知道”它已连接,因为它期望props.dispatch
存在。
我在我的博客Idiomatic Redux: Why use action creators?上写了一些关于这个主题的想法。
我不认为最受欢迎的答案,实际上是解决了这个问题。
以下所有示例基本上都做同样的事情,并遵循没有“预绑定”的概念。
// 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
vs选项#2
。我已经看到它们都在react-redux代码库中使用,我发现它相当令人困惑。
我认为混淆来自于examples doc中的所有react-redux
都使用bindActionCreators
这一事实,而bindActionCreators的文档(在问题本身中引用)表示不使用react-redux。
我想答案是代码库中的一致性,但我个人更喜欢在需要时明确地将调度包装在调度中。
更完整的示例,传递一个充满动作创建者的对象来连接:
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。
简而言之,这里的想法是组件应该分为智能(容器)和哑(表示)组件。愚蠢的组件在需要知道的基础上工作。他们的灵魂工作是将给定数据呈现给HTML,仅此而已。他们不应该意识到应用程序的内部工作原理。它们可以被视为应用程序的皮肤深层。
另一方面,智能组件是一种粘合剂,它为哑组件准备数据,并且最好不进行HTML渲染。
这种架构促进了UI层与下面的数据层之间的松散耦合。这反过来允许用其他东西(即UI的新设计)容易地替换两个层中的任何一个,这不会破坏另一层。
回答你的问题:哑组件不应该知道Redux(或者数据层的任何不必要的实现细节),因为我们可能希望将来用其他东西替换它。
您可以在Redux manual中找到更多有关此概念的信息,并在Dan Abramov的文章Presentational and Container Components中进行更深入的介绍。
第二个问题是关于给定示例的优点/缺点。
在第一个示例中,动作创建者在单独的actionCreators
文件/模块中定义,这意味着它们可以在其他地方重用。它几乎是定义动作的标准方式。我真的没有看到任何不利之处。
第二个示例定义了内联的动作创建者,它有多个缺点:
consts
,以便它们可以在reducers中引用 - 这样可以减少键入错误的机会第二个例子比第一个例子有一个优势 - 编写速度更快!因此,如果您没有更好的代码计划,那可能就好了。
我希望我能够澄清一些事情......
bindActionCreators
的一个很好的用例是使用redux-saga与redux-saga-routines集成。例如:
// 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开始)。
我也在寻找有关bindActionCreators的更多信息,这是我在项目中的实现方式。
// 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);
bindActionCreators()
的一个可能用途是将多个动作“映射”在一起作为单个道具。
正常的调度看起来像这样:
将一些常见的用户操作映射到道具。
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);
在较大的项目中,单独映射每个调度可能会感到笨拙。如果我们有一堆彼此相关的行动,我们可以将这些行动结合起来。例如,用户操作文件执行各种不同的用户相关操作。我们可以使用bindActionCreators()
而不是dispatch
,而不是将每个动作称为单独的调度。
使用bindActionCreators()的多个Dispatches
导入所有相关操作。它们可能都在redux商店的同一个文件中
import * as allUserActions from "./store/actions/user";
而现在使用bindActionCreators()而不是使用dispatch
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()映射到单个prop。 (映射到=> {return {}}
的额外userAction
)。您还可以使用bindActionCreators()
将单个文件的所有操作映射为单独的道具。但我觉得这样做可能令人困惑。我更喜欢给每个动作或“动作组”一个明确的名字。我还想将ownProps
命名为更具描述性,这些“儿童道具”是什么或来自哪里。当使用Redux + React时,在提供所有道具的地方会有点混乱,因此描述性越强越好。
docs声明很清楚:
bindActionCreators
的唯一用例是当你想将一些动作创建者传递给一个不知道Redux的组件时,你不想将调度或Redux存储传递给它。
这显然是一个用例,可能出现在以下情况中,只有一个条件:
可以说,我们有组件A和B:
// A use connect and updates the redux store
const A = props => {}
export default connect()(A)
// B doesn't use connect therefore it does not know about the redux store.
const B = props => {}
export default B
注入反应还原剂:(A)
const boundActionCreators = bindActionCreators(SomeActionCreator, dispatch)
// myActionCreatorMethod,
// myActionCreatorMethod2,
// myActionCreatorMethod3,
// when we want to dispatch
const action = SomeActionCreator.myActionCreatorMethod('My updates')
dispatch(action)
反应还原剂注入:(B)
const { myActionCreatorMethod } = props
<B myActionCreatorMethod={myActionCreatorMethod} {...boundActionCreators} />
注意到以下情况?