Khi nào bindActionCreators sẽ được sử dụng trong react / redux?


91

Tài liệu Redux cho bindActionCreators tuyên bố rằng:

Trường hợp sử dụng duy nhất bindActionCreatorslà khi bạn muốn chuyển một số người tạo hành động xuống thành phần không biết về Redux và bạn không muốn chuyển công văn hoặc cửa hàng Redux cho nó.

Ví dụ bindActionCreatorsnào sẽ được sử dụng / cần thiết?

Loại thành phần nào sẽ không được biết về Redux ?

Ưu điểm / nhược điểm của cả hai lựa chọn là gì?

//actionCreator
import * as actionCreators from './actionCreators'

function mapStateToProps(state) {
  return {
    posts: state.posts,
    comments: state.comments
  }
}

function mapDispatchToProps(dispatch) {
  return bindActionCreators(actionCreators, dispatch)
}

vs

function mapStateToProps(state) {
  return {
    posts: state.posts,
    comments: state.comments
  }
}

function mapDispatchToProps(dispatch) {
  return {
    someCallback: (postId, index) => {
      dispatch({
        type: 'REMOVE_COMMENT',
        postId,
        index
      })
    }
  }
}

Câu trả lời:


58

Tôi không nghĩ rằng câu trả lời phổ biến nhất, thực sự giải quyết được câu hỏi.

Tất cả các ví dụ bên dưới về cơ bản đều làm điều tương tự và không tuân theo khái niệm "ràng buộc trước".

// option 1
const mapDispatchToProps = (dispatch) => ({
  action: () => dispatch(action())
})


// option 2
const mapDispatchToProps = (dispatch) => ({
  action: bindActionCreators(action, dispatch)
})


// option 3
const mapDispatchToProps = {
  action: action
}

Quyền chọn #3chỉ là cách viết tắt của quyền chọn #1, vì vậy câu hỏi thực sự tại sao người ta lại sử dụng quyền chọn #1so với quyền chọn #2. Tôi đã thấy cả hai đều được sử dụng trong codebase react-redux và tôi thấy nó khá khó hiểu.

Tôi nghĩ rằng sự nhầm lẫn đến từ thực tế là tất cả các ví dụ trong react-reduxdoc đều sử dụng bindActionCreatorstrong khi doc cho bindActionCreators (như được trích dẫn trong câu hỏi) nói rằng không sử dụng nó với react-redux.

Tôi đoán câu trả lời là tính nhất quán trong cơ sở mã, nhưng cá nhân tôi thích gói các hành động một cách rõ ràng trong công văn bất cứ khi nào cần.


2
quyền chọn #3là cách viết tắt của quyền chọn #1như thế nào?
ogostos


@ArtemBernatskyi cảm ơn. Vì vậy, hóa ra có 3 trường hợp cho mapDispatchToProps: function, objectvà mất tích. Làm thế nào để xử lý từng trường hợp được xác định ở đây
ogostos

Tôi đã tìm kiếm câu trả lời này. Cảm ơn.
Kishan Rajdev

Tôi thực sự đã gặp trường hợp sử dụng cụ thể mà các tài liệu React nói về bây giờ "chuyển một số người tạo hành động xuống thành phần không biết về Redux" vì tôi có một thành phần đơn giản được kết nối với một thành phần phức tạp hơn và thêm chi phí của reduxconnectaddDispatchToPropsđối với thành phần đơn giản có vẻ quá mức cần thiết nếu tôi chỉ có thể chuyển một chỗ dựa xuống. Nhưng như xa như tôi biết hầu hết các trường hợp cho mapDispatchđến đạo cụ một trong hai sẽ được lựa chọn #1hoặc #3được đề cập trong câu trả lời
icc97

48

99% thời gian, nó được sử dụng với hàm React-Redux connect(), như một phần của mapDispatchToPropstham số. Nó có thể được sử dụng một cách rõ ràng bên trong mapDispatchhàm mà bạn cung cấp hoặc tự động nếu bạn sử dụng cú pháp viết tắt của đối tượng và chuyển một đối tượng đầy những người tạo hành động vào connect.

Ý tưởng là bằng cách ràng buộc trước những người tạo hành động, thành phần mà bạn chuyển đến connect()về mặt kỹ thuật "không biết" rằng nó được kết nối - nó chỉ biết rằng nó cần chạy this.props.someCallback(). Mặt khác, nếu bạn không ràng buộc người tạo hành động và được gọi this.props.dispatch(someActionCreator()), thì bây giờ thành phần "biết" rằng nó được kết nối vì nó đang mong đợi props.dispatchtồn tại.

