简介
这篇博客不会涉及像RxJs这样著名的传统反应式编程库。
但我建议你看一下这些博客。
这篇博客旨在强调如何在SAPUI5中简单地使用反应式编程,而不需要外部librairies或框架的开销。
我将介绍SAPUI5中原生反应式编程的两种方式。
我不会做任何比较,你必须根据使用情况调整使用其中一种。
请注意 ManagedObjectModel仍然在实验性API中
使用案例
在一个工厂里,我们确实有存储仓。在每个存储仓中,都存储着一组材料。我们想建立一个简单的SAPUI5应用程序,它应该根据可用的库存量来反映存储仓的状态。
规则1:如果一个材料的库存大于0,那么材料的状态应该是绿色的,否则是红色。
规则2:如果存储仓中的一种材料没有库存(库存等于0),那么存储仓状态应该是红色,否则是绿色。
规则3:存储仓和物料的状态应根据库存状态自动反映。
下面是我们的领域模型。

领域模型
使用ManagedObjectModel和ManagedObject
ManagedObject类引入了有趣的概念,允许对现实世界进行建模,如聚合和关联。
与ManagedObjectModel结合起来,它提供了一个强大的使用属性设置器/获取器的反应式编程功能。
让我们从创建扩展了ManagedObject的类开始吧

类
Material.js
sap.ui.define([
"sap/ui/base/ManagedObject"
], /**
* @param {typeof sap.ui.base.ManagedObject} ManagedObect
*/
function (ManagedObect) {
"use strict";
return ManagedObect.extend("sample.ui5.reactive.bean.Material", {
metadata: {
properties: {
uid: "string",
name: "string",
price: "int",
stock: "int",
status: "boolean"
},
aggregations: {},
events: {}
},
init: function () {},
setStock: function (value) {
this.setProperty("stock", value, true);
if (value && value > 0) {
this.setProperty("status", true);
} else {
this.setProperty("status", false);
}
}
});
});
每次股票变化时,我们将根据传递的值更新状态属性。这样,我们就不需要在应用逻辑中重新计算状态了。
StorageBin.js
sap.ui.define([
"sap/ui/base/ManagedObject"
], /**
* @param {typeof sap.ui.base.ManagedObject} ManagedObect
*/
function (ManagedObect) {
"use strict";
return ManagedObect.extend("sample.ui5.reactive.bean.StorageBin", {
metadata: {
properties: {
name: "string",
status:"boolean"
},
aggregations: {
materials:{
type:"sample.ui5.reactive.bean.Material",
multiple: true
}
},
events: {}
},
init: function () {},
getStatus:function(){
var aMaterials = this.getAggregation("materials");
var bStockZero = true;
$.each(aMaterials, function(index, item){
if(item.getStock() === 0){
bStockZero = false;
///exit
return false;
}
});
return bStockZero;
}
});
});
基于它的物料聚集状态,存储仓将动态地计算它的状态。
Warehouse.js
sap.ui.define([
"sap/ui/base/ManagedObject"
], /**
* @param {typeof sap.ui.base.ManagedObject} ManagedObect
*/
function (ManagedObect) {
"use strict";
return ManagedObect.extend("sample.ui5.reactive.bean.Warehouse", {
metadata: {
properties: {
name: "string"
},
aggregations: {
storageBins:{
type:"sample.ui5.reactive.bean.StorageBin",
multiple: true
}
},
events: {}
},
init: function () {}
});
});
不,我们将需要组装件来运行这个例子。
我们将使用一个静态数据。
{
"storageBins":[
{
"name":"Storage Bin 1",
"materials":[
{
"uuid":"11000211",
"name":"Material 1",
"price":100,
"stock":0
},
{
"uuid":"11000212",
"name":"Material 2",
"price":200,
"stock":5
}
]
},
{
"name":"Storage Bin 2",
"materials":[
{
"uuid":"11000211",
"name":"Material 3",
"price":45,
"stock":8
},
{
"uuid":"11000212",
"name":"Material 4",
"price":201570,
"stock":41
}
]
}
]
}
建立ManagedObject对象(与它的聚合)和创建ManagedObjectModel。
var oDataModelJson = this.getOwnerComponent().getModel("DataModelJson");
var oData = oDataModelJson.getData();
var oWarehouse = new Warehouse();
$.each(oData.storageBins, function(index, item){
var oStorageBin = new StorageBin();
oStorageBin.setName(item.name);
$.each(item.materials, function(indexM, itemM){
var oMaterial = new Material();
oMaterial.setUid(itemM.uuid);
oMaterial.setName(itemM.name);
oMaterial.setPrice(itemM.price);
oMaterial.setStock(itemM.stock);
oStorageBin.addMaterial(oMaterial);
});
oWarehouse.addStorageBin(oStorageBin);
});
this.getView().setModel(new ManagedObjectModel(oWarehouse), "DataModel");
在XLL视图中。
<View controllerName="sample.ui5.reactive.controller.Main" xmlns:mvc="sap.ui.core.mvc" displayBlock="true" xmlns="sap.m">
<Page id="page" title="{i18n>title}">
<content>
<Table id="idProductsTable" inset="false" items="{
path: 'DataModel>/storageBins'
}" mode="SingleSelectMaster" itemPress=".onItemPress">
<columns>
<Column width="12em">
<Text text="Storage Bin" />
</Column>
<Column minScreenWidth="Desktop" demandPopin="true" hAlign="End">
<Text text="Status" />
</Column>
</columns>
<items>
<ColumnListItem vAlign="Middle" type="Active" highlight="{= ${DataModel>status}?'Success':'Error'}">
<cells>
<ObjectIdentifier title="{DataModel>name}" text="{DataModel>uid}" />
<Text text="{DataModel>status}" />
</cells>
</ColumnListItem>
</items>
</Table>
<HBox height="100px" ></HBox>
<Table id="idMaterialsTable" inset="false" items="{
path: 'DataModel>materials'
}">
<columns>
<Column width="12em">
<Text text="Material" />
</Column>
<Column minScreenWidth="Desktop" demandPopin="true" hAlign="End">
<Text text="Status" />
</Column>
<Column minScreenWidth="Desktop" demandPopin="true" hAlign="End">
<Text text="Stock" />
</Column>
</columns>
<items>
<ColumnListItem vAlign="Middle" highlight="{= ${DataModel>status}?'Success':'Error'}">
<cells>
<ObjectIdentifier title="{DataModel>name}" text="{DataModel>uid}" />
<Text text="{DataModel>status}" />
<StepInput
value="{DataModel>stock}"
min="0"
max="1000"
width="120px"
step="1"/>
</cells>
</ColumnListItem>
</items>
</Table>
</content>
</Page>
</View>
现在,如果你运行该应用程序,你将能够测试该功能。储存箱状态将根据其材料的库存可用性来反映。如果你使用步骤输入在材料层面改变库存状态,状态将自动反映。

应用 (1)
使用简单的JSONModel + Object.defineProperty()
你可以通过简单地结合JSONModel和Object.defineProperty()实现同样的行为。正如我们在ManagedObject层面上通过定义setters/getters所做的那样,我们可以使用简单的 POJO (Plain Old Javascript Object)和Object.defineProperty()来完成同样的工作。
var oDataModelJson = this.getOwnerComponent().getModel("DataModelJson");
var oData = oDataModelJson.getData();
$.each(oData.storageBins, function(index, item){
Object.defineProperty(item, "status", {
get: function () {
var bStockZero = true;
$.each(item.materials, function(indexM, itemM){
if(itemM.stock === 0){
bStockZero = false;
///exit
return false;
}
});
return bStockZero;
}
});
$.each(item.materials, function(indexM, itemM){
Object.defineProperty(itemM, "status", {
get: function () {
return !!(itemM.stock && itemM.stock > 0);
}
});
});
});
this.getView().setModel(oDataModelJson, "DataModel");
当你运行这个应用程序时,你会得到完全相同的行为。
结论
在开发具有丰富用户体验的商业应用时,我们需要考虑降低开发的复杂性。例如,反应式编程可以帮助你大大降低算法的复杂性,这对代码维护也有积极的影响。
也可以考虑在你的应用程序中使用基于事件的逻辑,将你的应用程序分割成不同的组件(关注点分离)并使用事件总线。构建对事件做出反应的组件有助于它们的整合,也有助于它们的退役。请看这篇关于如何正确使用EventBus的文章。