什么时候在react/redux中使用bindActionCreators



Reduxdocs for bindActionCreators 指出:

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
})
}
}
}

我不认为最流行的答案实际上解决了这个问题。

下面的所有示例基本上都做同样的事情,并遵循无"预绑定"概念。

// 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文档中的所有示例都使用bindActionCreators,而 bindActionCreators 的文档(如问题本身所引用)说不要将其与 react-redux 一起使用。

我想答案是代码库中的一致性,但我个人更喜欢在需要时在调度中显式包装操作。

99%的情况下,它与React-Reduxconnect()函数一起使用,作为mapDispatchToProps参数的一部分。 它可以在您提供的mapDispatch函数中显式使用,或者如果您使用对象速记语法并将充满动作创建者的对象传递给connect,则可以自动使用它。

这个想法是,通过预先绑定动作创建者,您传递给connect()组件在技术上"不知道"它已连接 - 它只知道它需要this.props.someCallback()运行。 另一方面,如果您没有绑定动作创建者并调用this.props.dispatch(someActionCreator()),那么现在组件"知道"它已连接,因为它期望props.dispatch存在。

我在我的博客文章Idiomatic 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 手册中找到有关此概念的更多信息,并在 Dan Abramov 的文章 Representational and Container Components 中找到更深入的内容。

哪个例子更好

第二个问题是关于给定示例的优缺点。

第一个示例中,操作创建者在单独的actionCreators文件/模块中定义,这意味着它们可以在其他地方重用。这几乎是定义操作的标准方式。我真的看不出这有什么缺点。

第二个示例以内联方式定义操作创建者,这具有多个缺点:

  • 动作创作者不能重复使用(显然)
  • 事情更冗长,这意味着可读性较差
  • 操作类型是硬编码的 - 最好将它们单独定义为consts,以便可以在化简器中引用它们 - 这将减少键入错误的机会
  • 内联定义操作创建者是违反推荐/预期使用它们的方式 - 这将使您的代码对社区的可读性降低,以防您计划共享您的代码

第二个示例比第一个示例有一个优势- 编写速度更快!因此,如果你对代码没有更好的计划,那可能就好了。

我希望我设法澄清了一些事情...

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() 的多个调度

导入所有相关操作。它们可能都在 redux 存储中的同一个文件中

import * as allUserActions from "./store/actions/user";

现在不使用调度使用绑定行动创建者()

const mapDispatchToProps = (dispatch: Dispatch) => {
return {
...bindActionCreators(allUserActions, dispatch);
},
};
};
export default connect(mapStateToProps, mapDispatchToProps, 
(stateProps, dispatchProps, ownProps) => {
return {
...stateProps,
userAction: dispatchProps
ownProps,
}
})(MyComponent);

现在,我可以使用 propuserAction来调用组件中的所有操作。

即:userAction.login()userAction.editEmail()this.props.userAction.login()this.props.userAction.editEmail().

注意:您不必将 bindActionCreators() 映射到单个道具。(映射到userAction的附加=> {return {}})。您还可以使用bindActionCreators()将单个文件的所有动作映射为单独的 props。但我发现这样做可能会令人困惑。我更喜欢为每个操作或"操作组"指定一个明确的名称。我还想命名ownProps,以便更好地描述这些"儿童道具"是什么或它们来自哪里。当使用 Redux + React 时,所有道具的提供位置可能会有点混乱,所以描述性越强越好。

通过使用bindActionCreators,它可以对多个操作函数进行分组,并将其传递给不知道Redux(哑组件)的组件,如下所示

// actions.js
export const increment = () => ({
type: 'INCREMENT'
})
export const decrement = () => ({
type: 'DECREMENT'
})
// main.js
import { Component } from 'react'
import { bindActionCreators } from 'redux'
import * as Actions from './actions.js'
import Counter from './counter.js'
class Main extends Component {
constructor(props) {
super(props);
const { dispatch } = props;
this.boundActionCreators = bindActionCreators(Actions, dispatch)
}
render() {
return (
<Counter {...this.boundActionCreators} />
)
}
}
// counter.js
import { Component } from 'react'
export default Counter extends Component {
render() {
<div>
<button onclick={() => this.props.increment()}
<button onclick={() => this.props.decrement()}
</div>
}
}

绑定动作创建者的唯一用例是当您想要将一些动作创建者传递给不知道 redux 的组件时。所以它可以帮助我创建useActions钩子:

import { useDispatch } from "react-redux";
import { bindActionCreators } from "redux";
// I exported all the actions as actionCreators
// export * as actionCreators from "./action-creators";
import { actionCreators } from "../state";
export const useActions = () => {
const dispatch = useDispatch();
return bindActionCreators(actionCreators, dispatch);
};

actionCreators 是我从文件中导出的所有动作创建器函数。例如,假设我有更新发布操作创建者

export const updatePost = (id: string, content: string): UpdatePostAction => {
return { type: ActionType.UPDATE_POST, payload: { id, content } };
};

因此,每当我需要调度更新发布操作时,我都会这样写:

const {updatePost}=useActions()
updatePost({id,content})

我也在寻找更多关于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 };

在你的反应组件中

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的一个很好的用例是使用 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 开始)。

我觉得很少有人谈论什么时候不使用bindActionCreators。我已经看到无数次人们使用bindActionCreators创建钩子,只是为了在完全了解redux的组件中使用它。

但是这样做是没有意义的。只需使用钩子。useDispatch,直接从组件文件中的切片导入操作,您就很好。在这些情况下,您不需要 bindActionCreators。

一直写dispatch(fooAction)并没有错,dispatch fn 将很好地将 redux 操作与其他功能(例如从 props)分开,因此它使阅读内容变得更加容易。

只有当你有一些纯UI组件,它没有以任何方式连接到redux,并且只期望普通函数,在那里,构造"纯"函数,你可以使用bindActionCreators并将结果传递给你的哑组件。

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} />

注意到以下情况了吗?

  • 我们通过组件 A 更新了 redux 存储,而我们不知道组件 B 中的 redux 存储。

  • 我们不会在组件 A 中进行更新。要了解我到底是什么意思,您可以浏览这篇文章。我希望你能有一个想法。

相关内容

  • 没有找到相关文章

最新更新