Screen_Shot_2014-05-30_at_1.37.33_PM

SALSIFY智能工程博客

使用Ember组织数据流

发布的德弗斯斯Talmage

2017年4月11日下午1:38:23

使用Ember开发的最重要的核心原则之一是“数据下降,动作上升”。在各种Ember社区中,您可能会看到这个缩写为DDAU的概念。DDAU的主要前提是数据应该通过组件层次结构向下流动(通过并可能被各种组件修改),而对所述数据的更改应该通过操作通过该层次结构进行传播。

在过去,各种前端框架吹捧他们支持双向数据绑定的能力或者让数据在视图层上下流动的想法。这种模式很快就被发现了难以解释而且,在许多情况下,使应用程序状态难以从概念上和编程上确定。DDAU是Ember的解决方案,它使复杂状态更容易通过应用程序进行协调和传播。

这种模式也称为“单向数据流”,不鼓励组件中的状态发生变化。正如我在下面展开的那样,将组件实现为“智能”或“哑”将在应用程序中实现更易于扩展和理解的干净状态管理。

Ember 0410博客graphic.png

图形的@特罗帕

组织DDAU

要想组织组件以实现干净的DDAU架构,最简单的方法是将它们分为两种不同的类型:有状态容器和无状态表示组件。你可以把这两类看作是“聪明”和“愚蠢”。Ember控制器是有状态容器的一个很好的例子:它是一个单例,存储值(状态)并提供通过操作更新这些值的方法。它是对应模板的唯一真实来源。另一个可行的模式(我在示例代码中遵循的模式)是创建“容器”组件作为视图的顶层构造。除了它们将包含突变状态之外,这些组件在技术上与其他组件没有什么不同。

{{!——主要/模板。hbs -}} {{main-container title="播放一些声音"}}

顺便说一句,以这种方式组织组件也可能有助于迁移到可路由的组件一旦着陆。容器下的后续组件应力求仅具有表示性,但在视图树中嵌套多层容器也是可以接受的。

聪明vs愚蠢

那么,“智能”组件是什么样的呢?几乎和其他组件一样。关键的区别是自我强加的:智能组件应该是您在适当的地方更改组件数据成员的唯一地方。让我们看一个简单的例子网络音频我创建的应用程序。该应用程序本身很小,只有两条路线(只有一条路线是重要的)和一些组件。你可以在这里玩这个应用程序,我已经链接到GitHub存储库在文章末尾的应用程序。首先,我们将看一看主容器组件的一些部分。大多数特性与操纵用于产生声音的振荡器的状态有关。

/* main-container/ Component. js */导出默认组件。extend({playing: false, vibratortypes: A(['sin ', 'square', 'sawtooth', 'triangle']), vibratorfrequency: 500, vibratortype: computed(function() {return this.get(' vibratortypes . firstobject ');}), audioContext: computed(function() {return new audioContext ();}),振荡器:computed(function(){返回this. generate振荡器();}), generate振荡器(){/*使用AudioContext生成一个新的振荡器对象*/},startOscillator(){这个。集(“振荡器”,this.generateOscillator ());this.get(振荡器).start ();这一点。设置(“玩”,真正的);}, stop振荡器(){this.get('振荡器').stop();这一点。set('playing', false); }, updateOscillator() { const oscillator = this.get('oscillator'); oscillator.frequency.value = this.get('oscillatorFrequency'); oscillator.type = this.get('oscillatorType'); }, actions: { /* see below */ }, });

下面的操作严格用于操纵容器的状态,并将传递给子组件,以便在用户输入时调用。以这种方式传递操作可以让组件对它们正在执行的操作一无所知,从而允许在整个应用程序中更好地重用组件。

/*主容器/component.js*/actions:{changeforquency(newFreq){this.set('oscillatorFrequency',newFreq);this.updateOscillator();},changeType(newType){if(this.get('oscillatorTypes')。includes(newType)){this.set('oscillatorType',newType);}this.updateOscillator();},play(){this.startOscillator();},stop(),stop(),stop()}; }, },

