Redux: 示例-如何实现动作类型的命名空间?

创建于 2015-09-23  ·  32评论  ·  资料来源: reduxjs/redux

我花了大约一个星期的时间来尝试找到动作类型命名空间的任何良好实现,并基于转换类(基于包含动作创建者,reduce和其子转换的定义的Transition类)推出了自己的方法,但是这种方法需要过多的样板代码,当您执行许多非常简单的操作时就会出现问题。

在此仓库中可以找到的所有示例以及许多其他示例中,由于某些原因始终会忽略此问题,但这是我开始研究Redux时发现的第一个问题。

我在这里想念什么吗? 你们没有动作类型命名冲突吗?

有人可以更新示例并向我展示您如何处理此问题,或向我指出正确的方向吗?

我的具体情况与在前端有2个单独的管理面板有关。 一个用于测试者,一个用于客户,其状态将存储在商店的不同部分中(“ testerAccount”,“ customerAccount”),但是他们两个都将能够执行类似的操作,例如ADD_VIDEO_UPLOAD,ADD_COMMENT等。

非常感谢您的帮助:)

question

最有用的评论

字符串对于命名空间并不是天生就有害的。 URL是字符串,它们似乎可以正常工作。

所有32条评论

将accountType付诸行动? 另请参阅examples/real-world/reducers以了解如何编写一个reducer工厂并多次使用相同的代码来生成对不同动作做出响应的reducer。

同样,不要忘记动作创建者只是函数,您可以创建返回一堆其他函数的函数。

我认为,如果您提供问题的特定小示例,那么在现有示例中忽略该问题确实会有所帮助。

然后,我们可以帮助您找到简化该特定示例的方法。 对于没有代码的问题,很难提出具体的建议。

@gaearon我知道您要如何解决这个问题,但是我没有尝试创建可重用的reducer,动作创建者或DRY东西,相反。

我正在尝试将动作组彼此隔离,以便我可以让2个人在系统的两个不同部分上工作,并确保由于动作类型名称中缺少命名空间,他们不会开始意外触发彼此的reduce。

希望这使事情更加清楚。

我已经通过将https://www.npmjs.com/package/flux-constant与称为createActionTypes的超简单帮助函数结合使用,在项目中解决了此问题。

本质上,我的代码最终是:

// create-action-types.js
var fluxConstant = require('flux-constant');
module.exports = function (types) {
    return fluxConstant.set(types);
};

// in foo-action-types.js
module.exports = createAtionTypes([
    'ADD_FOO',
    'REMOVE_FOO'
]);

// in some-store.js
var fooActionTypes = require('foo-action-types');
function (state, action) {
    switch(action.type) {
        case fooActionTypes.ADD_FOO: 

        case fooActionTypes.REMOVE_FOO:
   }
}

为什么不将所有动作类型都保留为常量?
然后他们肯定不会冲突,因为您不能两次导出相同的名称。

为了更好地可视化,我们假设这是商店的初始状态:


let initialState = {
  "testerAccount": {
    "videoUploads": [],
    "messages": []
  },
  "customerAccount": {
    "videoUploads": [],
    "messages": []
  },
  "systemUserAccount": {
    "videoUploads": [],
    "messages": []
  }
};

当实现单独的化简为每个部分添加视频或消息时,如何避免动作类型名称冲突?

@gaearon您的建议不能解决问题的核心,更多的是胶带方法,因为随着时间的流逝,您最终会得到一个巨大的类型文件,这会导致很多问题。

它会立即在代码合并过程中引起问题,稍后需要使用命名黑客解决,例如:

ADD_VIDEO_UPLOAD,ADD_TESTER_VIDEO_UPLOAD,ADD_VIDEO_UPLOAD_IN_SOME_SECTION等。

这又是一个很大的头痛。

@koulmomo这正是我在寻找的东西,谢谢:):+1:简单而强大。

@gaearon我认为我们至少应该有一个使用此软件包或新软件包的示例,该软件包可以命名为redux-constant?

我认为在docs / examples中,解决名称空间问题的促进方法应该是新的默认设置。

谢谢您的帮助:)

在您的示例中,我真的不明白为什么不拥有一组类型,一个化简生成器和一组动作创建者。 这些动作将包含accountType属性以区分它们。 动作创建者会接受它作为参数。 减速器工厂将接受accountType并返回仅处理此帐户类型的操作的减速器。

我认为flux-constant在这里不是一个很好的解决方案。 它似乎依赖instanceof支票。 这意味着您无法序列化您的操作,以后再重播它们,因为-糟糕!它们的反序列化类型与生成的类型不匹配。

人们常常试图使Flux“更简单”,而没有意识到他们正在破坏其基本功能

在您的示例中,我真的不明白为什么不拥有一组类型,一个化简生成器和一组动作创建者。 这些动作将包含accountType属性以区分它们。 动作创建者会接受它作为参数。 减速器工厂将接受accountType并返回仅处理具有该帐户类型的操作的减速器。

