简体   繁体   English

React Redux - 我可以让mapStateToProps只占用部分状态吗?

[英]React Redux — can I make mapStateToProps only take in part of the state?

I want to make reusable modules that could be plugged in to any react-redux application. 我想制作可以插入任何react-redux应用程序的可重用模块。 Ideally, my module would have a container component, actions, and reducer at the top level (and then any presentational components below the container). 理想情况下,我的模块在顶层有一个容器组件,操作和reducer(然后是容器下面的任何表示组件)。 I would want the module to only work off its own slice of the app's state, and ideally to not have to know anything about the rest of the app state (so it's truly modular). 我希望模块只能处理自己应用程序状态的一部分,理想情况下不必了解应用程序状态的其余部分(因此它是真正的模块化)。

Reducers only work off of part of the state (using combineReducers), so I'm happy there. Reducers只能在部分状态下工作(使用combineReducers),所以我很高兴。 However, with container components, it seems like mapStateToProps always takes in the full state of the app. 但是,对于容器组件,mapStateToProps似乎总是处于应用程序的完整状态。

I'd like it if mapStateToProps only took in the same "state slice" that I am handling in my module (like the reducer does). 如果mapStateToProps只接受我在模块中处理的相同“状态切片”(就像reducer一样),我会喜欢它。 That way my module would truly be modular. 这样我的模块就会真正模块化。 Is this possible? 这可能吗? I guess I could just pass that slice of the state down to be the props of this component (so I could just use the second argument of mapStateToProps, ownProps), but am not sure if this would have the same effect. 我想我可以把状态的那一部分传递给这个组件的道具(所以我可以使用mapStateToProps的第二个参数,ownProps),但我不确定这是否具有相同的效果。

That is actually something of a complicated topic. 这实际上是一个复杂的话题。 Because Redux is a single global store, the idea of a completely encapsulated, fully reusable plug-and-play set of logic does become rather difficult. 因为Redux是一个单一的全局存储,所以完全封装,完全可重用的即插即用逻辑集的想法确实变得相当困难。 In particular, while the reducer logic can be fairly generic and ignorant of where it lives, the selector functions need to know where in the tree to find that data. 特别是,虽然reducer逻辑可以相当通用并且不知道它在哪里,但是选择器函数需要知道树中的哪个位置才能找到该数据。

The specific answer to your question is "no, mapState is always given the complete state tree". 你的问题的具体答案是“不, mapState总是给出完整的状态树”。

I do have links to a number of relevant resources, which may possibly help with your situation: 我确实有许多相关资源的链接,这可能对您的情况有所帮助:

Redux only has a single store as you know, so all it knows to do is pass the entire store to your mapStateToProps function. 如你所知,Redux只有一个商店,所以它知道要做的就是将整个商店传递给你的mapStateToProps函数。 However using object destructuring, you can specify which properties in the store you want and ignore the rest. 但是,使用对象解构,您可以指定所需的存储中的哪些属性,而忽略其余属性。 Something like 'function mapStateToProps({prop1, prop2})' would only capture those two properties in the store and ignore the rest. 像'function mapStateToProps({prop1,prop2})'这样的东西只能捕获商店中的那两个属性而忽略其余的属性。 Your function is still receiving the entire store, but you're indicating that only these props interest you. 您的功能仍在接收整个商店,但您只是指出这些道具只对您感兴趣。

In my example, 'prop1' and 'prop2' would be the names you assigned your reducers during the call to 'combineReducers'. 在我的例子中,'prop1'和'prop2'将是你在'combineReducers'调用期间为reducers指定的名称。

Ideally the way it works is you get the state and you extract the values from them by use deconstructors. 理想情况下,它的工作方式是获取状态,然后使用解构器从中提取值。 redux works on concept of single state redux适用于单一状态的概念

For example:- 例如:-

function mapStateToProps(state){
    const { auth } = state  //just taking a auth as example.
    return{
      auth
    }
}

I'm running into the same problem because, as you said, the current implementation of redux/react-redux allows for splitting up reducers on the state just fine but mapDispatchToProps always passes the whole state tree. 我遇到了同样的问题,因为正如你所说,redux / react-redux的当前实现允许在状态上拆分reducers,但mapDispatchToProps 总是传递整个状态树。

https://stackoverflow.com/a/39757853/444794 is not what I want, because it means we have to duplicate all our selector logic across each react-redux application that uses our module. https://stackoverflow.com/a/39757853/444794不是我想要的,因为这意味着我们必须在使用我们模块的每个react-redux应用程序中复制所有选择器逻辑。

My current workaround has been to pass the slice of the state down as a prop instead. 我目前的解决方法是将状态切片作为支柱传递下去。 This follows a sort of compositional pattern but at the same time removes the cleanliness of accessing the state directly, which I'm disappointed with. 这遵循一种组成模式,但同时消除了直接进入国家的清洁,我对此感到失望。

Example: 例:

Generally, you want to do this: 通常,您想要这样做:

const mapStateToProps = (state) => {
  return {
    items: mySelector(state)
  }
}

const mapDispatchToProps = (dispatch) => {
  return {
    doStuff: (item) => {
      dispatch(doStuff(item))
    }
  }
}

class ModularComponent extends React.Component {
  render() {
    return (
      <div>
        { this.props.items.map((item) => {
          <h1 onclick={ () => this.props.doStuff(item) }>{item.title}</h1>
        })}
      </div>
    )
  }
}

export default connect(mapStateToProps, mapDispatchToProps)(ModularComponent)

but since this module is included in an application where the state is now several things (ie. key-values) rather than a list of items, this won't work. 但由于此模块包含在一个应用程序中,其中状态现在是几个事物(即键值)而不是项目列表,这将不起作用。 My workaround instead looks like: 我的解决方法看起来像:

const mapStateToProps = (_, ownProps) => {
  return {
    items: mySelector(ownProps.items)
  }
}

const mapDispatchToProps = (dispatch) => {
  return {
    doStuff: (item) => {
      dispatch(doStuff(item))
    }
  }
}

class ModularComponent extends React.Component {
  render() {
    return (
      <div>
        { this.props.items.map((item) => {
          <h1 onclick={ () => this.props.doStuff(item) }>{item.title}</h1>
        })}
      </div>
    )
  }
}

export default connect(mapStateToProps, mapDispatchToProps)(ModularComponent)

And the application using the Module looks like: 使用该模块的应用程序如下所示:

const mapStateToProps = (state) => {
  return {
    items: state.items
    stuffForAnotherModule: state.otherStuff
  }
}

class Application extends React.Component {
  render() {
    return (
      <div>
        <ModularComponent items={ this.props.items } />
        <OtherComponent stuff={ this.props.stuffForAnotherModule } />
      </div>
    )
  }
}

export default connect(mapStateToProps)(Application)

Although mapStateToProps (the first function you pass to connect) gets passed the whole store as you said, its job is to map specific parts of the state to the component. 虽然mapStateToProps (你传递给connect的第一个函数)如你所说的那样传递整个商店,但它的工作是将状态的特定部分映射到组件。 So only what is returned from mapStateToProps will be mapped as a prop to your component. 因此,只有从mapStateToProps返回的内容才会被映射为组件的prop。

So lets say your state looks like this: 所以,让我们说你的州看起来像这样:

{
    account: {
        username: "Jane Doe",
        email: "janedoe@somemail.com",
        password: "12345",
        ....
    },
    someOtherStuff: {
        foo: 'bar',
        foo2: 'bar2'
    },
    yetMoreStuff: {
        usuless: true,
        notNeeded: true
    }
}

and your component needs everything from account and foo from someOtherStuff then your mapStateToProps would look like this: 并且你的组件需要来自someOtherStuff accountfoo所有内容,那么你的mapStateToProps将如下所示:

const mapStateToProps = ({ account, someOtherStuff }) => ({
    account,
    foo: { someOtherStuff }
});
export default connect(mapStateToProps)(ComponentName)

then your component will have the prop account and foo mapped from your redux state. 然后你的组件将具有从你的redux状态映射的prop accountfoo

You do have the option of writing a couple of wrapper utils for your modules that will do the work of: 1) Only running mapStateToProps when the module's slice of state changes and 2) only passes in the module's slice into mapStateToProps. 你可以选择为你的模块编写几个包装工具来完成以下工作:1)只有当模块的状态切片发生变化时才运行mapStateToProps,2)只将模块的切片传递给mapStateToProps。

This all assumes your module slices of state are root properties on the app state object (eg state.module1 , state.module2 ). 这都假设您的模块状态切片是app状态对象上的根属性(例如state.module1state.module2 )。

  1. Custom areStatesEqual wrapper function that ensures mapStateToProps will only run if the module's sub-state changes: 自定义areStatesEqual包装函数,确保mapStateToProps仅在模块的子状态更改时才会运行:
function areSubstatesEqual(substateName) {
  return function areSubstatesEqual(next, prev) {
    return next[substateName] === prev[substateName];
  };
}

Then pass it into connect : 然后将其传递给connect

connect(mapStateToProps, mapConnectToProps, null, {
  areStatesEqual: areSubstatesEqual('myModuleSubstateName')
})(MyModuleComponent);
  1. Custom mapStateToProps wrapper that only passes in the module substate: 自定义mapStateToProps包装器,只传递模块子状态:
function mapSubstateToProps(substateName, mapStateToProps) {
  var numArgs = mapStateToProps.length;

  if (numArgs !== 1) {
    return function(state, ownProps) {
      return mapStateToProps(state[substateName], ownProps);
    };
  }

  return function(state) {
    return mapStateToProps(state[substateName]);
  };
}

And you'd use it like so: 而且你会这样使用它:

function myComponentMapStateToProps(state) {
  // Transform state
  return props;
}
var mapSubstate = mapSubstateToProps('myModuleSubstateName', myComponentMapStateToProps);

connect(mapSubstate, mapDispatchToState, null, {
  areStatesEqual: areSubstatesEqual('myModuleSubstateName')
})(MyModuleComponent);

While untested, that last example should only run myComponentMapStateToProps when 'myModuleSubstateName' state changes, and it will only receive the module substate. 虽然未经测试,但最后一个示例只应在'myModuleSubstateName'状态更改时运行myComponentMapStateToProps ,并且它只接收模块子状态。

One additional enhancement could be to write your own module-based connect function that takes one additional moduleName param: 另外一个增强功能可能是编写自己的基于模块的connect函数,该函数需要一个额外的moduleName参数:

function moduleConnect(moduleName, mapStateToProps, mapDispatchToProps, mergeProps, options) {
  var _mapState = mapSubstateToProps(moduleName, mapStateToProps);
  var _options = Object.assign({}, options, {
    areStatesEqual: areSubstatesEqual('myModuleSubstateName')
  });
  return connect(_mapState, mapDispatchToProps, mergeProps, _options);
}

Then each module component would just need to do: 然后每个模块组件只需要做:

moduleConnect('myModuleName', myMapStateToProps)(MyModuleComponent);

The answer to your question is yes. 你的问题的答案是肯定的。 Both given answers cover different aspects of the same thing. 给出的答案都涵盖同一事物的不同方面。 First, Redux creates a single store with multiple reducers. 首先,Redux创建了一个包含多个reducer的商店。 So you'll want to combine them like so: 所以你会想要将它们组合起来:

export default combineReducers({
  people: peopleReducer,
  departments: departmentsReducer,
  auth: authenticationReducer
});

Then, say you have a DepartmentsList component, you may just need to map the departments from the store to your component (and maybe some actions mapped to props as well): 然后,假设您有一个DepartmentsList组件,您可能只需要将departments从商店映射到组件(也可能是映射到props的一些操作):

function mapStateToProps(state) {
  return { departments: state.departments.departmentsList };
}

export default connect(mapStateToProps, { fetchDepartments: fetchDepartments })(DepartmentsListComponent);

Then inside your component it is basically: 然后在组件内部基本上是:

this.props.departments
this.props.fetchDepartments()

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM