Redux: 将您的所有状态存储在一个不可变原子中的缺点是什么?

创建于 2016-02-10  ·  91评论  ·  资料来源: reduxjs/redux

我了解这是所有redux的基本原理,并且它具有所有众所周知的好处。

但是,我觉得redux缺少一个地方,那就是它没有公开描述使用这种架构的概念上的缺点。 也许这是一个不错的选择,因为您希望人们不要因为负面因素而回避。

我很好奇,因为它不仅适用于redux,而且在某种程度上适用于Omdatomic等其他东西,也涉及将数据库内向外的讨论。 我喜欢redux,我喜欢Om,我对datomic感兴趣,我真的很喜欢那个演讲。

但是,对于我所做的所有搜索,很难找到批评单个不变商店的人。 是一个示例,但似乎redux的冗长性比实际体系结构有更多问题。

我唯一能想到的是,可能需要更多的内存来保存状态的副本,或者使用redux快速进行原型制作变得有些困难。

因为你们对此的思考可能比我多得​​多,您能帮我阐明一下吗?

discussion docs question

最有用的评论

Redux基本上是事件采购,只有一个项目可以使用。

在分布式系统中,通常有一个事件日志(如Kafka),并且多个使用者可以将其投影/减少到托管在不同服务器上的多个数据库/存储中(通常,数据库副本实际上是一个简化器)。 因此,在分布式世界中,负载和内存使用情况是...分布式的,而在Redux中,如果您有数百个都具有其本地状态的小部件,则所有这些部件都在单个浏览器中运行,并且由于不可变,每个状态更改都会产生一些开销数据更新。

在大多数情况下,开销并不是什么大问题,但是,当将文本输入安装为不太好的状态形状,并在速度较慢的移动设备上快速键入时,性能并不总是足够好。

根据我的经验,从最顶层渲染该状态并不方便,而且性能也不总是足够好(至少使用React可能不是最快的VDom实现)。 Redux使用connect解决了这个问题,但随着连接数量的增加,它可能会成为瓶颈。 有解决方案

像ImmutableJS也持久数据结构并不总是提供一些操作方面的最佳表现,就像在一个大名单在随机指数增加的项目(见我的经验渲染大名单这里

Redux包括事件日志和投影,因为它很方便并且在大多数用例中都可以使用,但是可以在redux外部维护一个事件日志,并将其投影到2个或更多redux存储中,将所有这些redux存储添加到在不同的键下反应上下文,并通过指定我们要连接的商店来减少开销。 这是绝对可能的,但是这将使api和devtools难以实现,因为现在您需要在存储和事件日志之间进行明确区分。


我也同意@jquense

易于水合作用,快照,时间旅行等好处只有在没有其他重要国家生活的地方时才起作用。 在React的上下文中,这意味着您需要存储状态,该状态正确地属于商店中的组件,否则会失去很多好处。 如果您确实想将所有内容放到Redux中,那么它通常会变得繁琐且冗长,并增加了令人讨厌的间接级别。

将任何状态安装到Redux存储都需要更多样板。 榆木建筑可能以更优雅的方式解决了这一问题,但也需要大量样板。

但是也不可能或不能使所有状态都受控。 有时,我们使用现有的库很难为其建立声明式接口。 也很难有效地将某些状态挂载到redux存储,包括:

  • 文字输入
  • 滚动位置
  • 视口大小
  • 滑鼠位置
  • 插入位置/选择
  • 画布dataUrl
  • 不可序列化状态
  • ...

所有91条评论

这是一个很好的问题。 明天我将尝试写一个全面的答案,但我想首先听听一些用户的意见。 以后将此讨论总结为正式文档页面将是很好的。 我们最初没有这样做是因为我们不知道缺点。

非常感谢你! 我真的很想听听这件事。

顺便说一句,我鼓励每个想为这次讨论做出贡献的人也要介绍他们使用Redux构建的内容,以便更容易地理解项目的范围。

b / c项目(例如OM和redux以及其他单状态原子库)是一个棘手的问题,正在积极缓解不利因素。 没有不变性约束和门控受控访问,单个状态原子与将所有数据附加到window (其缺点是众所周知的)

但是,对于Redux方法而言,最大的缺点是单状态原子是全有还是全无。 易于水合作用,快照,时间旅行等的好处只有在没有其他重要国家生活的地方时才起作用。 在React的上下文中,这意味着您需要存储状态,该状态正确地属于商店中的组件,否则会失去很多好处。 如果您确实想将所有内容放到Redux中,那么它通常会变得繁琐且冗长,并增加了令人讨厌的间接级别。

但是,这些不利因素都没有对我们特别禁止:)

因此,我一直在盯着Redux和这种架构风格来建模游戏状态。 我对在浏览器环境中创建Civ风格的模拟世界以及更简单地完整记录棋盘游戏历史感兴趣,这对某些人来说可能是不寻常的用例,但我怀疑有人会想要我停止。

在这种情况下,我对管理历史数据结构的大小,将其部分移动到服务器或磁盘以进行保存,还原特定段等方面的工作很感兴趣。

作为新用户,我一直在努力的事情是试图克服Redux中的极端(imho)编码样式更改以及同时的概念更改。 即使在现在,一个月,它仍然有些令人生畏,在尝试深入研究它之后,它会发生变化。 我正在查看https://github.com/ngrx/store,以了解概念,但具有更熟悉的样式/语法和其他隐含/提供的框架。

我知道,对于某些人来说,框架是关键。 但是我们当中有些人需要这些; 很少有人会在游戏中处于顶峰,足以拥有有用的强有力的意见,所以框架总比在黑暗中摸索要好,您知道吗? 所以这是我的观点,一个中级,中年的程序员,不熟悉这些概念:)

基本上,Redux和您的视频教会了我如何使用原始JavaScript进行操作,但是作为一个经验不足的程序员,我仍然不知道如何在没有进一步指导的情况下交付产品,因此,在我看到完成示例之前,我只是像这样站着:

不是您的错,而是我很乐意帮助解决的问题:)

我认为目前我仍然有一个最大的问题,那就是诸如功能,实例或promise之类的不可序列化的项目应该放在哪里? 那天晚上在Reactiflux上想知道这个问题,但没有得到任何好的回应。 还只是看到有人发布了http://stackoverflow.com/questions/35325195/should-i-store-function-references-in-redux-store

多个商店的用例(请记住,这是我能想到的最佳案例):

将多个子应用程序组合在一起的应用程序。 想一想My Yahoo或其他可自定义的首页产品。 每个小部件都需要保持自己的状态,而这两者之间不能重叠。 每个小部件可能都有一个产品团队,因此他们可能不知道其他人的代码对环境的影响。

聚集起来,您可能会在Redux中通过违反一些规则并谨慎地在类似React的上下文中传播这些存储来实现这一目标。 但是,默认情况下使用处理多个状态原子的框架可能会更容易。

@gaearon我们正在建立交易平台。 我已经向您说明了为什么一家商店不适合我们(在React.js在圣彼得堡见面之后)。 我将尝试再次详细解释(在中等水平的博客文章中?),但由于我的英语水平,我可能需要帮助:)如果我将其发送给您或此处的其他人以直接在Twitter上进行审核,可以吗?消息何时会完成? 虽然我不能说出确切的日期,但是我会尽力写这篇文章(很可能是本月底)。

是的,我们仍然违反了@timdorr所说的一些文档规则,仍然在多个商店中使用Redux(为此,我们无法舒适地使用react-redux因为如果我们需要来自各个商店的数据如果是单店)

Redux基本上是事件采购,只有一个项目可以使用。

