Коли bindActionCreators використовуватимуться у реакції / зменшенні?


91

Redux docs для 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
      })
    }
  }
}

Відповіді:


58

Я не думаю, що найпопулярніша відповідь насправді стосується цього питання.

Усі наведені нижче приклади, по суті, роблять одне і те ж і відповідають принципу відсутності попереднього зобов’язування.

// 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. Я бачив, як вони обидва використовуються в коді-базі response-redux, і я вважаю це досить заплутаним.

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

Я думаю, що відповідь полягає у послідовності кодової бази, але я особисто віддаю перевагу явному загортанню дій у диспетчері, коли це потрібно.


2
як варіант #3є скороченим варіантом #1?
ogostos,


@ArtemBernatskyi дякую. Таким чином, виявляється, є 3 випадки для mapDispatchToProps: function, objectі зниклих без вести. Як вирішувати кожен випадок, визначено тут
ogostos

Я шукав цю відповідь. Дякую.
Кішан Радждев

Я насправді зіткнувся з конкретним випадком використання, про який зараз говорять документи React, "передають деяких творців дій компоненту, який не знає Redux", оскільки у мене є простий компонент, підключений до більш складного компонента і додає накладні витрати of 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 і зберігати стан. Крім того, якщо ви просто викликаєте функції відновлення (хоча ви отримуєте приємний клік), я б сказав, що це ще чистіше, ніж "додавання" творців дій, що, на мій погляд, безладно і безглуздо. Ваш розумний (як правило, компоненти екрану) все ще може використовувати підключення, лише для реквізиту.
Олівер Діксон,

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

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

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


-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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.