Lightning-组件(8)事件

学习目标

完成本单元后,您将能够:

  • 为您的应用定义自定义事件。
  • 从组件控制器创建和激发事件。
  • 创建动作处理程序来捕获和处理其他组件发送的事件。
  • 将一个大的组件重构成更小的组件。

连接组件与事件

在本单元中,我们将解决我们的小开支应用程序中最后一个未完成的功能:报销?复选框。你可能认为实现一个复选框将是一个简短的话题。我们当然可以采取一些捷径,并将其作为一个很短的话题。

但是除了使复选框工作之外,这个单元还有关于删除我们已经采取的所有快捷方式。我们要花这个单位“正确地做”,其中有几个地方意味着我们之前做过的重构工作。

在开始之前,我们先来谈谈我们采取的捷径,正确的道路,为什么正确的道路(有点)更难,但也更好。

组成和分解

如果您以源代码的形式看看我们的小费用应用程序,并列出单独的代码工件,您将得到如下内容。

  • expenses component
    • expenses.cmp
    • expensesController.js
    • expensesHelper.js
  • expensesList component
    • expensesList.cmp
  • expenseItem component
    • expenseItem.cmp
  • ExpensesController (server-side)
    • ExpensesController.apex

以下是所有内容如何组合在一起的情况,以及稍后您接线的createExpense和updateExpense事件。

The expenses app is made up of many smaller components.但是,如果你看屏幕上的应用程序,你看到了什么?你应该看到什么,你看到的最终会看到什么,是应用程序分解成更多的组件。您会看到,您可以将我们的应用程序进一步分解为更小的碎片,比迄今为止所做的更多。至少,我们希望您看到“添加费用”表单实际上应该是它自己的独立组件。 (这就是为什么我们在用户界面上画一个盒子的原因!)

为什么我们不把这个表格作为一个单独的组件?不这样做是迄今为止我们在这个模块的过程中最大的捷径。在软件设计方面,这比我们称之为“恶心”的黑客更糟。构建Lightning组件应用程序的正确方法是创建独立的组件,然后将它们组合在一起以构建新的更高级别的功能。为什么我们不采取这种方法?

我们采取了快捷方式,因为它将主要费用数组组件属性和影响它的控制器代码保存在同一个组件中,所以我们在“主费用”组件中保留了“添加费用”表单。我们希望createExpense()辅助函数能够直接触碰费用数组。如果我们将“添加费用”表单移到单独的组件中,那是不可能的。

为什么不?我们很早就介绍了这个原因,但是现在我们想要真正的琢磨一下。闪电组件应该是独立的。它们是封装所有基本功能的独立元素。一个组件不允许触及另一个组件,甚至是一个子组件,并且改变它的内部组件。

有两种主要的方式与另一个组件进行交互或影响其他组件。第一种方法就是我们已经看到并做了相当多的工作:在组件的标签上设置属性。组件的公共属性构成了其API的一部分。

与组件交互的第二种方式是通过事件。与属性一样,组件声明它们发出的事件以及它们可以处理的事件。和属性一样,这些公共事件构成了组件的公共API的一部分。我们实际上已经使用和处理了事件,但事件隐藏在一些便利功能之后。在这个单元中,我们将把事件拖入光明中,并创建一些我们自己的事物。

布线电路隐喻再次

这两种机制(属性和事件)是API“套接字”,即将组件连接在一起形成完整电路的方式。事件也在幕后,流经电路的电子。但这只是事件与属性不同的一种方式。

当您将<lightning:button>上的onclick属性设置为组件控制器中的操作处理程序时,将在这两个组件之间创建一个直接关系。他们是联系在一起的,当他们使用公共API保持彼此独立时,他们仍然是耦合的。

事件是不同的。组件不会将事件发送到其他组件。事实并非如此。组件广播特定类型的事件。如果有一个组件对这种类型的事件作出响应,并且该组件“听到”了你的事件,那么它就会采取行动。