在分布式系统中,通常有一个事件日志(如Kafka),并且多个使用者可以将其投影/减少到托管在不同服务器上的多个数据库/存储中(通常,数据库副本实际上是一个简化器)。 因此,在分布式世界中,负载和内存使用情况是...分布式的,而在Redux中,如果您有数百个都具有其本地状态的小部件,则所有这些部件都在单个浏览器中运行,并且由于不可变,每个状态更改都会产生一些开销数据更新。

在大多数情况下,开销并不是什么大问题,但是,当将文本输入安装为不太好的状态形状,并在速度较慢的移动设备上快速键入时,性能并不总是足够好。

根据我的经验,从最顶层渲染该状态并不方便,而且性能也不总是足够好(至少使用React可能不是最快的VDom实现)。 Redux使用connect解决了这个问题,但随着连接数量的增加,它可能会成为瓶颈。 有解决方案

像ImmutableJS也持久数据结构并不总是提供一些操作方面的最佳表现,就像在一个大名单在随机指数增加的项目(见我的经验渲染大名单这里

Redux包括事件日志和投影,因为它很方便并且在大多数用例中都可以使用,但是可以在redux外部维护一个事件日志,并将其投影到2个或更多redux存储中,将所有这些redux存储添加到在不同的键下反应上下文,并通过指定我们要连接的商店来减少开销。 这是绝对可能的,但是这将使api和devtools难以实现,因为现在您需要在存储和事件日志之间进行明确区分。


我也同意@jquense

易于水合作用,快照,时间旅行等好处只有在没有其他重要国家生活的地方时才起作用。 在React的上下文中,这意味着您需要存储状态,该状态正确地属于商店中的组件,否则会失去很多好处。 如果您确实想将所有内容放到Redux中,那么它通常会变得繁琐且冗长,并增加了令人讨厌的间接级别。

将任何状态安装到Redux存储都需要更多样板。 榆木建筑可能以更优雅的方式解决了这一问题,但也需要大量样板。

但是也不可能或不能使所有状态都受控。 有时,我们使用现有的库很难为其建立声明式接口。 也很难有效地将某些状态挂载到redux存储,包括:

  • 文字输入
  • 滚动位置
  • 视口大小
  • 滑鼠位置
  • 插入位置/选择
  • 画布dataUrl
  • 不可序列化状态
  • ...

我刚刚在SO上看到的这个问题:

http://stackoverflow.com/questions/35328056/react-redux-should-all-component-states-be-kept-in-redux-store

我回避了一些关于管理UI状态以及UI状态是属于组件还是进入商店的困惑。 #1287为该问题提供了一个很好的答案,但最初并不那么明显,可以进行辩论。

此外,如果您尝试实现类似https://github.com/ericelliott/react-pure-component-starter这样的内容,其中每个组件都是纯净的,并且在自己的状态下没有发言权,则这可能是一个障碍。 。

当我需要管理多个会话的数据时,我发现很难使用redux的单状态树。 我有任意数量的具有相同形状的分支,并且每个分支都有多个唯一的标识符(取决于数据的来源,您将使用不同的键)。 当遇到那些需求时,reducer函数和reselect函数的简单性很快消失了-必须编写自定义的getters / setter方法来针对特定分支,而在其他情况下简单且可组合的环境中感觉过于复杂和平坦。 我不知道多个存储是否是满足该需求的最佳选择,但是一些用于将会话(或任何其他形状相同的数据)管理到单个状态树中的工具会很好。

异化器之间状态键冲突的可能性增加。
数据突变和声明离使用数据的代码很远(当我们编写代码时,我们尝试将变量声明放在我们使用它们的位置附近)

让我以我的特定用例作为开头:我将Redux与virtual-dom一起使用,其中我的所有UI组件都是纯功能性的,并且无法为各种组件提供本地状态。

话虽这么说,最困难的部分肯定是绑在动画状态。 以下示例考虑了动画状态,但是其中很多可以推广到UI状态

Redux的动画状态不佳的一些原因:

  • 动画数据频繁更改
  • 特定于动画的动作会污染应用程序中的动作历史记录,从而很难以有意义的方式回滚动作
  • 如果存在许多特定于组件的动画,则Redux状态树将开始镜像组件树
  • 甚至animationStartedanimationStopped类的基本动画状态也开始将状态耦合到您的UI。

另一方面,完全在Redux状态树之外构建动画状态肯定存在挑战。

如果尝试通过操作DOM自己制作动画,则必须提防类似这样的事情:

  • virtual-dom diff不会考虑您的动画处理,例如您在节点上设置的自定义样式-如果您在DOM节点上设置了某些样式,则如果希望它们消失,则必须自己删除它们
  • 如果您在文档本身上执行动画,则必须痛苦地意识到虚拟域的更新-您可以在一个DOM节点上启动动画,而只是发现该DOM节点的内容在动画制作过程中被更改了。
  • 如果在文档本身上执行动画,则必须警惕在节点上设置的虚拟dom覆盖样式和属性,并且必须警惕由virtual-dom覆盖的样式和属性。

而且,如果您尝试让virtual-dom处理所有DOM操作(您应该这样做),但又没有在Redux中保持动画状态,那么您将遇到以下难题:

  • 如何将动画状态显示给组件? 您组件的本地状态? 其他一些全球状态?
  • 通常,您的状态逻辑在Redux reducer中,但是现在您必须直接向组件中添加很多渲染逻辑,以了解它们如何响应您的状态进行动画处理。 这可能是非常具有挑战性和冗长的。

当前有一些很棒的项目,例如react-motion,在解决这个问题方面向前迈进了一大步,但是React并不是Redux所独有的。 我感觉也不应该,很多人都带了自己的视图层并试图与Redux集成。

对于任何好奇的人,我为Redux + virtual-dom找到的最佳解决方案是保留两个状态原子:Redux保留核心应用程序状态,保留核心逻辑以响应操作等来操纵该状态,等等。是一个拥有动画状态的可变对象(我使用可移动的)。 然后,诀窍是订阅Redux状态更改和动画状态更改,并将UI呈现为两者的函数:

/* Patch h for jsx/vdom to convert <App /> to App() */
import h from './h'
import { diff, patch, create } from 'virtual-dom'
import { createStore } from 'redux'
import { observable, autorun } from 'mobservable'
import TWEEN from 'tween.js'
import rootReducer from './reducers'
import { addCard } from './actions'
import App from './containers/App'

// Redux state
const store = createStore()

// Create vdom tree
let tree = render(store.getState())
let rootNode = create(tree)
document.body.appendChild(rootNode)

// Animation observable
let animationState = observable({
  opacity: 0
})

// Update document when Redux state 
store.subscribe(function () {
  // ... anything you need to do in response to Redux state changes
  update()
})

// Update document when animation state changes
autorun(update)

// Perform document update with current state
function update () {
  const state = store.getState()
  let newTree = render(state, animationState)
  let patches = diff(tree, newTree)
  rootNode = patch(rootNode, patches)
  tree = newTree
}

// UI is a function of current state (and animation!)
function render (state, animation = {}) {
  return (
    <App {...state} animation={animationState} />
  )
}

// Do some animations
function animationLoop (time) {
  window.requestAnimationFrame(animationLoop)
  TWEEN.update(time)
}
animationLoop()

new TWEEN.Tween(animationState)
      .to({ opacity: 100 }, 300)
      .start()

// Or when you dispatch an action, also kick off some animation changes...
store.dispatch(addCard())
/* etc... */

谢谢大家的好评! 让他们继续来。

需要注意的一件事是,我们不打算将Redux用于_all_状态。 一切似乎对应用程序都重要。 我认为输入和动画状态应该由React处理(或其他短暂状态抽象)。 Redux在获取数据和本地修改模型等方面效果更好。

@taggartbg

当我需要管理多个会话的数据时,我发现很难使用redux的单状态树。 我有任意数量的具有相同形状的分支,并且每个分支都有多个唯一的标识符(取决于数据的来源,您将使用不同的键)。 当遇到那些需求时,reducer函数和reselect函数的简单性很快消失了-必须编写自定义的getters / setter方法来针对特定分支,而在其他情况下简单且可组合的环境中感觉过于复杂和平坦。

您介意创建一个问题来更详细地描述您的用例吗? 可能是有一种简单的方式来组织丢失的状态形状。 通常,具有相同状态形状但由不同异化器管理的多个分支是一种反模式。

@istarkov

异化器之间状态键冲突的可能性增加。

您介意更详细地说明这种情况吗? 通常,我们建议您仅在任何状态片上运行单个reducer。 碰撞如何发生? 您是否正在对该州进行多次通行证? 如果是这样,为什么?

@gaearon @istarkov :也许这意味着各种插件和相关库可能

是的,即使这不是@istarkov的意思,这也是一个好点。 (我不知道。)通常,库应该提供一种在树中任何地方安装reducer的方法,但是我知道有些库不允许这样做。

@gaearon

您介意创建一个问题来更详细地描述您的用例吗? 可能是有一种简单的方式来组织丢失的状态形状。 通常,具有相同状态形状但由不同异化器管理的多个分支是一种反模式。

我很乐意! 有空我会做的。

我认为它绝对被视为反模式,尽管我有一个共享的简化器来管理这些分支。 尽管如此,我觉得redux提供的范例距离将相同分支序列化/反序列化为不可变状态的完美用例并不遥远。 我不明白为什么用某种假定的逻辑通过某个键针对特定分支构建像重新选择之类的东西时会与redux的精神背道而驰。

我很乐意在脱机聊天,我很可能是错的。

@taggartbg :完全不知道您的代码在这里是什么样子。 您是在谈论尝试处理看起来像这样的状态吗?

{ groupedData : { first : {a : 1, b : 2}, second : {a : 3, b : 4}, third : {a : 5, b, 6} }

似乎您可以通过使用单个化简函数将每个组的ID密钥作为每个动作的一部分来处理化简。 事实上,你看到这样https://github.com/erikras/multireducerhttps://github.com/lapanoid/redux-delegatorhttps://github.com/reducks/redux-multiplex ,或https://github.com/dexbol/redux-register?

此外,在重新选择方面,React-Redux现在支持按组件选择器这一事实可能会有所帮助,因为这会改善您根据组件属性进行选择的方案。

@gaearon

需要注意的一件事是我们不打算将Redux用于所有状态。 一切似乎对应用程序都重要。 我认为输入和动画状态应该由React处理(或其他短暂状态抽象)。 Redux在获取数据和本地修改模型等方面效果更好。

阅读文档和教程的React开发人员已经可以使用此短暂状态抽象,因此在考虑Redux对于项目有意义的地方时,可能已经想到了这一点。

但是,从对React几乎没有经验或没有经验的开发人员希望对UI采用功能性方法的角度来看,哪个状态属于Redux可能并不明显。 我现在已经完成了几个项目,起初Redux和virtual-dom就足够了,但是随着应用程序变得越来越复杂,其他“短暂状态抽象”成为了必要。 初次添加带有一些基本动画标记的动画减速器时,这种情况并不总是很明显,但是后来变得相当痛苦。

对于文档来说,哪个状态适合Redux以及哪个状态可以通过其他工具更好地解决可能会很好。 对于React开发人员而言,这可能是多余的,但对于其他开发人员而言,在考虑Redux和规划其应用程序架构时可能会非常有益。

编辑:问题的标题是“将您的所有状态存储在一个不可变原子中的缺点是什么?” 这种“单个不变原子中的所有状态”正是这种措辞,最终在Redux树中得到了许多错误的状态。 该文档可以提供一些明确的示例,以帮助开发人员避免这种陷阱。

@gaearon在这里

我知道这与Redux没有100%的关系,但是我希望从使用Observable流作为应用程序周围的数据流的实现中提供不同的观点(对于Cycle,该应用程序是一组纯函数)。

因为“周期”中的所有内容都是可观察的,所以我很难让状态从一条路线更改转移到另一条路线。 这是由于一旦更改路线后,Observable状态就没有订阅者,并考虑了为什么不实现单个原子状态,因此在我的应用程序中任何时候更改状态,它都会报告回顶层存储,绕过任何管道(查看糟糕的情况)此处的图纸)。

使用Cycle时,因为在Drivers中会发生副作用,所以通常必须使该循环从顶级驱动程序循环到组件,然后再返回,因此我只想跳过该循环,直接回到组件侦听。 以Redux为灵感来源。

并非不是对与错,但是现在我进行管道传输,并且我们已经找到了状态驱动程序,能够根据需要将状态传输到不同的组件,对于重构,原型设计以及深刻的理解非常灵活每个状态的来源,每个消费者以及他们如何相处。

同样是该应用程序的新手(如果他们当然理解了Cycle),则可以轻松地将各个点连接起来,并在其中很快地绘制可视化表示形式,以了解如何处理和传递状态以及所有这些来自代码。

抱歉,如果这完全超出了上下文,想演示不同的范例如何进行类似的对话:笑脸:

@timdorr

每个小部件都需要保持自己的状态,而这两者之间不能重叠。 每个小部件可能都有一个产品团队,因此他们可能不知道其他人的代码对环境的影响。

我认为,当小部件具有其自己的状态(甚至可能是其自己的(redux?)存储)时,它会更好,因此它可以在任何(mashup-)应用程序中独立工作,并且仅接收其某些属性。 考虑一下天气小部件。 它本身可以获取和显示数据,并且仅接收城市,高度和宽度等属性。 另一个示例是Twitter小部件。

@chicoxyzzy相关: https :

很棒的讨论!

主要是,我发现将多个应用程序/组件/插件组合起来很不方便。

我不能只将自己构建的模块放入另一个模块/应用程序中,因为我需要将其reducers分别导入到应用程序的商店中,并且必须将其放在特定的密钥下模块知道。 如果使用@connect ,这也限制了我复制模块,因为它连接到整个状态。

例如:

我正在构建一个看起来像iMessage的Messenger。 它的还原状态为currentConversationId等。我的Messenger组件具有@connect(state => ({ currentConversationId: state.messenger.currentConversationId }))

我想将此Messenger在新应用中。 我将不得不import { rootReducer as messengerRootReducer } from 'my-messenger'并将其添加到combineReducers({ messenger: messengerRootReducer })才能工作。

现在,如果我想在应用程序中有两个具有不同数据的<Messenger>实例,那么就不能了,因为我绑定到Messenger组件中的state.messenger 。 制造针对商店的特定区域工作会使我使用自定义的@connect

另外,假设包含应用程序的某个动作名称存在于my-messenger模块中,将会发生冲突。 这就是为什么大多数redux插件都带有@@router/UPDATE_LOCATION类的前缀的原因。

一些随机/疯狂的想法:

1个

@connect可以连接到商店的特定部分,因此我可以在我的应用程序中包含多个<Messenger> ,它们可以连接到自己的数据部分,例如state.messenger[0] / state.messenger[1] 。 它对<Messenger>应该是透明的,但仍保持连接到state.messenger ,但是,包含的应用程序可以通过以下方式提供切片:

@connect(state => ({ messengers: state.messengers }))
class App extends Component {
  render() {
    return (
      <div>
        {this.props.messengers.map(messenger =>
          <ProvideSlice slice={{messenger: messenger}}><Messenger /></ProvideSlice>
        }
      </div>
    )
  }
}

使用全局规范化的实体时存在一个问题, state.entities也将需要存在,因此连同切片状态一起,还需要以某种方式显示整个状态。 ProvideSlice可以设置一些上下文,以便Messenger@connect可以读取。

另一个问题是如何绑定<Messenger>组件触发的操作仅影响该片。 也许@connect下的ProvideSlice仅在该州的这一部分上运行mapDispatchToProps操作。

2

合并storereducer定义,因此每个化径器也可以独立使用。 缩小器是视图时,声音像控制器一样存储。 如果我们采用React的方法,“一切都是组件”(与angular 1.x的控制器/指令不同),它可能允许组件及其状态/动作真正独立。 对于诸如标准化实体(即“全局状态”)之类的东西,可以使用诸如React上下文之类的东西,并且可以通过所有动作(例如getState中的thunk )/连接的组件进行访问。

@elado我现在看到的最聪明的想法是考虑“提供者”的架构: https ://medium.com/@timbur/react -automatic-redux-providers-and-replicators-c4e35a39f1

谢谢@sompylasar。 我正在自动取款机上旅行,但是当我有机会的时候,我打算写另一篇文章,为那些已经熟悉React和Redux的人更简洁地描述提供者。 任何已经熟悉的人都应该发现它非常简单/疯狂。 :)

@gaearon仍然想听听您对此的想法。

我最大的烦恼是,由于同时存在两个独立的层次结构(视图和化简器),因此很难组成,重用,嵌套以及通常在容器组件之间移动。 还不清楚如何编写将Redux用作实现细节或想要提供Redux友好界面的可重用组件。 (有不同的方法。)对于每个动作都必须“一直”向上而不是在某个地方短路,我也没有留下任何印象。 换句话说,我希望看到类似React Local State Model的模型,但是得到了reducer的支持,并且我希望它具有高度的实用性,并且可以针对实际用例进行调整,而不是完美的抽象。

@gaearon关于如何开始实施这种系统的想法?

@gaearon

换句话说,我希望看到类似React Local State Model的东西,但是得到了reducers的支持

我认为棘手的是,React本地状态与组件的生命周期相关联,因此,如果您尝试在Redux中对本地状态建模,则需要考虑“安装”和“卸载”。 Elm不会出现此问题,因为1)组件没有生命周期,以及2)视图的“安装”是从模型派生的,而在React中,局部状态仅在安装后才存在

