Quando verrebbe usato bindActionCreators in react / redux?


91

La documentazione di Redux per bindActionCreators afferma che:

L'unico caso d'uso bindActionCreatorsè quando si desidera trasferire alcuni creatori di azioni a un componente che non è a conoscenza di Redux e non si desidera passare la spedizione o l'archivio Redux ad esso.

Quale potrebbe essere un esempio in cui bindActionCreatorssarebbe utilizzato / necessario?

Quale tipo di componente non sarebbe a conoscenza di Redux ?

Quali sono i vantaggi / svantaggi di entrambe le opzioni?

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

Risposte:


58

Non credo che la risposta più popolare, in realtà, affronti la domanda.

Tutti gli esempi seguenti fanno essenzialmente la stessa cosa e seguono il concetto di non "pre-associazione".

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


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


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

L'opzione #3è solo una scorciatoia per l'opzione #1, quindi la vera domanda sul perché si dovrebbe usare l'opzione #1contro l'opzione #2. Li ho visti entrambi usati nel codebase di react-redux e trovo che sia piuttosto confuso.

Penso che la confusione derivi dal fatto che tutti gli esempi in react-reduxdoc usano bindActionCreatorsmentre il doc per bindActionCreators (come citato nella domanda stessa) dice di non usarlo con react-redux.

Immagino che la risposta sia la coerenza nella base di codice, ma personalmente preferisco il wrapping esplicito delle azioni in invio ogni volta che è necessario.


2
in che modo l'opzione è #3una scorciatoia per opzione #1?
ogostos


@ArtemBernatskyi thanks. Quindi, risulta ci sono 3 casi per mapDispatchToProps: function, objecte dispersi. Come gestire ogni caso è definito qui
ogostos

Ho cercato questa risposta. Grazie.
Kishan Rajdev

In realtà mi sono imbattuto nel caso d'uso specifico di cui parlano i documenti di React ora "passa alcuni creatori di azioni a un componente che non è a conoscenza di Redux" perché ho un componente semplice collegato a un componente più complesso e aggiungendo l'overhead di reduxe connected addDispatchToPropsalla componente semplice sembra eccessivo se posso solo passare uno giù prop. Ma per quanto ne so quasi tutti i casi mapDispatchdi oggetti di scena saranno opzioni #1o #3menzionati nella risposta
ICC97

48

Il 99% delle volte, viene utilizzato con la funzione React-Redux connect(), come parte del mapDispatchToPropsparametro. Può essere usato esplicitamente all'interno della mapDispatchfunzione che fornisci, o automaticamente se usi la sintassi abbreviata dell'oggetto e passi un oggetto pieno di creatori di azioni a connect.

L'idea è che pre-vincolando i creatori di azioni, il componente a cui passi connect()tecnicamente "non sa" di essere connesso - sa solo che deve funzionare this.props.someCallback(). D'altra parte, se non vincolavi i creatori di azioni e chiamavi this.props.dispatch(someActionCreator()), ora il componente "sa" di essere connesso perché si aspetta props.dispatchche esista.

Ho scritto alcune riflessioni su questo argomento nel mio post sul blog Idiomatic Redux: Perché usare i creatori di azioni? .


1
Ma è collegato a "mapDispatchToProps", il che va bene. Sembra che i creatori di azioni vincolanti siano in realtà una cosa negativa / inutile poiché perderai la definizione della funzione (TS o Flow) tra molte altre cose come il debug. Nei miei progetti più recenti non lo uso mai e non ho avuto problemi fino ad oggi. Usando anche Saga e persist state. Inoltre, se stai solo chiamando funzioni redux (ottieni un bel clic) direi che è ancora più pulito quindi 'allegare' i creatori di azioni che a mio parere è disordinato e inutile. Il tuo smart (solitamente i componenti dello schermo) può ancora utilizzare la connessione, solo per gli oggetti di scena.
Oliver Dixon

19

Esempio più completo, passa un oggetto pieno di creatori di azioni per connettersi:

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

questa dovrebbe essere la risposta
Deen John

16

Proverò a rispondere alle domande originali ...

Componenti intelligenti e stupidi

Nella tua prima domanda fondamentalmente chiedi perché bindActionCreatorsè necessario in primo luogo e che tipo di componenti non dovrebbero essere a conoscenza di Redux.

In breve, l'idea qui è che i componenti dovrebbero essere suddivisi in componenti intelligenti (contenitore) e stupidi (di presentazione). I componenti stupidi funzionano in base alla necessità di sapere. Il loro compito principale è rendere i dati dati in HTML e nient'altro. Non dovrebbero essere consapevoli del funzionamento interno dell'applicazione. Possono essere visti come lo strato anteriore profondo della pelle dell'applicazione.

D'altra parte i componenti intelligenti sono una specie di collante, che prepara i dati per i componenti stupidi e preferibilmente non esegue il rendering HTML.

