reactjs - medium - ¿Cuándo se usará bindActionCreators en react/redux?
redux react español (9)
Redux docs para bindActionCreators afirma que:
El único caso de uso para
bindActionCreators
es cuando desea pasar algunos creadores de acción a un componente que no conoce Redux, y no desea pasar el despacho o la tienda de Redux.
¿Cuál sería un ejemplo donde se usarían / necesitarían
bindActionCreators
?
¿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
})
}
}
}
Al usar
bindActionCreators
, puede agrupar múltiples funciones de acción y pasarlo a un componente que no conoce Redux (Componente tonto) de esta manera
// 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;
}
};
El 99% del tiempo, se usa con la función React-Redux
connect()
, como parte del parámetro
mapDispatchToProps
.
Se puede usar explícitamente dentro de la función
mapDispatch
que proporciona, o automáticamente si usa la sintaxis abreviada del objeto y pasa un objeto lleno de creadores de acción para
connect
.
La idea es que al vincular previamente a los creadores de la acción, el componente que pasa a
connect()
técnicamente "no sabe" que está conectado, solo sabe que necesita ejecutar
this.props.someCallback()
.
Por otro lado, si no
this.props.dispatch(someActionCreator())
creadores de acciones y llamó a
this.props.dispatch(someActionCreator())
, ahora el componente "sabe" que está conectado porque espera que exista
props.dispatch
.
Escribí algunas ideas sobre este tema en la publicación de mi blog Idiomatic Redux: ¿Por qué usar creadores de acción? .
La declaración de docs es muy clara:
El único caso de uso para
bindActionCreators
es cuando desea pasar algunos creadores de acción a un componente que no conoce Redux, y no desea pasar el despacho o la tienda de Redux.
Este es claramente un caso de uso que puede surgir en la siguiente y única 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 reaccionar-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} />
Notado de lo siguiente?
-
Actualizamos la tienda redux a través del componente A, mientras que desconocíamos la tienda redux en el componente B.
-
No estamos actualizando en el componente A. Para saber a qué me refiero exactamente, puede explorar esta publicación . Espero que tengas una idea.
No creo que la respuesta más popular, en realidad aborde la pregunta.
Todos los ejemplos a continuación esencialmente hacen lo mismo y siguen el concepto de "no 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 n.
#3
es solo una abreviatura de la opción n
#1
, por lo que la verdadera pregunta es por qué uno usaría la opción n
#1
frente a la opción n
#2
.
Los he visto a ambos usar 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
examples
en
react-redux
doc usan
bindActionCreators
mientras que el documento para
bindActionCreators
(como se cita en la pregunta en sí) dice no usarlo con react-redux.
Supongo que la respuesta es la coherencia en la base de código, pero personalmente prefiero envolver acciones explícitamente en el envío siempre que sea necesario.
También estaba buscando saber más sobre bindActionsCreators y así es como lo implementé en mi proyecto.
import * as allUserActions from "./store/actions/user";
En su componente de reacción
const mapDispatchToProps = (dispatch: Dispatch) => {
return {
...bindActionCreators(allUserActions, dispatch);
},
};
};
export default connect(mapStateToProps, mapDispatchToProps,
(stateProps, dispatchProps, ownProps) => {
return {
...stateProps,
userAction: dispatchProps
ownProps,
}
})(MyComponent);
Trataré de responder las preguntas originales ...
Componentes inteligentes y tontos
En su primera pregunta, básicamente pregunta por
bindActionCreators
se necesita
bindActionCreators
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 componentes tontos (presentacionales). Los componentes tontos funcionan según la necesidad de saber. Su trabajo principal es representar 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 los datos para los componentes tontos y, de preferencia, no hace renderizado HTML.
Este tipo de arquitectura promueve un acoplamiento flexible entre la capa de IU y la capa de datos debajo. Esto a su vez permite reemplazar fácilmente cualquiera de las dos capas con otra cosa (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 tener en cuenta Redux (o cualquier detalle de implementación innecesario de la capa de datos para el caso) porque es posible que deseemos reemplazarlo por otra cosa en el futuro.
Puede encontrar más información sobre este concepto en el manual de Redux y en mayor profundidad en el artículo Presentational and Container Components de Dan Abramov.
Que ejemplo es mejor
La segunda pregunta era sobre las ventajas / desventajas de los ejemplos dados.
En el
primer ejemplo,
los creadores de acciones se definen en un archivo / módulo de
actionCreators
separado, lo que significa que pueden reutilizarse en otro lugar.
Es más o menos 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, lo que tiene múltiples desventajas:
- los creadores de acciones no pueden ser reutilizados (obviamente)
- la cosa es más detallada, lo que se traduce en menos legible
-
los tipos de acción están codificados (es preferible definirlos como
consts
separado, para que puedan ser referenciados en reductores), lo que reduciría la posibilidad de errores de tipeo - la definición de 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 escribir! Entonces, si no tiene mayores planes para su código, podría estar bien.
Espero haber logrado aclarar un poco las cosas ...
Un buen caso de uso para
bindActionCreators
es para la integración con
redux-saga
usando
redux-saga-routines
.
Por ejemplo:
// 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);
}
// Actions.js
// Action Creator
const loginRequest = (username, password) => {
return {
type: ''LOGIN_REQUEST'',
username,
password,
}
}
const logoutRequest = () => {
return {
type: ''LOGOUT_REQUEST''
}
}
export default { loginRequest, logoutRequest };
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);
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);
Tenga en cuenta que este patrón podría implementarse utilizando React Hooks (a partir de React 16.8).
Un ejemplo más completo, pasa un objeto lleno de creadores de acción para conectar:
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);
Un posible uso de
bindActionCreators()
es "mapear" múltiples acciones juntas como un solo accesorio.
Un despacho normal se ve así:
Asigne un par de acciones de usuario comunes a accesorios.
// 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
En el mapeo de proyectos más grandes, cada despacho por separado puede parecer 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 del usuario que realizó todo tipo de acciones diferentes relacionadas con el usuario.
En lugar de llamar a cada acción como un despacho separado, podemos usar
bindActionCreators()
lugar de
dispatch
.
Despachos múltiples usando bindActionCreators ()
Importa todas tus acciones relacionadas. Es probable que todos estén en el mismo archivo en la tienda redux
const boundActionCreators = bindActionCreators(SomeActionCreator, dispatch)
// myActionCreatorMethod,
// myActionCreatorMethod2,
// myActionCreatorMethod3,
// when we want to dispatch
const action = SomeActionCreator.myActionCreatorMethod(''My updates'')
dispatch(action)
Y ahora, en lugar de usar el despacho, use bindActionCreators ()
const { myActionCreatorMethod } = props
<B myActionCreatorMethod={myActionCreatorMethod} {...boundActionCreators} />
Ahora puedo usar el prop
userAction
para 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
=> {return {}}
que se asigna a
userAction
).
También puede usar
bindActionCreators()
para asignar 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 acción" tenga un nombre explícito.
También me gusta nombrar
ownProps
para que sea más descriptivo sobre qué son estos "accesorios para niños" o de dónde provienen.
Al usar Redux + React, puede ser un poco confuso dónde se suministran todos los accesorios, por lo que cuanto más descriptivo, mejor.