@exodus/redux-dependency-injection

中文文档
5.0.0发布于 2026/06/06MITTypeScriptES Module近 30 天下载 2.2万

Reduux 模块的依赖性注射容器

中文使用文档
文档目录8 节
  1. 为什么
  2. Redux 模块定义
  3. 类型脚本帮助 API
  4. 选择器
  5. 行动
  6. 缩小器
  7. 使用量
  8. 解决问题

@ exodus/ redux - 依赖性 - 注入

依赖性注射框架在redux上方.

为什么

大型redux应用有上百个动作,还原器,特别是选择器. 动作甚至不假装支持依赖性注射,减员者不应该有任何依赖性,尽管选择者的设计中考虑到依赖性注射(PcreateSelector(...dependencies, resultFunction)),它们的依赖性通常是: require“d”/ import'ed,这失去了依赖性注射的许多好处.

在雷杜克地外 我们有 @exodus/dependency-injection一个国际奥委会的容器 可以让我们以宣示的方式定义依赖性 然后解析依赖图并即时分出所有节点. 这使得在测试过程中容易地注入假相,不同的平台适配器在不同平台上,并且根据需要不同地执行相同的接口. 但你不是来宣传的...

要适应redux,我们需要首先支持以能够依赖性注射的方式声明redux组件(动作,还原器,选择器). 输入"Redux模块定义".

Redux 模块定义

以下是由 JK Rowling 所写的 redux 模块定义的例子 :

{
  id: 'harryPotter',
  type: 'redux-module',
  // the `initialState` is used to auto-generate a base set of selectors
  initialState: {
    wand: null,
    knownSpells: [],
  },
  // how events get to the redux module is covered further down
  eventReducers: {
    harryGoesToOlivanders: (state, payload) => ({ ...state, wand: payload }),
    harryLearnsSpell: (state, payload) => ({ ...state, knownSpells: [...state.knownSpells, payload] }),
  },
  selectorDefinitions: [
    // vanilla selector
    {
      id: 'knowsBasicSpells',
      resultFunction: (knownSpells, basicSpells) => basicSpells.every((spell) => knownSpells.includes(spell)),
      // the 'knownSpells' dependency selector is auto-generated by @exodus/redux-dependency-injection
      // based on `initialState`
      dependencies: [
        { selector: 'knownSpells' },
        // NOTE: this selector is external to the module
        { module: 'hogwarts' selector: 'basicSpells' },
      ],
    },
    // selector factory
    {
      id: 'knowsSpell',
      selectorFactory: (knownSpellsSelector) => (spell) => createSelector(
        knownSpellsSelector,
        knownSpells =>knownSpells.includes(spell)
      ),
      // the 'knownSpells' dependency selector is auto-generated by @exodus/redux-dependency-injection
      // based on `initialState`
      dependencies: [{ selector: 'knownSpells' }],
    },
  ],
  // ACTIONS ARE (SORT OF) SUPPORTED, BUT NOT RECOMMENDED!
  // all your business logic belongs in `features` and `modules`, NOT in redux actions.
  // support for these may be removed in the future
  actionsDefinition: ({ hogwarts, harryPotter }) => {
    return {
      helpRonWithHomework: (spell) => (dispatch, getState) => {
        if (harry.selectors.knowsSpell(getState())(spell)) {
          dispatch(hogwarts.actions.submitHomework({ author: 'Ron' }))
        }
      }
    }
  },
  eventToActionMappers: {
    harryForgetsSpell: (spell) => ({ type: 'HARRY_POTTER_FORGETS_SPELL', payload: spell }),
  },
  dependencies: [
    'hogwarts',
  ]
}

类型脚本帮助 API

对于 TypeScript 代码, 偏好帮助者 API 而非原始 satisfies 定义:

import {
  defineReduxModule,
  defineSelectorFactory,
  defineSelectorResultFunction,
} from '@exodus/redux-dependency-injection'
import { createSelector } from 'reselect'

const selectorDefinitions = [
  defineSelectorResultFunction({
    id: 'loaded',
    resultFunction: (data: { loaded: boolean }) => data.loaded,
    dependencies: [{ selector: 'data' }],
  }),
  defineSelectorFactory({
    id: 'createById',
    selectorFactory: (dataSelector: (state: unknown) => { id: string }[]) => (id: string) =>
      createSelector(dataSelector, (items) => items.find((item) => item.id === id)),
    dependencies: [{ selector: 'data' }],
  }),
] as const

export default defineReduxModule({
  id: 'example',
  initialState: { data: [] as { id: string }[] },
  eventReducers: {},
  selectorDefinitions,
} as const)

潜入更深一点.

选择器

在此之前

// harry-potter/selectors/knows-basic-spells.js
import { createSelector } from 'reselect'

import basicSpellsSelector from '~/ui/state/hogwarts/selectors/basic-spells'
import harryPotterKnownSpells from './known-spells'

const knowsBasicSpellsSelector = createSelector(
  basicSpellsSelector, // e.g. state => state.hogwarts.basicSpells
  harryPotterKnownSpells, // e.g. state => state.harryPottern.knownSpells
  (basicSpells, knownSpells) => basicSpells.every((spell) => knownSpells.includes(spell))
)

