Когда bindActionCreators будет использоваться в react / redux?

93

В документации Redux для bindActionCreators говорится, что:

Единственный вариант использования bindActionCreators- это когда вы хотите передать некоторых создателей действий компоненту, который не знает о Redux, и вы не хотите передавать ему диспетчеризацию или хранилище Redux.

Какой будет пример, где bindActionCreatorsбудет использоваться / нужен?

Какой компонент не знает о Redux ?

Каковы преимущества / недостатки обоих вариантов?

//actionCreator
import * as actionCreators from './actionCreators'

function mapStateToProps(state) {
  return {
    posts: state.posts,
    comments: state.comments
  }
}

function mapDispatchToProps(dispatch) {
  return bindActionCreators(actionCreators, dispatch)
}

против

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
источник

Ответы:

59

Я не думаю, что самый популярный ответ действительно отвечает на этот вопрос.

Все приведенные ниже примеры по сути делают то же самое и следуют концепции отсутствия «предварительной привязки».

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


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


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

Option #3- это просто сокращение от option #1, поэтому возникает реальный вопрос, зачем использовать option #1vs option #2. Я видел, как они оба используются в кодовой базе react-redux, и мне это довольно запутанно.

Я думаю, что путаница возникает из-за того, что все примеры в react-reduxdoc используют, в bindActionCreatorsто время как doc для bindActionCreators (как указано в самом вопросе) говорит, что не использовать его с react-redux.

Я предполагаю, что ответ - последовательность в базе кода, но я лично предпочитаю явно переносить действия в диспетчеризацию, когда это необходимо.

Диана Суворова
источник
3
как вариант #3является сокращением для варианта #1?
ogostos
1
@ogostos github.com/reduxjs/react-redux/blob/master/docs/…
Артем Бернацкий
@ArtemBernatskyi спасибо. Таким образом, оказывается, есть 3 случая для mapDispatchToProps: function, objectи пропавших без вести. Здесь
описано,
Я долго искал этот ответ. Спасибо.
Кишан Радждев
Я действительно столкнулся с конкретным вариантом использования, о котором сейчас говорят документы React: «передать некоторых создателей действий компоненту, который не знает Redux», потому что у меня есть простой компонент, связанный с более сложным компонентом и добавляющий накладные расходы из reduxи connectи addDispatchToPropsдля простого компонента кажется излишним, если я могу просто передать одну опору. Но, насколько мне известно, почти все случаи для mapDispatchреквизита будут либо вариантами, #1либо #3упомянутыми в ответе
icc97
48

В 99% случаев он используется с connect()функцией React-Redux как часть mapDispatchToPropsпараметра. Его можно использовать явно внутри mapDispatchпредоставляемой вами функции или автоматически, если вы используете сокращенный синтаксис объекта и передаете объект, полный создателей действий connect.

Идея состоит в том, что путем предварительной привязки создателей действий компонент, connect()которому вы передаете технически, «не знает», что он подключен - он просто знает, что его нужно запустить this.props.someCallback(). С другой стороны, если вы не связали создателей действий и не вызвали их this.props.dispatch(someActionCreator()), теперь компонент «знает», что он подключен, потому что он ожидает props.dispatchсуществования.

Я написал несколько мыслей по этой теме в своем сообщении в блоге. Idiomatic Redux: зачем использовать создатели действий? .

маркериксон
источник
1
Но это связано с mapDispatchToProps, и это нормально. Кажется, что создатели связывающих действий на самом деле являются отрицательными / бессмысленными вещами, поскольку вы потеряете определение функции (TS или Flow) среди многих других вещей, таких как отладка. В своих новых проектах я никогда не использую его, и на сегодняшний день у меня не было проблем. Также используется Saga и постоянное состояние. Кроме того, если вы просто вызываете функции redux (хотя вы получаете хороший щелчок), я бы сказал, что это даже чище, чем «прикрепление» создателей действий, что, на мой взгляд, беспорядочно и бессмысленно. Ваш умный (обычно экранные компоненты) все еще может использовать соединение, но только для реквизита.
Оливер Диксон
19

Более полный пример, передайте объект, полный создателей действий для подключения:

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);
Чарли 木匠
источник
это должен быть ответ
Дин Джон
16

Постараюсь ответить на оригинальные вопросы ...

Умные и глупые компоненты

В первом вопросе вы в основном спрашиваете, зачем он bindActionCreatorsвообще нужен и какие компоненты не должны знать о Redux.

Короче говоря, идея состоит в том, что компоненты должны быть разделены на интеллектуальные (контейнерные) и глупые (презентационные) компоненты. Глупые компоненты работают по принципу служебной необходимости. Их работа по душе - преобразовывать данные в HTML и не более того. Они не должны знать о внутренней работе приложения. Их можно рассматривать как глубокий передний слой кожи вашего приложения.

