Kiedy bindActionCreators zostanie użyta w React / Redux?


93

Dokumentacja Redux dla bindActionCreators stwierdza, że:

Jedynym przypadkiem użycia bindActionCreatorsjest sytuacja, gdy chcesz przekazać niektórych twórców akcji do komponentu, który nie jest świadomy istnienia Redux, i nie chcesz przekazywać do niego wysyłki ani sklepu Redux.

Jaki byłby przykład, gdzie bindActionCreatorsbyłby używany / potrzebny?

Który rodzaj komponentu nie byłby świadomy Redux ?

Jakie są zalety / wady obu opcji?

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

Odpowiedzi:


59

Nie sądzę, aby najpopularniejsza odpowiedź faktycznie odpowiadała na pytanie.

Wszystkie poniższe przykłady zasadniczo robią to samo i są zgodne z koncepcją braku „wstępnego wiązania”.

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


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


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

Opcja #3to tylko skrót od opcji #1, więc prawdziwe pytanie, dlaczego ktoś miałby używać opcji #1vs opcji #2. Widziałem oba z nich używane w bazie kodu React-Redux i uważam, że jest to raczej zagmatwane.

Myślę, że zamieszanie bierze się z faktu, że wszystkie przykłady w react-reduxdokumencie są używane bindActionCreatorspodczas gdy dokument dotyczy bindActionCreators (jak cytowano w samym pytaniu) mówi, aby nie używać go z reakcją-redukcją.

Wydaje mi się, że odpowiedzią jest spójność w bazie kodu, ale osobiście wolę jawnie pakować akcje w wysyłce, gdy jest to potrzebne.


3
jak opcja #3jest skrótem opcji #1?
ogostos


@ArtemBernatskyi thanks. Tak więc okazuje się, że są 3 przypadki na mapDispatchToProps: function, objecta brakuje. Sposób postępowania w każdym przypadku jest zdefiniowany tutaj
ogostos

Szukałem tej odpowiedzi. Dzięki.
Kishan Rajdev

Właściwie natknąłem się na konkretny przypadek użycia, o którym teraz mówią dokumenty Reacta "przekazanie niektórych twórców akcji do komponentu, który nie jest świadomy Redux", ponieważ mam prosty komponent połączony z bardziej złożonym komponentem i dodając narzut od reduxa connecti addDispatchToPropsdo prostych komponent wydaje przesadą jeśli mogę tylko przekazać jeden prop dół. Ale o ile wiem, prawie wszystkie przypadki dla mapDispatchrekwizytów będą albo opcjami, #1albo #3wspomniane w odpowiedzi
icc97

48

W 99% przypadków jest używany z funkcją React-Redux connect(), jako część mapDispatchToPropsparametru. Może być używany bezpośrednio w mapDispatchfunkcji, którą udostępniasz, lub automatycznie, jeśli używasz skróconej składni obiektu i przekazujesz obiekt pełen twórców akcji connect.

Chodzi o to, że przez wstępne powiązanie twórców akcji komponent, do którego przekazujesz connect()technicznie, „nie wie”, że jest połączony - po prostu wie, że musi działać this.props.someCallback(). Z drugiej strony, jeśli nie powiązałeś kreatorów akcji i nie wywołałeś this.props.dispatch(someActionCreator()), teraz komponent „wie”, że jest połączony, ponieważ spodziewa props.dispatchsię istnienia.

Kilka przemyśleń na ten temat napisałem w poście na moim blogu Idiomatic Redux: Po co używać kreatorów akcji? .


1
Ale jest to związane z „mapDispatchToProps”, co jest w porządku. Wydaje się, że wiążące twórcy akcji są w rzeczywistości rzeczą negatywną / bezcelową, ponieważ stracisz definicję funkcji (TS lub Flow) wśród wielu innych rzeczy, takich jak debugowanie. W moich nowszych projektach nigdy go nie używam i do tej pory nie miałem problemu. Używam również Sagi i stanu trwałego. Ponadto, jeśli wywołujesz tylko funkcje redux (ale dostajesz niezłe kliknięcie), powiedziałbym, że jest to jeszcze czystsze niż „dołączanie” kreatorów akcji, co moim zdaniem jest niechlujne i bezcelowe. Twoje inteligentne (zwykle komponenty ekranu) mogą nadal używać connect, ale tylko do rekwizytów.
Oliver Dixon

19

Bardziej kompletny przykład, przekaż obiekt pełen kreatorów akcji do połączenia:

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

to powinna być odpowiedź
Deen John

16

Spróbuję odpowiedzieć na oryginalne pytania ...

Inteligentne i głupie komponenty

W swoim pierwszym pytaniu zasadniczo pytasz, dlaczego bindActionCreatorsjest potrzebny w pierwszej kolejności i jakiego rodzaju komponenty nie powinny być świadome Redux.

W skrócie chodzi o to, że komponenty powinny być podzielone na komponenty inteligentne (kontenerowe) i głupie (prezentacyjne). Głupie komponenty działają na zasadzie niezbędnej wiedzy. Ich duszą jest renderowanie danych do HTML i nic więcej. Nie powinni być świadomi wewnętrznego działania aplikacji. Mogą być postrzegane jako głęboka przednia warstwa skóry twojej aplikacji.

Z drugiej strony inteligentne komponenty są rodzajem kleju, który przygotowuje dane dla głupich komponentów i najlepiej nie renderuje HTML.

Ten rodzaj architektury sprzyja luźnemu sprzężeniu między warstwą interfejsu użytkownika a warstwą danych poniżej. To z kolei umożliwia łatwą wymianę którejkolwiek z dwóch warstw na coś innego (np. Nowy projekt interfejsu użytkownika), co nie spowoduje zniszczenia drugiej warstwy.

Odpowiadając na twoje pytanie: głupie komponenty nie powinny być świadome istnienia Reduksa (ani żadnych niepotrzebnych szczegółów implementacji warstwy danych), ponieważ w przyszłości możemy chcieć zastąpić ją czymś innym.

Więcej informacji na temat tej koncepcji można znaleźć w podręczniku Redux, a bardziej szczegółowo w artykule Komponenty prezentacyjne i kontenerowe autorstwa Dana Abramova.

Który przykład jest lepszy

Drugie pytanie dotyczyło zalet / wad podanych przykładów.

W pierwszym przykładzie twórcy akcji są zdefiniowani w oddzielnym actionCreatorspliku / module, co oznacza, że ​​można ich użyć ponownie w innym miejscu. To prawie standardowy sposób definiowania działań. Naprawdę nie widzę w tym żadnych wad.

Drugi przykład definiuje twórców działania inline, który ma wiele wad:

  • twórców akcji nie można ponownie wykorzystać (oczywiście)
  • sprawa jest bardziej szczegółowa, co przekłada się na mniejszą czytelność
  • typy akcji są zakodowane na stałe - najlepiej zdefiniować je jako constsosobne, aby można było się do nich odwoływać w reduktorach - co zmniejszyłoby szansę na błędy przy wpisywaniu
  • definiowanie kreatorów akcji inline jest sprzeczne z zalecanym / oczekiwanym sposobem ich używania - co sprawi, że Twój kod będzie nieco mniej czytelny dla społeczności, na wypadek gdybyś planował udostępnić swój kod

Drugi przykład ma jedną przewagę nad pierwszym - szybciej się pisze! Więc jeśli nie masz większych planów dotyczących swojego kodu, może być dobrze.

Mam nadzieję, że udało mi się trochę wyjaśnić ...


2

Jednym z możliwych zastosowań bindActionCreators()jest „mapowanie” wielu działań razem jako jednego rekwizytu.

Normalna wysyłka wygląda następująco:

Zamapuj kilka typowych działań użytkownika na rekwizyty.

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

W większych projektach mapowanie każdej wysyłki osobno może wydawać się nieporęczne. Jeśli mamy kilka działań, które są ze sobą powiązane, możemy je połączyć . Na przykład plik akcji użytkownika, który wykonał wszystkie rodzaje różnych działań związanych z użytkownikiem. Zamiast nazywać każdą akcję jako oddzielną wysyłkę, możemy użyć bindActionCreators()zamiast dispatch.

Wiele wysyłek za pomocą bindActionCreators ()

Zaimportuj wszystkie powiązane działania. Prawdopodobnie wszystkie znajdują się w tym samym pliku w sklepie Redux

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

A teraz zamiast korzystać z wysyłki użyj bindActionCreators ()

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

Teraz mogę użyć rekwizytu, userActionaby wywołać wszystkie akcje w twoim komponencie.

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

UWAGA: Nie musisz mapować bindActionCreators () do pojedynczego elementu. (Dodatkowy => {return {}}, do którego mapuje się userAction). Możesz także użyć bindActionCreators()do odwzorowania wszystkich działań pojedynczego pliku jako osobnych właściwości. Ale uważam, że może to być mylące. Wolę, aby każda akcja lub „grupa akcji” miała nadaną jawną nazwę. Lubię też nazywać te, ownPropsaby bardziej opisać, czym są te „dziecięce rekwizyty” lub skąd pochodzą. Podczas korzystania z Redux + React może być nieco zagmatwane, gdzie dostarczane są wszystkie rekwizyty, więc im bardziej opisowe, tym lepiej.


2

używając bindActionCreators, może grupować wiele funkcji akcji i przekazywać je do komponentu, który nie jest świadomy Redux (głupi komponent), tak jak tak

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

Chciałem też dowiedzieć się więcej o bindActionsCreators i oto jak wdrożyłem w moim projekcie.

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

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

export default { loginRequest, logoutRequest };

W Twoim komponencie 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

Jednym z fajnych przypadków użycia bindActionCreatorsjest integracja z Redux-saga przy użyciu procedur Redux-Saga . Na przykład:

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

Zauważ, że ten wzorzec można zaimplementować za pomocą React Hooks (od React 16.8).


-1

Docs stwierdzenie jest bardzo jasne:

Jedynym przypadkiem użycia bindActionCreatorsjest sytuacja, gdy chcesz przekazać niektórych twórców akcji do komponentu, który nie jest świadomy istnienia Redux, i nie chcesz przekazywać do niego wysyłki ani sklepu Redux.

Jest to oczywiście przypadek użycia, który może wystąpić w następujących i tylko jednym warunku:

Powiedzmy, że mamy składnik A i 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

Wstrzyknąć do reakcji-redueksu: (A)

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

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

Wstrzyknięty przez React-Redux: (B)

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

Zauważyłeś następujące?

  • Zaktualizowaliśmy sklep redux za pomocą składnika A, podczas gdy nie znaliśmy magazynu redux w komponencie B.

  • Nie aktualizujemy komponentu A. Aby dokładnie wiedzieć, o co mi chodzi, możesz przejrzeć ten post . Mam nadzieję, że masz pomysł.


7
Nic nie rozumiem
vsync
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.