Понимание React-Redux и mapStateToProps ()

220

Я пытаюсь понять метод подключения к реактиву-редуксу и функции, которые он принимает в качестве параметров. В частности mapStateToProps().

Насколько я понимаю, возвращаемое значение mapStateToPropsбудет объектом, полученным из состояния (как оно живет в хранилище), чьи ключи будут переданы целевому компоненту (к которому применяется компонент, к которому применяется соединение) в качестве реквизита.

Это означает, что состояние, используемое вашим целевым компонентом, может иметь совершенно отличную структуру от состояния, которое хранится в вашем магазине.

Q: это нормально?
Q: это ожидается?
В: Это анти-паттерн?

Пабло Баррия Уренда
источник
11
Я не хочу добавлять другой ответ к миксу ... но я понимаю, что на самом деле никто не отвечает на ваш вопрос ... по моему мнению, это НЕ анти-паттерн. Ключ в имени mapStateTo реквизита вы проходящие только для чтения свойства для компонента , чтобы потреблять. Я часто использую свои компоненты контейнера, чтобы взять состояние и изменить его, прежде чем передать его компоненту представления.
Мэтью Брент
3
Таким образом, мой презентационный компонент намного проще ... Я могу рендерить, this.props.someDataа не this.props.someKey[someOtherKey].someData... имеет смысл?
Мэтью Брент
3
Этот урок объясняет это достаточно хорошо: learn.co/lessons/map-state-to-props-readme
Ayan
Привет, Пабло, пожалуйста, пересмотри свой выбранный ответ.
Вс
Пересмотреть как?
Пабло Баррия Уренда

Ответы:

56

Q: Is this ok?
A: да

Q: Is this expected?
Да, это ожидаемо (если вы используете реактив-редукс).

Q: Is this an anti-pattern?
A: Нет, это не анти-паттерн.

Это называется «подключение» вашего компонента или «сделать его умным». Это по замыслу.

Это позволяет вам отделить ваш компонент от вашего состояния на дополнительное время, что увеличивает модульность вашего кода. Это также позволяет упростить состояние вашего компонента как подмножество состояния вашего приложения, что, на самом деле, помогает вам соответствовать шаблону Redux.

Подумайте об этом так: магазин должен содержать все состояние вашего приложения.
Для больших приложений это может содержать десятки свойств, вложенных во многие слои.
Вы не хотите таскать все это на каждом вызове (дорого).

Без этого mapStateToPropsили какого-либо аналога у вас возникнет соблазн разделить ваше состояние на другой способ повышения производительности / упрощения.

Ричард Стрикленд
источник
6
Я не думаю, что предоставление каждому компоненту доступа ко всему хранилищу, каким бы большим оно ни было, связано с производительностью. передача объектов вокруг не занимает памяти, так как это всегда один и тот же объект. Вероятно, единственная причина, по которой компонент получает необходимые детали, - это две причины: более (1) -простой глубокий доступ. (2) -Избегайте ошибок, когда компонент может испортить состояние, не принадлежащее ему
vsync
@vsync Не могли бы вы объяснить, как это облегчает глубокий доступ? Вы имеете в виду, что теперь можно использовать локальный реквизит вместо того, чтобы ссылаться на глобальное состояние, чтобы он был более читабельным?
Сиддхартха
Кроме того, как компонент может испортить состояние, не принадлежащее ему, когда состояние передается как неизменяемое?
Сиддхартха
если состояние неизменяемое, то, я думаю, это нормально, но, тем не менее, в качестве хорошей практики лучше предоставлять компонентам только те части, которые имеют к ним отношение. Это также помогает другим разработчикам лучше понять, какие части ( объекта состояния ) относятся к этому компоненту. Что касается «более легкого доступа», то в некотором смысле проще, что путь к некоторому глубокому состоянию напрямую передается компоненту в качестве реквизита, и этот компонент не учитывает тот факт, что Redux скрыт за кулисами. Компоненты не должны заботиться о том, какая система управления состоянием используется, и они должны работать только с теми реквизитами, которые они получают.
vsync
119

Да, это правильно. Это просто вспомогательная функция, чтобы иметь более простой способ доступа к вашим свойствам состояния

Представьте, что у вас есть postsключ в вашем приложенииstate.posts

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

И компонент Posts

По умолчанию connect()(Posts)все параметры состояния доступны для подключенного компонента.

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

Теперь, когда вы отображаете state.postsсвой компонент, он становится немного лучше

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

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

mapDispatchToProps

как правило, вы должны написать dispatch(anActionCreator())

с bindActionCreatorsвами можно сделать это также легче, как

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

Теперь вы можете использовать его в своем компоненте

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

Обновление на actionCreators ..

Пример actionCreator: deletePost

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

Итак, bindActionCreatorsпросто приму ваши действия, заверну их в dispatchвызов. (Я не читал исходный код Redux, но реализация может выглядеть примерно так:

const bindActionCreators = (actions, dispatch) => {
  return Object.keys(actions).reduce(actionsMap, actionNameInProps => {
    actionsMap[actionNameInProps] = (...args) => dispatch(actions[actionNameInProps].call(null, ...args))
    return actionsMap;
  }, {})
}
webdeb
источник
Я думаю, что могу что-то упустить, но откуда берется dispatch => bindActionCreators({fetchPosts, deletePost}, dispatch)действие fetchPostsи deletePostдействия?
Ильо
@ilyo это ваши создатели действий, вы должны импортировать их
webdeb
2
Хороший ответ! Я думаю, что также приятно подчеркнуть, что этот кусок кода state => state.posts( mapStateToPropsфункция) сообщит React, какие состояния будут вызывать повторную визуализацию компонента при обновлении.
Мигель
38

Вы правильно поняли первую часть:

Да mapStateToPropsимеет состояние Store в качестве аргумента / параметра (предоставляетсяreact-redux::connect ) и используется для связывания компонента с определенной частью состояния хранилища.

Под связью я подразумеваю, что возвращаемый объект mapStateToPropsбудет предоставлен во время строительства в качестве реквизита, и любые последующие изменения будут доступны через componentWillReceiveProps.

Если вы знаете шаблон проектирования Observer, то это именно то или иное его изменение.

Пример поможет прояснить ситуацию:

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

Может существовать другой компонент реагирования, называемый itemsFiltersобработчиком отображения и сохраняющий состояние фильтра в состоянии Redux Store, демо-компонент «прослушивает» или «подписывается» на фильтры состояния Redux Store, поэтому всякий раз, когда фильтры сохраняют изменения состояния (с помощью filtersComponent), реагируют -redux обнаруживает, что произошло изменение, и уведомляет или «публикует» все прослушивающие / подписанные компоненты, отправляя изменения в ихcomponentWillReceiveProps которые в этом примере вызовут повторный фильтр элементов и обновят отображение из-за того, что изменилось состояние реакции ,

Дайте мне знать, если пример сбивает с толку или недостаточно ясен, чтобы дать лучшее объяснение.

Что касается: Это означает, что состояние, используемое вашим целевым компонентом, может иметь совершенно отличную структуру от состояния, которое хранится в вашем магазине.

Я не получил вопрос, но просто знаю, что состояние реакции ( this.setState) полностью отличается от состояния магазина Redux!

Состояние реакции используется для обработки перерисовки и поведения компонента реакции. Состояние реакции содержится исключительно для компонента.

Состояние Redux Store представляет собой комбинацию состояний редукторов Redux, каждое из которых отвечает за управление небольшой частью логики приложения. Эти атрибуты редукторов могут быть доступны с помощью react-redux::connect@mapStateToPropsлюбого компонента! Что делает состояние хранилища Redux доступным для всего приложения, в то время как состояние компонента является эксклюзивным для самого себя.

Мохамед Меллуки
источник
5

Это реагировать и перевождь пример базируется на примере Мухаммеда Mellouki в. Но проверяет, используя правила prettify и linting . Обратите внимание, что мы определяем наши методы props и dispatch, используя PropTypes, чтобы наш компилятор не кричал на нас. Этот пример также включал некоторые строки кода, которые отсутствовали в примере Мохамеда. Чтобы использовать Connect, вам нужно будет импортировать его из Reaction-redux . Этот пример также связывает метод filterItems, это предотвратит проблемы области действия в компоненте . Этот исходный код был автоматически отформатирован с использованием 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);

Этот пример кода является хорошим шаблоном для отправной точки для вашего компонента.

Патрик В. МакМахон
источник
2

React-Redux connect используется для обновления хранилища для каждого действия.

import { connect } from 'react-redux';

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

export default AppContainer;

Это очень просто и понятно объяснено в этом блоге .

Вы можете клонировать проект github или скопировать и вставить код из этого блога, чтобы понять соединение Redux.

ArunValaven
источник
хороший ручной formapStateToProps thegreatcodeadventure.com/...
zloctb
1

Вот схема / шаблон для описания поведения mapStateToProps:

(Это значительно упрощенная реализация того, что делает контейнер 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} />;
  }
}

и следующий

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
источник
-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);
С.М. Чинна
источник