在您的示例中,明显的对称性使我误解了。 我现在了解到,您的意思是这里没有DRY,并且功能的对称性仅在您在https://github.com/rackt/redux/issues/786#issuecomment -142649749中说过的情况下才显而易见。

我认为这里不需要图书馆。 建立公约! 例如,如果您的子项目或功能是如此分开,则将其作为调用操作类型feature/ACTION_TYPE ,例如testers/UPLOAD_VIDEOcustomers/UPLOAD_VIDEO 。 如果这些“组”实际上对应于磁盘上的实际文件夹(或更好的软件包),则特别好。

违反行为很容易在代码审查中发现。 如果确实愿意,您可以自动执行此操作,但是看不到它带来了手动命名空间。 在每个模块内,您仍希望在一个文件中声明所有常量,以便更轻松地控制功能,防止意外的工作重复并作为文档。

我全都愿意添加一个huge-apps示例,其中包含代码拆分,命名空间操作类型等。但这将是一个单独的问题。

字符串对于命名空间并不是天生就有害的。 URL是字符串,它们似乎可以正常工作。

@gaearon是的,我忘记了基于基于flux-constant的解决方案的序列化问题。

我对基于字符串的命名空间没有任何问题,只要我们可以轻松地以其自身名称区分模块/名称空间即可。

我以前看过基于命名约定的解决方案:

https://github.com/erikras/ducks-modular-redux

但我希望可以根据您的经验,采取更好或“正确”的方式进行此操作。

我想我将尝试将恒定流量方法转换为可以实际以某种方式序列化的方法。

如果您有时间,请添加一个“ huge-apps”示例,因为这将节省其他人很多时间,并且也许会导致建立某种约定,我们以后可以轻松遵循。

再次感谢你 :)

是的,很抱歉,很遗憾,我已经有一段时间没有使用大型应用程序了,即使我这样做了,我实际上还是喜欢我们有一个将常量分为几部分的单个巨型文件,因为它很好地概述了可能发生的情况在应用程序中。

我希望看到这个“问题”在文档中有更详细的解释。 我曾经有过@pbc这样的问题。 我不认为“为什么不将所有动作类型都保留为常数?” 如果您在项目中重复使用多个模块,并且建议“建立约定!”,则可以正常工作听起来非常像CSS领域中的BEM。 但是,我们从BEM转向了CSS模块,我想有些类似于CSS模块的东西,在大型项目中,动作类型也需要哈希类名。

据我所知,无法同时实现序列化和没有冲突。

关于可重用模块的良好约定,可以使用现有的“唯一性提供程序”,例如反向域名或github上的用户名/存储库,或npm上已注册的模块名称。


编辑:CSS模块通过在CSS上定义自定义语言并在预处理过程中为类名加上前缀来实现此目的(归根结底是约定俗成,而是生成的)。

为问题和讨论+1。 对于@pbc这个确切的问题,我也努力。 让我感到困惑的是,我们为减速器提供了CombineReducers,这使状态树变得很漂亮,但另一方面,操作却或多或少地像一个全局变量。 从外观上看,任何类型的命名间隔都必须手动完成。

我想我已经找到了可序列化字符串操作类型的解决方案。 我认为这是我们的思想对某些事物施加人为限制的情况之一。

基本思想是类型常量不需要以任何方式与字符串值相关。 因此,您可以使用随机生成的值,哈希文件路径或其他类型常量字符串值唯一的值。 在化简器中,按名称导入类型常量,然后按名称进行比较。 该名称可能会被其他动作和其他化简器使用,但这并不重要,因为其值将不同。

此处的示例: https :

这是明智的。 您仍然想在动作名称中保留一些易于理解的部分,但是生成唯一的前缀完全可以。

当然,如果您可以证明它们是独特的。
最好的方法仍然是使用现有的唯一性提供程序:反向域名或npm模块名称。

在编写模块时,使用约定进行命名间隔会带来危险,而无法在控制范围之外的其他地方使用。

假设您有一个模块“ Geometry”,其ActionType为“ Area”。 该模块在两个地方使用:

  1. AppA->绘图->几何(名称空间=“绘图/几何/区域”)
  2. AppB->三角->形状->几何(namespace =“ Trigonometry / Shape / Area”)

现在,您有两个相互冲突的名称空间,具体取决于模块的使用位置。

  • 在您的几何模块中为ActionType“ Area”硬编码此完整路径不是一个好主意。
  • 相反,保持名称简单:“区域”。
  • 以与化简器组成相似的方式,通过让每个包含父元素的对象添加前缀来组成名称空间。

我正在尝试以下模式:

为每个包含以下内容的集合类型创建一个目录:

  • consts
  • 减速器
  • 组件
  • 萨加斯

创建以下格式的常量:

// song-store/song-store-consts.js
export const ADD = 'SONG_STORE.ADD'
export const REMOVE = 'SONG_STORE.REMOVE'

在reducer,saga或action import使用常量时,它们全部带有*

// song-store/song-store-actions.js
import * as SONG_STORE from './song-store-consts'

