เมื่อใดที่จะใช้ bindActionCreators ใน react / redux


93

เอกสารReduxสำหรับ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
      })
    }
  }
}

คำตอบ:


59

ฉันไม่คิดว่าคำตอบที่ได้รับความนิยมมากที่สุดจะตอบคำถามได้จริง

ตัวอย่างทั้งหมดด้านล่างทำสิ่งเดียวกันเป็นหลักและเป็นไปตามแนวคิด "ไม่มีการผูกมัดล่วงหน้า"

// 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 codebase และฉันพบว่ามันค่อนข้างสับสน

ฉันคิดว่าความสับสนมาจากข้อเท็จจริงที่ว่าตัวอย่างทั้งหมดในreact-reduxdoc ใช้bindActionCreatorsในขณะที่ doc สำหรับ bindActionCreators (ตามที่ยกมาในคำถามนั้นเอง) บอกว่าห้ามใช้กับ react-redux

ฉันเดาว่าคำตอบคือความสอดคล้องใน codebase แต่โดยส่วนตัวแล้วฉันชอบการตัดการกระทำอย่างชัดเจนในการจัดส่งทุกครั้งที่จำเป็น


3
option #3เป็นชวเลขสำหรับ option #1อย่างไร?
ogostos


@ArtemBernatskyi ขอบคุณ ดังนั้นจะเปิดออกมี 3 กรณีmapDispatchToProps: function, objectและที่ขาดหายไป วิธีจัดการแต่ละกรณีมีกำหนดไว้ที่นี่
ogostos

ฉันกำลังมองหาคำตอบนี้ ขอบคุณ.
Kishan Rajdev

ฉันเจอกรณีการใช้งานเฉพาะที่เอกสาร React พูดถึงตอนนี้"ส่งต่อผู้สร้างการดำเนินการบางอย่างไปยังส่วนประกอบที่ไม่รู้จัก Redux"เพราะฉันมีส่วนประกอบง่ายๆที่เชื่อมต่อกับส่วนประกอบที่ซับซ้อนกว่าและเพิ่มค่าโสหุ้ย ของreduxและconnectและaddDispatchToPropsไปที่องค์ประกอบที่เรียบง่ายดูเหมือน overkill ถ้าฉันเพียงแค่สามารถผ่านหนึ่งเสาลง แต่เท่าที่ฉันทราบเกือบทุกกรณีสำหรับmapDispatchอุปกรณ์ประกอบฉากอาจเป็นตัวเลือก#1หรือ#3กล่าวถึงในคำตอบ
icc97

48

99% ของเวลาใช้กับconnect()ฟังก์ชันReact-Redux เป็นส่วนหนึ่งของmapDispatchToPropsพารามิเตอร์ มันสามารถนำมาใช้อย่างชัดเจนภายในฟังก์ชั่นที่คุณให้หรือโดยอัตโนมัติหากคุณใช้ไวยากรณ์วัตถุจดชวเลขและผ่านวัตถุที่เต็มรูปแบบของผู้สร้างการดำเนินการเพื่อmapDispatchconnect

แนวคิดก็คือว่าโดยก่อนที่มีผลผูกพันผู้สร้างการกระทำที่เป็นส่วนประกอบที่คุณส่งผ่านไปconnect()ในทางเทคนิค "ไม่ทราบ" ว่ามันเชื่อมต่อ - this.props.someCallback()มันก็รู้ว่าจะต้องวิ่ง ในทางกลับกันถ้าคุณไม่ได้ผูกมัดผู้สร้างแอคชั่นและเรียกว่าthis.props.dispatch(someActionCreator())ตอนนี้คอมโพเนนต์ "รู้" ว่าเชื่อมโยงกันเพราะคาดว่าprops.dispatchจะมี

ฉันเขียนความคิดเกี่ยวกับหัวข้อนี้ในบล็อกโพสต์Idiomatic Redux: ทำไมต้องใช้ผู้สร้างการกระทำ .


