Dokumentacja Redux dla bindActionCreators stwierdza, że:
Jedynym przypadkiem użycia
bindActionCreators
jest 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 bindActionCreators
był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
})
}
}
}
źródło
#3
jest skrótem opcji#1
?mapDispatchToProps
:function
,object
a brakuje. Sposób postępowania w każdym przypadku jest zdefiniowany tutajredux
aconnect
iaddDispatchToProps
do prostych komponent wydaje przesadą jeśli mogę tylko przekazać jeden prop dół. Ale o ile wiem, prawie wszystkie przypadki dlamapDispatch
rekwizytów będą albo opcjami,#1
albo#3
wspomniane w odpowiedziW 99% przypadków jest używany z funkcją React-Redux
connect()
, jako częśćmapDispatchToProps
parametru. Może być używany bezpośrednio wmapDispatch
funkcji, którą udostępniasz, lub automatycznie, jeśli używasz skróconej składni obiektu i przekazujesz obiekt pełen twórców akcjiconnect
.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ż spodziewaprops.dispatch
się istnienia.Kilka przemyśleń na ten temat napisałem w poście na moim blogu Idiomatic Redux: Po co używać kreatorów akcji? .
źródło
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);
źródło
Spróbuję odpowiedzieć na oryginalne pytania ...
Inteligentne i głupie komponenty
W swoim pierwszym pytaniu zasadniczo pytasz, dlaczego
bindActionCreators
jest 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
actionCreators
pliku / 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:
consts
osobne, aby można było się do nich odwoływać w reduktorach - co zmniejszyłoby szansę na błędy przy wpisywaniuDrugi 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ć ...
źródło
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()
zamiastdispatch
.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,
userAction
aby wywołać wszystkie akcje w twoim komponencie.IE:
userAction.login()
userAction.editEmail()
lubthis.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,ownProps
aby 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.źródło
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> } }
źródło
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);
źródło
Jednym z fajnych przypadków użycia
bindActionCreators
jest 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).
źródło
Docs stwierdzenie jest bardzo jasne:
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ł.
źródło