繁体   English   中英

Redux:使用Reducers共置选择器

[英]Redux: Colocating Selectors with Reducers

在这个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}.`);
  }
};

这样做有什么不利吗?

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

  • 您希望在状态树中的多个位置具有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); 

(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!

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

export function selectSomeState(todos, user) {...}

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

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

暂无
暂无

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

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