1
แต่มันเชื่อมต่อกับ 'mapDispatchToProps' ซึ่งก็ใช้ได้ ดูเหมือนว่าผู้สร้างการกระทำที่มีผลผูกพันนั้นเป็นสิ่งที่เป็นลบ / ไม่มีจุดหมายเนื่องจากคุณจะสูญเสียนิยามฟังก์ชัน (TS หรือ Flow) ท่ามกลางสิ่งอื่น ๆ อีกมากมายเช่นการดีบัก ในโปรเจ็กต์ใหม่ ๆ ของฉันฉันไม่เคยใช้เลยและฉันก็ไม่มีปัญหาจนถึงปัจจุบัน ยังใช้ Saga และสถานะคงอยู่ นอกจากนี้หากคุณกำลังเรียกใช้ฟังก์ชัน redux (คุณจะได้รับการคลิกที่ดี) ฉันจะบอกว่ามันสะอาดยิ่งกว่านั้นจากนั้นผู้สร้างแอคชั่น 'แนบ' ซึ่งในความคิดของฉันยุ่งเหยิงและไม่มีจุดหมาย สมาร์ทของคุณ (โดยปกติคือส่วนประกอบของหน้าจอ) ยังคงใช้การเชื่อมต่อได้ แต่สำหรับอุปกรณ์ประกอบฉาก
Oliver Dixon

19

ตัวอย่างที่สมบูรณ์ยิ่งขึ้นส่งผ่านวัตถุที่เต็มไปด้วยผู้สร้างการกระทำเพื่อเชื่อมต่อ:

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

นี่น่าจะเป็นคำตอบ
Deen John

16

ฉันจะพยายามตอบคำถามเดิม ...

ส่วนประกอบ Smart & Dumb

ในคำถามแรกของคุณโดยพื้นฐานแล้วคุณถามว่าเหตุใดจึงbindActionCreatorsจำเป็นตั้งแต่แรกและส่วนประกอบประเภทใดที่ไม่ควรทราบเกี่ยวกับ Redux

โดยสรุปแนวคิดในที่นี้คือควรแบ่งส่วนประกอบออกเป็นส่วนประกอบสมาร์ท (คอนเทนเนอร์) และใบ้ (การนำเสนอ) ส่วนประกอบ Dumbทำงานบนพื้นฐานที่จำเป็นต้องรู้ งานจิตวิญญาณของพวกเขาคือการแสดงข้อมูลที่กำหนดเป็น HTML และไม่มีอะไรเพิ่มเติม พวกเขาไม่ควรตระหนักถึงการทำงานภายในของแอปพลิเคชัน สามารถมองเห็นได้ว่าเป็นผิวหนังชั้นลึกด้านหน้าของใบสมัครของคุณ

ในทางกลับกันส่วนประกอบที่ชาญฉลาดเป็นกาวชนิดหนึ่งซึ่งเตรียมข้อมูลสำหรับส่วนประกอบที่เป็นใบ้และไม่ควรแสดง HTML

สถาปัตยกรรมประเภทนี้ส่งเสริมการเชื่อมต่อแบบหลวม ๆ ระหว่างเลเยอร์ UI และชั้นข้อมูลที่อยู่ด้านล่าง สิ่งนี้จะช่วยให้สามารถเปลี่ยนเลเยอร์สองเลเยอร์ด้วยสิ่งอื่นได้ง่าย (เช่นการออกแบบ UI ใหม่) ซึ่งจะไม่ทำให้เลเยอร์อื่นแตก

เพื่อตอบคำถามของคุณ: ส่วนประกอบที่โง่เขลาไม่ควรตระหนักถึง Redux (หรือรายละเอียดการใช้งานที่ไม่จำเป็นของชั้นข้อมูลสำหรับเรื่องนั้น) เนื่องจากเราอาจต้องการแทนที่ด้วยสิ่งอื่นในอนาคต

คุณสามารถค้นหาเพิ่มเติมเกี่ยวกับแนวคิดนี้ได้ในคู่มือ Reduxและในเชิงลึกมากขึ้นในบทความการนำเสนอและส่วนประกอบคอนเทนเนอร์โดย Dan Abramov

ตัวอย่างไหนดี

คำถามที่สองเกี่ยวกับข้อดี / ข้อเสียของตัวอย่างที่ให้มา

