简体   繁体   English

顶级减速器中的redux状态选择器

[英]redux state selectors in top level reducer

After watching the new egghead course by Dan Abramov, I have question regarding the selectors that was mentioned. 在观看Dan Abramov的新蛋头课程之后,我对所提到的选择器有疑问。

The purpose of the selectors is to hide the details of the state tree from the components, so that it is easy to manage code later if tree changes. 选择器的目的是从组件中隐藏状态树的详细信息,以便在树更改时更容易管理代码。

If I understand it correctly, that means, the selectors called inside mapStateToProps should only be the ones that live in the top-level reducer. 如果我理解正确,那意味着,在mapStateToProps调用的选择器应该只是那些存在于顶级reducer中的选择器。 Because the state that is passed to mapStateToProps is the whole application state tree. 因为state传递给mapStateToProps是整个应用程序状态树。 If this is true, as the application grows, I can imagine it would become very difficult to manage the top level selectors. 如果这是真的,随着应用程序的增长,我可以想象管理顶级选择器会变得非常困难。

Have I miss understood the concept here? 我想念这里的概念吗? or is this a valid concern? 或者这是一个有效的关注?

Edit : trying to make my question clearer. 编辑 :试图让我的问题更清晰。

Say my whole state start with { byIds, listByFilter } and I have 说我的整个州开始于{ byIds, listByFilter }而且我有

export const getIsFetching = (state, filter) =>
  fromList.getIsFetching(state.listByFilter[filter]);

in my top level reducer reducers/index.js , and components would simply use getIsFetching passing the whole state to is, which is totally fine because it is the top level. 在我的顶级reducers/index.js getIsFetching reducers/index.js ,组件只是使用getIsFetching将整个状态传递给is,这是完全正常的,因为它是顶级的。

However, later on, I decided my whole app is going to contain a todo app and an counter app. 但是,稍后,我决定我的整个应用程序将包含一个待办事项应用程序和一个计数器应用程序。 So it make sense to put the current top level reducers into reducers/todo.js , and create a new top level reducers reducers/index.js like this: 因此将当前的顶级reducers/todo.js器放入reducers/todo.js ,并创建一个新的顶级reducers reducers/todo.js reducers/index.js如下所示:

combineReducers({
  todo: todoReducer,
  counter: counterReducer
})

at the point my state would be like 在我的状态将是这样的

{
  todo: {
    byIds,
    listByFilter
  },
  counter: {
    // counter stuff
  }
}

components can no longer use the getIsFetching from reducers/todo.js , because the state in getIsFetching is now actually dealing with state.todo . 组件可以不再使用getIsFetchingreducers/todo.js ,因为在状态getIsFetching现在实际处理state.todo So i have to in the top level reducer reducers/index.js export another selector like this: 所以我必须在顶级reducer reducers/index.js导出另一个选择器,如下所示:

export const getIsFetching = (state, filter) =>
  fromTodo.getIsFetching(state.todo);

only at this point, the component is able to use getIsFetching without worring about the state shape. 只有在这一点上,组件才能使用getIsFetching而不必担心状态形状。

However, this raises my concern which is all the selectors directly used by components must live in the top-level reducer. 但是,这引起了我的担忧,即组件直接使用的所有选择器必须存在于顶级reducer中。

Update 2 : essentially we are exporting selectors from the deepest level all the way up to the top-level reducers, while all the exports in the intermediate reducers are not using them, but they are there because the reducer knows the shape of the state at that level. 更新2 :基本上我们将选择器从最深层一直导出到顶层缩减器,而中间缩减器中的所有导出都没有使用它们,但它们在那里因为缩减器知道状态的形状那个级别。

It is very much like passing props from parent all the way down to children, while the intermediate component aren't using props . 这非常类似于将父母的props一直传递给儿童,而中间组件不使用props We avoided this by context , or connect . 我们通过contextconnect避免这种context

apologize for the poor English. 为可怜的英语道歉。

So while mapStateToProps does take the entire state tree, it's up to you to return what you'd like from that state in order to render your component. 因此,虽然mapStateToProps确实占用了整个状态树,但是您可以从该状态返回您想要的内容以呈现您的组件。

For instance, we can see he calls getVisibleTodos and passes in state (and params from the router), and gets back a list of filtered todos : 例如,我们可以看到他调用getVisibleTodos并传入state (以及来自路由器的params ),并获取已过滤的todos列表:

components/VisibleTodoList.js 组件/ VisibleTodoList.js

const mapStateToProps = (state, { params }) => ({
  todos: getVisibleTodos(state, params.filter || 'all'),
});

And by following the call, we can see that the store is utilizing combineReducers (albeit with a single reducer), as such, this necessitates that he pass the applicable portion of the state tree to the todos reducer, which is, of course, state.todos . 通过跟随调用,我们可以看到商店正在使用combineReducers (虽然只有一个减速器),因此,这需要他将状态树的适用部分传递给todos reducer,当然,这是state.todos

reducer/index.js 减速机/ index.js

import { combineReducers } from 'redux';
import todos, * as fromTodos from './todos';

const todoApp = combineReducers({
  todos,
});

export default todoApp;

export const getVisibleTodos = (state, filter) =>
  fromTodos.getVisibleTodos(state.todos, filter);

And while getVisibleTodos returns a list of todos , which by is a direct subset of the top-level state.todos (and equally named as such), I believe that's just for simplicity of the demonstration: 虽然getVisibleTodos返回一个todos列表,它是顶层state.todos的直接子集(同样如此命名),我相信这只是为了简化演示:

We could easily write another perhaps another component where there's a mapStateToProps similar to: 我们可以很容易地写另一个组件,其中mapStateToProps类似于:

components/NotTopLevel.js 组件/ NotTopLevel.js

const mapStateToProps = (state, { params }) => ({
  todoText: getSingleTodoText(state, params.todoId),
});

In this case, the getSingleTodoText still accepts full state (and an id from params ), however it would only return the text of todo , not even the full object, or a list of top-level todos . 在这种情况下, getSingleTodoText仍然接受完整state (以及来自paramsid ),但它只返回todo的文本,甚至不返回完整对象或顶级todos列表。 So again, it's really up to you to decide what you want to pull out of the store and stuff into your components when rendering. 因此,在呈现时,您需要决定从商店中取出什么以及填充到组件中。

I also came across this issue (and also had a hard time explaining it...). 我也遇到过这个问题(并且很难解释它......)。 My solution for compartmentalization this follows from how redux-forms handles it. 我的redux-forms解决方案来自redux-forms如何处理它。

Essentially the problem boils down to one issue - where is the reducer bound to? 基本上问题归结为一个问题 - 减速器绑定在哪里? In redux-forms they assume you set it at form (though you can change this) in the global state. redux-forms他们假设你在全局状态下将其设置为form (尽管你可以改变它)。

Because you've assumed this, you can now write your module's selectors to accept the globalState and return a selector as follows: (globalState) => globalState.form.someInnerAttribute or whatever you want. 因为你已经假设了这一点,你现在可以编写模块的选择器来接受globalState并返回一个选择器,如下所示: (globalState) => globalState.form.someInnerAttribute或者你想要的任何东西。

To make it even more extensible you can create an internal variable to track where the state is bound to in the global state tree and also an internal function that's like getStateFromGlobalState = (globalState) => globalState[boundLocation] and uses that to get the inner state tree. 为了使其更具可扩展性,您可以创建一个内部变量来跟踪状态在全局状态树中的绑定位置,还可以创建一个内部函数,如getStateFromGlobalState = (globalState) => globalState[boundLocation]并使用它来获取内部州树。 Then you can change this variable programatically if you decide to bind your state to a different spot in the global state tree. 然后,如果您决定将状态绑定到全局状态树中的不同位置,则可以以编程方式更改此变量。

This way when you export your module's selectors and use them in mapStateToProps, they can accept the global state. 这样,当您导出模块的选择器并在mapStateToProps中使用它们时,它们可以接受全局状态。 If you make any changes to where the where the reducer is bound, then you only have to change that one internal function. 如果对reducer绑定的位置进行任何更改,则只需更改该内部函数。

IMO, this is better than rewriting every nested selector in the top level. IMO,这比重写顶层的每个嵌套选择器要好。 That is hard to scale/maintain and requires a lot of boilerplate code. 这很难扩展/维护,需要大量的样板代码。 This keeps the reducer/selector module contained to itself. 这使减速器/选择器模块保持自身。 The only thing it needs to know is where the reducer is bound to. 它唯一需要知道的是减速器必须遵守的地方。

By the way - you can do this for some deeply nested states where you wouldn't necessarily be referring about this from globalState but rather some upper level node on the state tree. 顺便说一句 - 你可以为一些深度嵌套的状态执行此操作,在这些状态中,您不必从globalState引用它,而是在状态树上引用某个上级节点。 Though if you have a super nested state it may make more sense to write the selector from a upper state's POV. 虽然如果你有一个超级嵌套状态,从较高状态的POV中编写选择器可能更有意义。

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

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