您可以将属性和事件之间的区别视为有线电路和无线电路之间的区别。我们在这里不是说无线电话。一个组件不能获得另一个组件的“编号”并调用它。那将是一个属性。不,事件就像无线广播。你的组件到达收音机,并发出一条消息。有没有人打开收音机,并调整到正确的频率?你的组件没有办法知道 – 所以你应该以这样的方式编写你的组件,如果没有人听到他们广播的事件。 (也就是说,事情可能不起作用,但什么都不应该崩溃。)

从组件发送事件

好的,足够的理论,让我们做一些特定的应用程序,看看事件如何在代码中工作。我们将开始实施报销?复选框。然后,我们将采取我们所学到的,并用它来重构“添加费用”表格到自己的组件,这是伟大的工程师的意图。

首先,我们将重点放在Reimbursed__c字段的<lightning:input>上的点击处理程序。

<lightning:input type="toggle" 
            label="Reimbursed?"
            name="reimbursed"
            class="slds-p-around--small"
            checked="{!v.expense.Reimbursed__c}"
            messageToggleActive="Yes"
            messageToggleInactive="No"
            onchange="{!c.clickReimbursed}"/>

在我们深入点击处理程序之前,让我们回过头来看一下<lightning:input>必须提供的内容。 type =“toggle”实际上是一个带有切换开关设计的复选框。类使您可以应用自定义CSS样式或使用SLDS实用程序。 messageToggleActive和messageToggleInactive为选中和未选中的位置提供自定义标签。这些便捷的属性只是<lightning:input>上的其他几个属性。最后,<lightning:input>的onchange属性为我们提供了一个简单的方法,可以将切换开关连接到一个动作处理程序,当您向右滑动(选中)或向左滑动(未选中)时更新记录。

现在,让我们考虑一下选中或不选中时会发生什么。根据我们编写的代码来创建新的费用,更新费用可能是这样的。

  1. 获取已更改的费用项目。
  2. 创建一个服务器操作来更新基础费用记录。
  3. 将费用计入行动。
  4. 设置一个回调来处理响应。
  5. 触发操作,将请求发送到服务器。
  6. 当响应到来并且回调运行时,更新费用属性。

呃,什么费用属性?再看看我们的组件标记。没有开支,只是一个单一的开支。嗯,对,这个组件只是一个单一的项目。在费用清单组件上有一个费用属性…但这甚至不是“真正的”费用。真正的是顶级费用组件中的一个组件属性。嗯。

有没有一个component.get(“v.parent”)?或者,它必须是component.get(“v.parent”)。get(“v.parent”) – 让我们得到父母的父母的引用,所以我们可以在那里设置费用?

停止。对。那里。

组件不会触及其他组件,并在其上设置值。没有办法说“嘿,祖父母,我会更新费用。”组件保持自己的手。当一个组件想要一个祖先组件改变某些东西时,它会问。奈斯利。通过发送一个事件。

这是最酷的部分。发送事件看起来几乎与直接处理更新相同。这是clickReimbursed处理程序的代码。

({
    clickReimbursed: function(component, event, helper) {
        var expense = component.get("v.expense");
        var updateEvent = component.getEvent("updateExpense");
        updateEvent.setParams({ "expense": expense });
        updateEvent.fire();
    }
})
哇。这很简单!它看起来有点像我们上面所设想的。 clickReimbursed的上述代码执行以下操作:
  1. 获取更改的费用。
  2. 创建一个名为updateExpense的事件。
  3. 将费用打包到事件中。
  4. Fires 事件。

回调的东西丢失,但否则这是熟悉的。但是…什么是处理调用服务器和服务器响应,并更新主费用数组属性?我们怎么知道这个updateExpense事件呢?

updateExpense是一个自定义事件,也就是我们自己写的一个事件。你可以这样说,因为与获取服务器动作不同,我们使用component.getEvent()而不是component.get()。另外,我们所得到的没有价值提供者,只是一个名字。我们将在一瞬间定义这个事件。

