Web开发编程网
分享Web开发相关技术

无需样板即可扩展的Redux命令操作

我应该看这篇文章吗?

我认为,如果您是:

  1. 尝试减少Redux样板;要么
  2. 对改善Redux架构或文件结构感兴趣;要么
  3. 尝试将Redux操作作为“命令”与“事件”进行导航。

关键要点在这篇文章的底部


我最近观看了Yazan Alaboudi的精彩演讲的录音“我们的Redux反模式:可预测的可伸缩性指南”在此处幻灯片)。我真的很喜欢听到和阅读人们对Redux体系结构的想法,这是我已经考虑了很多的东西。

在演讲中,Yazan提供了两个很好的案例:

  1. 将Redux操作作为命令编写1是一种反模式;和
  2. 精心编写的Redux动作应代表业务事件。

在这篇特别的文章中,我将回应其中的第一点,以期在另一篇文章中讨论第二点。

在这里,我的核心论点是:Redux-Leaves解决了Yazan对命令行动的“反模式”批评中的大部分(也许全部)。

我将分为两个部分:

Yazan反对命令行动的情况是什么?

我建议您看一下Yazan自己的解释,但在下面,我将概述我对他所说的话的解释。

范例程式码

Yazan提供了一些命令动作及其后果的示例:

命令操作示例(Redux设置)
  // in scoreboardReducer.js
  const INITIAL_STATE = {
    home: 0,
    away: 0
  };

  function scoreboardReducer(state = INITIAL_STATE, action) {
    switch(action.type) {
      case "INCREMENT_SCORE": {
        const scoringSide = action.payload;
        return { ...state, [scoringSide]: state[scoringSide] + 1};
      }
      default: return state;
    }
  }

  //in crowdExcitmentReducer.js
  const INITIAL_STATE = 0;

  function crowdExcitementReducer(state = INITIAL_STATE, action) {
    switch(action.type) {
      case "INCREASE_CROWD_EXCITEMENT": return state + 1;
      default: return state;
    }
  }
命令操作后果(组件分派)
  // in GameComponent
  class GameComponent extends React.Component {
    scoreGoal() {
      dispatch({ type: "INCREMENT_SCORE", scoringSide: "home"});
      dispatch({ type: "INCREASE_CROWD_EXCITEMENT"});
      // potentially more dispatches
    }

    render() {
      //...
    }
  }

然后在一个关键幻灯片中,他列出了在这些面向命令的示例中看到的一些成本

指挥行动的缺点

这是我对上述每一项的观察(“业务语义”除外,我将在另一篇文章中讨论):

动作与减速器耦合

我认为,在谈到Yazan提供的示例代码时,非常公平地注意到动作与缩减器相关。所述"INCREMENT_SCORE"动作类型看起来完全耦合到scoreboardReducer,并且"INCREASE_CROWD_EXCITEMENT"看起来完全耦合到crowdExcitementReducer

这不是一个好的模式,因为这意味着我们的代码可重用性极低。如果我想增加其他内容,例如体育场观众人数,则我需要使用另一种动作类型"INCREMENT_AUDIENCE_SIZE",即使状态变化会非常相似。

动作太多

同样,当涉及到Yazan的示例代码,我认为这是公平地注意到,更多的动作在分派scoreGoal该功能感觉必要的。如果已达成目标,那么一件事情就已经发生了,但是我们正在触发多个动作。

这不是一个好方法,因为它将使Redux DevTools阻塞很多噪音,并可能导致Redux状态多次更新而不是进行一次大的更新,从而导致不必要的重新渲染。

不清楚为什么状态在改变

我不认为这是个大问题。对我来说,在Yazan的示例代码中,将链接从和链接scoreGoal到我并不难。"INCREASE_SCORE""INCREASE_CROWD_EXCITEMENT"

就不清楚“为什么”的程度而言,我认为可以通过注释更好的代码来解决-这不是Redux中命令操作所特有的情况,而是适用于所有命令式代码的情况。

导致很多样板/不缩放

我认为这两个都是合法的关注点(并且从本质上讲,是相同的关注点):如果每次我们决定要实现状态的新变化时,我们都必须决定新的操作类型并实现一些新的reducer逻辑,我们将快速了解与Redux相关的代码,这意味着它不能很好地扩展。

Redux-Leaves如何解决这些问题?

首先,让我们看一些与前面的示例等效的示例代码:

Redux-Leaves设置
  // store.js

  import { createStore } from 'redux'
  import reduxLeaves from 'redux-leaves'

  const initialState = {
    crowdExcitment: 0,
    scoreboard: {
      home: 0,
      away: 0
    }
  }

  const [reducer, actions] = reduxLeaves(initialState)

  const store = createStore(reducer)

  export { store, actions }
组件分派
  // in GameComponent
  import { bundle } from 'redux-leaves'
  import { actions } from './path/to/store'

  class GameComponent extends React.Component {
    scoreGoal() {
      // create and dispatch actions to increment both:
      //    * storeState.scoreboard.home
      //    * storeState.crowdExcitement
      dispatch(bundle([
        actions.scoreboard.home.create.increment(),
        actions.crowdExcitement.create.increment()
        // potentially more actions
      ]));
    }

    render() {
      //...
    }
  }

这是一个带有类似代码的交互式RunKit游乐场,供您测试和试验。

希望,与Yazan给出的更典型的命令操作示例相比,这种Redux-Leaves设置可以说明一切:

  • 只有一个初始状态和减速器要处理
  • 不再需要手动编写减速器
  • 无需您手动手动编写案例逻辑

现在,我还将介绍它如何解决上述每个特定问题:

动作不再与减速器耦合

Redux-Leaves为您提供了一个increment开箱即actions的动作创建者,可用于的任意状态路径

增加… createdispatch这个动作…
storeState.crowdExcitement actions.crowdExcitement.create.increment()
storeState.scoreboard.away actions.scoreboard.away.create.increment()
storeState.scoreboard.home actions.scoreboard.home.create.increment()

您还会得到一大堆其他默认操作创建者,这些创建者都可以在状态树的任意叶子上实现。

动作太多?将它们捆绑成一个

Redux-Leaves有一个命名的bundle导出,它接受Redux-Leaves创建的一系列动作,并返回一个可以在单个调度中影响所有这些更改的动作

  import { createStore } from 'redux'
  import reduxLeaves, { bundle } from 'redux-leaves'

  const initialState = {
    crowdExcitment: 0,
    scoreboard: {
      home: 0,
      away: 0
    }
  }

  const [reducer, actions] = reduxLeaves(initialState)

  const store = createStore(reducer)

  store.getState()
  /*
    {
      crowdExcitement: 0,
      scoreboard: {
        home: 0,
        away: 0
      }
    }
  */

  store.dispatch(bundle([
    actions.scoreboard.home.create.increment(7),
    actions.scoreboard.away.create.increment(),
    actions.crowdExcitement.create.increment(9001)
  ]))

  store.getState()
  /*
    {
      crowdExcitement: 9001,
      scoreboard: {
        home: 7,
        away: 1
      }
    }
  */

非常清楚地了解正在改变的状态

在Yazan的命令操作示例中,尚不清楚整体存储状态将如何受到调度的影响-哪个得分在哪个状态位递增?

使用Redux-Leaves,actionsAPI意味着您可以非常清楚地知道要更改的状态:您使用属性路径指向要在其上创建操作的状态,就像查看状态树并描述时一样。您想影响哪个状态。

(这不是解决相当的相同点Yazan做,我认为这是在问,“但为什么是我们增加的人群激动呢?” -但是,正如我在表明在讨论这一点,我认为这是开发商作出的责任必要时通过注释清除命令的原因。)

极少的样板

这是获取根减少器和动作创建者所需执行的操作:

import reduxLeaves from 'redux-leaves'

const [reducer, actions] = reduxLeaves(initialState)

而已。两行,其中之一是导入。无需case亲自编写动作常量,创建者或化简语句。

易于扩展

假设我们要引入某种状态来跟踪团队名称。

我们要做的就是改变我们的初始状态。

import reduxLeaves from 'redux-leaves'

const initialState = {
  crowdExcitement: 0,
  scoreboard: {
    home: 0,
    away: 0
  },
+  teams: {
+    home: 'Man Red',
+    away: 'Man Blue'
  }
}

const [reducer, actions] = reduxLeaves(initialState)
const store = createStore(reducer)

…然后我们可以立即开始调度动作以更新该状态,而无需编写任何其他的reducer,动作创建者或动作类型:

store.dispatch(actions.teams.away.create.update('London Blue'))
store.getState().teams.away // => 'London Blue'

外卖


尾注

1如果Redux动作表示要执行某项操作,则可以将其视为命令。Yazan给出的示例是:

{ type: 'SEND_CONFIRMATION' }
{ type: 'START_BILLING' }
{ type: 'SEND_LETTER' }
{ type: 'INCREMENT_SCORE' }
{ type: 'INCREASE_CROWD_EXCITEMENT' }

主要

未经允许不得转载:WEB开发编程网 » 无需样板即可扩展的Redux命令操作
微信扫码关注微信公众号

WEB开发编程网

谢谢支持,我们一直在努力

安全提示:您正在对WEB开发编程网进行赞赏操作,一但支付,不可返还。