【问题标题】:How to write Redux selectors that are both reusable and modular?如何编写可重用和模块化的 Redux 选择器?
【发布时间】:2019-07-19 23:35:50
【问题描述】:

我是 Redux 的新手,并试图弄清楚如何充分利用它。

为应用程序的模块编写选择器时,应将状态树的哪一部分传递给选择器,以使选择器既可重用又可模块化?

例如,给定下面的代码,以类似于stateShapeExample 的状态形状编写selectModuleItemsById 的好方法是什么?

let stateShapeExample = {
    module: {
        items: {
            firstItemId: {...},
            secondItemId: {...},
            ...
        }
    }
}

const selectModuleRoot = (state) => state.module;

// First Option: starts from the module root
const selectModuleItemById = (state, id) => state.items[id];

// Second Option: starts from the global root
const selectModuleItemById = (state, id) => state.module.items[id];

// Something Else: ???
const selectItemById = (state, id) => state[id];

【问题讨论】:

  • 如果您的选择器引用状态的全局根,一旦您决定将子树分解为几个或将一些数据移动到不同状态的分支,则需要做更多的工作
  • @skyboyer 从应用程序的其他部分使用它们也会更早。由于您只需要一个函数来访问您正在查找的数据,因此您不需要知道要调用的其他选择器的顺序来获取传递给您需要的选择器的状态。对状态形状的更改可能会更改选择器的顺序。然后,首先使用选择器没有任何优势。如果您要查找的项目深深嵌套在状态树中,则尤其如此。
  • 不,拥有export const selectItemById = (globalState, id) => selectModuleRoot(globalState)[id],您将不需要知道序列
  • @skyboyer 但是selectItemById不能用于通过id选择其他类型的项目,所以它的可重用性降低了。如果状态树中有多个模块实例也会有问题。
  • 也许您正在寻找类似getEntityByIdentity(state, {id: 'abc123', type: 'module'}) 的东西。此外,您应该避免在状态下拥有相同数据的副本;改为存储 id 引用。

标签: javascript redux react-redux


【解决方案1】:

简短的回答是它非常棘手。

我见过的最好的文章是 Randy Coulman 关于模块化选择器的系列文章:

总的总结似乎是让“模块缩减器”编写选择器,这些选择器知道如何从自己的状态中挑选出数据片段,然后根据模块/切片的安装位置在应用程序级别“全球化”它们在状态树中。但是,由于模块本身可能需要使用选择器,您可能必须将注册/设置过程移到单独的文件中以避免循环依赖问题。

【讨论】:

    【解决方案2】:

    根据定义,选择器接收整个状态并返回部分状态。其他任何东西基本上都只是一个数据实用功能。

    我使用ramda 镜头来管理这种事情。

    考虑这样的目录结构:

    store
      module
        data.js
        selectors.js
        reducers.js
        actions.js
    

    data.js 将导出初始状态(在这种情况下,只是 modules 的初始状态)和描述状态片段位置的 ramda 镜头。

    import { lensPath } from 'ramda'
    
    export default {
        items: {
            firstItemId: {...},
            secondItemId: {...},
            ...
        }
    }
    
    export const itemsLens = lensPath(['module', 'items'])
    export const makeItemLens = id => lensPath(['module', 'items', id])
    

    然后,在selectors.js 中导入镜头以从整个状态树中选择数据。

    import {view} from 'ramda'
    import {itemsLens, makeItemLens} from './data.js'
    
    export const selectModuleItems = state => view(itemsLens, state)
    export const selectModuleItemById = (state, id) => view(makeItemLens(id), state)
    

    这个策略有几个好处:

    1. 使用 ramda 的 lensPathview 可以让您进行深入的属性查找而不会冒Cannot read propery firstItemId of undefined 错误的风险。如果 ramda 不是你的东西(lodash、immutable.js 等),其他库也有相同的功能。
    2. 将镜头与您的初始状态放在一起为其他开发人员增加了很多清晰度。
    3. 如果需要,将对象路径抽象到镜头可以更轻松地重构您的状态树。

    缺点是它是一堆额外的样板代码,但明确并避免使用魔法代码 IMO 是有价值的。

    说了这么多,您还应该查看reselect 了解更高级的选择器策略(我还没有广泛使用)。

    【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-01-22
    • 2017-06-04
    • 2021-03-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多