但是正如您所提到的,“一直走下去”的Elm架构有其自身的缺点,并不能很好地适应React的更新和协调周期(例如,它需要自由使用shouldComponentUpdate()优化...如果您天真地“转发” render()内部的调度方法,则会中断。)

在某种程度上,我感觉就像只是使用reducers + setState并称其为:D除非/直到React找到一个将状态树外部化的故事...虽然也许这就是我们在这里真正讨论的

我想这就是@threepointone试图通过https://github.com/threepointone/redux-react-local解决的问题

@acdlite Elm的概念模型非常适合于本地状态,但是在现实世界中使用它似乎很痛苦,因为我们必须使用库,专注于安装,处理视差效果或其他任何东西。请参见https:// github.com/evancz/elm-architecture-tutorial/issues/49

@slorber是的,我同意。 这不是我刚才所说的吗? :D

我想您的担心略有不同。 _If_(大“ if”)表示您将在React中使用Elm架构,但我认为您不会遇到提到的问题,因为我们仍然可以访问生命周期,将setState用作逃生舱口等。

是的,我们在同一页面上@acdlite
使用React的Elm架构可以解决这个问题。
甚至Elm在将来也可能会允许使用vdom挂钩。

但是,Elm体系结构需要一些样板。 我认为,除非您使用Flow或Typescript进行包装/展开动作,否则这不是真正可持续的,最好有一种更简单的方法来处理此局部状态,例如@threepointone的解决方案

