Zrozumienie React-Redux i mapStateToProps ()

220

Próbuję zrozumieć metodę Connect Re-Redux i funkcje, które przyjmuje jako parametry. W szczególności mapStateToProps().

W moim rozumieniu zwracana wartość mapStateToPropsbędzie obiektem pochodzącym ze stanu (w postaci, w jakiej znajduje się on w sklepie), którego klucze zostaną przekazane do komponentu docelowego (do którego zostanie zastosowane połączenie) jako rekwizyty.

Oznacza to, że stan wykorzystany przez komponent docelowy może mieć zupełnie inną strukturę niż stan przechowywany w sklepie.

P: Czy to w porządku?
P: Czy jest to oczekiwane?
P: Czy to jest anty-wzór?

Pablo Barría Urenda
źródło
11
Nie chcę dodawać kolejnej odpowiedzi do miksu ... ale zdaję sobie sprawę, że nikt tak naprawdę nie odpowiada na twoje pytanie ... moim zdaniem NIE jest to anty-wzór. Klucz znajduje się w nazwie mapStateTo Props , które przekazujesz właściwości tylko do odczytu dla składnika do konsumpcji. Często używam moich składników kontenera do zmiany stanu i zmiany go przed przekazaniem go do elementu prezentacji.
Matthew Brent
3
W ten sposób mój komponent prezentacyjny jest znacznie prostszy ... Mogę renderować, this.props.someDataa nie this.props.someKey[someOtherKey].someData... mieć sens?
Matthew Brent
3
Ten samouczek wyjaśnia to dość dobrze: learn.co/lessons/map-state-to-props-readme
Ayan
Cześć Pablo, proszę ponownie rozważyć wybraną odpowiedź.
vsync
Zastanów się, jak to zrobić?
Pablo Barría Urenda

Odpowiedzi:

56

P: Is this ok?
O: tak

P: Is this expected?
Tak, jest to oczekiwane (jeśli używasz React-Redux).

P: Is this an anti-pattern?
O: Nie, to nie jest anty-wzór.

Nazywa się to „podłączeniem” komponentu lub „uczynieniem go inteligentnym”. To jest z założenia.

Pozwala oddzielić swój komponent od stanu o dodatkowy czas, co zwiększa modułowość kodu. Pozwala także uprościć stan komponentu jako podzbiór stanu aplikacji, co w rzeczywistości pomaga zachować zgodność ze wzorcem Redux.

Pomyśl o tym w ten sposób: sklep powinien zawierać cały stan twojej aplikacji.
W przypadku dużych aplikacji może zawierać dziesiątki właściwości zagnieżdżonych w wielu warstwach.
Nie chcesz przewozić wszystkiego podczas każdego połączenia (drogie).

Bez niego mapStateToPropslub jakiejś jego analogii pokusiłbyś się o wyrzeźbienie swojego stanu w inny sposób na poprawę wydajności / uproszczenie.

Richard Strickland
źródło
6
Nie sądzę, że zapewnienie każdemu komponentowi dostępu do całego sklepu, jakkolwiek by to było duże, ma coś wspólnego z wydajnością. przekazywanie obiektów nie zajmuje pamięci, ponieważ zawsze jest to ten sam obiekt. Jedynym powodem, dla którego komponenty wymagają części, są prawdopodobnie 2 powody: (1) -Łatwiejszy głęboki dostęp (2) -Unikaj błędów, w których komponent może zepsuć stan, który do niego nie należy
vsync
@vsync Czy możesz wyjaśnić, w jaki sposób umożliwia to głębszy dostęp? Czy masz na myśli, że można teraz używać lokalnych rekwizytów zamiast odwoływać się do stanu globalnego, a więc jest bardziej czytelny?
Siddhartha
Ponadto, w jaki sposób komponent może zepsuć stan, który do niego nie należy, gdy stan zostanie przekazany jako niezmienny?
Siddhartha
jeśli stan jest niezmienny, to chyba w porządku, ale nadal, zgodnie z dobrą praktyką, lepiej jest wystawiać komponentom tylko te części, które są z nimi związane. Pomaga to również innym programistom lepiej zrozumieć, które części ( obiektu stanu ) są istotne dla tego komponentu. Jeśli chodzi o „łatwiejszy dostęp”, łatwiej jest w tym sensie, że ścieżka do jakiegoś głębokiego stanu jest bezpośrednio przekazywana do komponentu jako rekwizyt, a ten komponent jest ślepy na fakt, że za kulisami znajduje się Redux. Komponenty nie powinny dbać o to, który system zarządzania stanem jest używany i powinny działać tylko z otrzymanymi rekwizytami.
vsync
119

Tak, to jest poprawne. Jest to tylko funkcja pomocnicza, która pozwala na łatwiejszy dostęp do właściwości stanu

Wyobraź sobie, że masz postsklucz w swojej aplikacjistate.posts

state.posts //
/*    
{
  currentPostId: "",
  isFetching: false,
  allPosts: {}
}
*/

I komponent Posts

Domyślnie connect()(Posts)wszystkie rekwizyty stanu będą dostępne dla podłączonego komponentu

const Posts = ({posts}) => (
  <div>
    {/* access posts.isFetching, access posts.allPosts */}
  </div> 
)

Teraz, gdy mapujesz na state.postsswój komponent, robi się to trochę ładniej

const Posts = ({isFetching, allPosts}) => (
  <div>
    {/* access isFetching, allPosts directly */}
  </div> 
)

connect(
  state => state.posts
)(Posts)

mapDispatchToProps

normalnie musisz pisać dispatch(anActionCreator())

z bindActionCreatorstobą możesz to zrobić również łatwiej

connect(
  state => state.posts,
  dispatch => bindActionCreators({fetchPosts, deletePost}, dispatch)
)(Posts)

Teraz możesz go używać w swoim Komponencie

const Posts = ({isFetching, allPosts, fetchPosts, deletePost }) => (
  <div>
    <button onClick={() => fetchPosts()} />Fetch posts</button>
    {/* access isFetching, allPosts directly */}
  </div> 
)

Aktualizacja na ActionCreators ..

Przykład działania ActionCreator: deletePost

const deletePostAction = (id) => ({
  action: 'DELETE_POST',
  payload: { id },
})