export function addSongStore(name) {
  return {
    type: SONG_STORE.ADD,
    name
  }
}

export function removeSongStore(songStoreId) {
  return {
    type: SONG_STORE.REMOVE,
    songStoreId
  }
}

可悲的是,这对于摇树不是很好。 如果允许ES6,那就更好了:

import { ADD, REMOVE } as SONG_STORE from './song-store-actions'

任何人都知道Webpack 2是否可以智能地摇晃import *以便即使使用*导入时也不会捆绑未使用的导出代码?

这似乎是一个非常普遍的问题,并且经常在我们的办公室中弹出,或者:

  • 名称冲突会导致不良行为。
  • 抱怨与创建唯一动作类型相关的样板。

我们尝试了几种不同的解决方案,但似乎没有任何效果:

  1. 将所有操作类型都保留为一个常数无疑可以简化管理,但是我发现当应用程序扩展时,它变得有些笨拙。
  2. @samsch提到的随机生成的值确实引起了我的注意,并且确实有效,但是是的,失去了人类可读的部分会使其更难以使用。
  3. @philholden的上述评论确实吸引了我,因为我们通常使用功能驱动的体系结构,并且目录结构具有描述性。

经过近几个月的试验,我们决定使用目录结构为操作类型命名。 手动输入这些内容会很快变老,因此我尝试使用__filename做某事,但是由于代码捆绑等原因而无法使用。然后,我制作了第一个Babel插件,该__filenamespace转换为静态字符串,这似乎运行良好。

App/testerAccount/index.js

// Something like this
const ADD_VIDEO_UPLOAD = `${__filenamespace}/ADD_VIDEO_UPLOAD`;
const ADD_COMMENT = `${__filenamespace}/ADD_COMMENT`;

// Will be transformed into something like this
const ADD_VIDEO_UPLOAD = 'App/testerAccount/ADD_VIDEO_UPLOAD';
const ADD_COMMENT = 'App/testerAccount/ADD_COMMENT';

随时尝试一下,希望它有用。 得到任何反馈都是很棒的,特别是作为问题创建者和库创建者的@pbc@gaearon

https://www.npmjs.com/package/babel-plugin-filenamespace

我真的希望有人会提出“标准”。 为此,我们不需要符号或对象,我们只需要一个约定。

我们要去做"featureName$actionType""fileName/ACTION_TYPE""PROJECT.FEATURE.ACTION"吗? 如果我们都可以达成共识,那么共享reduces会更加容易。

我认为,随着其他可用约定的缺乏, Ducks已成为事实上的标准。

const ACTION = 'app/feature/ACTION';足够了。

@gaearon一直提到动作与减速器之间的1:1关系可能是个坏主意,我确实同意。 但是,如果两个不同的视图实际上要调用相同的reducer,该怎么办?
例如。 可以在两个不同的地方启用或禁用一个简单的首选项切换:
/ myaccount / toggleNotification
/ dashboard / toggleNotification
所以我们应该在两个减速器中写相同的内容吗
reducers / notifications.js
抄送: @ samit4mephilholden


换个说法,我认为这是一个好主意,在一个文件中有两个或多个reducer可以使用不同的动作名称来完成相同的工作。 通过仅查看化简器,便可以了解到,从几个不同的位置可以修改特定状态。

无论如何,很想听听专家的意见。 谢谢。

@ivks :这里有几点想法。

首先,您可以从应用程序中的多个位置分派相同的操作。

其次,您肯定可以使用相同的reducer监听多种操作类型,并以相同的方式处理它们。

第三,“ 1:1关系”方面是关于能够让多个切片缩减器侦听同一动作,每个切片缩减器独立更新其状态位。 是的,在很多时候,可能只有一个减速器关心某个给定的动作,但是让多个减速器响应是Redux的一个非常有用的用例。

@markerikson

其次,您肯定可以使用相同的reducer监听多种操作类型,并以相同的方式处理它们。

我实际上是在使用redux-actions,但不知道它有一个实用的方法CombineAction来完成任务。 刚发现它和您上面的陈述只是提及相同。 (本来应该更清楚,对不起)
非常感谢您的回复。

我不认为Ducks在ActionTypes中定义正确:

动作类型必须为npm-module-or-app / reducer / ACTION_TYPE形式

原因是一个动作可能被多个减速器所订阅。 正如@mherodev所说, const ACTION = 'app/feature/ACTION';更有意义,此外,我想添加一个操作模式const ACTION = 'action://app/feature/ACTION';

为什么不只使用全局自增量整数来标识操作类型? 例如

let id = 0

function generateActionType (label /* for readability */) {
  id++
  return `app/feature/${id}/${label}`
}

考虑一下这一点,为了确保我们不与其他开发人员发生冲突,我们正在编写一个lint规则,该规则将查找目录(在该目录中,我们拥有所有化简器,每个化简器都有其自己的操作类型作为常量)

我们正在计划使用lint,以便在存在两个具有相同值的常量时报告违规情况。

如果有任何建议,请告诉我:)

此页面是否有帮助?
0 / 5 - 0 等级