ในตัวอย่างแรกผู้สร้างการกระทำถูกกำหนดไว้ในactionCreatorsไฟล์ / โมดูลแยกต่างหากซึ่งหมายความว่าสามารถนำมาใช้ซ้ำที่อื่นได้ ค่อนข้างเป็นวิธีมาตรฐานในการกำหนดการกระทำ ฉันไม่เห็นข้อเสียในนี้จริงๆ

ตัวอย่างที่สองกำหนดกรรมวิธีการกระทำแบบอินไลน์ซึ่งมีข้อเสียหลาย

  • ไม่สามารถใช้ผู้สร้างการกระทำซ้ำได้ (เห็นได้ชัด)
  • สิ่งนี้มีรายละเอียดมากกว่าซึ่งแปลว่าอ่านได้น้อยลง
  • ประเภทการดำเนินการเป็นแบบฮาร์ดโค้ด - ควรกำหนดconstsแยกกันเพื่อให้สามารถอ้างอิงได้ในตัวลดขนาดซึ่งจะช่วยลดโอกาสในการพิมพ์ผิด
  • การกำหนดผู้สร้างการดำเนินการแบบอินไลน์นั้นขัดต่อวิธีการใช้งานที่แนะนำ / คาดหวังซึ่งจะทำให้ชุมชนอ่านโค้ดของคุณได้น้อยลงในกรณีที่คุณวางแผนที่จะแชร์โค้ดของคุณ

ตัวอย่างที่สองมีข้อดีกว่าข้อแรกคือเขียนเร็วกว่า! ดังนั้นหากคุณไม่มีแผนเพิ่มเติมสำหรับโค้ดของคุณก็อาจจะดี

ฉันหวังว่าฉันจะสามารถชี้แจงสิ่งต่างๆได้เล็กน้อย ...


2

วิธีใช้อย่างหนึ่งที่เป็นไปได้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 ()

    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 () กับเสาเดียว ( => {return {}}แผนที่เพิ่มเติมที่ userAction) คุณยังสามารถใช้bindActionCreators()เพื่อแมปการกระทำทั้งหมดของไฟล์เดียวเป็นอุปกรณ์ประกอบฉากแยกกัน แต่ฉันพบว่าการทำเช่นนั้นอาจสร้างความสับสน ฉันชอบให้แต่ละการกระทำหรือ "กลุ่มการกระทำ" ได้รับชื่อที่ชัดเจน ฉันชอบตั้งชื่อownPropsให้สื่อความหมายมากขึ้นว่า "อุปกรณ์ประกอบฉากเด็ก" เหล่านี้คืออะไรหรือมาจากไหน เมื่อใช้ Redux + React อาจทำให้เกิดความสับสนเล็กน้อยเมื่อมีการจัดหาอุปกรณ์ประกอบฉากทั้งหมดดังนั้นยิ่งอธิบายได้ดีเท่าไร


2

โดยการใช้bindActionCreatorsมันสามารถจัดกลุ่มฟังก์ชันการทำงานหลายอย่างและส่งต่อไปยังส่วนประกอบที่ไม่ทราบถึง Redux (Dumb Component) เช่นนั้น

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

ฉันยังต้องการทราบข้อมูลเพิ่มเติมเกี่ยวกับ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 Component ของคุณ

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

หนึ่งในกรณีการใช้งานที่ดีสำหรับbindActionCreatorsสำหรับการทำงานร่วมกับRedux-วีรชนใช้Redux-เทพนิยาย-กิจวัตร ตัวอย่างเช่น:

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


-1

เอกสารคำสั่งมีความชัดเจนมาก:

กรณีใช้งานเพียงอย่างเดียว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

ฉีดเพื่อ 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} />

สังเกตเห็นสิ่งต่อไปนี้?

  • เราอัปเดตที่เก็บ redux ผ่านส่วนประกอบ A ในขณะที่เราไม่รู้จักที่เก็บ redux ในองค์ประกอบ B

  • เราไม่ได้อัปเดตในองค์ประกอบ A หากต้องการทราบว่าฉันหมายถึงอะไรคุณสามารถสำรวจโพสต์นี้ ฉันหวังว่าคุณจะมีความคิด


7
ฉันไม่เข้าใจอะไรเลย
vsync
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.