Więc bindActionCreatorspo prostu podejmie twoje działania, dispatchzawinie je w wezwanie. (Nie przeczytałem kodu źródłowego redux, ale implementacja może wyglądać mniej więcej tak:

const bindActionCreators = (actions, dispatch) => {
  return Object.keys(actions).reduce(actionsMap, actionNameInProps => {
    actionsMap[actionNameInProps] = (...args) => dispatch(actions[actionNameInProps].call(null, ...args))
    return actionsMap;
  }, {})
}
webdeb
źródło
Myślę, że mogę coś przeoczyć, ale skąd dispatch => bindActionCreators({fetchPosts, deletePost}, dispatch)bierze się fetchPostsi jakie deletePostdziałania są przekazywane?
ilyo
@ilyo to twoi twórcy akcji, musisz je zaimportować
webdeb
2
Niezła odpowiedź! Myślę, że miło jest również podkreślić, że ten fragment kodu state => state.posts( mapStateToPropsfunkcja) powie Reactowi, które stany spowodują ponowne renderowanie komponentu po aktualizacji.
Miguel Péres,
38

Masz pierwszą część rację:

Tak, mapStateToPropsma stan Store jako argument / parametr (dostarczony przez react-redux::connect) i jest używany do łączenia komponentu z pewną częścią stanu sklepu.

Przez linkowanie rozumiem, że obiekt zwracany przez mapStateToPropszostanie dostarczony w czasie budowy jako rekwizyty, a wszelkie późniejsze zmiany będą dostępne za pośrednictwem componentWillReceiveProps.

Jeśli znasz wzór projektowy Observer, jest to dokładnie ta lub jego niewielka odmiana.

Przykład pomoże to wyjaśnić:

import React, {
    Component,
} from 'react-native';

class ItemsContainer extends Component {
    constructor(props) {
        super(props);

        this.state = {
            items: props.items, //provided by connect@mapStateToProps
            filteredItems: this.filterItems(props.items, props.filters),
        };
    }

    componentWillReceiveProps(nextProps) {
        this.setState({
            filteredItems: this.filterItems(this.state.items, nextProps.filters),
        });
    }

    filterItems = (items, filters) => { /* return filtered list */ }

    render() {
        return (
            <View>
                // display the filtered items
            </View>
        );
    }
}

module.exports = connect(
    //mapStateToProps,
    (state) => ({
        items: state.App.Items.List,
        filters: state.App.Items.Filters,
        //the State.App & state.App.Items.List/Filters are reducers used as an example.
    })
    // mapDispatchToProps,  that's another subject
)(ItemsContainer);

Może być inny tak zwany komponent reagujący, itemsFiltersktóry obsługuje wyświetlanie i utrwalanie stanu filtra do stanu Sklepu Redux, składnik Demo „nasłuchuje” lub „subskrybuje” filtry stanu Sklepu Redux, więc za każdym razem, gdy filtersComponentreagują na zmiany stanu magazynu filtrów (z pomocą ) -redux wykrywa zmianę i powiadamia lub „publikuje” wszystkie komponenty nasłuchujące / subskrybowane, wysyłając zmiany do nich, componentWillReceivePropsktóre w tym przykładzie uruchomią refilter elementów i odświeżą ekran z powodu zmiany stanu reakcji .

Daj mi znać, jeśli przykład jest mylący lub niewystarczająco jasny, aby zapewnić lepsze wyjaśnienie.

Co do: Oznacza to, że stan wykorzystany przez komponent docelowy może mieć zupełnie inną strukturę niż stan przechowywany w sklepie.

Nie dostałem pytania, ale po prostu wiem, że stan reagowania ( this.setState) jest zupełnie inny niż stan sklepu Redux!

Stan reakcji służy do obsługi przerysowania i zachowania komponentu reakcji. Stan reakcji jest zawarty wyłącznie w komponencie.

Stan sklepu Redux jest kombinacją stanów reduktorów Redux, z których każdy odpowiada za zarządzanie logiką aplikacji w małej części. Do tych atrybutów reduktorów można uzyskać dostęp za pomocą react-redux::connect@mapStateToPropsdowolnego komponentu! Które sprawiają, że stan sklepu Redux jest szeroko dostępny, podczas gdy stan składnika jest wyłączny.

Mohamed Mellouki
źródło
5

Ten przykład „ zareaguj i przeprowadź” oparty jest na przykładzie Mohameda Mellouki. Ale Sprawdza użyciu upiększać i strzępienia zasady . Zauważ, że definiujemy nasze rekwizyty i metody wysyłania za pomocą PropTypes , aby nasz kompilator nie krzyczał na nas. Ten przykład zawiera również niektóre wiersze kodu, których brakowało w przykładzie Mohameda. Aby użyć Connect, musisz zaimportować go z React-Redux . Ten przykład wiąże również metodę filterItems, co pozwoli uniknąć problemów z zakresem w komponencie . Ten kod źródłowy został automatycznie sformatowany przy użyciu JavaScript Prettify .

import React, { Component } from 'react-native';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';

class ItemsContainer extends Component {
  constructor(props) {
    super(props);
    const { items, filters } = props;
    this.state = {
      items,
      filteredItems: filterItems(items, filters),
    };
    this.filterItems = this.filterItems.bind(this);
  }

  componentWillReceiveProps(nextProps) {
    const { itmes } = this.state;
    const { filters } = nextProps;
    this.setState({ filteredItems: filterItems(items, filters) });
  }

  filterItems = (items, filters) => {
    /* return filtered list */
  };

  render() {
    return <View>/*display the filtered items */</View>;
  }
}

/*
define dispatch methods in propTypes so that they are validated.
*/
ItemsContainer.propTypes = {
  items: PropTypes.array.isRequired,
  filters: PropTypes.array.isRequired,
  onMyAction: PropTypes.func.isRequired,
};

/*
map state to props
*/
const mapStateToProps = state => ({
  items: state.App.Items.List,
  filters: state.App.Items.Filters,
});

/*
connect dispatch to props so that you can call the methods from the active props scope.
The defined method `onMyAction` can be called in the scope of the componets props.
*/
const mapDispatchToProps = dispatch => ({
  onMyAction: value => {
    dispatch(() => console.log(`${value}`));
  },
});

/* clean way of setting up the connect. */
export default connect(mapStateToProps, mapDispatchToProps)(ItemsContainer);

Ten przykładowy kod jest dobrym szablonem dla miejsca początkowego dla twojego komponentu.

Patrick W. McMahon
źródło
2

React-Redux connect służy do aktualizacji sklepu dla każdej akcji.

import { connect } from 'react-redux';

const AppContainer = connect(  
  mapStateToProps,
  mapDispatchToProps
)(App);

export default AppContainer;

Jest to bardzo prosto i jasno wyjaśnione na tym blogu .

Możesz sklonować projekt github lub skopiować i wkleić kod z tego bloga, aby zrozumieć połączenie Redux.

ArunValaven
źródło
dobry manual formapStateToProps thegreatcodeadventure.com/…
zloctb
1

Oto zarys / schemat do opisania zachowania mapStateToProps:

(Jest to znacznie uproszczona implementacja tego, co robi kontener Redux.)

class MyComponentContainer extends Component {
  mapStateToProps(state) {
    // this function is specific to this particular container
    return state.foo.bar;
  }

  render() {
    // This is how you get the current state from Redux,
    // and would be identical, no mater what mapStateToProps does
    const { state } = this.context.store.getState();

    const props = this.mapStateToProps(state);

    return <MyComponent {...this.props} {...props} />;
  }
}

i dalej

function buildReduxContainer(ChildComponentClass, mapStateToProps) {
  return class Container extends Component {
    render() {
      const { state } = this.context.store.getState();

      const props = mapStateToProps(state);

      return <ChildComponentClass {...this.props} {...props} />;
    }
  }
}
zloctb
źródło
-2
import React from 'react';
import {connect} from 'react-redux';
import Userlist from './Userlist';

class Userdetails extends React.Component{

render(){
    return(
        <div>
            <p>Name : <span>{this.props.user.name}</span></p>
            <p>ID : <span>{this.props.user.id}</span></p>
            <p>Working : <span>{this.props.user.Working}</span></p>
            <p>Age : <span>{this.props.user.age}</span></p>
        </div>
    );
 }

}

 function mapStateToProps(state){  
  return {
    user:state.activeUser  
}

}

  export default connect(mapStateToProps, null)(Userdetails);
SM Chinna
źródło