我对Elm架构还有一个担心,那就是嵌套动作对于本地组件状态非常有效,但是一旦需要解耦组件之间的通信,我认为这并不是一个好主意。 如果widget1必须解开并内省深层嵌套的动作才能找到widget2触发的某些事件,则存在耦合问题。 我在这里表达了一些想法

我开始对如何构建可伸缩的redux应用程序有一个好主意,并会在我不太忙时尝试写一些有关它的内容。

@gaearon您是否在这里最后一次谈论react-redux-provide ? 我要问的是,根据您的回答,您是否很显然没有对此进行仔细的研究。

同样,关于@acdlite提到的将状态树外部化的问题,这正是提供程序旨在解决的问题。

provide装饰器提供的API当然是抽象的,是的,它当前依赖于redux ,但是认为它依赖于redux是错误的方法。考虑一下,因为它实际上只是组件和状态树之间的“胶水”,因为React的context尚未得到充分开发。 将其视为通过actionsreducers (即商店的州)将状态树处理为props的理想方法,您可以在其中基本上将actionsreducerspropTypes (和/或将来的contextTypes ),类似于您如何import在您的应用中。 当您执行此操作时,一切都变得很容易推论。 React的context可能会演变为包括这些基本功能,在这种情况下,将花费整整2秒钟的时间来grep并删除@provideimport provide from 'react-redux-provide'这样您就可以然后留下不再依赖它的简单组件。 而且,即使那永远不会发生,也没什么大不了的,因为一切都会继续完美且可预测地工作。

我认为您过于简化了这个问题。 您仍然需要解决安装和卸载问题。

@acdlite有一个例子吗?

如果您要指的一件事是(例如)基于某些id或诸如此类的东西从某个数据库中检索props ,那么这(您可以想到的几乎所有其他实际应用程序中)提供商可以轻松实现。 在完成其他工作之后,我很高兴向您展示它的简易性。 :)

可能很有趣:比较了无功状态实现
Elm,Redux,CycleJS ...(正在进行中)

多个商店的另一个可能需求是,当可能需要多个时间线,时间旅行,保存位置和频率时。 例如,特定于单个用户的user datauser actions和特定于一组用户的common datagroup actions (例如,共享多用户项目)或文档)。 这样可以更轻松地将可以撤消的内容(对个人数据的更改)与固定的内容(其他用户已经增强的编辑内容)分离开,并且还有助于实现不同的保存和/或同步周期。

我们正处于角度1.x应用程序的设计/早期开发阶段,具有角度2.0的开发思路并考虑使用redux。 过去,我们一直在使用带有多个商店的Flow来响应一些常见的调度和一些专用的调度。

Redux对我来说看起来很棒,但是我无法为整个应用程序推断单一商店的概念。 正如上面其他人也指出的那样,我们有许多大的小部件,它们应该托管在不同的消费者应用程序上,并且每个小部件都需要专用的数据存储。

即使reducer可以拆分并且可以与使用它的功能部件/小部件一起使用,但在创建商店时,我们必须将所有这些部件/部件合并在一起。 我们希望看到我们的小部件存在,而没有对它不关心的减速器的任何直接/间接引用。

在开始在我们的应用中使用Redux之前,我们非常期待以文档形式或注释的形式提供一些指导,以概述在这种情况下的最佳做法。 任何帮助将不胜感激。 谢谢。

@VivekPMenon :您有什么特别的关注? 状态树的大小? 能够对动作进行命名空间/将减速器隔离吗? 是否可以动态添加或删除减速器?

对于它的价值,我刚刚完成了一个链接列表,其中列出了Redux插件生态系统。 有许多库试图解决其中的一些隔离/复制/作用域概念。 您可能需要查看reducer页面local / component state页面,以查看其中的任何库是否对您的用例有所帮助。

@markerikson。 非常感谢您的答复。 我的困惑主要集中在第二点(隔离)上。 假设我们的一个团队创建了基于流量的小部件A,并且需要使用商店来管理其状态。 假设他们将创建一个jspm / npm软件包并分发。 另一个创建窗口小部件B的团队也是如此。这些都是独立的窗口小部件/程序包,不需要彼此直接/间接地依赖。

