
那会是什么?
让我们看看过去几年我的Redux / NGRX应用程序中的reducers的变形。 从Oak switch-case
,继续通过键从对象中进行选择,最后是带有装饰器,二十一点和TypeScript的类。 我们不仅会尝试回顾这条路径的历史,而且还会发现一些因果关系。
如果您以及我在Redux / NGRX中询问如何处理样板的问题,那么本文对您可能会很有趣。
如果您已经使用了通过键从对象中选择减速器的方法并受够了,则可以立即切换到“基于类的减速器”。
巧克力开关盒
通常switch-case
, switch-case
香草的,但是在我看来,这严重地区别于所有其他类型的switch-case
。
因此,让我们看一下异步创建实体(例如Jedi)的典型问题。
const actionTypeJediCreateInit = 'jedi-app/jedi-create-init' const actionTypeJediCreateSuccess = 'jedi-app/jedi-create-success' const actionTypeJediCreateError = 'jedi-app/jedi-create-error' const reducerJediInitialState = { loading: false,
我会很坦率地说,并承认我在实践中从未使用过switch-case
。 我想相信我甚至列出了这样做的原因:
switch-case
太容易破坏:您可以忘记插入break
,也可以忽略default
。switch-case
太冗长了。switch-case
几乎 switch-case
O(n)。 这本身不是很重要,因为 Redux本身并不夸耀惊人的性能,但是这个事实激怒了我内在的美丽鉴赏家。
Redux官方文档提供了梳理所有这些的逻辑方法-通过键从对象中选择一个减速器。
通过键从对象选择减速器
这个想法很简单-每个状态更改都可以通过状态和动作的功能来描述,每个这样的功能都有一个与其对应的特定键(动作中的type
字段)。 因为 type
是一个字符串,没有什么可以阻止我们为所有这样的函数找出一个对象,其中键是type
,而值是纯状态转换函数(缩减器)。 在这种情况下,当新操作到达根减速器时,我们可以通过键(O(1))选择必要的减速器。
const actionTypeJediCreateInit = 'jedi-app/jedi-create-init' const actionTypeJediCreateSuccess = 'jedi-app/jedi-create-success' const actionTypeJediCreateError = 'jedi-app/jedi-create-error' const reducerJediInitialState = { loading: false, data: [], error: undefined, } const reducerJediMap = { [actionTypeJediCreateInit]: (state) => ({ ...state, loading: true, }), [actionTypeJediCreateSuccess]: (state, action) => ({ loading: false, data: [...state.data, action.payload], error: undefined, }), [actionTypeJediCreateError]: (state, action) => ({ ...state, loading: false, error: action.payload, }), } const reducerJedi = (state = reducerJediInitialState, action) => {
最可口的是, reducerJedi
内部的逻辑对于任何reducer都是相同的,我们可以重用它。 甚至还有一个redux-create-reducer纳米库 。
import { createReducer } from 'redux-create-reducer' const actionTypeJediCreateInit = 'jedi-app/jedi-create-init' const actionTypeJediCreateSuccess = 'jedi-app/jedi-create-success' const actionTypeJediCreateError = 'jedi-app/jedi-create-error' const reducerJediInitialState = { loading: false, data: [], error: undefined, } const reducerJedi = createReducer(reducerJediInitialState, { [actionTypeJediCreateInit]: (state) => ({ ...state, loading: true, }), [actionTypeJediCreateSuccess]: (state, action) => ({ loading: false, data: [...state.data, action.payload], error: undefined, }), [actionTypeJediCreateError]: (state, action) => ({ ...state, loading: false, error: action.payload, }), })
似乎什么也没发生。 的确,一勺蜂蜜并非没有一桶焦油:
- 对于复杂的异径管,我们必须留下评论,因为 这种方法没有提供开箱即用的方式来提供一些说明性的元信息。
- 带有大量化简键和键的对象无法很好地读取。
- 每个异径管只有一把钥匙。 但是,如果您想为多个动作游戏运行同一个减速器,该怎么办?
当我转向基于班级的减速器时,我差点幸福的泪水,下面我将解释原因。
基于类的减速器
包子:
- 类方法是我们的简化器,方法有名称。 只是非常简单的元信息,可以说明该减速器的功能。
- 可以修饰类方法,这是链接化简器及其相应动作(即动作,而不仅仅是一个动作!)的一种简单的声明方式。
- 在后台,您可以使用所有相同的对象获得O(1)。
最后,我想得到类似的东西。
const actionTypeJediCreateInit = 'jedi-app/jedi-create-init' const actionTypeJediCreateSuccess = 'jedi-app/jedi-create-success' const actionTypeJediCreateError = 'jedi-app/jedi-create-error' class ReducerJedi {
我看到目标,没有障碍。
步骤1.装饰器@Action
。
我们需要在该装饰器中粘贴任意数量的动作游戏,并将这些服务另存为一些元信息,以便以后访问。 为此,我们可以使用修补Reflect的出色的polyfill 反射元数据 。
const METADATA_KEY_ACTION = 'reducer-class-action-metadata' export const Action = (...actionTypes) => (target, propertyKey, descriptor) => { Reflect.defineMetadata(METADATA_KEY_ACTION, actionTypes, target, propertyKey) }
第2步。实际上,将类变成化简器。
画一个圆,画一秒钟,现在再加上一点魔法,然后得到一只猫头鹰!
众所周知,每个化简器都是一个纯函数,它接受当前状态和操作并返回新状态。 当然,一个类是一个函数,但并不完全是我们需要的一个函数,如果没有new
则无法调用ES6类。 通常,我们需要以某种方式对其进行转换。
因此,我们需要一个函数,该函数将采用当前类,遍历其每个方法,收集具有操作类型的元信息,收集具有reducer的对象并从该对象创建最终的reducer。
让我们从收集元信息开始。
const getReducerClassMethodsWthActionTypes = (instance) => {
现在我们可以将结果集合转换为对象
const getReducerMap = (methodsWithActionTypes) => methodsWithActionTypes.reduce((reducerMap, { method, actionType }) => { reducerMap[actionType] = method return reducerMap }, {})
因此,最终功能可能如下所示:
import { createReducer } from 'redux-create-reducer' const createClassReducer = (ReducerClass) => { const reducerClass = new ReducerClass() const methodsWithActionTypes = getReducerClassMethodsWthActionTypes( reducerClass, ) const reducerMap = getReducerMap(methodsWithActionTypes) const initialState = reducerClass.initialState const reducer = createReducer(initialState, reducerMap) return reducer }
接下来,我们可以将其应用于我们的ReducerJedi
类。
const reducerJedi = createClassReducer(ReducerJedi)
第3步。我们看看结果如何。
如何生活?
我们在幕后留下了一些东西:
- 如果相同的动作类型对应多个减速器,该怎么办?
- 开箱即用添加沉浸感将是很棒的。
- 如果我们想使用类来创建动作怎么办? 还是功能(动作创建者)? 我希望装饰者不仅可以接受动作类型,还可以接受动作创建者。
一个小型的reducer-class库具有所有这些功能以及其他示例。
值得注意的是,将类用于减速器的想法并不新鲜。 @amcdnl曾经创建了一个很棒的ngrx-actions库,但是似乎他现在已经对其评分并切换到NGXS 。 另外,我想要更严格的键入并以特定于Angular功能的形式重置镇流器。 这里列出了reducer-class和ngrx-actions之间的主要区别。
如果您喜欢减速器类的概念,那么您可能还希望使用类进行操作。 看一下flux-action-class 。
希望您不要白白浪费时间,并且这篇文章至少对您有用。 请踢并批评。 我们将学习一起更好地编码。