至于什么是处理调用服务器和处理响应,让我们来谈谈它。我们可以在costItem组件中实现服务器请求并处理响应。然后,我们会发送一个事件,只是放弃依赖于费用数组的事情。这将是一个完全有效的设计选择,并将保持费用项目组件完全独立,这是可取的。

但是,正如我们将看到的,创建新费用的代码和更新现有费用的代码非常相似,足以避免重复的代码。因此,我们所做的设计选择是发送一个updateExpense事件,主费用组件将处理。后来,当我们重构我们的表单时,我们也会创建一个新的开销。

通过让所有子组件委托处理服务器请求和管理费用数组属性的责任,我们打破封装一点。但是,如果将这些子组件视为费用组件的内部实现细节,那也没关系。主要费用部分是独立的。

您可以选择:合并关键逻辑或封装。您可以在Lightning Components中进行权衡,就像在任何软件设计中进行权衡一样。只要确保你记录的细节。

定义一个事件

我们要做的第一件事是定义我们的自定义事件。在开发者控制台中,选择 File | New | Lightning Event, 并将事件命名为“updateEvent”。用下面的标记替换默认的内容。

<aura:event type="COMPONENT">
    <aura:attribute name="expense" type="Expense__c"/>
</aura:event>

有两种类型的事件,组件和应用程序。这里我们使用了一个组件事件,因为我们想要一个祖先组件来捕捉和处理这个事件。祖先是组件层次结构中的一个组件“之上”。如果我们想要一个“通用广播”类事件,任何组件都可以接收它,我们将使用应用程序事件。

应用程序和组件事件的完全区别和正确的用法并不是我们能够在这里得到的。这是一个更高级的话题,有足够复杂的细节,这是我们在这个模块中的目的。当你准备好了更多,资源将帮助你。

另外要注意的是事件的定义是多么紧凑。我们在创建时命名了这个事件,其中包括了费用更新日期,它的标记是一个开始和结束的<aura:event>标记和一个<aura:attribute>标记。事件的属性描述了它可以携带的有效载荷。在clickReimbursed操作处理程序中,我们通过调用setParams()来设置有效载荷。在事件定义中,我们看到事件参数是如何定义的,并且没有其他有效的参数。

而这几乎都是定义事件。您不要将实现或行为细节添加到事件本身。他们只是包。事实上,有些事件根本没有任何参数。他们只是消息。 “发生了这种情况!”发生和接收事件的组件中定义了“如果发生这种情况”的所有行为。

发送事件

我们已经看过如何在clickReimbursed操作处理程序中实际触发一个事件。但是要做到这一点,我们需要做最后一件事,那就是注册事件。将这一行标记添加到费用项组件,正好在其属性定义下方。

    <aura:registerEvent name="updateExpense" type="c:expensesItemUpdate"/>

这个标记说我们的组件触发了一个名为“updateExpense”的类型为“c:expensesItemUpdate”的事件。但是,当我们定义它时,不是“expensesItemUpdate”这个事件的名字吗?而组件或应用程序事件类型发生了什么?

你认为这有点令人困惑 – 这实际上是一个开关式的转换。这可能有助于将“应用程序”和“组件”视为Lightning Components框架事件类型,而来自您定义的事件名称的类型是自定义事件类型或事件结构类型。也就是说,当你定义一个事件时,你可以定义一个包的格式。当你注册发送一个事件时,你声明了它使用的格式。

定义和注册一个事件的过程可能看起来有些奇怪,所以让我们先看一下。这里在expenseItem中,我们将发送一个名为updateExpense的事件。稍后在expenseForm中,我们将发送一个名为createExpense的事件。这两个事件都需要包含一笔费用才能保存到服务器上。因此,他们都使用c:expensesItemUpdate事件类型或包格式发送事件。

在接收方,我们的主要支出部分将要注册来处理这两个事件。尽管服务器调用结束了,但用户界面更新略有不同。那么费用如何知道是否在c:expensesItemUpdate包中创建或更新费用?通过正在发送的事件的名称。

