¿Cuándo se usaría bindActionCreators en react / redux?

91

Los documentos de Redux para bindActionCreators establecen que:

El único caso de uso bindActionCreatorses cuando desea pasar algunos creadores de acciones a un componente que no conoce Redux, y no desea pasarle el envío o la tienda de Redux.

¿Cuál sería un ejemplo donde bindActionCreatorsse usaría / necesitaría?

¿Qué tipo de componente no conocería Redux ?

¿Cuáles son las ventajas / desventajas de ambas opciones?

//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
      })
    }
  }
}
d.code
fuente

Respuestas:

58

No creo que la respuesta más popular realmente aborde la pregunta.

Todos los ejemplos a continuación esencialmente hacen lo mismo y siguen el concepto de no "pre-vinculante".

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


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


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

La opción #3es solo una abreviatura de opción #1, por lo que la pregunta real de por qué se usaría opción #1vs opción#2 . He visto a ambos usados ​​en la base de código react-redux, y me parece bastante confuso.

Creo que la confusión proviene del hecho de que todos los ejemplos en react-reduxdoc usa bindActionCreatorswhile el doc para bindActionCreators (como se cita en la pregunta) dice que no lo uses con react-redux.

Supongo que la respuesta es la coherencia en el código base, pero personalmente prefiero envolver acciones explícitamente en el envío cuando sea necesario.

Diana Suvorova
fuente
2
¿Cómo es la opción #3una abreviatura de la opción #1?
ogostos
@ArtemBernatskyi gracias. Así, resulta que hay 3 casos de mapDispatchToProps: function, objecty desaparecidos. Cómo manejar cada caso se define aquí
ogostos
He estado buscando esta respuesta. Gracias.
Kishan Rajdev
De hecho, me encontré con el caso de uso específico del que hablan los documentos de React ahora "pasar algunos creadores de acciones a un componente que no conoce Redux" porque tengo un componente simple conectado a un componente más complejo y agrego la sobrecarga de reduxy connecty addDispatchToPropspara el componente simple parece exagerado si puedo pasar un apoyo hacia abajo. Pero hasta donde yo sé, casi todos los casos de mapDispatchaccesorios serán opciones #1o se #3mencionarán en la respuesta
icc97
48

El 99% de las veces, se utiliza con la función React-Redux connect(), como parte del mapDispatchToPropsparámetro. Puede usarse explícitamente dentro de la mapDispatchfunción que proporciones, o automáticamente si usas la sintaxis abreviada del objeto y le pasas un objeto lleno de creadores de acciones connect.

La idea es que al vincular previamente los creadores de acciones, el componente al que pasa connect()técnicamente "no sabe" que está conectado, solo sabe que debe ejecutarse this.props.someCallback(). Por otro lado, si no vinculó a los creadores de acciones y los llamó this.props.dispatch(someActionCreator()), ahora el componente "sabe" que está conectado porque espera props.dispatchexistir.

Escribí algunas ideas sobre este tema en la publicación de mi blog Idiomatic Redux: ¿Por qué usar creadores de acciones? .

markerikson
fuente
1
Pero está conectado con 'mapDispatchToProps', lo cual está bien. Parece que los creadores de acciones vinculantes son en realidad algo negativo / sin sentido, ya que perderá la definición de la función (TS o Flow) entre muchas otras cosas, como la depuración. En mis proyectos más nuevos nunca lo uso y no he tenido ningún problema hasta la fecha. También usando Saga y persist state. Además, si solo está llamando a las funciones redux (aunque obtiene un buen clic), diría que es aún más limpio que 'adjuntar' creadores de acción, que en mi opinión es desordenado y sin sentido. Su inteligente (generalmente componentes de pantalla) aún puede usar Connect, aunque solo para accesorios.
Oliver Dixon
19

Ejemplo más completo, pase un objeto lleno de creadores de acciones para conectarse:

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);
Charlie 木匠
fuente
esta debería ser la respuesta
Deen John
16

Intentaré responder las preguntas originales ...

Componentes inteligentes y tontos

En su primera pregunta, básicamente pregunta por qué bindActionCreatorsse necesita en primer lugar y qué tipo de componentes no deben conocer Redux.

En resumen, la idea aquí es que los componentes deben dividirse en componentes inteligentes (contenedor) y tontos (presentación). Los componentes tontos funcionan sobre la base de la necesidad de saber. Su trabajo principal es renderizar los datos dados a HTML y nada más. No deben estar al tanto del funcionamiento interno de la aplicación. Pueden verse como la capa frontal profunda de su aplicación.

Por otro lado, los componentes inteligentes son una especie de pegamento, que prepara datos para los componentes tontos y preferiblemente no hace renderizado HTML.

Este tipo de arquitectura promueve un acoplamiento flexible entre la capa de interfaz de usuario y la capa de datos subyacente. Esto, a su vez, permite un fácil reemplazo de cualquiera de las dos capas por otra (es decir, un nuevo diseño de la interfaz de usuario), que no romperá la otra capa.

Para responder a su pregunta: los componentes tontos no deberían estar al tanto de Redux (o cualquier detalle de implementación innecesario de la capa de datos para el caso) porque es posible que deseemos reemplazarlo con algo más en el futuro.

Puede encontrar más sobre este concepto en el manual de Redux y en mayor profundidad en el artículo Componentes de presentación y contenedor de Dan Abramov.

Que ejemplo es mejor

La segunda pregunta se refería a las ventajas / desventajas de los ejemplos dados.

En el primer ejemplo, los creadores de acciones se definen en un actionCreatorsarchivo / módulo separado , lo que significa que se pueden reutilizar en otro lugar. Es prácticamente la forma estándar de definir acciones. Realmente no veo ninguna desventaja en esto.

El segundo ejemplo define a los creadores de acciones en línea, que tiene múltiples desventajas:

  • los creadores de acciones no se pueden reutilizar (obviamente)
  • la cosa es más detallada, lo que se traduce en menos legible
  • Los tipos de acción están codificados de forma rígida; es preferible definirlos como constsseparados, de modo que se pueda hacer referencia a ellos en los reductores, lo que reduciría la posibilidad de errores de escritura.
  • definir los creadores de acciones en línea va en contra de la forma recomendada / esperada de usarlos, lo que hará que su código sea un poco menos legible para la comunidad, en caso de que planee compartir su código

El segundo ejemplo tiene una ventaja sobre el primero: ¡es más rápido de escribir! Entonces, si no tiene planes más amplios para su código, podría estar bien.

Espero haber logrado aclarar un poco las cosas ...

rodilla-cola
fuente
2

Un posible uso de bindActionCreators()es "mapear" múltiples acciones juntas como un solo accesorio.

Un envío normal se ve así:

Asigne un par de acciones de usuario comunes a los accesorios.

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

En proyectos más grandes, el mapeo de cada envío por separado puede resultar difícil de manejar. Si tenemos un montón de acciones relacionadas entre sí, podemos combinar estas acciones . Por ejemplo, un archivo de acción de usuario que realizó todo tipo de acciones relacionadas con el usuario. En lugar de llamar a cada acción como un envío separado, podemos usar en bindActionCreators()lugar de dispatch.

Múltiples despachos usando bindActionCreators ()

Importe todas sus acciones relacionadas. Es probable que estén todos en el mismo archivo en la tienda redux.

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

Y ahora, en lugar de usar dispatch, use bindActionCreators ()

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

Ahora puedo usar el accesorio userActionpara llamar a todas las acciones en su componente.

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

NOTA: No es necesario que asigne bindActionCreators () a un solo accesorio. (El adicional al => {return {}}que se asigna userAction). También puede usar bindActionCreators()para mapear todas las acciones de un solo archivo como accesorios separados. Pero encuentro que hacer eso puede ser confuso. Prefiero que cada acción o "grupo de acciones" reciba un nombre explícito. También me gusta nombrar el ownPropspara ser más descriptivo acerca de qué son estos "accesorios para niños" o de dónde vienen. Cuando se usa Redux + React, puede resultar un poco confuso dónde se proporcionan todos los accesorios, por lo que cuanto más descriptivos, mejor.

mateo
fuente
2

al usar bindActionCreators, puede agrupar múltiples funciones de acción y pasarlas a un componente que no conoce Redux (Componente tonto) como tal

// 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>
  }
}
JXLai
fuente
1

También estaba buscando saber más sobre bindActionsCreators y así es como lo implementé en mi proyecto.

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

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

export default { loginRequest, logoutRequest };

En su componente 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);
Bimal Grg
fuente
0

Un buen caso de uso bindActionCreatorses para la integración con redux-saga usando redux-saga-routines . Por ejemplo:

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

Tenga en cuenta que este patrón podría implementarse usando React Hooks (a partir de React 16.8).

tomc
fuente
-1

La declaración de documentos es muy clara:

El único caso de uso bindActionCreatorses cuando desea pasar algunos creadores de acciones a un componente que no conoce Redux, y no desea pasarle el envío o la tienda de Redux.

Este es claramente un caso de uso que puede surgir en la siguiente y solo una condición:

Digamos que tenemos los componentes A y 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

Inyectar para react-redux: (A)

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

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

Inyectado por react-redux: (B)

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

¿Has notado lo siguiente?

  • Actualizamos la tienda redux a través del componente A mientras no conocíamos la tienda redux en el componente B.

  • No estamos actualizando en el componente A. Para saber exactamente a qué me refiero, puede explorar esta publicación . Espero que tengas una idea.

Bhojendra Rauniyar
fuente
7
No entendí nada
vsync