export default knowsBasicSpellsSelector

之后

  1. 我们不出口选择器本身,而是 定义 因此,它可以被国际奥委会的容器瞬间化为懒散。 这也能让我们注射假药 wand 财务报告和审定财务报表 knownSpells 测试中的依赖性.
  2. 我们可以依赖外部选择器,例如 basicSpells 从 hogwarts 模块,不导入它们.
  3. 我们不再进入全球 state。所有数据通过其他选择器访问。 这个 knownSpells 在依赖性中声明的选择器由此库从 initialState。 。 。 .
{
  ...,
  selectorDefinitions: [
    // vanilla selector
    {
      id: 'knowsBasicSpells',
      resultFunction: (knownSpells, basicSpells) => basicSpells.every((spell) => knownSpells.includes(spell)),
      // the 'knownSpells' dependency selector is auto-generated by @exodus/redux-dependency-injection
      // based on `initialState`
      dependencies: [
        { selector: 'knownSpells' },
        // NOTE: this selector is external to the module
        { module: 'hogwarts' selector: 'basicSpells' },
      ],
    },
    // selector factory
    {
      id: 'knowsSpell',
      selectorFactory: (knownSpells) => (spell) => knownSpells.includes(spell),
      // the 'knownSpells' dependency selector is auto-generated by @exodus/redux-dependency-injection
      // based on `initialState`
      dependencies: [{ selector: 'knownSpells' }],
    }
  ]
}

访问状态

如前所述,每件财产 initialState 单独的选择器是自动生成的. 您可以使用这些选择器, 在定义中将它们作为依赖性 .

在下面的例子中,将产生两个州选手, wand 财务报告和审定财务报表 knownSpells, (中文(简体) ), 各返回其状态中的当前值.

const initialState = {
  wand: null,
  knownSpells: [],
}

export default initialState

如果要访问redux模块的整个状态片段,您可以使用 特殊选择器 MY_STATE中定义的字符串常数 redux-dependency-injection。 。 。 .

import { MY_STATE } from '@exodus/redux-dependency-injection'

const someSelectorDefinition = {
  // ...
  dependencies: [
    // ...
    { selector: MY_STATE },
  ],
}

选择定义属性

编号

所需经费

在其他选择者中将其用作依赖时, 确定这个选择者 。 我们也使用 命名公约 对于属于redux模块的选定者.

结果函数

不使用时需要 selectorFactory

这个 resultFunc 定义 重新选择。 。 。 .

此函数接受 产出 中定义的选定者 dependencies。 。 。 .

const definition = {
  id: 'knowsBasicSpells',
  resultFunction: (knownSpells, basicSpells) => basicSpells.every((spell) => knownSpells.includes(spell)),
  dependencies: [
    { selector: 'knownSpells' },
    { module: 'hogwarts' selector: 'basicSpells' },
  ],
}

属于上述定义的客户端使用示例 harryPotter redux 模块 :

import { selectors } from '#/flux'

selectors.harryPotter.knowsBasicSpells(state)('finite')
选择器事实

不使用时需要 resultFunction

此函数接受选择器, 列在 dependencies,而不是输出,返回一个选择器工厂函数.

它是一种比香草选择器更能表现的替代品,它能返回一个函数. 看我们的 选择者最佳做法 更多细节.

import lodash from 'lodash'
import { createSelector } from 'reselect'

const { memoize } = lodash

const definition = {
  id: 'createKnowsCharm',
  selectorFactory: (knownCharmsSelector) =>
    // meomize on charm since it is frequently used
    memoize((charm) =>
      createSelector(knownCharmsSelector, (knownCharms) => knownCharms.includes(charm))
    ),
  dependencies: [{ selector: 'knownCharms' }],
}

属于上述定义的客户端使用示例 harryPotter redux 模块 :

import { selectors } from '#/flux'

selectors.harryPotter.createKnowsCharm('expelliarmus')(state)
依赖关系

选项

允许您通过提供标识对象列表来注入和使用其他选择对象。 该条目将传递给 resultFunction 或者说 selectorFactory 基于他们的立场.

属于模块本身的选择器只需提供选择器的ID即可被引用.

{
  //...
  dependencies: [
    // first arg of resultFunction/selectorFactory
    { selector: 'charmSpells' },
    // second arg of resultFunction/selectorFactory
    { selector: 'curseSpells' },
  ],
}

当引用来自不同模块的选取者时,除了选取者id外,还需要模块id.

{
  //...
  dependencies: [
    // another redux module `hogwarts` that has a `basicSpells` selector
    { module: 'hogwarts', selector: 'basicSpells' }
  ],
}

行动

在此之前

import hogwartsActions from '../hogwarts'
import harryKnowsSpell from '#/harry-potter/selectors/knows-spell'

export const helpRonWithHomework = (spell) => (dispatch, getState) => {
  if (harryKnowsSpell(getState())(spell)) {
    dispatch(hogwartsActions.submitHomework({ author: 'Ron' }))
  }
}

