簡體   English   中英

什么時候在 react/redux 中使用 bindActionCreators?

[英]When would bindActionCreators be used in react/redux?

bindActionCreators 的Redux文檔指出:

bindActionCreators的唯一用例是當您想將一些動作創建者傳遞給不知道 Redux 的組件時,並且您不想將 dispatch 或 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 (引問題本身)說,不與反應,終極版使用它。

我想答案是代碼庫的一致性,但我個人更喜歡在需要時在dispatch 中顯式包裝操作。

99% 的情況下,它與 React-Redux connect()函數一起使用,作為mapDispatchToProps參數的一部分。 它可以在您提供的mapDispatch函數中顯式使用,或者如果您使用對象速記語法並將一個充滿動作創建者的對象傳遞給connect ,則可以自動使用它。

這個想法是,通過預先綁定動作創建者,您傳遞給connect()的組件在技術上“不知道”它已連接 - 它只知道它需要運行this.props.someCallback() 另一方面,如果您沒有綁定動作創建者並調用this.props.dispatch(someActionCreator()) ,那么現在組件“知道”它已連接,因為它期望props.dispatch存在。

我在我的博客文章Idiomatic Redux:Why use action creators 中寫了一些關於這個主題的想法 .

更完整的例子,傳遞一個充滿動作創建者的對象來連接:

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需要bindActionCreators ,以及哪些組件不應該知道 Redux。

簡而言之,這里的想法是組件應該分為智能(容器)和(展示)組件。 啞組件在需要知道的基礎上工作。 他們的靈魂工作是將給定的數據呈現為 HTML,僅此而已。 他們不應該知道應用程序的內部工作原理。 它們可以被視為您應用程序的皮膚深層前層。

另一方面,智能組件是一種粘合劑,它為組件准備數據,並且最好不進行 HTML 渲染。

這種架構促進了 UI 層和下面的數據層之間的松散耦合。 這反過來又允許用其他東西(即 UI 的新設計)輕松替換兩層中的任何一層,而不會破壞另一層。

回答你的問題:愚蠢的組件不應該知道 Redux(或任何不必要的數據層實現細節),因為我們將來可能想用其他東西替換它。

您可以在Redux 手冊中找到有關此概念的更多信息,並在 Dan Abramov 的文章Presentational 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";

現在使用 bindActionCreators() 而不是使用 dispatch

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

現在我可以使用道具userAction來調用組件中的所有操作。

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

注意:您不必將 bindActionCreators() 映射到單個道具。 (附加的=> {return {}}映射到userAction )。 您還可以使用bindActionCreators()將單個文件的所有操作映射為單獨的道具。 但我發現這樣做可能會令人困惑。 我更喜歡給每個動作或“動作組”一個明確的名稱。 我還喜歡命名ownProps以更好地描述這些“子道具”是什么或它們來自哪里。 當使用 Redux + React 時,在提供所有 props 的地方可能會有點混亂,所以描述性越好。

通過使用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>
  }
}

我也在尋找更多關於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);

bindActionCreators一個很好的用例是使用redux-saga-routinesredux-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 開始)。

我用它來創建 useActions 鈎子:

import { useDispatch } from "react-redux";
import { bindActionCreators } from "redux";
import { actionCreators } from "../state";

export const useActions = () => {
  const dispatch = useDispatch();
  return bindActionCreators(actionCreators, dispatch);
};

actionCreators 是我從文件中導出的所有動作創建器函數。 例如,假設我有 updatePost 動作創建者

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

因此,每當我需要調度 updatePost 操作時,我都會這樣寫:

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

文檔聲明非常清楚:

bindActionCreators的唯一用例是當您想將一些動作創建者傳遞給不知道 Redux 的組件時,並且您不想將 dispatch 或 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

注入 react-redux:(A)

const boundActionCreators = bindActionCreators(SomeActionCreator, dispatch)
// myActionCreatorMethod,
// myActionCreatorMethod2,
// myActionCreatorMethod3,

// when we want to dispatch
const action = SomeActionCreator.myActionCreatorMethod('My updates')
dispatch(action)

由 react-redux 注入:(B)

const { myActionCreatorMethod } = props
<B myActionCreatorMethod={myActionCreatorMethod} {...boundActionCreators} />

注意到以下幾點了嗎?

  • 我們通過組件 A 更新了 redux 存儲,而我們不知道組件 B 中的 redux 存儲。

  • 我們不會在組件 A 中更新。要了解我的確切意思,您可以瀏覽這篇文章 我希望你會有一個想法。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM