Javascript Redux:使用还原符对选择器进行排序

Javascript Redux:使用还原符对选择器进行排序,javascript,redux,reducers,Javascript,Redux,Reducers,在本Egghead教程中,建议使用接受完整状态树的选择器(而不是状态切片)来封装来自组件的状态知识。他认为,这使得改变国家结构变得更容易,因为组成部分对此一无所知,这一点我完全同意 然而,他建议的方法是,对于对应于特定状态片的每个选择器,我们在根减缩器旁边再次定义它,以便它可以接受完整状态。当然,这种实现开销破坏了他试图实现的目标。。。简化未来改变国家结构的过程 在一个有许多简化程序的大型应用程序中,每个简化程序都有许多选择器,如果我们在根简化程序文件中定义所有选择器,我们是否会不可避免地遇到命

在本Egghead教程中,建议使用接受完整状态树的选择器(而不是状态切片)来封装来自组件的状态知识。他认为,这使得改变国家结构变得更容易,因为组成部分对此一无所知,这一点我完全同意

然而,他建议的方法是,对于对应于特定状态片的每个选择器,我们在根减缩器旁边再次定义它,以便它可以接受完整状态。当然,这种实现开销破坏了他试图实现的目标。。。简化未来改变国家结构的过程

在一个有许多简化程序的大型应用程序中,每个简化程序都有许多选择器,如果我们在根简化程序文件中定义所有选择器,我们是否会不可避免地遇到命名冲突?直接从选择器的相关减缩器导入选择器并传递全局状态而不是相应的状态片有什么不对?e、 g

const todos = (state = [], action) => {
  switch (action.type) {
    case 'ADD_TODO':
      return [...state, todo(undefined, action)];
    case 'TOGGLE_TODO':
      return state.map(t => todo(t, action));
    default:
      return state;
  }
};

export default todos;

export const getVisibleTodos = (globalState, filter) => {
  switch (filter) {
    case 'all':
      return globalState.todos;
    case 'completed':
      return globalState.todos.filter(t => t.completed);
    case 'active':
      return globalState.todos.filter(t => !t.completed);
    default:
      throw new Error(`Unknown filter: ${filter}.`);
  }
};

这样做有什么缺点吗?

我自己也犯了这个错误(不是使用Redux,而是使用类似的内部通量框架),问题是您建议的方法将选择器耦合到整个状态树中相关减速器状态的位置。这在一些情况下会导致问题:

  • 您希望在状态树中的多个位置使用reducer(例如,因为相关组件出现在屏幕的多个部分,或者被应用程序的多个独立屏幕使用)
  • 您希望在另一个应用程序中重用reducer,并且此应用程序的状态结构与原始应用程序不同
它还向每个模块的选择器添加了对根减缩器的隐式依赖(因为它们必须知道它们在哪个键下,这实际上是根减缩器的责任)

如果选择器需要来自多个不同减速器的状态,则问题可能会扩大。理想情况下,模块应该只导出一个将状态片转换为所需值的纯函数,然后由应用程序的根模块文件将其连接起来

一个很好的技巧是使用一个只输出选择器的文件,所有选择器都使用状态片。这样,它们就可以批量处理:

// in file rootselectors.js
import * as todoSelectors from 'todos/selectors';
//...
// something like this:
export const todo = shiftSelectors(state => state.todos, todoSelectors); 
(shiftSelectors有一个简单的实现——我怀疑重新选择库已经有了合适的功能)

这还提供了名称间距-todo选择器在“todo”导出下都可用。现在,如果您有两个todo列表,您可以轻松导出todo1和todo2,甚至可以通过导出一个记忆函数来为特定的索引或id创建它们,从而提供对动态列表的访问。(例如,如果您可以一次显示任意一组待办事项列表)。例如

有时选择器需要应用程序多个部分的状态。同样,避免布线,除非在根部。在您的模块中,您将拥有:

export function selectSomeState(todos, user) {...}
然后,您的根选择器文件可以导入它,并重新导出将“TODO”和“user”连接到状态树相应部分的版本


因此,对于一个小型的一次性应用程序,它可能不是很有用,只是添加了样板文件(特别是在JavaScript中,它不是最简洁的函数式语言)。对于使用许多共享组件的大型应用程序套件,它将支持大量的重用,并使责任明确。它还使模块级别选择器更简单,因为它们不必首先进入适当的级别。此外,如果您添加FlowType或TypeScript,您可以避免所有子模块都必须依赖于根状态类型这一非常糟糕的问题(基本上,我提到的隐式依赖变成了显式的)。

是的,我刚刚看了那个视频,我可以看到一旦一个应用开始增长,一个动作就有3个真实来源,很糟糕。通过jsx文件中的一个操作,我们调用reducer/index文件,它现在有一个对保存状态的reducer文件的引用。我不知道,这对我来说似乎是很大的开销,要实例化一个需要一片数据的方法,我们现在必须在3个不同的文件中感受到它的存在。现在,再乘以50或100。。。也许在像“todos”这样的基础应用程序中,这很好。但这并不是真实的世界。Redux中的很多东西都是建议的最佳实践,但这并不一定意味着它们对所有情况或您的用例都是最佳的。我做事情的方式和你一样:把选择器和它们对应的减速机放在一起。我认为这更有意义,因为访问一部分状态的知识位于定义该部分状态的函数旁边。这实际上是关于什么最适合你,Redux社区中的许多人都认为这是你应该追求的。在一个大型应用程序中,我认为教程中提出的结构不再有效。您需要根据还原器/选择器/操作所针对的特定域对象来拆分它们。为了回答你的问题,除了在组件和特定的还原器之间引入了很多依赖项之外,这并没有什么缺点。我在上找到了一篇关于这个问题的好文章:我正在寻找解决这个确切问题的方法,这个答案激发了一个很好的解决方案。如果您想了解移位选择器的示例,请查看以下要点中的bindSelectors:
export function selectSomeState(todos, user) {...}