Quando bindActionCreators seria usado em react / redux?


91

Redux docs para bindActionCreators afirma que:

O único caso de uso bindActionCreatorsé quando você deseja passar alguns criadores de ação para um componente que não está ciente do Redux e não quer passar o envio ou o armazenamento do Redux para ele.

Qual seria um exemplo onde bindActionCreatorsseria usado / necessário?

Qual tipo de componente não estaria ciente do Redux ?

Quais são as vantagens / desvantagens de ambas as opções?

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

Respostas:


58

Eu não acho que a resposta mais popular, na verdade, aborda a questão.

Todos os exemplos abaixo fazem essencialmente a mesma coisa e seguem o conceito de não "pré-ligação".

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


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


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

Opção #3é apenas uma abreviação de opção #1, então a verdadeira questão de por que alguém usaria opção #1versus opção #2. Eu vi os dois usados ​​na base de código react-redux e acho que é um pouco confuso.

Acho que a confusão vem do fato de que todos os exemplos no react-reduxdoc usam bindActionCreatorsenquanto o doc para bindActionCreators (como citado na própria pergunta) diz para não usá-lo com react-redux.

Acho que a resposta é consistência na base de código, mas pessoalmente prefiro agrupar ações explicitamente no despacho sempre que necessário.


2
como opção é #3uma abreviação de opção #1?
ogostos de


@ArtemBernatskyi obrigado. Então, acontece que existem 3 casos para mapDispatchToProps: function, objecte desaparecidos. Como lidar com cada caso é definido aqui
ogostos

Tenho procurado por esta resposta. Obrigado.
Kishan Rajdev

Na verdade, eu encontrei o caso de uso específico sobre o qual os documentos do React falam agora "passar alguns criadores de ação para um componente que não conhece Redux" porque tenho um componente simples conectado a um componente mais complexo e adicionando a sobrecarga de reduxe connecte addDispatchToPropsao componente simples parece um exagero se eu posso simplesmente passar um para baixo prop. Mas, pelo que eu sei, quase todos os casos de mapDispatchadereços serão opções #1ou #3mencionados na resposta
icc97

48

99% das vezes, é usado com a connect()função React-Redux , como parte do mapDispatchToPropsparâmetro. Ele pode ser usado explicitamente dentro da mapDispatchfunção que você fornece, ou automaticamente se você usar a sintaxe abreviada do objeto e passar um objeto cheio de criadores de ação para connect.

A ideia é que, ao pré-vincular os criadores da ação, o componente para o qual você passa connect()tecnicamente "não sabe" que está conectado - apenas sabe que precisa ser executado this.props.someCallback(). Por outro lado, se você não vinculou criadores de ação e chamou this.props.dispatch(someActionCreator()), agora o componente "sabe" que está conectado porque espera props.dispatchque exista.

Escrevi algumas reflexões sobre esse tópico em minha postagem do blog Idiomatic Redux: Por que usar criadores de ação? .


1
Mas ele está conectado com 'mapDispatchToProps', o que é bom. Parece que vincular criadores de ação é na verdade algo negativo / sem sentido, pois você perderá a definição da função (TS ou Fluxo) entre muitas outras coisas, como depuração. Em meus projetos mais recentes, nunca o uso e não tive problemas até agora. Também usando Saga e estado de persistência. Além disso, se você está apenas chamando funções redux (no entanto, você obtém um bom clique), eu diria que isso é ainda mais limpo do que 'anexar' criadores de ação, o que, na minha opinião, é confuso e inútil. Seu smart (geralmente componentes de tela) ainda pode usar conectar, apenas para adereços.
Oliver Dixon

19

Um exemplo mais completo, passe um objeto cheio de criadores de ação 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);

esta deve ser a resposta
Deen John

16

Vou tentar responder às perguntas originais ...

Componentes inteligentes e burros

Em sua primeira pergunta, você basicamente pergunta por que bindActionCreatorsé necessário em primeiro lugar, e que tipo de componentes não devem estar cientes do Redux.

Resumindo, a ideia aqui é que os componentes devem ser divididos em componentes inteligentes (contêiner) e burros (apresentação). Componentes burros funcionam com base na necessidade de saber. Seu trabalho principal é renderizar dados em HTML e nada mais. Eles não devem estar cientes do funcionamento interno do aplicativo. Eles podem ser vistos como a camada frontal profunda de seu aplicativo.

Por outro lado, os componentes inteligentes são uma espécie de cola, que prepara os dados para os componentes burros e, de preferência, não renderiza HTML.