了解这里的区别,以及如何将一个事件用于多种目的,是学习Lightning组件的灯泡时刻。如果你还没有那个时刻,那么当你看其余的代码的时候,你就会拥有它。

在开始处理事件之前,让我们总结一下发送它们的过程。

  1. 通过创建闪电事件定义自定义事件,给它一个名称和属性。
  2. 注册您的组件发送这些事件,通过选择一个自定义事件类型,并给这种类型的具体使用一个名称。
  3. 通过以下方式在控制器(或助手)代码中触发事件:
    1. 使用component.getEvent()来创建特定的事件实例。
    2. 用fire()发送事件。

如果你继续执行所有我们刚刚看过的代码,你可以测试一下。重新加载您的应用程序,并切换报销?复选框几次。如果你错过了一步,你会得到一个错误,你应该重新检查你的工作。如果你做的一切正确…嘿,等等,花费改变颜色显示其报销?状态,就像预期的一样!

这个行为在我们开始这个单位之前就已经存在了。这就是<lightning:input>组件具有value =“{!v.expense.Reimbursed__c}”集合的效果。切换开关时,费用的本地版本将更新。但是这个改变没有被发送到服务器。如果您查看Salesforce中的费用记录,或重新加载应用程序,则不会看到更改。

为什么不?我们只做了一半的工作来为我们的活动创建一个完整的电路。我们必须通过在另一端创建事件处理程序来完成电路布线。该事件处理程序将负责将更改发送到服务器,并使更新持久。

处理事件

使费用项组件发送一个事件需要三个步骤。启用费用组件来接收和处理这些事件需要三个并行的步骤。

  1. 定义一个自定义事件。我们已经做到了这一点,因为费用项目正在发送费用正在接收的相同的自定义事件。
  2. 注册组件来处理事件。这将事件映射到动作处理程序。
  3. 实际上在动作处理程序中处理事件。

由于我们已经完成了第一步,我们立即转到第二步,注册费用来接收和处理updateExpense事件。就像注册发送一个事件一样,注册来处理一个事件是一行标记,你应该在init处理程序之后添加到费用组件中。

     <aura:handler name="updateExpense" event="c:expensesItemUpdate"
        action="{!c.handleUpdateExpense}"/>

与init处理程序一样,它使用<aura:handler>标记,并具有为该事件设置控制器操作处理程序的action属性。就像当你在costItem中注册事件一样,在这里你设置了事件的名字和类型 – 尽管注意到这个类型使用了更明智的事件属性。

换句话说,在这里你没有看到太多。什么是新的,特定于处理自定义事件,是属性的组合,并知道如何在费用中的接收器“套接字”与费用项中的发件人“套接字”相匹配。

这完成了这项工作的接线部分。剩下的就是实际编写动作处理程序!

我们将从handleUpdateExpense动作处理程序开始。这里是代码,并确保把它放在clickCreate动作处理器下。

    handleUpdateExpense: function(component, event, helper) {
        var updatedExp = event.getParam("expense");
        helper.updateExpense(component, updatedExp);
    }

呵呵。那很有意思。除了表单验证检查和特定的帮助函数,我们正在委托工作,它看起来像这个动作处理程序是一样的handleCreateExpense。

现在让我们添加updateExpense帮助函数。正如我们对动作处理器所做的那样,请确保将该代码放在createExpense帮助器函数的正下方。

    updateExpense: function(component, expense) {
        var action = component.get("c.saveExpense");
        action.setParams({
            "expense": expense
        });
        action.setCallback(this, function(response){
            var state = response.getState();
            if (state === "SUCCESS") {
                // do nothing!
            }
        });
        $A.enqueueAction(action);
    },

两件事你应该注意到的蝙蝠。首先,除了回调细节外,updateExpense帮助器方法与createExpense帮助器方法相同。这听起来像机会。