Tôi đã viết một số suy nghĩ về chủ đề này trong bài đăng trên blog Idiomatic Redux: Tại sao lại sử dụng trình tạo hành động? .


1
Nhưng nó được kết nối với 'mapDispatchToProps', điều này tốt. Có vẻ như người tạo hành động ràng buộc thực sự là một điều tiêu cực / vô nghĩa vì bạn sẽ mất định nghĩa hàm (TS hoặc Flow) trong số nhiều thứ khác như gỡ lỗi. Trong các dự án mới hơn, tôi không bao giờ sử dụng nó và cho đến nay tôi vẫn chưa gặp vấn đề gì. Cũng sử dụng Saga và trạng thái tồn tại. Ngoài ra, nếu bạn chỉ đang gọi các hàm redux (mặc dù bạn sẽ nhận được cú nhấp chuột tốt), tôi sẽ nói rằng điều đó thậm chí còn rõ ràng hơn sau đó 'đính kèm' các trình tạo hành động theo ý kiến ​​của tôi là lộn xộn và vô nghĩa. Tuy nhiên, thông minh của bạn (thường là các thành phần màn hình) vẫn có thể sử dụng kết nối, chỉ cho các đạo cụ.
Oliver Dixon

19

Ví dụ đầy đủ hơn, hãy chuyển một đối tượng đầy người tạo hành động để kết nối:

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

đây phải là câu trả lời
Deen John

16

Tôi sẽ cố gắng trả lời các câu hỏi ban đầu ...

Các thành phần Smart & Dumb

Trong câu hỏi đầu tiên của bạn, về cơ bản, ngay từ đầu bạn đã hỏi tại sao lại bindActionCreatorscần và loại thành phần nào không nên biết về Redux.

Tóm lại, ý tưởng ở đây là các thành phần nên được chia thành các thành phần thông minh (container) và câm (trình bày). Các thành phần câm hoạt động trên cơ sở cần biết. Công việc linh hồn của họ là hiển thị dữ liệu đã cho sang HTML và không có gì khác. Họ không nên biết về hoạt động bên trong của ứng dụng. Chúng có thể được coi là lớp da sâu phía trước của ứng dụng của bạn.

Mặt khác, các thành phần thông minh là một loại keo, chuẩn bị dữ liệu cho các thành phần câm và tốt nhất là không hiển thị HTML.

Loại kiến ​​trúc này thúc đẩy sự kết hợp lỏng lẻo giữa lớp giao diện người dùng và lớp dữ liệu bên dưới. Điều này đến lượt nó cho phép dễ dàng thay thế bất kỳ lớp nào trong hai lớp bằng thứ khác (tức là một thiết kế mới của giao diện người dùng), điều này sẽ không phá vỡ lớp kia.

Để trả lời câu hỏi của bạn: các thành phần ngu ngốc không nên biết về Redux (hoặc bất kỳ chi tiết triển khai không cần thiết nào của lớp dữ liệu cho vấn đề đó) vì chúng tôi có thể muốn thay thế nó bằng một thứ khác trong tương lai.

Bạn có thể tìm thêm về khái niệm này trong sổ tay Redux và chuyên sâu hơn trong bài viết Các thành phần trình bày và thùng chứa của Dan Abramov.

Ví dụ nào tốt hơn

Câu hỏi thứ hai là về ưu / nhược điểm của các ví dụ đã cho.

Trong ví dụ đầu tiên , người tạo hành động được xác định trong một actionCreatorstệp / mô-đun riêng biệt , có nghĩa là chúng có thể được sử dụng lại ở nơi khác. Đó là cách tiêu chuẩn để xác định các hành động. Tôi thực sự không thấy bất kỳ nhược điểm nào trong việc này.

Các ví dụ thứ hai định nghĩa sáng tạo hành động nội tuyến, trong đó có nhiều nhược điểm:

  • Người tạo hành động không thể được sử dụng lại (hiển nhiên)
  • có điều là dài dòng hơn, dịch thành khó đọc hơn
  • các loại hành động được mã hóa cứng - tốt hơn là bạn nên xác định chúng thành từng loại constsriêng biệt, để chúng có thể được tham chiếu trong các bộ giảm bớt - điều này sẽ giảm cơ hội nhập sai
  • xác định nội tuyến người tạo hành động là đi ngược lại cách sử dụng chúng được khuyến nghị / mong đợi - điều này sẽ làm cho mã của bạn ít đọc hơn đối với cộng đồng, trong trường hợp bạn định chia sẻ mã của mình

Ví dụ thứ hai có một lợi thế so với ví dụ đầu tiên - nó nhanh hơn để viết! Vì vậy, nếu bạn không có kế hoạch lớn hơn cho mã của mình, nó có thể ổn.

Tôi hy vọng tôi đã quản lý để làm rõ mọi thứ một chút ...


2

Một cách có thể sử dụng bindActionCreators()là "ánh xạ" nhiều hành động với nhau như một chỗ dựa duy nhất.

Một công văn bình thường trông như thế này:

Ánh xạ một vài hành động phổ biến của người dùng với đạo cụ.

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

Trong các dự án lớn hơn, việc lập bản đồ từng công văn riêng biệt có thể cảm thấy khó sử dụng. Nếu chúng ta có một loạt các hành động có liên quan đến nhau, chúng ta có thể kết hợp các hành động này . Ví dụ: một tệp hành động của người dùng đã thực hiện tất cả các loại hành động khác nhau liên quan đến người dùng. Thay vì gọi mỗi hành động là một công văn riêng biệt, chúng ta có thể sử dụng bindActionCreators()thay thế dispatch.

Nhiều công văn sử dụng bindActionCreators ()

Nhập tất cả các hành động liên quan của bạn. Chúng có thể nằm trong cùng một tệp trong cửa hàng redux

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

Và bây giờ thay vì sử dụng công văn, hãy sử dụng bindActionCreators ()

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

Bây giờ tôi có thể sử dụng phần mềm hỗ trợ userActionđể gọi tất cả các hành động trong thành phần của bạn.

IE: userAction.login() userAction.editEmail() hoặc this.props.userAction.login() this.props.userAction.editEmail().

LƯU Ý: Bạn không phải ánh xạ bindActionCreators () với một hỗ trợ duy nhất. (Phần bổ sung => {return {}}ánh xạ tới userAction). Bạn cũng có thể sử dụng bindActionCreators()để ánh xạ tất cả các hành động của một tệp dưới dạng các đạo cụ riêng biệt. Nhưng tôi thấy làm điều đó có thể khó hiểu. Tôi muốn đặt tên rõ ràng cho mỗi hành động hoặc "nhóm hành động". Tôi cũng muốn đặt tên ownPropsđể mô tả nhiều hơn về những "đạo cụ trẻ em" này là gì hoặc chúng đến từ đâu. Khi sử dụng Redux + React, nó có thể hơi khó hiểu khi tất cả các đạo cụ đang được cung cấp, vì vậy càng có nhiều mô tả càng tốt.


2

bằng cách sử dụng bindActionCreators, nó có thể nhóm nhiều chức năng hành động và chuyển nó xuống một thành phần không biết về Redux (Thành phần Dumb) như vậy

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

1

Tôi cũng đang muốn biết thêm về bindActionsCreators và đây là cách tôi thực hiện trong dự án của mình.

// Actions.js
// Action Creator
const loginRequest = (username, password) => {
 return {
   type: 'LOGIN_REQUEST',
   username,
   password,
  }
}

const logoutRequest = () => {
 return {
   type: 'LOGOUT_REQUEST'
  }
}

export default { loginRequest, logoutRequest };

Trong Thành phần React của bạn

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

0

Một trường hợp sử dụng hay bindActionCreatorslà để tích hợp với redux-saga bằng cách sử dụng redux-saga-routines . Ví dụ:

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

Lưu ý rằng mẫu này có thể được triển khai bằng cách sử dụng React Hooks (kể từ React 16.8).


-1

Các tài liệu tuyên bố là rất rõ ràng:

Trường hợp sử dụng duy nhất bindActionCreatorslà khi bạn muốn chuyển một số người tạo hành động xuống thành phần không biết về Redux và bạn không muốn chuyển công văn hoặc cửa hàng Redux cho nó.

Đây rõ ràng là một trường hợp sử dụng có thể phát sinh trong các điều kiện sau và chỉ một điều kiện:

Giả sử, chúng ta có thành phần A và 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

Tiêm phản ứng-redux: (A)

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

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

Được tiêm bởi react-redux: (B)

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

Nhận thấy những điều sau đây?

  • Chúng tôi đã cập nhật cửa hàng redux thông qua thành phần A trong khi chúng tôi không biết cửa hàng redux trong thành phần B.

  • Chúng tôi không cập nhật thành phần A. Để biết chính xác ý tôi là gì, bạn có thể khám phá bài đăng này . Tôi hy vọng bạn sẽ có một ý tưởng.


7
Tôi chẳng hiểu gì cả
vsync
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.