Questo tipo di architettura promuove l'accoppiamento libero tra il livello dell'interfaccia utente e il livello dati sottostante. Ciò a sua volta consente una facile sostituzione di uno qualsiasi dei due livelli con qualcos'altro (cioè un nuovo design dell'interfaccia utente), che non interromperà l'altro livello.

Per rispondere alla tua domanda: i componenti stupidi non dovrebbero essere a conoscenza di Redux (o di qualsiasi dettaglio di implementazione non necessario del livello dati per quella materia) perché potremmo volerlo sostituire con qualcos'altro in futuro.

Puoi trovare ulteriori informazioni su questo concetto nel manuale di Redux e in modo più approfondito nell'articolo Presentational and Container Components di Dan Abramov.

Quale esempio è migliore

La seconda domanda riguardava i vantaggi / svantaggi degli esempi forniti.

Nel primo esempio i creatori di azioni sono definiti in un actionCreatorsfile / modulo separato , il che significa che possono essere riutilizzati altrove. È praticamente il modo standard di definire le azioni. Non vedo davvero alcuno svantaggio in questo.

Il secondo esempio definisce i creatori di azioni in linea, che presenta molteplici svantaggi:

  • i creatori di azioni non possono essere riutilizzati (ovviamente)
  • la cosa è più prolissa, che si traduce in meno leggibile
  • i tipi di azione sono hardcoded - è preferibile definirli constsseparatamente, in modo che possano essere referenziati nei riduttori - ciò ridurrebbe la possibilità di errori di battitura
  • definire i creatori di azioni in linea è contro il modo consigliato / previsto di usarli, il che renderà il tuo codice un po 'meno leggibile per la comunità, nel caso in cui prevedi di condividere il tuo codice

Il secondo esempio ha un vantaggio rispetto al primo: è più veloce da scrivere! Quindi, se non hai piani più grandi per il tuo codice, potrebbe andare bene.

Spero di essere riuscito a chiarire un po 'le cose ...


2

Un possibile utilizzo di bindActionCreators()è quello di "mappare" più azioni insieme come un unico sostegno.

Un normale invio si presenta così:

Associa un paio di azioni comuni degli utenti agli oggetti di scena.

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

Nei progetti più grandi, mappare ciascuna spedizione separatamente può sembrare ingombrante. Se abbiamo un gruppo di azioni correlate tra loro, possiamo combinare queste azioni . Ad esempio un file di azioni utente che ha eseguito tutti i tipi di diverse azioni correlate all'utente. Invece di chiamare ogni azione come un invio separato, possiamo usare al bindActionCreators()posto di dispatch.

Più invii utilizzando bindActionCreators ()

Importa tutte le tue azioni correlate. Probabilmente sono tutti nello stesso file nell'archivio redux

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

E ora invece di usare l'invio usa bindActionCreators ()

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

Ora posso usare l'elica userActionper chiamare tutte le azioni nel tuo componente.

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

NOTA: non è necessario mappare bindActionCreators () a un singolo prop. (L'addizionale a => {return {}}cui si mappa userAction). Puoi anche usare bindActionCreators()per mappare tutte le azioni di un singolo file come oggetti di scena separati. Ma trovo che ciò possa creare confusione. Preferisco che a ciascuna azione o "gruppo di azioni" venga assegnato un nome esplicito. Mi piace anche nominare il ownPropsper essere più descrittivo su cosa sono questi "oggetti di scena per bambini" o da dove provengono. Quando si usa Redux + React può creare un po 'di confusione dove vengono forniti tutti gli oggetti di scena, quindi più sono descrittivi, meglio è.


2

utilizzando bindActionCreators, può raggruppare più funzioni di azione e passarlo a un componente che non è a conoscenza di Redux (Dumb Component) in questo modo

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

Stavo anche cercando di saperne di più su bindActionsCreators ed ecco come l'ho implementato nel mio progetto.

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

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

export default { loginRequest, logoutRequest };

Nel tuo 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

Un bel caso d'uso bindActionCreatorsè l'integrazione con redux-saga usando redux-saga-routine . Per esempio:

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

Nota che questo modello potrebbe essere implementato usando React Hooks (a partire da React 16.8).


-1

La dichiarazione dei documenti è molto chiara:

L'unico caso d'uso bindActionCreatorsè quando si desidera trasferire alcuni creatori di azioni a un componente che non è a conoscenza di Redux e non si desidera passare la spedizione o l'archivio Redux ad esso.

Questo è chiaramente un caso d'uso che può verificarsi nelle seguenti e solo una condizione:

Diciamo, abbiamo i componenti A e 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

Iniettare per Reagire-Redux: (A)

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

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

Iniettato da react-redux: (B)

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

Notato di quanto segue?

  • Abbiamo aggiornato il redux store tramite il componente A mentre non conoscevamo il redux store nel componente B.

  • Non stiamo aggiornando nel componente A. Per sapere cosa intendo esattamente, puoi esplorare questo post . Spero tu abbia un'idea.


7
Non ho capito niente
vsync
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.