Wann würden bindActionCreators in React / Redux verwendet?


91

In Redux- Dokumenten für bindActionCreators heißt es:

Der einzige Anwendungsfall bindActionCreatorsbesteht darin, dass Sie einige Aktionsersteller an eine Komponente weitergeben möchten, die Redux nicht kennt, und den Versand oder den Redux-Speicher nicht an diese übergeben möchten.

Was wäre ein Beispiel, wo bindActionCreatorsverwendet / benötigt würde?

Welche Art von Komponente würde Redux nicht kennen ?

Was sind die Vor- und Nachteile beider Optionen?

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

Antworten:


57

Ich denke nicht, dass die beliebteste Antwort tatsächlich die Frage anspricht.

Alle folgenden Beispiele machen im Wesentlichen dasselbe und folgen dem Konzept "keine Vorbindung".

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


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


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

Option #3ist nur eine Abkürzung für Option #1, also die eigentliche Frage, warum man Option #1gegen Option verwenden würde #2. Ich habe gesehen, dass beide in der React-Redux-Codebasis verwendet werden, und ich finde es ziemlich verwirrend.

Ich denke, die Verwirrung rührt von der Tatsache her, dass alle Beispiele in react-reduxdoc verwendet werden, bindActionCreatorswährend das doc für bindActionCreators (wie in der Frage selbst angegeben) sagt, dass es nicht mit React-Redux verwendet werden soll.

Ich denke, die Antwort ist Konsistenz in der Codebasis, aber ich persönlich bevorzuge es, Aktionen bei Bedarf explizit in den Versand einzubinden.


2
Wie ist Option #3eine Abkürzung für Option #1?
Ogostos


@ArtemBernatskyi danke. So dreht, gibt es 3 Fälle mapDispatchToProps: function, objectund fehlt. Wie mit jedem Fall
umzugehen

Ich habe nach dieser Antwort gesucht. Vielen Dank.
Kishan Rajdev

Ich bin tatsächlich auf den speziellen Anwendungsfall gestoßen, über den in den React-Dokumenten jetzt gesprochen wird: "Übergeben Sie einige Aktionsersteller an eine Komponente, die Redux nicht kennt", weil ich eine einfache Komponente habe, die mit einer komplexeren Komponente verbunden ist und den Overhead hinzufügt von reduxund connectund addDispatchToPropszu der einfachen Komponente scheint übertrieben, wenn ich nur eine Requisite weitergeben kann. Aber soweit ich weiß, werden fast alle Fälle für mapDispatchRequisiten entweder Optionen sein #1oder #3in der Antwort erwähnt
icc97

48

In 99% der Fälle wird es mit der React-Redux- connect()Funktion als Teil des mapDispatchToPropsParameters verwendet. Es kann explizit in der von mapDispatchIhnen bereitgestellten Funktion verwendet werden oder automatisch, wenn Sie die Kurzkurzsyntax des Objekts verwenden und ein Objekt voller Aktionsersteller an übergeben connect.

Die Idee ist, dass durch das Vorbinden der Aktionsersteller die Komponente, an die Sie übergeben, connect()technisch "nicht weiß", dass sie verbunden ist - sie weiß nur, dass sie ausgeführt werden muss this.props.someCallback(). Wenn Sie dagegen keine Aktionsersteller gebunden und aufgerufen haben this.props.dispatch(someActionCreator()), "weiß" die Komponente jetzt, dass sie verbunden ist, weil sie erwartet props.dispatch, dass sie existiert.

Ich habe einige Gedanken zu diesem Thema in meinem Blog-Beitrag Idiomatic Redux: Warum Action Creators verwenden? .


1
Aber es ist mit 'mapDispatchToProps' verbunden, was in Ordnung ist. Es scheint, dass das Binden von Aktionserstellern tatsächlich eine negative / sinnlose Sache ist, da Sie unter anderem die Funktionsdefinition (TS oder Flow) verlieren. Bei meinen neueren Projekten verwende ich es nie und hatte bisher kein Problem. Auch mit Saga und Zustand bestehen. Wenn Sie nur Redux-Funktionen aufrufen (Sie erhalten jedoch einen schönen Klick), würde ich sagen, dass dies noch sauberer ist, als Action-Ersteller anzuhängen, was meiner Meinung nach chaotisch und sinnlos ist. Ihr Smart (normalerweise Bildschirmkomponenten) kann weiterhin Connect verwenden, jedoch nur für Requisiten.
Oliver Dixon

18

Ein vollständigeres Beispiel: Übergeben Sie ein Objekt voller Aktionsersteller, um eine Verbindung herzustellen:

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

Dies sollte die Antwort sein
Deen John

16

Ich werde versuchen, die ursprünglichen Fragen zu beantworten ...

Smart & Dumb-Komponenten

In Ihrer ersten Frage fragen Sie im Grunde, warum überhaupt bindActionCreatorsbenötigt wird und welche Art von Komponenten Redux nicht kennen sollten.

Kurz gesagt, die Idee hier ist, dass Komponenten in intelligente (Container) und dumme (Präsentations-) Komponenten aufgeteilt werden sollten. Dumme Komponenten arbeiten auf der Basis, die man wissen muss. Ihre Seelenaufgabe ist es, gegebene Daten in HTML zu rendern und nicht mehr. Sie sollten sich des Innenlebens der Anwendung nicht bewusst sein. Sie können als die hauttiefe vordere Schicht Ihrer Anwendung angesehen werden.

Auf der anderen Seite sind intelligente Komponenten eine Art Klebstoff, der Daten für die dummen Komponenten aufbereitet und vorzugsweise kein HTML-Rendering ausführt.

