Redux:使用Reducers共置选择器

pji*_*ers 19 javascript reducers redux

在这个Redux:Colocating Selectors with Reducers Egghead教程中,Dan Abramov建议使用接受完整状态树而不是状态切片的选择器来封装状态知识,远离组件.他认为这使得更容易改变状态结构,因为组件不知道它,我完全赞同.

然而,他建议的方法是,对于对应于特定状态切片的每个选择器,我们再次将其与根减速器一起定义,以便它可以接受完整状态.当然,这种实现开销会破坏他想要实现的目标......简化将来改变状态结构的过程.

在一个包含许多reducers的大型应用程序中,每个都有很多选择器,如果我们在root reducer文件中定义所有选择器,我们不会不可避免地遇到命名冲突吗?直接从相关的reducer导入选择器并传入全局状态而不是相应的状态片段有什么问题?例如

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}.`);
  }
};
Run Code Online (Sandbox Code Playgroud)

这样做有什么不利吗?

Tom*_*omW 10

我自己犯了这个错误(不是使用Redux,而是使用类似的内部Flux框架),问题在于您建议的方法将选择器耦合到整个状态树中关联的reducer状态的位置.这在少数情况下会导致问题:

  • 您希望在状态树中的多个位置具有reducer(例如,因为相关组件出现在屏幕的多个部分中,或者由应用程序的多个独立屏幕使用).
  • 您希望在另一个应用程序中重用reducer,并且此应用程序的状态结构与原始应用程序不同.

它还为每个模块的选择器添加了对根reducer的隐式依赖(因为它们必须知道它们所处的键,这实际上是root reducer的责任).

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

一个好方法就是拥有一个只导出选择器的文件,所有文件都采用状态切片.这样他们就可以批量处理:

// in file rootselectors.js
import * as todoSelectors from 'todos/selectors';
//...
// something like this:
export const todo = shiftSelectors(state => state.todos, todoSelectors); 
Run Code Online (Sandbox Code Playgroud)

(shiftSelectors有一个简单的实现 - 我怀疑重选库已经有了一个合适的函数).

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

export const todo = memoize(id => shiftSelectors(state => state.todos[id], todoSelectors)); 
// but be careful if there are lot of ids!
Run Code Online (Sandbox Code Playgroud)

有时,选择器需要来自应用程序的多个部分的状态.同样,除了根目录外,请避免接线.在您的模块中,您将拥有:

export function selectSomeState(todos, user) {...}
Run Code Online (Sandbox Code Playgroud)

然后你的根选择器文件可以导入它,并将连接'todos'和'user'的版本重新导出到状态树的相应部分.

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

  • 我本来想解决这个问题,这个答案激发了一个很好的解决方案.对于任何想要查看shiftSelectors外观的示例的人,请查看此要点中的bindSelectors:https://gist.github.com/jslatts/1c5d4d46b6e5b0ac0e917fa3b6f7968f (2认同)