本文介绍了“onBeforeRendering"或“onAfterRendering"每次打开视图时都不会调用的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在我的 UI5 应用程序中,我有一个带有表 (sap.m.Table) 的视图,由来自后端的数据填充在 onInit 挂钩上.问题是 onInit 每个视图实例只执行一次:

onBeforeRenderingonAfterRendering 钩子不同,每个 View 实例只调用一次.

如果用户决定离开这个视图(例如,返回导航)并稍后重新打开它,onInit 将不会被调用,因此数据将不会被再次检索,并且表格内容不会反映可能的变化.

为了确保每次打开视图时都检索到数据,我尝试在onBeforeRendering 处获取数据,但是这个钩子也只调用了一次.我发现,强制每次打开视图时都调用 onBeforeRendering 的唯一方法是将以下代码添加到 onInit 方法中:

onInit: function () {this.getView().addEventDelegate({onBeforeShow: this.onBeforeShow,}, 这);}

我的问题:

  1. 为什么没有 onInit 中的上述代码片段,每次显示视图时都不会触发 onBeforeRendering?

  2. 上面的代码片段到底做了什么?

  3. 替代技术:使用 patternMatchedrouteMatched.但这三种方法中哪一种更常见?

解决方案

我认为对什么是渲染"存在误解.方法.在 UI5 中,当控件正在渲染"时,RenderManager 正在 DOM 中修改或创建其相应的 HTML 元素.IE.onBeforeRendering 字面意思是在调用控件(此处:View)的 render 函数之前".

onBeforeRenderingnot 意味着它在浏览器的 paint 事件之前调用(为此,现代浏览器提供高级 API,例如作为交叉路口观察员).
渲染的控件可以在 DOM 中,但不能同时在视口中可见.

回到问题;之所以没有触发on*Rendering,是因为控件之前已经渲染过了.当用户通过 navTo 导航然后再次返回时,可以看到这一点.对应的视图元素已经在DOM中,所以不需要再次调用render,意味着没有触发on*Rendering.


  1. 代码片段究竟做了什么?

    this.getView().addEventDelegate({onBeforeShow: this.onBeforeShow,}, 这);

addEventDelegate控件上触发的事件添加一个监听器(不是由控件触发).

例如:视图包含事件,如afterInitbeforeExit、...
执行 addEventDelegate({onAfterInit}) 将不起作用,因为 afterInit 被此控件(视图)触发.
执行 addEventDelegate({onmouseover}) 有效,因为它这个控件上触发.

同样适用于 onBeforeShow.该视图不包含任何事件,如 beforeShowafterShow 等.这些事件由 NavContainer<on 触发./code>(例如通过 在其子视图上).可以在以下位置找到有关这些事件的文档:

另见类似问题https://stackoverflow.com/questions/44882085/why-does-onbeforefirstshow-work/44882676


  1. 替代技术:使用patternMatched (...).但是这三种方法中哪一种更常见?

通过三种方法"我假设你的意思是:

  • on*Rendering(第一种方法),
  • on*Show(第二种方法),
  • 以及上面提到的路由事件,如 patternMatched(第三种方法).

与往常一样,答案是这取决于您要实现的目标.但通常,我们:

  • 如果应用程序没有路由概念(manifest.json 中没有 sap.ui5/routing),请使用第二种方法(NavContainerChild 事件).

  • 如果意图是在视图显示后设置初始焦点,请使用带有 onAfterShow 的第二种方法.请参阅如何在视图中设置初始焦点?

  • 使用第 3 种方法获取有关正在匹配的路由模式的通知.这种方法通常用于在每次显示视图 (NavContainerChild) 时执行某些操作,例如执行 上下文绑定 导航到详细信息页面后.工作原理:

    1. router.navTo()被调用时,下一个对应的视图和控制器被创建.
    2. 在新创建的控制器的 onInit 中,您分配一个 patternMatched handler.
    3. 在导航时,URL 哈希值会发生变化.路由器(内部为 HashChanger) 注意到 URL 更改,导致路由触发 patternMatched 将调用您的处理程序.请参阅下面的TL;DR.

  • ⚠️ 个人意见:作为 应用程序 开发人员,避免第一种方法.避免在 onBeforeRenderingonAfterRendering 中做任何事情,因为从应用程序的角度来看,render 函数被调用的频率是不可预测的.对于控制开发人员来说,这些钩子是绝对必要的.但对于应用程序开发人员来说,通常有更好的选择.


TL;DR