Diese Art von Architektur fördert die lose Kopplung zwischen der UI-Schicht und der darunter liegenden Datenschicht. Dies ermöglicht wiederum das einfache Ersetzen einer der beiden Ebenen durch etwas anderes (dh ein neues Design der Benutzeroberfläche), wodurch die andere Ebene nicht beschädigt wird.

Um Ihre Frage zu beantworten: Dumme Komponenten sollten Redux (oder unnötige Implementierungsdetails der Datenschicht) nicht kennen, da wir sie möglicherweise in Zukunft durch etwas anderes ersetzen möchten.

Weitere Informationen zu diesem Konzept finden Sie im Redux-Handbuch und ausführlicher im Artikel Präsentations- und Containerkomponenten von Dan Abramov.

Welches Beispiel ist besser

Die zweite Frage betraf die Vor- und Nachteile der angegebenen Beispiele.

Im ersten Beispiel werden die Aktionsersteller in einer separaten actionCreatorsDatei / einem separaten Modul definiert, sodass sie an anderer Stelle wiederverwendet werden können. Es ist so ziemlich die Standardmethode zum Definieren von Aktionen. Ich sehe darin keine wirklichen Nachteile.

Das zweite Beispiel definiert Aktionsersteller inline, was mehrere Nachteile hat:

  • Aktionsersteller können (offensichtlich) nicht wiederverwendet werden
  • Die Sache ist ausführlicher, was weniger lesbar bedeutet
  • Aktionstypen sind fest codiert - es ist vorzugsweise, sie als constsseparat zu definieren , damit sie in Reduzierungen referenziert werden können -, wodurch die Wahrscheinlichkeit von Tippfehlern verringert wird
  • Das Inline-Definieren von Aktionserstellern widerspricht der empfohlenen / erwarteten Verwendung. Dadurch wird Ihr Code für die Community weniger lesbar, falls Sie vorhaben, Ihren Code freizugeben

Das zweite Beispiel hat einen Vorteil gegenüber dem ersten - es ist schneller zu schreiben! Wenn Sie also keine größeren Pläne für Ihren Code haben, ist dies möglicherweise in Ordnung.

Ich hoffe, ich habe es geschafft, die Dinge ein bisschen zu klären ...


2

Mithilfe von bindActionCreatorskann es mehrere Aktionsfunktionen gruppieren und an eine Komponente weitergeben, die Redux (dumme Komponente) nicht so kennt

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

Ich wollte auch mehr über bindActionsCreators erfahren und hier ist, wie ich es in meinem Projekt implementiert habe.

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

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

export default { loginRequest, logoutRequest };

In Ihrer Reaktionskomponente

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

1

Eine mögliche Verwendung bindActionCreators()besteht darin, mehrere Aktionen als eine einzige Requisite zusammen "abzubilden".

Ein normaler Versand sieht folgendermaßen aus:

Ordnen Sie Requisiten einige allgemeine Benutzeraktionen zu.

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

In größeren Projekten kann es sich unhandlich anfühlen, jeden Versand einzeln zuzuordnen. Wenn wir eine Reihe von Aktionen haben, die miteinander zusammenhängen, können wir diese Aktionen kombinieren . Zum Beispiel eine Benutzeraktionsdatei, die alle Arten von verschiedenen benutzerbezogenen Aktionen ausgeführt hat. Anstatt jede Aktion als separaten Versand aufzurufen, können wir bindActionCreators()stattdessen verwenden dispatch.

Mehrfachversand mit bindActionCreators ()

Importieren Sie alle zugehörigen Aktionen. Sie befinden sich wahrscheinlich alle in derselben Datei im Redux-Speicher

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

Und jetzt, anstatt den Versand zu verwenden, verwenden Sie bindActionCreators ()

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

Jetzt kann ich die Requisite verwenden userAction, um alle Aktionen in Ihrer Komponente aufzurufen.

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

HINWEIS: Sie müssen die bindActionCreators () nicht einer einzelnen Requisite zuordnen. (Die zusätzliche => {return {}}, die zugeordnet ist userAction). Sie können auch bindActionCreators()alle Aktionen einer einzelnen Datei als separate Requisiten zuordnen. Aber ich finde das verwirrend. Ich bevorzuge es, jeder Aktion oder "Aktionsgruppe" einen expliziten Namen zu geben. Ich nenne das auch gerne, ownPropsum genauer zu beschreiben, was diese "Kinderrequisiten" sind oder woher sie kommen. Bei Verwendung von Redux + React kann es etwas verwirrend werden, wo alle Requisiten geliefert werden. Je aussagekräftiger, desto besser.


0

Ein guter Anwendungsfall für bindActionCreatorsist die Integration mit Redux-Saga unter Verwendung von Redux-Saga-Routinen . Beispielsweise:

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

Beachten Sie, dass dieses Muster mithilfe von React Hooks (ab React 16.8) implementiert werden kann .


-1

Die docs- Aussage ist sehr klar:

Der einzige Anwendungsfall bindActionCreatorsbesteht darin, dass Sie einige Aktionsersteller an eine Komponente weitergeben möchten, die Redux nicht kennt, und den Versand oder den Redux-Speicher nicht an diese übergeben möchten.

Dies ist eindeutig ein Anwendungsfall, der unter folgenden und nur einer Bedingung auftreten kann:

Nehmen wir an, wir haben die Komponenten A und 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

Injizieren, um zu reduzieren: (A)

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

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

Injiziert durch React-Redux: (B)

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

Haben Sie Folgendes bemerkt?

  • Wir haben den Redux-Speicher über die Komponente A aktualisiert, während uns der Redux-Speicher in Komponente B nicht bekannt war.

  • Wir aktualisieren nicht in Komponente A. Um genau zu wissen, was ich meine, können Sie diesen Beitrag untersuchen . Ich hoffe du hast eine Idee.


7
Ich habe nichts verstanden
vsync
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.