在这种情况下,处于规则的通量世界。 窗口小部件A和B在其程序包中都有自己的存储(它们可以侦听一些常见的动作,但是假设它们有更多不常见的动作)。 并且这两个小部件都将依赖于全局调度程序。 在redux模式中,将在何处创建存储(物理上)。 小部件A和B UI组件都需要使用商店实例来侦听更改并获取状态。 它们以常规方式在自己的商店实例上运行。 我试图形象化这种情况下单店概念将如何发挥作用。

@VivekPMenon,这是我们一些人试图解决的有效问题。

您可以在此处获得一些见解: https :

另外,在这里查看有关Redux-saga的答案: http ://stackoverflow.com/a/34623840/82609

@slorber非常感谢您的指导。 期待这里出现的想法https://github.com/slorber/scalable-frontend-with-elm-or-redux

@taggartbg您最终打开了一个问题吗? 我对类似的事情感兴趣-当具有带有不同独立子树的react-router时,我发现导航到新分支时保持先前路由的状态很成问题。

@slorber

首先,我要说的是redux saga是一个很棒的项目,尽管投入了很多,但这绝不试图减少它。

但是在目前的状态下,我不会在任何项目中使用它。 以下是一些原因

萨加斯人无国籍

您无法序列化生成器的状态,它们在运行时很有意义,但它们完全绑定到传奇的状态。 这导致了一系列限制:

  • 你不能定时旅行发电机
  • 您无法跟踪导致传奇故事当前状态的路径
  • 您无法定义初始状态
  • 您无法将业务逻辑与应用程序的状态[*]分开
  • 你不能存储萨加斯

[*]这可以解释为在应用程序模型的定义内定义业务逻辑,使MVC中的M和C成为同一事物,可以将其视为反模式

调度动作是不确定的

使用redux的一大优点是对状态如何对动作做出反应保持简单的了解。 但是redux-saga会使动作容易产生意想不到的结果,因为您无法分辨从哪一部分传奇开始。

可序列化状态

事实是,传奇的当前状态会影响应用程序的当前状态,人们强调保持该状态可序列化,但是您如何将其转换为sagas? 如果您无法完全序列化状态,那么您就不会_really_对其进行序列化。 您基本上也无法恢复传奇,就无法恢复应用程序的状态。


在状态中存储逻辑是不好的,我认为这是redux saga所做的某种方式。

假设您的应用程序状态特定于_your_应用程序是完全可以的。 这意味着只要应用程序在_your_应用程序之上运行,就可以假定其状态是可预测的。 这意味着可以保留存储的信息,该信息可以确定应用程序的行为,而无需实际在其中保存逻辑。

@eloytoro

我了解您的担忧。 如果您查看redux-saga问题,那么您会发现这些问题已得到讨论,并且有解决方案。

还要注意,redux-saga选择使用生成器来实现,但是完全不需要实现saga模式,并且有其优点和缺点。

有一个选择效果,允许您在redux-saga中使用redux状态,因此,如果您不想在生成器中隐藏状态,则可以直接将saga状态直接放在redux-store中,但这需要更多工作。

有传奇的devtools允许跟踪sagas的执行。

对于时间旅行有可能的解决方案,但尚未具体实现

对。 很长的话题,很有趣。 我一直在比较不同的状态管理方法。

在比较时,我谨记以下几点:

  1. 单一真理
  2. 在服务器端渲染并在客户端提供初始数据有多么容易/困难
  3. 重新加载后,诸如撤消/重做和持久性历史存储之类的devtools。
  4. 动画,动画,动画。 我总是问自己_“动画在解决方案a或b中如何工作” _。 这对我来说很关键,因为我正在开发的产品以UX为中心。 我分享@jsonnull的许多困惑。
  5. 重用另一个应用程序中的组件
  6. 引用数据的水化(_major pain_),以保持完整性(与从服务器端或任何其他数据源获取初始数据有关)
  7. 像这样的其他开发者社区进行讨论

所以这些是我在考虑国家管理时要警惕的要点。 Redux通过勾选大多数框来成为赢家。 我非常了解Redux,这是让我在客户端js领域平静下来的第一件事。

我一直关注的最著名的其他项目是SAM模式(很难进入思维模式,没有工具_)和Mobx(以可变的存储能力而闻名)。

两者都拥有单一真理来源的想法,我认为看到它们如何处理真有趣。

我对Mobx的研究(当然是有限的)就是这样的:

  1. Mobx(就像Redux一样)提倡真理的单一来源,但它是具有可观察属性的类。 这个概念与redux和通量整体上产生了差异:

    1. Mobx还提倡子商店(嵌套/全球单一one_的一部分)。 对我来说,它看起来很像定义客户端模式。 有点烦人,因为您可能在服务器上也有架构。 我看到与redux-orm相似。 AFAIK子存储库旨在作为prop传递给组件(这样,组件就不依赖于将状态映射到prop或全局树的某些键)。 但是,传递单个属性会破坏我对“属性”的理解-它们应该描述组件的需求。 这实际上让我想到,如果可以使用React propTypes来定义要传递的商店的形状,那是否会有问题。 另外:将存储和子存储作为类可以直接引用数据而无需规范化,但是如果要馈送初始数据,则这是没有意义的,因为:

    2. 但是,按类存储的想法意味着开发人员没有开箱即用的序列化功能,这意味着如果您要提供初始数据,这就是您要做的工作。

  2. 历史记录和撤消/重做可以通过更改状态后的快照来实现。 但是您需要自己编写序列化器,反序列化器。 示例在这里给出: https :
  3. 编写序列化程序的好处是您可以省去一些东西。 它看起来像是存储与动画无关的永久数据的好地方。 因此,如果将其序列化,则只需忽略它,就不会重做/撤消完整的动画步骤。
  4. Mobx提倡保持存储中的数据派生(作为存储方法),而不是reselect + connect装饰器。 这对我来说似乎是错误的。
  5. 由于其可观察的特性,mobx使定义有效的纯组件变得容易。 简短地比较属性不是问题,因为无论如何都可以观察内容。

比较SAM和Redux,我想考虑一下逻辑流程:

  1. Redux:

    1. 定义商店

    2. 定义处理商店变更的功能(归约器)

    3. 为每个商店更改定义事件(redux操作)标识符(redux操作类型),并在匹配时运行特定的reduce

    4. 将存储映射到组件容器属性,可以选择将用户交互的处理程序(单击,键入,滚动等)传递为仅触发事件/调度动作的函数。

    5. 组件触发与所有reducer匹配的事件(redux操作)

    6. 用新状态重新渲染。

  2. SAM:

    1. 定义商店。

    2. 定义商店的访问器/设置器功能。 该setter接受任何数据,并且纯粹基于它试图找出将其放置在存储中的数据以及是否需要从服务器请求数据或将其保留在那里的数据。 仅通过检查数据并不一定总能弄清楚该怎么做,因此有时会使用标识符/标志(例如isForgottenPassword )。 此设置程序还旨在验证数据并存储错误。 setter完成后->重新渲染为新状态。

    3. 定义接受数据的函数,对其进行转换(redux reducer的替代方法),并执行传递已转换数据的setter。 这些功能现在对商店的形状有所了解。 他们只知道已传递的数据。

    4. 将存储数据映射到组件容器属性,但是也必须传递用户交互处理程序(单击,键入,滚动等)的处理程序。 请注意,这些是实际功能(在redux中称为reducers),而不是执行调度调用的功能。

    5. 注意:没有调度动作,没有减速器直接通过。

因此,所有的redux,SAM和Mobx都将状态保持在一个地方。 他们的方式带来了自己的挫折。 到目前为止,没有人能击败redux及其生态系统。

我看到的最大缺点还在于不可序列化状态的处理。
就我而言,我有一个插件API,因此操作和逻辑与Redux无关,但需要与我的应用程序进行交互。 因此,基本上,我首先需要根据给定状态重新创建一堆类。

我正在研究一些概念,如何以更好的方式处理这种不可序列化的状态。 包括从任何给定状态重新创建它。 我已经设法将除函数/对象引用之外的所有内容移回商店。 剩下的就是具有自己生命周期并带有已定义API的对象,这些API可与我的应用程序交互,并因此与我的操作交互。 -感觉类似于React组件,只是不用于UI渲染,而是自定义逻辑。

我不知道你们是否读过Erik Meijer的这篇文章。 只是一个简单的报价:

与普遍的看法相反,使状态变量不可变几乎无法消除不可接受的隐式命令性影响

也许是时候重新讨论所有这些不变性的废话了。 当单个状态树的大小增加时,每个人都会告诉您这是性能方面的损失。 SAM模式使突变成为编程模型的主要公民,当您这样做时,会发生很多简化,从不再需要Sagas到跟踪效果(例如,获取)所需的所有这些辅助状态机。

这篇文章并没有真正对您有利。 埃里克·梅耶(Erik Meijer)确实重访了“这种不变性废话”,并完全接受了它。 他认为消除状态突变是基本的第一步,是

我是埃里克·梅杰(Erik Meijer)的工作迷,并且会热切地阅读和倾听他必须说的任何话,同时要记住,他的经营不受我的业务,​​技术和知识的约束。

软件工程中的关键问题是,每个人似乎都相信IS-A变异。 编程语言仅支持分配,变异留给程序员解释。

将函数包装器添加到一堆分配中并不是自动地成为改变状态的最佳方法。 废话

(应用程序)状态突变背后有一种理论,叫做TLA +,奇怪的是... Erik从不提及TLA + ...

JJ-

让我详细说明一下,因为似乎存在很多混乱。 Erik的论文雄辩地证明,当涉及到变异状态时,命令式编程语言就有些破裂了。 我相信每个人都同意这一点。

问题是您如何解决它? 您是否同意这是需要“第一原则”方法的问题?

兰珀特博士指出,在计算机科学中我们几乎可以达成一致的一个原则:“许多计算机科学都是关于状态机的。” 他继续说:“然而,计算机科学家非常关注用于描述计算的语言,以至于他们根本不知道这些语言都在描述状态机。”

实际上,所有编程语言都使我们能够指定“状态机”而不管其语义如何。 不幸的是,许多编程语言偏向于“动作”而不是“状态动作”。 这是有充分理由的,使用基于动作的形式主义(状态机的状态在很大程度上可以忽略)可以有效地编码大量问题。 当您忽略它们时,您实质上是在说所有动作在任何给定状态下都是可能的,我们都知道这通常是不正确的。

您可以对Erik的哈哈示例尽情地笑:
// prints Ha var ha = Ha(); var haha = ha+ha;

但他只说您在给定状态下采取了错误的措施。 仅此而已。

函数式编程为您带来了什么? 一点也不。 它以非常复杂的方式表达了采取行动与其可能产生的结果之间的关系。 有点像试图在巨大的迷宫里扔一支箭(甚至是一条聪明的单子箭头),希望您会击中目标。

TLA +带来了一种更为自然的应对效果的方法,首先是因为它对步骤是什么以及突变与作用之间的关系有清晰的认识。 您可以随意忽略TLA +,但是这有点像您在抱怨您试图将火箭发射到月球上,但前提是地球是平坦的,并且很难操纵飞船。 我知道,我们生活在一个人们相信他们可以改变现实的世界中,只要有足够的人相信他们在说什么……那仍然无法带给您任何地方。

再次,我真的,真的,真的很好奇,看看Erik对TLA +的看法,我确实在LinkedIn上与他联系并提出了问题。 从来没有答案...毕竟,兰珀特博士只因此获得了图灵奖,这可能不值得他花时间。

老实说, @jdubray ,您现在处于边缘状态。 您已经在此线程,其他问题和其他地方重复了数十次论点。 您已经侮辱了Dan和Redux社区,挥舞了您的凭据,并继续调用“ TLA +”和“ Lamport”一词,就好像它们是圣杯以及生命,宇宙和一切的答案。 您一直坚持认为SAM远远优于Redux,但是尽管有大量邀请提供证据证明,但它们并没有以有意义的比较应用程序的方式编写很多内容。 您现在真的不会改变现在还没有说服的任何人的想法。 老实说,我建议您花时间做一些更有生产力的事情。

我现在要关闭线程。 我猜,评论可以继续,但是这里绝对没有任何可操作的内容。

@markerikson我想继续进行此讨论,即使其进展无济于事,该话题也让我颇感兴趣。 最好清除他的有害评论,并保持良好的开源讨论进行。
除了@antitoxic干预而导致其他问题的严重性,因此不再需要倾听

可以,您可以删除我的评论,为什么会破坏如此精彩的对话?

@jdubray只有您将其视为辉煌。 我认为,我们所有人都觉得您尝试向我们出售的东西没有“ Lamport获得图灵奖,所以请听我说,我是对的!”。

我不会说TLA +或其他不好的东西,也许将来某人会做得比您更好。 只是您解释它的方式根本无法帮助我们理解,并且您永远的居高临下的语调不会邀请我花时间学习TLA +。

Erik的论文雄辩地证明,当涉及到变异状态时,命令式编程语言就有些破裂了。 我相信每个人都同意这一点。

实际上,您在所有帖子中总是使用I believe everyone agrees on that类的术语。 这不是好事,对别人没有帮助或欢迎。 默认情况下,我们并非所有人都同意您的观点,而且我们大多数人都不会也不会阅读Erik的论文。 如果您不能以一种热情的方式说服人们,而又不要求他们之前阅读过数吨的纸张,并且如果他们不同意或不同意您的想法,就会使他们感到愚蠢,那将无助于传播您的想法。

您可以随意忽略TLA +,但是这有点像您在抱怨您试图将火箭发射到月球上,但前提是地球是平坦的,并且很难操纵飞船。

显然,这看起来像是类比,当然不是首要原则。 认真地说,您认为此声明应被视为“精彩的讨论”吗?

我认为,当您不进行讨论时,讨论实际上更为轻松,因为您将所有注意力都集中在TLA +上,实际上阻止了对TLA +不感兴趣的人们尝试使用更具体的东西将火箭发射到月球,人们可以理解,例如Redux。

仍然不是说TLA +不值得,只是您对错误的人以错误的方式谈论它。 也许Erik没有回答你,那是因为他也听不懂你吗? 也许您太聪明了,只有Lamport才能理解您? 也许将来您会获得图灵奖? 我不知道,但是可以肯定的是,当前的讨论没有带来任何好处。

也许喜欢@gaearon并为

@Niondir我想出了一种在需要恢复状态的情况下使用sagas的方法。
它更像是要遵循的模式或一组规则

  • 分开您的sagas,以便每个人最多只能有一个put 。 如果您的Sagas中包含超过put元素,请将其拆分并以call效果将其链接。 例如
function* mySaga() {
  yield put(action1());
  yield put(action2());
}

// split into

function* mySaga() {
  yield put(action1());
  yield call(myNextSaga);
}

function* myNextSaga() {
  yield put(action2());
}
  • 具有take效果的Sagas应该分为遵循take和传奇起点的逻辑。 例如
function* mySaga() {
  yield take(ACTION);
  // logic
}

// split it into

function* rootSaga() {
  yield takeLatest(ACTION, mySaga);
}

