Javascript 如何编写可重用和模块化的Redux选择器?

Javascript 如何编写可重用和模块化的Redux选择器?,javascript,redux,react-redux,Javascript,Redux,React Redux,我是Redux的新手,正在尝试找出如何充分利用它 为应用程序的模块编写选择器时,应将状态树的哪一部分传递给选择器,以使选择器既可重用又模块化 例如,给定下面的代码,使用类似于stateShapeExample的状态形状编写selectModuleItemsById的好方法是什么 let stateShapeExample={ 模块:{ 项目:{ firstItemId:{…}, secondItemId:{…}, ... } } } const selectModuleRoot=(state)=

我是Redux的新手,正在尝试找出如何充分利用它

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

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

let stateShapeExample={
模块:{
项目:{
firstItemId:{…},
secondItemId:{…},
...
}
}
}
const selectModuleRoot=(state)=>state.module;
//第一个选项:从模块根开始
const selectModuleItemById=(state,id)=>state.items[id];
//第二个选项:从全局根开始
const selectModuleItemById=(state,id)=>state.module.items[id];
//还有别的吗???
const selectItemById=(state,id)=>state[id];

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

我用镜头来处理这类事情

考虑如下目录结构:

store
  module
    data.js
    selectors.js
    reducers.js
    actions.js
data.js
将导出初始状态(在本例中,仅导出
模块的初始状态)和描述状态片段所在位置的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)
这一战略有几个好处:

  • 使用ramda的
    lensPath
    视图
    可以进行深入的属性查找,而不会产生风险,
    无法读取未定义的
    错误的属性firstItemId。如果ramda不是你的东西,其他库也有相同的函数(lodash、immutable.js等)
  • 将镜头放在初始状态旁边可以为其他开发人员增加很多清晰度
  • 如果需要的话,将对象路径抽象到镜头会使重构状态树变得更容易
  • 缺点是它是一堆额外的样板代码,但在IMO中明确和避免使用神奇的代码是有价值的


    说到这里,您还应该查看更高级的选择器策略(我还没有广泛使用过这种策略)。

    简单的回答是,它相当棘手

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


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

    如果您的选择器引用州的全局根,则在您决定将子树分解为几个或将一些数据移动到不同州的根目录后,需要做更多的工作branch@skyboyer从世界其他地方使用它们也会更早应用程序。因为您只需要一个函数来访问正在查找的数据,所以您不需要知道要调用的其他选择器的顺序,就可以将状态传递给所需的选择器。对状态形状的更改可能会更改选择器的顺序。那么,首先使用选择器是没有好处的。如果您要查找的项目深入嵌套在状态树中,则尤其如此。否,您不需要知道
    export const selectItemById=(globalState,id)=>selectModuleRoot(globalState)[id]
    sequence@skyboyer但是,
    selectItemById
    不能用于按id选择其他类型的项目,因此,它的可重用性降低了。如果状态树中有多个模块实例,也会出现问题。可能您正在查找类似以下内容的内容
    getEntityByIdentity(state,{id:'abc123',type:'module'})
    。此外,您应该避免在状态中使用相同数据的副本;取而代之的是存储id引用。选择器占据了“整个”状态,这似乎是一个事实,它们不能用纯Redux模块化。你会说这是对的吗?你想模块化什么?也许这就是你想要的: