提问者:小点点

如何让React组件智能地在用户交互上分派Redux操作,而不传递不影响视觉外观的道具


TL/DR:React组件有两种代码:

  1. 绘制组件的渲染代码,这取决于影响组件视觉外观的某些道具(称为“视觉道具”),以及
  2. 事件处理代码,例如onclick处理程序,它依赖于某些不影响组件视觉外观的道具(称之为“事件道具”)

当事件道具改变时,它们会导致组件重新渲染,即使它的外观没有改变。唯一改变的是它未来的事件处理行为。

删除事件道具以避免不必要的重新渲染,同时仍然允许智能事件处理的最佳实践是什么?

我的问题与这个关于如何为哑反应组件提供处理程序的问题略有不同;请参见下面的解释。

我有一个包含许多React组件的应用程序(数百到数千个SVG元素;它是一个CAD应用程序)。

此应用程序中有许多编辑模式(想象一下像Inkscape这样的绘图程序):根据编辑模式,您可能需要左键单击以选择对象,或拖动以绘制选择轮廓矩形,或对根据编辑模式,单击的组件。

在我最初的架构中,这些组件中的每一个都有当前的编辑模式作为道具。每个组件都将使用mode prop来决定如何响应诸如单击之类的事件:根据当前模式,为响应单击而调度不同种类的Redux操作。这意味着,每次用户切换编辑模式时,每个组件都会重新渲染,即使它们在视觉上都不会改变。在大型设计中,重新渲染需要几秒钟。

我修改了它以提高性能。现在,每个组件都更笨了:它们都不知道编辑模式。但这意味着他们不知道如何应对点击。在某些情况下,我解决了这个问题,每次发送一个“更愚蠢”的动作,本质上说是“我被点击了”。中间件拦截此操作,在Redux存储中查找编辑模式,并基于编辑模式发送适当的智能操作。在其他情况下,我只是让组件分派原始操作(例如,Select),即使该操作可能对当前编辑模式无效,并且类似地,如果该操作对当前编辑模式无效,则依赖中间件拦截并停止该操作。

这种解决方案感觉不雅观。现在,更多的操作被调度,尽管它们中的大多数都被丢弃了。这和我在中间件介绍/教程中看到的完全不同,其中主要讨论了异步操作的好处(我不需要任何异步操作,因为这些操作通常不会与网络或文件进行通信)以及日志记录等副作用(这里没有副作用;我只是希望用户交互触发正常的Redux操作以进行调度)。

我觉得更好的解决方案是在事件处理代码中作为全局变量访问Redux存储。我知道这显然不安全,因为它打破了“React视图应该是其道具和状态的确定函数”的规则。但是使用事件处理代码感觉更安全。

我意识到将单击处理程序作为道具传递给“非常愚蠢”的React组件是很常见的(例如,这个stackoverflow答案),但我不认为这是一个解决方案。如果处理程序将编辑模式编码为绑定值,那么当编辑模式更改时,处理程序本身需要更改,由于处理程序是一个道具,因此需要重新呈现组件。所以我认为我描述的这个问题与处理程序是作为道具传递到组件中,还是专门为组件编写正交。

总的来说,我认为有三种选择:

>

让React组件“杂乱地”调度操作,并依赖中间件(可以访问Redux存储)在必要时停止和/或转换操作。(当我实现它的时候,很难理解,并且把许多不相关的应用程序逻辑放在一个地方,感觉它不属于这个地方。这也使得Redux的操作历史更加混乱,使得使用Redux DevTools调试变得更加困难,并且不是我在任何关于Redux中间件的留档/教程中看到的模式。)

允许事件处理程序代码(与呈现代码不同)作为全局变量访问Redux存储,以智能地决定要分派的操作。(看起来还可以,但这样使用全局变量让我感到害怕,我担心这可能会导致我没有看到的问题。)

有没有第四个选择我错过了?


共1个答案

匿名用户

我有一个想法,如何以一种接近Redux精神的方式解决这个问题。(尽管我仍然倾向于在事件处理程序中访问全局变量来解决问题。)

Redux有一些“动作创建者”的概念,这是一个返回动作对象的函数。对我来说,这似乎总是一个不必要的抽象层。但也许这里也可以使用类似的想法。(我使用的是Dart,不是Javascript,所以下面的代码是Dart;希望答案是有意义的。)

我们的想法是在ActionCreator中创建一种新类型的动作

A create(AppState state)

换句话说,它接受整个AppState并返回一个Action。这使得它可以进行必要的数据查找。作为对象,它可以包含描述从实例化它的代码(通常是视图事件处理程序代码)中收集的数据的字段。例如,它可以引用一个Sseltable来选择。create()返回null或一些特殊的值来指示该操作应该被丢弃。

例如,如果我们有一个单击处理程序,我们会调度一个ActionCreator

class Select {
  final Item item_clicked;

  Select(this.item_clicked);
}

class ClickedAction implements ActionCreator<Select> {
  final Item item_clicked;

  ClickedAction(this.item_clicked);

  Select create(AppState state) =>
    state.ui_state.select_mode_is_on ? Select(this.item_clicked) : null;
}

// ...

onClick = (event) {
  props.dispatch(ClickedAction(props.item));
}

在中间件中,一旦我们能够访问完整的状态,这可以转化为具体的行动,但前提是它是合法的。但好的是,下一段代码是通用的,可以处理任何这样的ActionCreator,因此每当我创建一个需要“有条件地调度”的新Action时,我不必记得一直编辑这段代码。

action_creator_middleware(Store<AppState> store, action, NextDispatcher next) {
  if (action is ActionCreator) {
    var maybe_action = action.create(store.state);
    if (maybe_action != null) {
      dispatch(maybe_action);
    }
  } else {
    next(action);
  }
}

这样做的缺点是,它仍然派遣了比我们真正需要的更多的行动;大多数会被扔掉。它是我所需要的一个“更干净”的实现,但我仍然认为对于异步事件处理程序,作为全局变量访问Redux存储可能非常好。如果视图代码超出了它的React props并访问了全局变量,我看不出会有什么问题。