其次,关于这些回调细节。是什么赋予了?怎样才能做正确的事情呢?

想一想。之前,在测试发送事件(如果不是之前)时,我们看到了费用项组件的颜色是否改变以响应已报销的?复选框。记得解释吗?费用记录的本地副本已经更新!所以,至少现在,当服务器告诉我们它已经成功地更新它的版本时,我们不必做任何事情。

请注意,此代码仅处理服务器成功更新费用记录的情况。如果出现错误,我们肯定会有一些工作要做。说,会计将这笔费用标记为无法报销,因此无法将此字段设置为true。但是,正如他们所说,这是另一天的教训。

重构帮助函数

让我们回到我们看到的那个机会来分解一些常见的代码。除了回调之外,这两个辅助函数是相同的。所以,我们来创建一个新的,更广义的函数,将回调作为参数。

    saveExpense: function(component, expense, callback) {
        var action = component.get("c.saveExpense");
        action.setParams({
            "expense": expense
        });
        if (callback) {
            action.setCallback(this, callback);
        }
        $A.enqueueAction(action);
    },
回调参数是可选的。如果它在那里,我们会把它传递给行动。简单。现在我们可以将我们的事件特定的帮助函数减少到下面的代码。
    createExpense: function(component, expense) {
        this.saveExpense(component, expense, function(response){
            var state = response.getState();
            if (state === "SUCCESS") {
                var expenses = component.get("v.expenses");
                expenses.push(response.getReturnValue());
                component.set("v.expenses", expenses);
            }
        });
    },

    updateExpense: function(component, expense) {
        this.saveExpense(component, expense);
    },
createExpense只是稍微短一些,但它完全专注于响应返回时的操作(回调)。哇,updateExpense是一个单线!

重构添加费用表单

那个小小的重构练习是如此令人满意,使用事件是如此(我们很抱歉)让人激动,让我们再做一次,但是做得更大。更多的牛铃!

接下来的任务包括从费用组件中提取“添加费用”表单,并将其移至自己的新组件。提取表单标记很简单,只需简单的复制粘贴练习即可。但是还有什么动作呢?在我们开始移动碎片之前,让我们先思考一下什么是移动和停留。

在当前的设计中,表单的动作处理器clickCreate处理输入验证,将请求发送到服务器,并更新本地状态和用户界面元素。表单仍然需要一个动作处理程序,而且应该还可以处理表单验证。但是我们打算让其余的留下来,因为我们将我们的服务器请求逻辑合并到费用组件中。

所以有一点点(但只有一点点),在那里戏弄。那么我们的计划就是先移动表单标记,然后尽可能少地移动,以使其正常工作。我们将重构这两个组件以通过事件进行通信,而不是直接访问费用数组组件。

让我们开始吧!

在主费用组件中,选择两个<! – CREATE NEW EXPENSE – >注释之间的所有内容,包括开始和结束注释本身。把它剪切到你的剪贴板。 (是的,切,我们承诺)

创建一个新的Lightning组件,并将其命名为“expenseForm”。将复制的“添加费用”表单标记粘贴到<aura:component>标记之间的新组件中。

返回费用组件。将新的expenseForm组件添加到标记中。这部分费用应该是这样的。

    <!-- 新的费用表格 -->
    <lightning:layout >
        <lightning:layoutItem padding="around-small" size="6">
            <c:expenseForm/>
        </lightning:layoutItem>
    </lightning:layout>

此时,您可以重新加载您的应用程序以查看更改。应该没有明显的变化。但是,不出所料,创建费用按钮不再起作用。

让我们快速浏览其余的部分动作。

接下来,将费用组件中的newExpense属性移至expenseForm组件标记。这是用于表单域,所以它需要在表单组件。它移动不需要更改,所以只需从一个剪切并粘贴到另一个。

在expenseForm组件中,创建控制器和帮助器资源。

