Vaadin的AbstractJavascriptComponent中的部分状态更改

3
我正在实现一个基于JavaScript的Vaadin组件,需要显示并更新一个相对较大的数据集。我通过扩展AbstractJavaScriptComponent来实现这一点。
我试图尽量让JS端保持“愚蠢”,将用户交互委托给使用RPC与服务器通信的共享状态,并更新了共享状态。然后JS连接器包装器的onStateChange 函数被调用以使用新状态更新DOM。
我有两个问题:
1.当小部分数据集更新时,我不想每次都传输整个数据集。 2.我也不希望每次都完全重新构建UI。
我可以通过保留先前的状态并比较其部分来解决第二个问题,以找出更改的内容并仅进行必要的DOM更改。但是这仍然存在第一个问题。
我必须停止使用Vaadin的共享状态机制,而只使用RPC来通信变化吗?
更新: 我已经进行了一些测试,它似乎表明Vaadin的共享状态机制在效率方面非常糟糕:
每当组件调用getState()以更新状态对象中的某个属性(或者甚至没有更新任何内容),整个状态对象就会被传输。据我所知,避免这种情况的唯一方法是不使用共享状态机制,而是使用RPC调用向客户端通信特定的状态更改。
RPC方法存在一些问题需要解决,例如:如果在单个请求/响应周期内多次更改值,则不希望多次进行RPC调用。相反,您只希望像共享状态机制一样仅发送最后一个值作为响应中的最终状态。您可以为要单独发送的每个状态部分保留脏标志(或仅保留先前状态并进行比较),但然后您需要以某种方式触发RPC调用在请求处理结束时。该如何实现?
欢迎提出任何关于这方面的想法!
更新2: Vaadin 8修复了根本问题:它仅发送更改的状态属性。此外,仅在执行RPC调用时(而不更改任何状态)时,它不再调用JS连接器上的onStateChange()函数。
3个回答

2

OP在指出对于基于AbstractJavaScriptComponent的组件来说,共享状态同步是低效的。每当连接器被标记为脏时,整个状态对象都被序列化并提供给Javascript连接器的onStateChange方法。其他非Javascript组件通过仅发送更改来更智能地处理状态更新。代码中确切发生这种情况的位置是com.vaadin.server.LegacyCommunicationManager.java中的第97行。

boolean supportsDiffState = !JavaScriptConnectorState.class
            .isAssignableFrom(stateType);

我不确定为什么AbstractJavaScriptComponent基于组件的状态更新处理方式不同。也许是为了简化javascript连接器并消除从增量中组装完整状态对象的需要。如果可以在将来的版本中解决这个问题,那就太好了。

正如你所建议的,你可以完全放弃JavaScriptComponentState并依靠服务器->客户端RPC进行更新。在您的服务器端组件中保留脏标志或通过任何机制比较旧状态和新状态。

为了合并更改并仅发送每个更改的一个RPC调用,您可以在您的服务器端组件中覆盖beforeClientResponse(boolean initial)。这在向客户端发送响应之前调用,是您添加一组RPC调用以更新客户端组件的机会。

或者,您可以覆盖encodeState,在其中自由地发送到客户端所需的任何JSON。您可以选择将更改列表添加到super.encodeSate返回的基本JSON对象中。您的javascript连接器可以根据需要在其onStateChange方法中进行解释。

编辑以添加:在您的服务器端组件中调用getState()将标记连接器为脏。如果您想获取状态而不将其标记为脏,请改用getState(false)


这正是我在寻找的答案。当通过覆盖encodeState而不发送完整状态来“作弊”时,是否存在关于detach和attach的任何注意事项?例如,在分离和重新连接后,是否应该发送完整状态? - herman
不幸的是,我现在必须取消接受答案。原因是无法防止完整状态的传输(以及调用 onStateChange() JS 函数),因为 AbstractComponent#beforeClientResponse(boolean initial) 重复调用 getState(),将连接器标记为脏。当然,我们可以覆盖,但我相信那段代码存在是有原因的。在我看来,这是一个 Vaadin 的 bug。 - herman
我在他们的问题跟踪器上提交了这个票。我必须找到一些解决方法来绕过它。 - herman
尝试通过覆盖getState()以调用super.getState(false)并仅在需要时显式调用markAsDirty()来解决问题,但发现另一个问题:任何JS函数调用(通过callFunction(...))都将调用markAsDirty()并导致JS连接器的onStateChange()被调用。已提交另一张工单 - herman
1
顺便提一下,Vaadin 8修复了所有这些问题。请参见https://dev.vaadin.com/ticket/20335,其中还修复了https://dev.vaadin.com/ticket/19837和https://dev.vaadin.com/ticket/19828。 - herman

