学习目标
完成本单元后,您将能够:
- 为您的应用定义自定义事件。
- 从组件控制器创建和激发事件。
- 创建动作处理程序来捕获和处理其他组件发送的事件。
- 将一个大的组件重构成更小的组件。
连接组件与事件
在本单元中,我们将解决我们的小开支应用程序中最后一个未完成的功能:报销?复选框。你可能认为实现一个复选框将是一个简短的话题。我们当然可以采取一些捷径,并将其作为一个很短的话题。
但是除了使复选框工作之外,这个单元还有关于删除我们已经采取的所有快捷方式。我们要花这个单位“正确地做”,其中有几个地方意味着我们之前做过的重构工作。
在开始之前,我们先来谈谈我们采取的捷径,正确的道路,为什么正确的道路(有点)更难,但也更好。
组成和分解
如果您以源代码的形式看看我们的小费用应用程序,并列出单独的代码工件,您将得到如下内容。
- expenses component
- expenses.cmp
- expensesController.js
- expensesHelper.js
- expensesList component
- expensesList.cmp
- expenseItem component
- expenseItem.cmp
- ExpensesController (server-side)
- ExpensesController.apex
以下是所有内容如何组合在一起的情况,以及稍后您接线的createExpense和updateExpense事件。
但是,如果你看屏幕上的应用程序,你看到了什么?你应该看到什么,你看到的最终会看到什么,是应用程序分解成更多的组件。您会看到,您可以将我们的应用程序进一步分解为更小的碎片,比迄今为止所做的更多。至少,我们希望您看到“添加费用”表单实际上应该是它自己的独立组件。 (这就是为什么我们在用户界面上画一个盒子的原因!)
为什么我们不把这个表格作为一个单独的组件?不这样做是迄今为止我们在这个模块的过程中最大的捷径。在软件设计方面,这比我们称之为“恶心”的黑客更糟。构建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}"/>
- 获取已更改的费用项目。
- 创建一个服务器操作来更新基础费用记录。
- 将费用计入行动。
- 设置一个回调来处理响应。
- 触发操作,将请求发送到服务器。
- 当响应到来并且回调运行时,更新费用属性。
呃,什么费用属性?再看看我们的组件标记。没有开支,只是一个单一的开支。嗯,对,这个组件只是一个单一的项目。在费用清单组件上有一个费用属性…但这甚至不是“真正的”费用。真正的是顶级费用组件中的一个组件属性。嗯。
有没有一个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();
}
})
- 获取更改的费用。
- 创建一个名为updateExpense的事件。
- 将费用打包到事件中。
- Fires 事件。
回调的东西丢失,但否则这是熟悉的。但是…什么是处理调用服务器和服务器响应,并更新主费用数组属性?我们怎么知道这个updateExpense事件呢?
updateExpense是一个自定义事件,也就是我们自己写的一个事件。你可以这样说,因为与获取服务器动作不同,我们使用component.getEvent()而不是component.get()。另外,我们所得到的没有价值提供者,只是一个名字。我们将在一瞬间定义这个事件。
至于什么是处理调用服务器和处理响应,让我们来谈谈它。我们可以在costItem组件中实现服务器请求并处理响应。然后,我们会发送一个事件,只是放弃依赖于费用数组的事情。这将是一个完全有效的设计选择,并将保持费用项目组件完全独立,这是可取的。
但是,正如我们将看到的,创建新费用的代码和更新现有费用的代码非常相似,足以避免重复的代码。因此,我们所做的设计选择是发送一个updateExpense事件,主费用组件将处理。后来,当我们重构我们的表单时,我们也会创建一个新的开销。
通过让所有子组件委托处理服务器请求和管理费用数组属性的责任,我们打破封装一点。但是,如果将这些子组件视为费用组件的内部实现细节,那也没关系。主要费用部分是独立的。
您可以选择:合并关键逻辑或封装。您可以在Lightning Components中进行权衡,就像在任何软件设计中进行权衡一样。只要确保你记录的细节。
定义一个事件
我们要做的第一件事是定义我们的自定义事件。在开发者控制台中,选择
, 并将事件命名为“updateEvent”。用下面的标记替换默认的内容。<aura:event type="COMPONENT">
<aura:attribute name="expense" type="Expense__c"/>
</aura:event>
发送事件
我们已经看过如何在clickReimbursed操作处理程序中实际触发一个事件。但是要做到这一点,我们需要做最后一件事,那就是注册事件。将这一行标记添加到费用项组件,正好在其属性定义下方。
<aura:registerEvent name="updateExpense" type="c:expensesItemUpdate"/>
了解这里的区别,以及如何将一个事件用于多种目的,是学习Lightning组件的灯泡时刻。如果你还没有那个时刻,那么当你看其余的代码的时候,你就会拥有它。
在开始处理事件之前,让我们总结一下发送它们的过程。
- 通过创建闪电事件定义自定义事件,给它一个名称和属性。
- 注册您的组件发送这些事件,通过选择一个自定义事件类型,并给这种类型的具体使用一个名称。
- 通过以下方式在控制器(或助手)代码中触发事件:
- 使用component.getEvent()来创建特定的事件实例。
- 用fire()发送事件。
如果你继续执行所有我们刚刚看过的代码,你可以测试一下。重新加载您的应用程序,并切换报销?复选框几次。如果你错过了一步,你会得到一个错误,你应该重新检查你的工作。如果你做的一切正确…嘿,等等,花费改变颜色显示其报销?状态,就像预期的一样!
这个行为在我们开始这个单位之前就已经存在了。这就是<lightning:input>组件具有value =“{!v.expense.Reimbursed__c}”集合的效果。切换开关时,费用的本地版本将更新。但是这个改变没有被发送到服务器。如果您查看Salesforce中的费用记录,或重新加载应用程序,则不会看到更改。
为什么不?我们只做了一半的工作来为我们的活动创建一个完整的电路。我们必须通过在另一端创建事件处理程序来完成电路布线。该事件处理程序将负责将更改发送到服务器,并使更新持久。
处理事件
使费用项组件发送一个事件需要三个步骤。启用费用组件来接收和处理这些事件需要三个并行的步骤。
- 定义一个自定义事件。我们已经做到了这一点,因为费用项目正在发送费用正在接收的相同的自定义事件。
- 注册组件来处理事件。这将事件映射到动作处理程序。
- 实际上在动作处理程序中处理事件。
由于我们已经完成了第一步,我们立即转到第二步,注册费用来接收和处理updateExpense事件。就像注册发送一个事件一样,注册来处理一个事件是一行标记,你应该在init处理程序之后添加到费用组件中。
<aura:handler name="updateExpense" event="c:expensesItemUpdate"
action="{!c.handleUpdateExpense}"/>
handleUpdateExpense: function(component, event, helper) {
var updatedExp = event.getParam("expense");
helper.updateExpense(component, updatedExp);
}
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);
},
想一想。之前,在测试发送事件(如果不是之前)时,我们看到了费用项组件的颜色是否改变以响应已报销的?复选框。记得解释吗?费用记录的本地副本已经更新!所以,至少现在,当服务器告诉我们它已经成功地更新它的版本时,我们不必做任何事情。
请注意,此代码仅处理服务器成功更新费用记录的情况。如果出现错误,我们肯定会有一些工作要做。说,会计将这笔费用标记为无法报销,因此无法将此字段设置为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);
},
重构添加费用表单
那个小小的重构练习是如此令人满意,使用事件是如此(我们很抱歉)让人激动,让我们再做一次,但是做得更大。更多的牛铃!
接下来的任务包括从费用组件中提取“添加费用”表单,并将其移至自己的新组件。提取表单标记很简单,只需简单的复制粘贴练习即可。但是还有什么动作呢?在我们开始移动碎片之前,让我们先思考一下什么是移动和停留。
在当前的设计中,表单的动作处理器clickCreate处理输入验证,将请求发送到服务器,并更新本地状态和用户界面元素。表单仍然需要一个动作处理程序,而且应该还可以处理表单验证。但是我们打算让其余的留下来,因为我们将我们的服务器请求逻辑合并到费用组件中。
所以有一点点(但只有一点点),在那里戏弄。那么我们的计划就是先移动表单标记,然后尽可能少地移动,以使其正常工作。我们将重构这两个组件以通过事件进行通信,而不是直接访问费用数组组件。
让我们开始吧!
在主费用组件中,选择两个<! – CREATE NEW EXPENSE – >注释之间的所有内容,包括开始和结束注释本身。把它剪切到你的剪贴板。 (是的,切,我们承诺)
创建一个新的Lightning组件,并将其命名为“expenseForm”。将复制的“添加费用”表单标记粘贴到<aura:component>标记之间的新组件中。
返回费用组件。将新的expenseForm组件添加到标记中。这部分费用应该是这样的。
<!-- 新的费用表格 -->
<lightning:layout >
<lightning:layoutItem padding="around-small" size="6">
<c:expenseForm/>
</lightning:layoutItem>
</lightning:layout>
现在我们需要做一些实际的改变。但是这些会很熟悉,因为我们只是添加事件发送,这是我们之前做的费用项目。您会记得,expenseItem还会发送一个由费用组件处理的费用负载的事件。
在expenseForm助手中,创建createExpense函数。
createExpense: function(component, newExpense) {
var createEvent = component.getEvent("createExpense");
createEvent.setParams({ "expense": newExpense });
createEvent.fire();
},
<aura:registerEvent name="createExpense" type="c:expensesItemUpdate"/>
<aura:handler name="createExpense" event="c:expensesItemUpdate"
action="{!c.handleCreateExpense}"/>
handleCreateExpense: function(component, event, helper) {
var newExpense = event.getParam("expense");
helper.createExpense(component, newExpense);
},
奖金课初级视觉改进
在我们进入夕阳之前,或更确切地说,挑战之前,这里是两个适度的视觉改善。
首先,你有没有注意到报销和相关日期时间标签在绿色背景上几乎是看不见的蓝色?
如果它也困扰着你,那么这里是如何解决它的。 (我们一直这样做直到我们将表单重构为自己的组件)。在费用项目组件中,通过单击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>