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

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

vs

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

function mapDispatchToProps(dispatch) {
  return {
    someCallback: (postId, index) => {
      dispatch({
        type: 'REMOVE_COMMENT',
        postId,
        index
      })
    }
  }
}

person d.code    schedule 20.01.2017    source источник


Ответы (10)


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

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

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


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


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

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

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

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

person Diana Suvorova    schedule 05.02.2018
comment
как вариант #3 является сокращением для варианта #1? - person ogostos; 15.10.2018
comment
@ogostos github.com/reduxjs/react-redux/blob/master/docs/ - person Artem Bernatskyi; 18.10.2018
comment
@ArtemBernatskyi спасибо. Итак, оказывается, есть 3 случая для mapDispatchToProps: function, object и отсутствует. Как обрабатывать каждый случай, определяется здесь - person ogostos; 18.10.2018
comment
Я ждал этого ответа. Спасибо. - person Kishan Rajdev; 16.01.2019
comment
Я действительно столкнулся с конкретным вариантом использования, о котором сейчас говорится в документации React передать некоторых создателей действий компоненту, который не знает о Redux, потому что у меня есть простой компонент, связанный с более сложным компонент и добавление накладных расходов redux, connect и addDispatchToProps к простому компоненту кажется излишним, если я могу просто передать одну опору вниз. Но, насколько мне известно, почти все случаи mapDispatch для реквизита будут либо вариантами #1, либо #3, упомянутыми в ответе - person icc97; 25.03.2019

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

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

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

person markerikson    schedule 20.01.2017
comment
Но это связано с mapDispatchToProps, и это нормально. Кажется, что создатели связывающих действий на самом деле являются отрицательными / бессмысленными вещами, поскольку вы потеряете определение функции (TS или Flow) среди многих других вещей, таких как отладка. В своих новых проектах я никогда не использую его, и на сегодняшний день у меня не было проблем. Также используется Saga и постоянное состояние. Кроме того, если вы просто вызываете функции redux (хотя вы получаете хороший щелчок), я бы сказал, что это даже чище, чем «прикрепление» создателей действий, что, на мой взгляд, беспорядочно и бессмысленно. Ваш умный (обычно это экранные компоненты) все еще может использовать соединение, но только для реквизита. - person Oliver Dixon; 27.02.2019

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

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);
person Charlie 木匠    schedule 26.09.2017
comment
это должен быть ответ - person Deen John; 12.09.2019

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

person knee-cola    schedule 27.10.2017

Одно из возможных применений 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() or this.props.userAction.login() this.props.userAction.editEmail().

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

person matthew    schedule 12.06.2019

используя 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>
  }
}
person JXLai    schedule 07.09.2019
comment
это похоже на использование useDispatch () из react-redux - person Tony Jin; 01.01.2021

Я также хотел узнать больше о 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);
person Bimal Grg    schedule 13.03.2019

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

// 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 (начиная с React 16.8).

person tomc    schedule 06.03.2019

Я использую его для создания хука useActions:

import { useDispatch } from "react-redux";
import { bindActionCreators } from "redux";
import { actionCreators } from "../state";

export const useActions = () => {
  const dispatch = useDispatch();
  return bindActionCreators(actionCreators, dispatch);
};

actionCreators - это функции создания действий, которые я экспортировал из файла. Например, скажем, у меня есть создатель действия updatePost

export const updatePost = (id: string, content: string): UpdatePostAction => {
  return { type: ActionType.UPDATE_POST, payload: { id, content } };
};

Поэтому всякий раз, когда мне нужно отправить действие updatePost, я пишу следующее:

const {updatePost}=useActions()
updatePost({id,content})
person Yilmaz    schedule 20.11.2020

Заявление docs очень четкое:

Единственный вариант использования 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.

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

person Bhojendra Rauniyar    schedule 31.08.2018
comment
Я ничего не понял - person vsync; 14.10.2018