将clickCreate操作处理程序从费用控制器移至expenseForm控制器。按钮位于表单组件中,因此按钮的操作处理程序也需要在那里。相信与否,这也不需要改变。 (你可能在这里开始感受一个主题。)

现在我们需要做一些实际的改变。但是这些会很熟悉,因为我们只是添加事件发送,这是我们之前做的费用项目。您会记得,expenseItem还会发送一个由费用组件处理的费用负载的事件。

在expenseForm助手中,创建createExpense函数。

    createExpense: function(component, newExpense) {
        var createEvent = component.getEvent("createExpense");
        createEvent.setParams({ "expense": newExpense });
        createEvent.fire();
    },

这看起来非常像在expenseItem中的clickReimbursed操作处理程序。

如果组件要发送事件,则需要注册该事件。将以下内容添加到expenseForm组件标记中,位于newExpense属性下方。

    <aura:registerEvent name="createExpense" type="c:expensesItemUpdate"/>

在这一点上,我们已经完成了所有的工作来实现expenseForm组件。您应该可以重新加载应用程序,并且表单现在“有效”,因为没有错误,您输入无效数据时应该会看到相应的表单消息。如果您使用的是Salesforce Lightning Inspector,则甚至可以看到正在触发expenseItemUpdate事件。剩下的就是处理它。

在我们处理事件之前,请注意这个重构是多么容易。大部分的代码没有改变。在前面的步骤中总共有六行新的代码和标记。今天做这个工作是不熟悉的,但是要做几次,而且你意识到你只是在移动一些代码。

好的,让我们来完成这个。 expenseForm触发createExpense事件,但我们还需要费用组件来捕获它。首先我们注册createExpense事件处理程序,并将其连接到handleCreateExpense操作处理程序。再次,这是一个单一的标记线。将此行添加到updateExpense事件处理程序的正上方或下方。

    <aura:handler name="createExpense" event="c:expensesItemUpdate"
        action="{!c.handleCreateExpense}"/>
最后,在最后一步,在费用控制器中创建handleCreateExpense操作处理程序。在handleUpdateExpense操作处理程序的上方或下方添加此代码。
    handleCreateExpense: function(component, event, helper) {
        var newExpense = event.getParam("expense");
        helper.createExpense(component, newExpense);
    },

是的,那很简单。所有这些工作都委托给createExpense帮助函数,并且不会移动或更改。我们的handleCreateExpense动作处理程序就在那里把正确的东西连接在一起。

于是,我们完成了如何使用事件松散地耦合组件。在一个组件中创建和激发事件,在另一个组件中捕获并处理事件。无线电路!

奖金课初级视觉改进

在我们进入夕阳之前,或更确切地说,挑战之前,这里是两个适度的视觉改善。

首先,你有没有注意到报销和相关日期时间标签在绿色背景上几乎是看不见的蓝色?

Blue labels on green fix

如果它也困扰着你,那么这里是如何解决它的。 (我们一直这样做直到我们将表单重构为自己的组件)。在费用项目组件中,通过单击STYLE按钮添加一个样式资源。用下面的代码替换默认的样式规则。

.THIS.slds-theme_success .slds-form-element__label {
    color: #ffffff;
}
.THIS.slds-theme_success .slds-text-title {
    color: #ffffff;
}
重新加载应用程序的甜美,甜美的眼睛应变救济。

最后,我们想通过添加一些容器组件来改进我们的应用程序的布局。最后一点也让您有机会在所有的变化之后看到完整的费用部分。在费用组件中,将费用列表标记替换为以下内容。

<lightning:layout>
    <lightning:layoutItem padding="around-small" size="6">
        <c:expensesList expenses="{!v.expenses}"/>
    </lightning:layoutItem>
    <lightning:layoutItem padding="around-small" size="6">
        Put something cool here
    </lightning:layoutItem>
</lightning:layout>
这些变化的影响是增加一些边距和填充,并使费用列表更窄。布局留下的空间把东西放在右边。在下一个单元中,我们会建议你可以自己做一些练习。