function* mySaga() {
  // logic
}
  • 为每个传奇创建某种“检查点”功能,这意味着在初始化时要从州读取,并从那里读取call您需要的传奇
function* rootSaga() {
  // emit all forks and take effects that need to run in the background
  yield call(recoverCheckpoint);
}

function* recoverCheckpoint() {
  const state = yield select();
  if (state.isFetching) {
    // run the saga that left the state like this
  }
}

解释为何可行的解释是基于一组通常是正确的假设

  • take效果需要将操作分派到商店,即使您正在恢复,也可以始终在初始化时调用这些sagas,因为它们在用户与页面交互和/或sagas已经运行之前一直保持闲置状态,因此可以安全地在初始化时调用这些sagas
  • 每个传奇不允许一个以上的put调用会导致状态发生原子变化,就像动作只能以一种方式改变状态一样,传奇也仅限于该容量,这种创建Sagas与动作调度之间的相关性。

以这种方式分裂野生动物也有一些积极的效果

  • Sagas更可重用
  • Sagas更容易更改
  • Sagas更容易理解

但是@slorber

  • (10分钟的介绍性视频)在这里: https :
  • 我在视频中讨论的代码示例在这里: https :

许多人发现此“钓鱼游戏”示例在代码上非常有趣,因为它直接显示了SAM的整体工作原理,包括动态状态表示和“下一个动作谓词”。 NAP减轻了对Sagas的需求,并且不需要破坏Redux原则#1(这是一个单状态树),但是我知道什么?

有时,当我使用Ableton Live时,这种想法浮现在脑海中:“是否有可能将Ableton Live GUI编写为React / Redux应用程序?” 我认为答案是否定的,要获得可接受的性能将太困难了。 它是用Qt编写的原因,也是Qt具有信号/插槽架构的原因。

我一直在用Meteor使用React / Redux。 我通过将回调的Redux动作调度到Mongo.Collection.observeChanges来将Mongo文档存储在Immutable.js状态。 我最近发现,在订阅几千个文档时,UI极度延迟,因为当Meteor一对一发送初始订阅结果时,数千个Redux操作被调度,从而导致数千个Immutable.js操作和数千个重新渲染速度如此之快。尽可能。 我不确定RethinkDB也可以这样工作,尽管我不确定。

我的解决方案是创建一个特殊的中间件,该中间件将搁置这些收集操作,然后以限制的速率分批分发它们,以便我可以在单个状态更改中添加约800个文档。 它解决了性能问题,但是更改事件顺序具有内在的风险,因为它可能导致状态不一致。 例如,我必须确保订阅状态操作是通过同一调节器调度的,以便在所有文档都添加到Redux状态之前,订阅不会被标记为就绪。

@ jedwards1211我遇到了类似的问题(很多小消息组成了初始数据)。

通过批量更新以相同方式解决。 我使用的是(小型)自定义队列机制(而不是中间件)(将所有更新推送到一个数组中,并且如果有任何要更新的内容,则定期更新所有更新)。

@andreieftimie是的,在我的应用程序中比较棘手,因为有些UI交互需要立即更新,例如拖动和缩放绘图。 因此,我必须有一个中间件层来决定是立即分派动作还是将其放入队列以进行批量分派。

对于它的价值,我有一个创建“分支”的想法。 它完全违背了单店原则,但这似乎是一个不错的折衷方案。

https://github.com/stephenbunch/redux-branch

@ jedwards1211我认为随着应用程序复杂性的提高,最终需要分解某些类型的数据和某些类型的更新,以便某些存储区无法处理全部负载。

假设您要与后端同步一些核心状态,有一些动画状态,以便您的应用程序UI组件根据正在执行的当前操作正确显示,也许您正在跟踪一些调试数据分别。

在这个人为的示例中,当然可以将所有这些都构建在单个Redux存储中,但是取决于您的架构方式,状态树的各个分支之间可能存在一些模糊的界线,并且可能缺乏清晰度关于给定动作打算与哪个状态进行交互。

我认为根据您希望状态的范围和期望的性能特征使用不同的状态管理策略是完全合理的。 Redux适合用于您可能需要回滚或重放操作,序列化并返回到更高状态的状态。 对于动画状态,您可以根据需要使用可变存储或反应存储-这样会减少开销,并且不会因此瞬态(但仍是必需)交互状态而污染应用程序状态。

很快,我想使用即将到来的React光纤体系结构提出一点意见。 如果我的理解有点过时,这是一个链接,很抱歉。 粗略地讲,光纤架构承认存在通过React组件树传播的不同类型的更新,并且对于何时以及如何执行这些更新有不同的期望。 您希望动画更新快速,敏感且可靠,并且您不希望大的交互更新在动画等中引入垃圾。

因此,光纤体系结构将更新分解为工作包,并根据正在执行的工作根据优先级对它们进行调度。 动画具有较高的优先级,因此不会被中断,而较慢的机械更新的优先级较低。

我已经跳过了很多有关React光纤的细节,并且可能在处理过程中出错了一些,但是我的观点是,我认为这是一种细粒度的方法,我认为这对于使用不同类型数据的数据存储是必需的。

如果我今天要构建一个复杂的应用程序,那么我将从类似Redux的顶级商店类开始,并由几个不同的商店作为后盾。 大致:

  • 顶层数据存储区有一个队列来处理传入的操作
  • 动作要么用于应迅速分派的动画动作,要么用于优先级较低的应用程序交互
  • 队列中的动画操作从requestAnimationFrame循环调度到可观察的后端
  • 队列中的动画动作完成后,交互动作将分派给redux

这个故事似乎非常接近基于Redux的完整状态解决方案。 Redux适用于您想要在一系列操作后查看的数据,并且还有许多其他状态也需要仔细处理。

@jsonnull我从来不需要在商店中存储动画状态-本地状态一直是我的动画用例的理想选择。 我很想知道是否有某些用例完全不适合使用本地动画状态。 听起来他们对新架构有很好的想法。

@ jedwards1211我可以想到几种情况,其中本地动画状态不起作用。 我会同意的,并不是您会遇到每个应用程序的情况,但我认为它们经常出现。

在一种情况下,您使用的是Redux以外的其他没有本地状态的库。 (好吧,是的,我知道我在这里有点作弊。)如果您使用的是非常精简的超脚本方法,没有虚拟dom,纯功能组件,那么除了本地状态外,您还必须传递一些动画状态到根节点或您要重新渲染的任何节点。

在这种情况下,设置状态树并设置一些动画详细信息将使您可以解决缺少局部状态的问题,从而可以触发动画,使动画运行一段持续时间等等。

这里的关键是,有两种方法可以制作这些动画-将它们作为渲染的一部分,并完整保留“ UI作为状态的函数”的隐喻,或者分别对DOM进行突变。 几乎总是更好的答案是只是重新渲染而不是变异。


现在来看一个示例,其中您确实可以将某些动画状态保持在本地状态—构建一个基于React的UI的浏览器游戏。

  • 通常,您会使用滴答声来驱动游戏。游戏时间从0开始,每帧递增。 核心游戏动画可以在没有本地状态的画布或WebGL上完成,因此您将根据这些刻度来确定动画的开始时间和进度。

例如:一个精灵角色的动画包含10帧,并且您希望在〜400ms的时间内播放该动画,因此您将每隔2个滴答声就更改一次绘制的精灵。

如果您需要更高的分辨率,则刻度也可能是时间戳。

  • 您的游戏也可以暂停,在这种情况下,您可能需要暂停在React UI端完成的某些动画。

在此游戏示例中,您不想执行的是在tick操作中增加刻度线...而是希望以可观察的结构单独增加刻度线。 而且,当您调度Redux操作(例如键盘字符移动或攻击)时,您会将当前的滴答声或当前时间传递给它们,以便您可以记录发生动作的滴答声,而UI组件可以在本地记录动画开始的时间。

现在,您已经将全局动画状态与交互状态分离了,并且如果您想仅使用Redux状态或通过重播动作来进行“重播”,则可以,而且不会因增加刻度而成为您的负担树。

一般而言,通过在Redux中传递开始/停止时间并使组件在本地保持其余状态来协调动画也更好。

这不仅适用于游戏。 任何扩展的动画序列都可能会经历此过程,例如,如果您想使用React在站点上进行一系列长时间运行的动画。

@jsonnull很酷,这是一个很好的例子,感谢您的分享。

我只是说一句-在Redux中,您无法存储具有相互链接的状态对象。 例如-用户可以有很多帖子,而帖子可以有很多评论,而我想以一种方式存储这些对象:

var user = {id: 'user1', posts: [], comments: []}
var post = {id: 'post1', user: user, comments: []}
user.posts.push(post);
var comment = {id: 'comment1', post: post, user: user}
post.coments.push(comment)
user.comments.push(comment)
appState.user = user

Redux不允许将对象层次结构与循环引用一起使用。 在Mobx(或Cellx)中,您仅可以简单地拥有对象的一对多和多对多引用,并且可以多次简化业务逻辑

@bgnorlov我认为redux包中的任何内容都不能禁止循环引用,就像您在说的那样-它们只是使在您的reducer中克隆state变得更加困难。 另外,如果您不确定使用Immutable.js表示状态,则使用循环引用对状态进行更改可能会更容易。

您还可以通过使用外键在Redux状态下对关系建模,尽管我知道这不像使用类似ORM的对象引用那样方便:

var user = {id: 'user1', postIds: [], commentIds: []}
var post = {id: 'post1', userId: user.id, commentIds: []}
user.postIds.push(post.id);
var comment = {id: 'comment1', postId: post.id, userId: user.id}
post.commentIds.push(comment.id)
user.commentIds.push(comment.id)
appState.userId = user.id
appState.posts = {[post.id]: post}
appState.comments = {[comment.id]: comment}

// then join things like so:
var postsWithComments = _.map(appState.posts, post => ({
  ...post,
  comments: post.commentIds.map(id => appState.comments[id]),
})

@ jedwards1211以在redux中用循环引用克隆状态,reduce必须返回受更改影响的对象的每个新副本。 如果引用对象更改,则相关对象也需要是新副本,并且将以递归方式重复该副本,并且每次更改都会生成全新的状态。 Immutable.js无法存储循环引用。
使用规范化方法时,当某些事件处理程序需要一些数据时,它每次都需要通过其ID从全局状态获取对象。 例如-我需要在事件处理程序中的某个位置过滤任务同级(任务可以具有层次结构),在redux中,我需要调度thunk来访问应用程序状态

var prevTask = dispatch((_, getState)=>getState().tables.tasks[task.parentId]).children.map(childId=>dispatch((_, getState)=>getState().tables.tasks[childId])).filter(task=>...) [0]

或带有选择器

var prevTask = dispatch(getTaskById(task.parentId)).children.map(childId=>dispatch(getTaskById(childId)).filter(task=>...)[0]

当我可以简单地编写时,与样板版本相比,此样板将代码变成混乱

var prevTask = task.parent.children.filter(task=>...)[0]

@ jedwards1211,@bgnorlov:FWIW,这是原因为什么我像一个终极版-ORM 。 它可以使Redux存储保持规范化,但使这些关系更新和查找变得更简单。 实际上,最后一个示例与Redux-ORM基本相同。

我刚刚写了一些博客文章,描述了Redux-ORM的基础知识核心概念和高级用法

@markerikson很酷,感谢您的提示!

@gaearon

需要注意的一件事是我们不打算将Redux用于所有状态。 一切似乎对应用程序都重要。 我认为输入和动画状态应该由React处理(或其他短暂状态抽象)。 Redux在获取数据和本地修改模型等方面效果更好。

我发现此评论是如此有用。 我知道我参加晚会很晚,但这也许就是我的观点的一部分。 自发表评论以来已有一年多了,这仍然是我唯一看到过这个想法的地方。

来自有角度且绝对非flux / redux的背景,很难独自提出这个想法。 特别是当许多示例仍为文本框中的每个快捷键创建动作时。 我希望有人在每个Redux文档页面的顶部以50px的文字添加引号。

@leff同意。 我早些时候已经综合了这个确切的想法,但是并没有引起足够的重视。 即使您将Redux用于历史记录管理,通常也有很多短暂状态不需要负担您的Redux样式状态管理。

对于那些有机会进行研究的人们来说,这不一定是一个惊喜,例如,具有丰富的撤消/重做功能的成熟桌面应用程序。 但是,对于通过Redux提出这些想法的新移民来说,这将是一个启示。

@bgnorlov很好的一点是,不可能用Immutable.js存储循环引用,我从没想过! 我认为这是一个不错的功能。

如今,我们确实倾向于将几乎所有内容都存储在Redux中,即使它是瞬态视图特定状态,我们也从未在其上下文之外使用它(例如, redux-form实例的状态)。 在大多数情况下,我们可以将这种状态从redux中移出,而不会遇到任何问题。 但是有一个好处就是万一我们将来需要外部组件来响应它。 例如,我们可以在导航栏中添加一些图标,当任何表单有错误或正在提交时,这些图标都会更改。

我要牢记的最重要的一点是,将所有规范化状态放入Redux(即,需要结合在一起以呈现视图的任何内容)将使其最易于使用。 不需要与任何事物连接的状态可以在Redux之外生活而不会给您带来麻烦,但是将其放入Redux通常也不会受到损害。

FWIW,文档确实指出,并非所有内容都必须进入Redux,具体取决于http://redux.js.org/docs/faq/OrganizingState.html#organizing -state-only-redux-state。

最近网上有很多关于Redux“适合”的话题。 有些人看到了从字面上将所有内容放入Redux的好处,另一些人则发现麻烦太多了,只想存储从服务器检索到的数据。 因此,这里绝对没有固定的规则。

与往常一样,如果人们有改进文档的想法,则完全欢迎PR :)

如果您需要重用reducer并能够在redux状态下分配“子状态”,我开发了一个插件来轻松实现(通过React集成)

https://github.com/eloytoro/react-redux-uuid

@eloytoro IMO如果您对某些具有

@eloytoro我要写类似的东西。 非常感谢您的参考!

@ jedwards1211我认为您的想法有误。 我没有提到第三方减速器或以任何方式出现的问题,只是试图展示我的摘要如何解决使减速器可在具有动态尺寸的集合中重用的问题

@avesus希望它对您

@eloytoro啊,这更有意义。

去年,我使用Redux创建了一个相当复杂的游戏。 我还参与了另一个不使用Redux的项目,而是一个定制的,简约的框架,该框架将所有内容分离为UI商店和服务器商店(包括输入)。 无需过多讨论,到目前为止,我的结论如下:

单一状态总是更好,但是,不要过度这样做。 通过存储区获取每个keyDown()并返回视图只是令人困惑和不必要的。 因此,瞬态应由本地组件状态(例如React的状态)处理。

我认为,由于Redux和反应使可观察的Web泛滥,因此页面上好的动画数量有所下降。

@ mib32您可能是对的! 希望人们最终会习惯于在React中制作出色的动画。

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

相关问题

jbri7357 picture jbri7357  ·  3评论

caojinli picture caojinli  ·  3评论

timdorr picture timdorr  ·  3评论

vslinko picture vslinko  ·  3评论

CellOcean picture CellOcean  ·  3评论