С другой стороны, интеллектуальные компоненты - это своего рода клей, который подготавливает данные для немых компонентов и предпочтительно не выполняет рендеринг HTML.

Такая архитектура способствует слабой связи между уровнем пользовательского интерфейса и уровнем данных под ним. Это, в свою очередь, позволяет легко заменить любой из двух слоев чем-то другим (например, новым дизайном пользовательского интерфейса), что не нарушит работу другого уровня.

Чтобы ответить на ваш вопрос: глупые компоненты не должны знать о Redux (или о любых ненужных деталях реализации уровня данных, если на то пошло), потому что мы, возможно, захотим заменить его чем-то другим в будущем.

Вы можете найти больше об этой концепции в руководстве Redux и более подробно в статье « Компоненты представления и контейнера » Дэна Абрамова.

Какой пример лучше

Второй вопрос касался достоинств / недостатков приведенных примеров.

В первом примере создатели действий определены в отдельном actionCreatorsфайле / модуле, что означает, что их можно повторно использовать в другом месте. Это в значительной степени стандартный способ определения действий. Минусов в этом особо не вижу.

Во втором примере определяются встроенные создатели действий, у которых есть несколько недостатков:

  • Создатели действий не могут быть повторно использованы (очевидно)
  • это более подробный, что переводится в менее читаемый
  • типы действий жестко запрограммированы - желательно определять их constsотдельно, чтобы на них можно было ссылаться в редукторах - это снизит вероятность ошибок при вводе
  • определение встроенных создателей действий противоречит рекомендуемому / ожидаемому способу их использования - что сделает ваш код менее читаемым для сообщества, если вы планируете поделиться своим кодом

У второго примера есть одно преимущество перед первым - писать быстрее! Так что, если у вас нет больших планов относительно вашего кода, все может быть хорошо.

Надеюсь, мне удалось немного прояснить ситуацию ...

колено
источник
2

Одно из возможных применений bindActionCreators()- «сопоставить» несколько действий вместе как одну опору.

Обычная рассылка выглядит так:

Сопоставьте пару общих действий пользователя с реквизитами.

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

В более крупных проектах отображение каждой отправки по отдельности может показаться громоздким. Если у нас есть набор действий, связанных друг с другом, мы можем объединить эти действия . Например, файл действий пользователя, который выполнял всевозможные действия, связанные с пользователем. Вместо того, чтобы вызывать каждое действие как отдельную отправку, мы можем использовать bindActionCreators()вместо dispatch.

Несколько отправок с использованием bindActionCreators ()

Импортируйте все связанные с вами действия. Скорее всего, все они находятся в одном файле в магазине redux.

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

И теперь вместо использования отправки используйте bindActionCreators ()

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

Теперь я могу использовать опору userActionдля вызова всех действий в вашем компоненте.

IE: userAction.login() userAction.editEmail() или this.props.userAction.login() this.props.userAction.editEmail().

ПРИМЕЧАНИЕ. Вам не нужно сопоставлять bindActionCreators () с одной опорой. (Дополнительное, => {return {}}которое соответствует userAction). Вы также можете использовать bindActionCreators()для отображения всех действий одного файла как отдельных свойств. Но я считаю, что это может сбивать с толку. Я предпочитаю давать каждому действию или «группе действий» явное имя. Я также люблю называть их, ownPropsчтобы более подробно описать, что это за «дочерний реквизит» и откуда они берутся. При использовании Redux + React может немного запутаться, где предоставляются все реквизиты, поэтому чем более наглядно, тем лучше.

Мэтью
источник
2

используя bindActionCreators, он может группировать несколько функций действий и передавать их компоненту, который не знает Redux (Dumb Component), например

// 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
источник
1

Я также хотел узнать больше о bindActionsCreators, и вот как я реализовал в своем проекте.

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

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

export default { loginRequest, logoutRequest };

В вашем компоненте 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
источник
0

Один из хороших вариантов использования bindActionCreators- интеграция с redux-saga с использованием программ redux-saga . Например:

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

Обратите внимание, что этот шаблон может быть реализован с помощью React Hooks (начиная с React 16.8).

Tomc
источник
-1

Документы заявление очень ясно:

Единственный вариант использования bindActionCreators- это когда вы хотите передать некоторых создателей действий компоненту, который не знает о Redux, и вы не хотите передавать ему диспетчеризацию или хранилище Redux.

Очевидно, что это вариант использования, который может возникнуть при следующем и только одном условии:

Допустим, у нас есть компоненты A и 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

Ввести в реакцию-редукцию: (A)

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

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

Введено реакцией-редуксом: (B)

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

Заметили следующее?

  • Мы обновили хранилище redux с помощью компонента A, пока нам не было известно о хранилище redux в компоненте B.

  • Мы не обновляем компонент А. Чтобы точно знать, что я имею в виду, вы можете изучить этот пост . Надеюсь, у вас есть идея.

Бходжендра Раунияр
источник
7
Ничего не понял
vsync