之后

  1. 我们定义行动创造者,而不是即时攻击他们.
  2. 所有依赖都是注入而不是导入的,这使得动作可以测试并避免静态依赖其他模块的动作/选择器.
{
  ...,
  actionsDefinition: ({ hogwarts, harryPotter }) => {
    return {
      helpRonWithHomework: (spell) => (dispatch, getState) => {
        if (harry.selectors.knowsSpell(getState())(spell)) {
          dispatch(hogwarts.actions.submitHomework({ author: 'Ron' }))
        }
      }
    }
  },
  // in case you need to map events to actions for some reason, e.g.
  // because some other consumer expects this action. These are mutually exclusive with `eventReducers`
  eventToActionMappers: {
    harryForgetsSpell: (spell) => ({ type: 'HARRY_POTTER_FORGETS_SPELL', payload: spell }),
  },
  dependencies: ['hogwarts'],
}

缩小器

在此之前

function harryPotterReducer(state, { type, payload }) {
  switch (payload.type) {
    case 'HARRY_POTTER_GOES_TO_OLIVANDERS':
      return { ...state, wand: payload.wand }
    case 'HARRY_POTTER_LEARNS_SPELL':
      return { ...state, knownSpells: [...state.knownSpells, payload.spell] }
    default:
      return state
  }
}

之后

缩小器被宣布为从事件名称到转换函数的映射. 这样可以跳过事件名称映射到reduux动作类型的人工步骤. 这是(有点)有意见的,因为Exodus钱包使用事件来泡出从SDK到UI的日期.

{
  ...,
  eventReducers: {
    harryGoesToOlivanders: (state, payload) => ({ ...state, wand: payload }),
    harryLearnsSpell: (state, payload) => ({ ...state, knownSpells: [...state.knownSpells, payload] }),
  }
}

在某些情况下,可能有必要通过经常性行动而不是源自SDK的事件来吸收数据。 例如,这可能是只与UI相关的数据。 为了支持这一点,一个redux模块也可以定义 actionReducers数字 :

{
  ...,
  actionReducers: {
    USE_DIFFERENT_WAND: (state, payload) => ({ ...state, wand: payload }),
    POTTER_CASTS_SPELL: (state, payload) => ({ ...state, spell: 'lumos' ?? payload }),
  }
}

使用量

参见完整示例 @exodus/wallet-账户 下划线 /redux 文件夹。 这里有一个小例子:

// flux/index.js
import { setupRedux } from '@exodus/redux-dependency-injection'
import basicSpellsSelector from '~/ui/state/hogwarts/selectors/basic-spells'

const dependencies = [
  // external selector, e.g. from a client repo
  {
    id: 'hogwarts.selectors.basicSpells',
    factory: () => basicSpellsSelector,
  },
  harryPotterReduxModule,
]

const {
  reducers: mergedReducers,
  initialState: mergedInitialState,
  selectors,
  handleEvent,
  ioc,
} = setupRedux({
  // legacy reducers
  reducers,
  initialState,
  dependencies,
  logger: console,
})

const store = createStore(mergedReducers, mergedInitialState, enhancers)

const mergedActions = setupActions({ ioc, actions: legacyActionCreators })
const actions = bindAllActionCreators(store, mergedActions)

export { store, actions, selectors }

// to handle an event:
handleEvent('harryLearnsSpell', 'lumos')
// somewhere else in the app
import { selectors } from '#/flux'

selectors.harryPotter.knowsSpell('lumos')

解决问题

选择者是 undefined

这可能是由要求周期引起的。 如果测试不报告这一点,则使用客户端的操纵台,例如移动式的操纵台来方便地找到造成循环的原因.

删除要求的周期应再次提供选择器.

未排放或处理事件

确保事件发射器存在,例如模块生命周期插件中的观察者.

还确保redux模块定义中发射器和事件减少器之间的事件名称相同.

版本与发布记录

@exodus/redux-dependency-injection 共发布 24 个版本,首次发布于 2023/05/08;过去 12 个月发布了 5 个版本。

版本发布日期距今
5.0.0latest2026/06/065 个月前
4.5.02026/05/016 个月前
4.4.02026/04/286 个月前
4.3.02026/04/286 个月前
4.2.02026/04/147 个月前
4.1.22025/08/131.3 年前

关于 @exodus/redux-dependency-injection 的常见问题

@exodus/redux-dependency-injection 是什么?

Reduux 模块的依赖性注射容器

如何安装 @exodus/redux-dependency-injection?

在项目目录运行 npm install @exodus/redux-dependency-injection,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:redux(^4.0.0)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@exodus/redux-dependency-injection 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@exodus/redux-dependency-injection 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@exodus/redux-dependency-injection 可以商用吗?

@exodus/redux-dependency-injection 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@exodus/redux-dependency-injection 的下载量有多少?

根据 npm 官方下载统计,@exodus/redux-dependency-injection 在 2026/10/14 – 2026/11/12 这 30 天内被下载 22,293 次,日均约 743 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@exodus/redux-dependency-injection 还在维护吗?

最新版本 5.0.0 发布于 2026/06/06(5 个月前);至今共发布 24 个版本,其中过去 12 个月发布了 5 个;首次发布于 2023/05/08。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。