忘记on(Before|After)Rendering.使用 (pattern)Matched event 来自路由:

{//控制器onInit:函数(){const myRoute = this.getOwnerComponent().getRouter().getRoute(routeName");myRoute.attachPatternMatched(this.onMyRoutePatternMatched, this);},onMyRoutePatternMatched:函数(事件){//当视图即将显示时你的代码..},}

In my UI5 app, I have a view with a table (sap.m.Table), populated by data coming from the back-end at onInit hook. The problem is that onInit is executed only once per view instance:

And if a user decides to leave this view (e.g., back navigation) and to reopen it later, the onInit will not be recalled, and thus the data will not be retrieved again, and the table content will not reflect the possible changes.

To ensure that the data are retrieved every time the view is opened, I tried to get the data at onBeforeRendering, but this hook is also called just once. The only way, that I have found, to force onBeforeRendering to be called every time the view is opened, is to add the following code into onInit method:

onInit: function () {
  this.getView().addEventDelegate({
    onBeforeShow: this.onBeforeShow,
  }, this);
}

My questions:

  1. Why, without the code snippet above in onInit, is the onBeforeRendering not triggered every time the view is displayed?

  2. What does exactly the code snippet above do?

  3. The alternative technique: to use patternMatched and routeMatched. But which one of these three approaches is more common?

解决方案

I think there is a misconception of what "rendering" means. In UI5, when a control is "rendering", its corresponding HTML element is being modified or created in the DOM by the RenderManager. I.e. onBeforeRendering means literally "before the render function of the control (here: View) is called".

onBeforeRendering does not mean that it's called before the paint event from the browser (For that, modern browsers provide high-level APIs such as Intersection Observer).
Rendered controls can be in the DOM but not visible in the viewport at the same time.

Coming back to the question; the reason why on*Rendering is not triggered, is because the control has been already rendered before. This can be seen when user navigates via navTo and then back again. The corresponding view element is already in the DOM, so there is no need to call render again, meaning no on*Rendering triggered.


addEventDelegate adds a listener to the events that are fired on the control (not by the control).

E.g.: The view contains events like afterInit, beforeExit, ...
Doing addEventDelegate({onAfterInit}) won't work since afterInit is fired by this control (view).
Doing addEventDelegate({onmouseover}) works since it's fired on this control.

The same applies to the onBeforeShow. The view doesn't contain any events like beforeShow, afterShow, etc.. Those are fired on the view by the NavContainer (e.g. by <App> on its child, view). Documentation about those events can be found in:

See also a similar question https://stackoverflow.com/questions/44882085/why-does-onbeforefirstshow-work/44882676


By "three approaches" I assume you mean:

  • on*Rendering (1st approach),
  • on*Show (2nd approach),
  • and the above mentioned routing events like patternMatched (3rd approach).

The answer is, as always, it depends on what you're trying to achieve. But usually, we:

  • Use the 2nd approach (NavContainerChild events) if the application does not have a routing concept (no sap.ui5/routing in manifest.json).

  • Use the 2nd approach with onAfterShow if the intent is to set initial focus after the view is displayed. See How to Set Initial Focus in a View?

  • Use the 3rd approach to get notified about the route pattern being matched. This approach is commonly used to do something every time the view (NavContainerChild) is displayed, for example, to do Context Binding after navigating to a detail page. How it works:

    1. When router.navTo() is called, the next corresponding view and controller are created.
    2. In onInit of the newly created controller, you assign a patternMatched handler.
    3. On navigation, the URL hash value will change. The router (internally the HashChanger) notices the URL change, leading to Route firing patternMatched by which your handler will be invoked. See the TL;DR below.

  • ⚠️ Personal opinion: avoid 1st approach as an application developer. Avoid doing anything in onBeforeRendering and in onAfterRendering since it's unpredictable how often the render function is called from the viewpoint of the application. For control developers, those hooks are absolutely necessary. But for application developers, there are often better alternatives.


TL;DR

Forget on(Before|After)Rendering. Use the (pattern)Matched event from the route instead:

{ // Controller
  onInit: function() {
    const myRoute = this.getOwnerComponent().getRouter().getRoute("routeName");
    myRoute.attachPatternMatched(this.onMyRoutePatternMatched, this);
  },
  onMyRoutePatternMatched: function(event) {
    // your code when the view is about to be displayed ..
  },
}

这篇关于“onBeforeRendering"或“onAfterRendering"每次打开视图时都不会调用的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

08-29 04:07