了解 React-Redux 和 mapStateToProps()

Understanding React-Redux and mapStateToProps()

我想了解react-redux的connect方法,以及它作为参数的函数。特别是 mapStateToProps().

按照我的理解,mapStateToProps 的 return 值将是一个派生自状态的对象(因为它存在于商店中),其键将传递给您的目标组件(组件连接被应用到)作为道具。

这意味着您的目标组件使用的状态可能与存储在您的商店中的状态具有截然不同的结构。

问:这样可以吗?
问:这是预期的吗?
问:这是反模式吗?

你答对了第一部分:

mapStateToProps 将存储状态作为 argument/param(由 react-redux::connect 提供),它用于 link 具有存储状态特定部分的组件。

linking 我的意思是 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 状态,Demo 组件正在“侦听”或“订阅”Redux Store 状态过滤器,因此每当过滤器存储状态变化(在 filtersComponent 的帮助下)react-redux 检测到有变化并通过将变化发送到它们的 componentWillReceiveProps 来通知或“发布”所有 listening/subscribed 组件由于反应状态已更改,此示例将触发项目的重新过滤并刷新显示。

如果示例令人困惑或不够清楚以提供更好的解释,请告诉我。

至于:这意味着您的目标组件使用的状态可能与存储在您的商店中的状态具有截然不同的结构。

我没听懂,只是知道反应状态(this.setState)与 Redux Store 状态完全不同!

反应状态用于处理反应组件的重绘和行为。反应状态仅包含在组件中。

Redux Store 状态是 Redux reducers 状态的组合,每个 reducers 状态负责管理一小部分应用程序逻辑。任何组件都可以在 react-redux::connect@mapStateToProps 的帮助下访问这些 reducers 属性!这使得 Redux store state 可以在应用程序范围内访问,而组件状态是它自己独有的。

是的,没错。它只是一个帮助函数,可以更简单地访问您的状态属性

假设您的应用程序中有一个 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;
  }, {})
}

React-Redux connect 用于为每个操作更新存储。

import { connect } from 'react-redux';

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

export default AppContainer;

这篇blog解释的非常简单明了。

您可以克隆 github 项目或复制粘贴该博客中的代码以了解 Redux 连接。

react & redux 示例基于 Mohamed Mellouki 的示例。 但使用 prettify and linting rules 进行验证。请注意,我们定义了道具 和 dispatch methods using PropTypes 这样我们的编译器就不会对我们尖叫。 这个例子还包含了 Mohamed 的一些代码行 例子。要使用连接,您需要从 react-redux 导入它。这个 示例中还有 binds the method filterItems this will prevent scope 个问题 component. This source code has been auto formatted using 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);

这个示例代码是一个很好的模板,可以作为您组件的起点。

问:Is this ok?
答:是

问:Is this expected?
是的,这是预期的(如果您使用的是 react-redux)。

问:Is this an anti-pattern?
A: 不,这不是反模式。

它称为 "connecting" 您的组件或 "making it smart"。这是设计使然。

它允许您将组件与您的状态分离更多时间,从而增加代码的模块化。它还允许您将组件状态简化为应用程序状态的子集,这实际上可以帮助您遵守 Redux 模式。

这样想:商店应该包含应用程序的整个状态。
对于大型应用程序,这可能包含许多深嵌套的属性。
您不想在每次通话时都拖拖拉拉(昂贵)。

如果没有 mapStateToProps 或类似的东西,您可能会想通过另一种方式分割您的状态以改善 performance/simplify。

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

这里有一个 outline/boilerplate 用于描述 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} />;
    }
  }
}

我想 re-structure 您提到的声明是:

This means that the state as consumed by your target component can have a wildly different structure from the state as it is stored on your store

你可以说你的目标组件消耗的状态有一小部分存储在 redux 存储中。换句话说,您的组件消耗的状态将是 redux 存储状态的 sub-set。

就理解connect()方法而言,相当简单! connect() 方法可以为你的组件添加新的 props,甚至覆盖现有的 props。通过这个 connect 方法,我们也可以访问 Provider 抛给我们的 redux store 的状态。两者的组合对您有利,您可以将 redux store 的状态添加到组件的 props 中。

以上是一些理论,我建议您看一下这个 video 一次以更好地理解语法。

这是一个简单的概念。 Redux 从 reducers 中的动作创建一个无处不在的状态对象(一个商店)。与 React 组件一样,这种状态不必在任何地方显式编码,但它可以帮助开发人员在 reducer 文件中查看默认状态对象以可视化正在发生的事情。您在组件中导入减速器以访问该文件。然后 mapStateToProps 只选择其组件需要的商店中的 key/value 对。可以把它想象成 Redux 创建 React 组件的全球版本

this.state = ({ 
cats = [], 
dogs = []
})

无法使用 mapStateToProps() 更改状态的结构。您正在做的是仅选择组件需要的商店 key/value 对并将值(从商店中的 key/value 列表)传递给组件中的道具(本地键)。您在列表中一次执行一个值。过程中不能发生结构变化。

P.S。这家商店是地方政府。 Reducer 通常还会将状态与 Action Creators 一起传递到数据库,但是对于这个特定的帖子,首先要理解这个简单的概念。

P.P.S。将 reducer 分成单独的文件并仅导入组件需要的 reducer 是一种很好的做法。

是的,你可以做到。您甚至可以处理状态和 return 对象。

function mapStateToProps(state){  
  let completed = someFunction (state);
   return {
     completed : completed,  
   }
}
 

如果您想将与状态相关的逻辑从渲染函数转移到渲染函数之外,这将很有用。