0
根据我们的讨论,我创建了一个可替换AbstractJavaScriptComponent的插件,它可以传输状态增量并包含一些额外的增强功能。它还处于早期阶段,但应该很有用。

https://github.com/emuanalytics/vaadin-enhancedjavascript

解决方案看似简单:通过绕过在com.vaadin.server.LegacyCommunicationManager.java中的这行代码,重新启用状态差异计算即可:
boolean supportsDiffState = !JavaScriptConnectorState.class
        .isAssignableFrom(stateType);

实现该解决方案的复杂性在于Vaadin类不易扩展,因此我不得不复制并重新实现了6个类。


谢谢,我有时间会看一下。我之前也注意到 Vaadin 类很难扩展,因为有 private/final 内容。即使使用了差异算法,我仍认为需要修复 JS 连接器包装器的 onStateChange() 函数不必要的调用。目前,我通过在状态中添加一个 version 属性,并仅在实际更改某些状态属性时递增该值,然后在连接器包装器中将其与先前值进行比较来解决了该问题的一部分。 - herman
顺便问一下,使用这个插件是否比根本不使用共享状态,而仅显式调用JS方法(使用callFunction)来通信某些状态更可取(如您在其他答案中描述的在beforeClientResponse中合并)?这样客户端也不必通过检查增量来弄清楚发生了什么变化。 - herman
使用此组件时,使用callFunction()进行服务器->客户端RPC时不会调用onStateChange()。 您可以自由地使用共享状态或RPC而无需任何臃肿的有效载荷。 在这方面,使用此组件与使用“本机”vaadin/GWT组件完全相同。 使用共享状态还是RPC(或两者混合)取决于您,并且将取决于您包装的Javascript组件公开了什么样的API。 例如,如果API是扁平的基于过程的API,则RPC可能更合适。 我正在使用它来包装MapboxGL.js并使用混合模式。 - Robin Summerhill
共享状态的一个好用处是用于传递初始化JavaScript组件所需的初始参数和选项。这些可以在Java组件构造函数中设置为共享状态,并且立即可用于创建JavaScript组件的JavaScript端。 - Robin Summerhill
确实。我正在考虑绕过状态问题,并仍然有一种方法通过调用一个RPC函数(称为 'setState'或类似名称)在服务器端组件构造时立即初始化组件的初始状态。我认为它将与共享状态一起在同一初始响应中发送。 - herman
现在有一个关于此事的新Vaadin票:https://dev.vaadin.com/ticket/20335 - 听起来可能比你所做的要复杂一些(虽然我不确定为什么),但他们会尝试将其纳入Vaadin 8中(请参见https://vaadin.com/forum#!/thread/8350615/14119965)。 - herman

-1
Vaadin的共享状态完全符合您的预期:当组件第一次添加到DOM时,整个共享状态从服务器传输到客户端,以便显示该组件。之后,只有更改会被传输。例如,通过调用component.setCaption("new caption")更改可见组件的标题,Vaadin仅将新的标题文本传输到客户端,并将其“合并”到组件的客户端共享状态实例中。

当嵌套属性被更改时,它是如何工作的?例如,如果我的状态对象有一个人员列表,并且其中一个人的地址已更改,那么会传输什么:整个列表、单个人员还是单个地址? - herman
抱歉,但必须投下反对票。我进行了测试:该组件调用getState()以更新任何值(也在setCaption()中),这会导致连接器被标记为脏,并且我看到整个状态被传输,即使只有单个值发生了更改。实际上,即使没有任何更改(仅调用getState())。 - herman
赫尔曼正确地指出,基于AbstractJavaScriptComponent的组件没有实现变更检测。请参阅我的答案以获取详细信息。 - Robin Summerhill

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接