Esse tipo de arquitetura promove um acoplamento fraco entre a camada de interface do usuário e a camada de dados abaixo dela. Isso, por sua vez, permite a fácil substituição de qualquer uma das duas camadas por outra (ou seja, um novo design da IU), o que não quebrará a outra camada.

Para responder à sua pergunta: componentes burros não devem estar cientes do Redux (ou de qualquer detalhe de implementação desnecessário da camada de dados) porque podemos querer substituí-lo por outra coisa no futuro.

Você pode encontrar mais sobre este conceito no manual do Redux e com mais detalhes no artigo Presentational and Container Components de Dan Abramov.

Qual exemplo é melhor

A segunda pergunta era sobre vantagens / desvantagens dos exemplos dados.

No primeiro exemplo, os criadores da ação são definidos em um actionCreatorsarquivo / módulo separado , o que significa que podem ser reutilizados em outro lugar. É basicamente a maneira padrão de definir ações. Eu realmente não vejo nenhuma desvantagem nisso.

O segundo exemplo define criadores de ação embutidos, que tem várias desvantagens:

  • criadores de ação não podem ser reutilizados (obviamente)
  • a coisa é mais prolixo, o que se traduz em menos legível
  • os tipos de ação são codificados permanentemente - é preferível defini-los constsseparadamente, de modo que possam ser referenciados em redutores - isso reduziria a chance de erros de digitação
  • definir criadores de ação inline vai contra a maneira recomendada / esperada de usá-los - o que tornará seu código um pouco menos legível para a comunidade, caso você planeje compartilhá-lo

O segundo exemplo tem uma vantagem sobre o primeiro - é mais rápido de escrever! Portanto, se você não tem planos maiores para o seu código, pode ser ótimo.

Espero ter conseguido esclarecer um pouco as coisas ...


2

Um possível uso de bindActionCreators()é "mapear" várias ações juntas como um único objeto.

Um envio normal se parece com isto:

Mapeie algumas ações comuns do usuário para adereços.

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

Em projetos maiores, o mapeamento de cada despacho separadamente pode parecer complicado. Se tivermos um monte de ações relacionadas entre si, podemos combinar essas ações . Por exemplo, um arquivo de ação do usuário que executa todos os tipos de ações diferentes relacionadas ao usuário. Em vez de chamar cada ação como um despacho separado, podemos usar em bindActionCreators()vez de dispatch.

Vários despachos usando bindActionCreators ()

Importe todas as suas ações relacionadas. Eles provavelmente estão todos no mesmo arquivo na loja redux

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

E agora, em vez 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);

Agora posso usar o prop userActionpara chamar todas as ações em seu componente.

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

NOTA: Você não precisa mapear o bindActionCreators () para um único prop. (O adicional para o => {return {}}qual mapeia userAction). Você também pode usar bindActionCreators()para mapear todas as ações de um único arquivo como adereços separados. Mas acho que fazer isso pode ser confuso. Prefiro que cada ação ou "grupo de ação" receba um nome explícito. Também gosto de nomear o ownPropspara ser mais descritivo sobre o que são esses "adereços infantis" ou de onde vêm. Ao usar Redux + React pode ficar um pouco confuso onde todos os adereços estão sendo fornecidos, então quanto mais descritivo, melhor.


2

usando bindActionCreators, ele pode agrupar várias funções de ação e passá-las para um componente que não reconhece Redux (componente mudo) 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>
  }
}

1

Também estava procurando saber mais sobre bindActionsCreators e aqui está como eu implementei no meu projeto.

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

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

export default { loginRequest, logoutRequest };

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

0

Um bom caso de uso bindActionCreatorsé para integração com redux-saga usando redux-saga-routines . Por exemplo:

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

Observe que esse padrão pode ser implementado usando React Hooks (a partir do React 16.8).


-1

A declaração docs é muito clara:

O único caso de uso bindActionCreatorsé quando você deseja passar alguns criadores de ação para um componente que não está ciente do Redux e não quer passar o envio ou o armazenamento do Redux para ele.

Este é claramente um caso de uso que pode surgir nas seguintes e apenas uma condição:

Digamos que temos os componentes 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

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

Injetado por react-redux: (B)

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

Notou o seguinte?

  • Atualizamos a loja redux por meio do componente A, enquanto não conhecíamos a loja redux no componente B.

  • Não estamos atualizando no componente A. Para saber exatamente o que quero dizer, você pode explorar este post . Espero que você tenha uma ideia.


7
Não entendi nada
vsync
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.