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?


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.


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;
  }, {})
}

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.


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.


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.


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

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