在模板中,您可以看到如何传递多个组件关闭操作和从容器状态。理想情况下,这些组件都不会改变这些值,而是通过传递的操作将新值“向上”发送到容器。

{{!--main container/template.hbs--}

{{title}}

{{selector control options=oscillatorTypes selected=oscillatorTypes onChange=(action'changeType')}{{{stepper control min=0 max=22000 smallStep=100 largeStep=1000 value=oscillatorFrequency onChange=(action'changerequency')}{{play button playing=playing onClick=(如果播放(action'stop'))(动作‘玩’)}

注意,所有的业务逻辑都在这里实现。在一个较大的应用程序中,你可以想象任何处理Web Audio API的代码都将被转移到一个服务中,并可以在许多不同的容器中共享。

将状态分组到单个组件中的主要优点是,它允许您从单个位置协调状态更改。这通常指的是拥有“单一的真相来源”。状态只存储在一个地方,并通过管道传递给需要通过属性访问它的组件。如果不这样做,部分状态通常会传递给多个组件,并在多个位置进行修改。这很快就站不住脚了。当涉及到多个组件层次结构时,调用函数来更改数据似乎很乏味,但它几乎总是比替代方法更好。

假设您开始处理一个没有任何经验的大型应用程序,并且这个项目中的许多组件是聪明和愚蠢的混合体。如果有一个很深的组件树,并且没有明确的指示状态操作逻辑驻留在哪里,那么跟踪状态从a到b的原因可能是非常令人沮丧的体验。是存在bug,还是这是预期的行为?不同组件中的所有状态更改如何协同工作?

通过将一个操作发送到逻辑域状态的单一存储库,您不仅可以得到状态应该更改的显式通知,还可以了解如何将多个更改组合在一起以形成完整的状态图。

表象的组件

应用程序的其余部分完全由哑组件组成——这些组件只通过属性将数据传递给其他组件,并公开可以调用的函数,以表明传递给它们的数据发生了变化。一个非常简单的例子就是“播放/停止”按钮:

{{!——播放按钮/模板。哈佛商学院——}}<按钮onclick = {{onclick}} >{{#如果玩}}停止{{其他}}打{{/如果}}> < /按钮

事实上,这个组件非常简单,不需要component.js文件。这类似于React的纯功能组件概念. 组件只需要传递两个属性:playing和onClick。不保存任何状态,单击按钮时通过调用onClick指示“更改”。按钮上的文本由传入的其他值控制。请注意,单击按钮不一定会更改按钮中的文本;这由组件的使用者决定。

您可以想象一个世界,在这个世界中,可能只有在某个异步操作完成后,文本才会更改。在这种情况下,您需要等待更改在组件上播放的值,如果该逻辑包含在组件本身中,则会严重阻碍可重用性。这就是哑组件的威力。

测试

呈现组件的另一个巨大好处是它们更容易测试。当结构正确时,表示组件不需要任何复杂的模拟用户交互,就可以使组件达到需要测试的状态。

例如,让我们假设我们有两个组件:一个是来自示例项目的播放按钮,另一个是上面描述的有状态播放/停止按钮。对于愚蠢的播放按钮,我们只需要改变传入的播放属性的值,以验证多种可能的状态。此外,您可以将mock函数传递给onClick属性,并确保在单击按钮时调用该函数。

在该组件的有状态版本中,您可能需要模拟用户单击按钮,这将需要与DOM交互。此外,您还需要一些方法来验证单击实际工作,只有这样才能检测按钮文本是否正确——您可能会看到,对于一个具有许多状态的现实组件,这可能会导致复杂性的激增。组合在一起的小的、无状态的组件天生就更容易测试应用程序。

回顾

希望此时您已经开始理解仔细考虑如何在应用程序的组件树中安排状态的重要性。智能和哑组件为UI工程师提供了一个简单而强大的范例,以防止混乱、混乱的状态修改。如果您对实现此模式的策略有任何疑问,或者只是对余烬有任何疑问,请随时向我推荐一个DM安贝社区松弛组@devers

链接和来源

评论由Disqus

最近的帖子

    Baidu