回溯重新渲染是指在渲染过程中,您修改了已经渲染的内容的场景。
例如:
{{foo}} {{foo-bar parent=this}}
// app/components/foo-bar.js
export default Ember.Component.extend({
init() {
this._super(...arguments);
this.get('parent').set('foo', 'bar');
}
});
如您所见,当foo-bar
组件被实例化和呈现时,我们已经使用了来自父上下文的foo
值来填充{{foo}}
卷曲。 然而,在它的构造函数中,它试图修改相同的值,因此。 “回溯”。
这是一个非常极端的例子,但它说明了问题。 除了init
, didInitAttrs
, didReceiveAttrs
, willInsertElement
和willRender
也在渲染过程中同步发生。 此外,回溯通常是由双向绑定属性的行为引起的问题。
这种行为一直不可靠,但部分支持被弃用(自 Ember 1.13 起):
You modified ApplicationController.foo twice in a single render. This was unreliable in Ember 1.x and will be removed in Ember 3.0
从 1.13 开始,Ember 通过在检测到回溯时立即进行第二次重新渲染来支持这一点(然后重复直到系统稳定)。 这种策略本身可能是性能问题的根源。 在极端情况下,这可能会导致无限循环。
在 Glimmer 2 中,虽然额外的重新渲染相对便宜,但用于检测回溯set
的额外簿记却不是。 Glimmer 2 系统的优势之一是它不需要急切地设置观察者来跟踪变化。 此外,Glimmer 2 中的某些优化允许系统在不知道其中的任何内容发生变化时跳过遍历子树。
总之,这些因素意味着我们不能轻易检测到这些回溯set
s(或某些东西是否“已经呈现”),而无需进行大量额外的簿记并故意破坏这些优化。
我们已经编写了支持此功能的代码,但由于该功能已经不可靠的性质,以及(非常重要的)簿记成本,我们在不知道是否仍然需要它的情况下犹豫是否为每个人自动启用它们。
作为妥协,我们目前仅在开发模式下执行检测,并将弃用消息转换为开发模式断言(硬错误)。 在生产模式下,检测代码被剥离,回溯将不起作用。
我们在第二个功能标志后面的代码库中保留了支持此功能的功能(没有断言)。 代码在 CI 上持续测试,但是默认情况下是禁用的,直到我们有足够的使用信息来确定下一步。
如果您认为自己的使用模式受此影响,请在下方提供有关您的场景的尽可能详细信息。 很有可能我们可以使用不需要对引擎进行大规模更改的替代方案和/或有针对性的解决方案。 因此,如果您提供有关您的使用情况的背景信息和上下文,而不是仅向我们展示您的代码库中的小代码片段,那将会很有帮助。
仅供参考,我们的应用中有一些警告。 具体来说,我们在组件的init
中更新了服务的一些属性,这会触发页面上的其他内容以不同的方式呈现。
通过在下一个运行循环中安排属性更改来修复此警告非常简单。 我花了大约一个小时来追踪并修复我们(相当大的)应用程序中的所有警告。 虽然这在技术上是一个突破性的变化,但我同意你的评估,即使它给我带来了一些额外的工作。
@fivetanley改进这里的错误消息听起来不错。 我知道@krisselden和@stefanpenner有一个跟踪这些问题的工作流程,也许他们可以帮助您在这方面提供一些指导。
@joukevandermaas run.next() 不是解决此错误的好方法,但我理解如果您对这些错误感到不知所措,为什么会去那里。 最好尝试了解为什么数据的回流会使已经呈现的内容无效。
很可能如果您在可以注入任何组件的服务上设置道具,它会增加该设置使已经呈现的内容无效的机会。 一般来说,模式应该是 set() 只在渲染钩子期间用于内部状态,不绑定到输入或服务,或者 set() 用于事件,输入状态应该在渲染时间确定。
@joukevandermaas run.next() 不是解决这个错误的好方法,
这样做会导致性能问题,因为在这种情况下 glimmer2 会通知“正在发生重复的工作,如果你想要一个高性能的应用程序,你真的不想要这个”。 以前, ember 会吸收这一点,但会导致严重的性能损失。
我们还有更多的知识共享工作要做……最终,我们相信这是应用程序的健康发展之路。 但是我们需要确保每个人都拥有可以从中受益的工具 + 知识:)
作为一个密切关注 Ember 的人(推特,在 github 上,邮件列表等)这个问题悄悄地在我身上发生,所以我怀疑如果它作为 Ember 2.10 的一部分出现,这可能会让其他人感到惊讶,特别是因为与之相关的弃用警告明确指出,该行为将在 3.0 之前得到支持。 我不相信我在任何地方看到过这种行为在 Glimmer 2 中不起作用(尽管我可能只是错过了它)。
怀疑如果它作为 Ember 2.10 的一部分出现,这可能会让其他人感到惊讶,特别是因为与之相关的弃用警告明确指出该行为将在 3.0 之前得到支持。 我不相信我在任何地方看到过这种行为在 Glimmer 2 中不起作用(尽管我可能只是错过了它)。
是的,我们需要在这里改进一些消息传递/细节。
我看到这使它成为 2.10 版本。 这会在 2.10 版本的博文中提到吗?
这在 2.10 的博客文章中没有提到,这让我感到惊讶,因为之前的弃用警告说它将在 3.0 之前得到支持,如上所述。
我有一个受此影响的使用模式。 我确定问题一定是我的使用模式,而不是这个特殊的变化,但我希望能提供一些关于什么是好的替代使用模式的意见!
基本上,我有一个页面显示一组可过滤的数据,为了实现这一点,我使用 Ember 计算值根据页面上几个查询参数的值过滤数据。 但是,为了防止将无效输入(例如不是字母或数字)从用户输入添加到查询参数中,我有以下模式:
filteredModel: Ember.computed('model', /*list of individual query params*/, function(){
let model = this.get('model').filterBy('pdf.pdf_path.url'); //removes all records that don't have a pdf uploaded
this.get('queryParams').forEach((filter)=> { // for each possible filter
if ((this.get(filter).length > 0)) { //if the filter has content...
//guardian pattern to prevent invalid inputs
let valid = new RegExp('^[A-Za-z0-9 _]*[A-Za-z0-9][A-Za-z0-9 _]*$');
while (this.get(filter).length > 0 && !valid.test(this.get(filter))){
this.set(filter, this.get(filter).slice(0,-1));
}
//block of code where the model gets filtered
//...
//...
});
return model;
}),
所以基本上,当我计算过滤模型应该是什么样子时,如果任何过滤器值中包含无效字符,我就会去掉最后一个字符,直到它们变得有效。 有没有人建议用更简洁的方法对这些输入进行有效性检查?
这也让我们感到惊讶 - 特别是因为当应用程序在 2.9 下运行时我们没有看到任何警告消息。 当我们升级到 2.10 时,应用程序将不会加载并引用此错误。 有没有其他人看到过这种行为?
@revanar我可以完全离开这里,但看起来你的 CP 实际上是两个不同的东西:一个 CP 返回过滤后的模型,一个更新过滤器。 我将更新过滤器部分移动到一个可观察的部分。 或者,如果这是一个组件并且您将查询参数传递到组件中,我会将逻辑移到didReceiveAttrs
挂钩中。 根据我遇到“回溯重新渲染”错误的经验,我认为将set
操作移出 CP 应该会使错误消失。
我们在这方面也很艰难。 我已经解决了很多问题,但是我们看到的一个弃用错误的例子令人费解。
Assertion Failed: You modified transitioningIn twice on <app<strong i="6">@component</strong>:link-to::ember1159> in a single render.
看起来失败是因为 ember 内部属性被不止一次更新。 不幸的是,它会在我们的 selenium 测试期间重现,因此很难调试(selenium 驱动程序阻止开发工具在测试运行时工作)。 我将问题的至少一个实例追溯到在我们登录过程结束时进行的 controller.transitionToRoute 调用,但它似乎发生在几种不同的场景中。
我不知道如何继续解决这个问题。
@chancancode提到了一个用于禁用此弃用错误的功能标志,但我在https://github.com/emberjs/ember.js/blob/master/FEATURES.md 上没有看到任何有关它的信息
对于我们的 ember 2.10 迁移,这也是主要的痛点。 我们也一直在解决很多这些问题。 似乎没有任何单一/明确的策略来修复这些错误。 我们尝试了以下方法,具体取决于用例
Ember.run.next
包装代码setter
代码从computed properties
到生命周期钩子中,或者移动到event handlers
。我们在这方面也遇到了很多困难。 在过去的几年里,我们积累了大量的下拉菜单,它们会自动从 ember 数据存储缓存中选择某种类型的第一个元素。 这会导致重新渲染,因为页面的某些部分是由下拉选择驱动的。 我不太确定该怎么做,因为我不想重复相同的代码来填充和选择使用下拉列表的每个页面上的列表的第一项。
@scottmessinger感谢您的反馈。 使用组件最终效果很好。 我已经设法摆脱了回溯错误,而且我认为我的代码更简洁一些。
Kris Selden 有一个有用的技巧来调试这些:
我在这里更详细地概述了步骤: https :
我正在改进回溯断言消息。 我削减了2.10.2-with-improved-backtracking-assertion
构建,其中包含以下更好的消息:
前:
您在一次渲染中对<(subclass of Ember.Model):ember3486>
修改message.message_goal.description
两次<(subclass of Ember.Model):ember3486>
。 这在 Ember 1.x 中不可靠且缓慢,不再受支持。 有关更多详细信息,请参阅https://github.com/emberjs/ember.js/issues/13948 。
后:
您在一次渲染中对<(subclass of Ember.Model):ember3486>
修改message.message_goal.description
两次<(subclass of Ember.Model):ember3486>
。 它在component:message-edit-expanding-container-component
中呈现并在component:rules/predicate-date-value-component
。 这在 Ember 1.x 中不可靠且缓慢,不再受支持。 有关更多详细信息,请参阅 #13948。
在它准备好之前,我还有一些事情要做,但是如果有几个人在他们的应用程序中尝试过它会非常有用。 /立方厘米@fivetanley,@bryanhickerson,@revanar,@phammers,@scottmessinger,@ tharrington1,@ manimis902,@jakesjews,@ elwayman02。 如果您在应用中看到任何非理想的断言消息,请告诉我。
要尝试一下,请更新您的bower.json
以包含 ember 依赖项,如下所示:
{
"name": "backtracking",
"dependencies": {
"ember": "intercom/ember#2.10.2-with-improved-backtracking-assertion",
"ember-cli-shims": "0.1.3"
},
"resolutions": {
"ember": "2.10.2-with-improved-backtracking-assertion"
}
}
您可以在此处查看运行此构建的示例应用程序: https :
我刚刚剪掉了一个 1.11.0-canary 版本
这是打字错误吗?
@rwjblue谢谢,这是一个错字。 更新
@GavinJoyce感谢您接受这个! 我在我们的应用程序中尝试了这个,它绝对比之前的消息更有帮助。 对我们来说可悲的是,它仍然不能让修复这个问题变得非常容易,因为它指出被修改的属性在错误消息指示的任何一个位置都没有明显修改。 我不确定为什么会这样,但我们希望明年能更深入地研究它。
@Dhaulagiri如果您有兴趣,我很乐意在某个时间组织屏幕
FWIW 我发现了另一个与此相关的问题: https :
还尝试了@GavinJoyce的分支,当试图找到一个错误时,正在努力将选项卡控件带到 ember-paper(参考上面的 PR)。 不幸的是,在我的情况下,我似乎也引用了似乎没有涉及的组件。
@bjornharrtell你有我可以尝试的余烬纸分支吗?
^ 我们花了一点时间来深入研究 ember-paper 问题 (https://github.com/miguelcobain/ember-paper/pull/590)。 看起来:
{{yield}}
被报告为有用的来源,但并不像它可能的那样有用)它可能是也可能不是插件的错误,但我在https://github.com/DockYard/ember-one-way-controls/issues/136 中遇到了这个错误
我发现了一个问题。
我正在使用一个 mixin,它有一个入口点来更新它被混合到的控制器的相同属性。 这些绝对是不同的属性,因为它们只是属于不同的控制器,并且断言失败并阻塞了 js。
我已经通过将其解包到几个控制器中并在要复制的路由之间进行转换来测试 mixin - 它没有被复制。
现在我正在尝试摆脱 mixin,所以我将简单地杀死它并创建一个解决方法。
我相信我有一个有效的用例,我们看到了这个重新渲染问题。 在我们的应用程序中,我们有一个包含一些状态(ok、validating、warning、error)的按钮。 它是一个组件,显示当前状态,在某些事情发生时进行验证,并根据验证或激活的结果显示不同的内容(微调器、不同的按钮文本、不同的按钮类)。
我们在init()
上检查验证,并根据验证的响应,设置适当的按钮状态。 按钮类是一个计算属性,它根据按钮状态设置适当的类。 由于它发生在 init 上,因此似乎正在触发此错误,因为我们在实例化时以 ok 状态开始,然后在我们根据响应验证版本和最终状态时过渡到验证。 然而,用例本身似乎是合理的,因此正在发生的状态变化也似乎是合理的。
@tundal45您能否创建一个 twiddle 或示例应用程序来演示您认为不正确的错误?
@ Blackening999 @tundal45你能把你的用例简化成一个
@chancancode @ Blackening999我会尽快在这里发布一个。 感谢您的快速回复。
@chancancode https://ember-twiddle.com/936d549b5625b0cf4f3c945d0ed04d3b?openFiles=components.button-with-state.js将是摆弄,但我没有看到我在应用程序中看到的错误,所以可能是其他一些事情导致它。
我在几个计算属性上看到了这一点,其中之一是一个简单的Ember.computed.or
。 由于@manimis902的所有建议都不适用于这种情况,那么合适的解决方法是什么?
正如@tharrington1提到的, 哪里?
@cbou据我所知该标志不存在
我不完全确定如何处理这些弃用通知,也不确定我的修复是否有效,但我在init()
组件挂钩中执行 AJAX 请求,该请求触发了不同的值更改服务的属性(跟踪/显示是否访问远程服务器)。
我将我的 AJAX 请求代码从init()
组件挂钩移到了didRender()
组件挂钩,它似乎解决了我的弃用通知。
@lvl99我做了同样的事情。
我们正在努力将我们的企业项目从 2.3 升级到 Ember 2.12。 在我们的项目中,我们有一个验证插件和一个单独的表单组件插件。 验证加载项在 Ember.components 上工作以生成验证错误,表单组件加载项通过帮助程序显示验证加载项生成的错误。 附加组件太复杂,无法共享源代码; 因此,我们创建了以下twiddle来说明我们面临的情况。
给定的示例包含两个组件( person-detail
和address-detail
),它们负责生成自己的验证错误。 address-detail
生成的验证错误通过在errors
计算属性中抛出的操作级联到包含组件( person-detail
)。 在帮助器error-formatter
的帮助下,每个组件生成的错误显示在error-displayer
组件中。 如您所见,提供的代码按预期工作。
然而; 我们有如下弃用警告: DEPRECATION: The
error property of
is an
Ember.Binding connected to
validatable.errors.name , but
Ember.Binding is deprecated. Consider using an
别名computed property instead. [deprecation id: ember-metal.binding] See http://emberjs.com/deprecations/v2.x#toc_ember-binding for more details.
为了避免这种情况; 请进入助手error-formatter
并注释掉第 9 行和取消注释第 8 行,以便我们按照警告解释中的建议进行操作。
现在我们遇到了臭名昭著的Assertion Failed: You modified "error" twice on <Ember.Object:ember338> in a single render. It was rendered in "component:error-displayer" and modified in "component:error-displayer". This was unreliable and slow in Ember 1.x and is no longer supported. See https://github.com/emberjs/ember.js/issues/13948 for more details.
我们明白为什么我们会收到这个错误; 因为在address-detail
s errors
计算属性内触发的操作会导致重新计算person-detail
的errors
计算属性,并且已经呈现的内容导致此错误. 以下是我们想要学习的内容:
Ember.Binding
与Ember.computed.alias
在重新计算(重新渲染)阶段的工作方式肯定大不相同。 确切的区别是什么? 建议使用后者作为第一个的替代品似乎是在破坏代码; 至少对于我们的情况。Ember.run.scheduleOnce('afterRender', ...)
语句来包装动作触发。 这是正确的方法吗?FWIW,我已经看到我的组件在某些情况下比我预期的更频繁地重新渲染。 追踪重新渲染的原因非常耗时,因为它通常与 runloop 同步。 我很想知道这方面是否有捷径或调试技巧。
有没有办法在调试版本中捕获或抑制此断言? 我们已经在大多数地方通过如上所述的重构追踪并修复了这个问题,但有 1 或 2 个相当顽固。 在一种特殊情况下,我们正在销毁一个对象,然后进行转换。 被销毁的对象(在我们的 API 上)发送一个推送通知来卸载/销毁更多的对象。
在生产构建中,重新渲染不是问题,因为我们只是在销毁对象,然后无论如何都要过渡(因此整个路线都被拆除了)。 然而,关于开发构建的断言非常令人沮丧,因为它需要刷新才能恢复站点。 我理解为什么我们会收到错误,但在这种情况下它是一个相当复杂的重构以避免它,并且在任何情况下都无关紧要,因为整个路线都被破坏了。 有没有办法捕捉/抑制/将其更改为警告? 我尝试了 try/catch 以及路由的错误处理程序,但都没有发现。
@feanor07从 get 内部发送一个使已经呈现的属性无效的操作是数据回流,在与循环绑定之前会检测并默默地选择前进方向作为赢家,但是如果它流过,这将付出很大的代价几件事情又回到了源头。
基本上你有一个循环依赖,你需要数据向下流动,在运行验证之前不要呈现错误。 您可以在生成模型和错误的父组件中进行验证,以在其块内呈现表单。
@feanor07 Ember.run.scheduleOnce...
不起作用,但只向属性set
添加一个Ember.run.schedule("afterRender", () => { ... });
set
第一个由堆栈跟踪指示消除了许多错误消息,这些错误消息在初始消息之后级联。
@neilthawani可能会隐藏错误,但不一定能解决问题。 该错误意味着您将一个值设置了两次,而您应该只设置一次。 通过将set
放入schedule
,您只是延迟了第二组,以便它发生在不同的运行循环中。 当您只需要渲染一次时,您还没有解决渲染两次的核心问题,只是诱使 Ember 不知道存在问题,因为现在渲染发生在单独的运行循环中。
@elwayman02哦哦。 谢谢。 基本上完全没有抓住重点。
编辑:在苦苦挣扎之后,我插入了比我感到舒服的更多的afterRenders
。 我们在我们的数据可视化组件之一上有一个click
处理程序,用于切换工具提示。 设置isDisplaying
内的标志Ember.run.schedule("afterRender", () => { ... });
使我们能够调试真正的问题,这实际上是在控制器中调用数据即组件和工具提示组件既是回溯。
tl; dr:我没有把它放在那里(也有一次Maximum call stack size exceeded
错误),但使用它有助于调试,直到发现真正的问题。
伙计们,我需要一些帮助:当我尝试使用“属于”另一个模型属性时,会出现此错误。 我创建了一个空白项目,它仍然显示相同的内容。 我究竟做错了什么?
另外,在我的后端,我使用 .Net Core 和 JSON API .Net Core (https://github.com/Research-Institute/json-api-dotnet-core) - 按照他们的说明。
此时 UI 渲染已损坏,但数据已加载并且我可以看到所需的值。
// Profile Model:
import DS from 'ember-data';
export default DS.Model.extend({
'firstName': DS.attr(),
'lastName': DS.attr(),
'applicationUser': DS.attr(),
'contactProfile': DS.belongsTo('address', {
async: true
}),
'companyProfile': DS.belongsTo('address'),
'companyMailingAddress': DS.belongsTo('address'),
"companyPhysicalAddress": DS.belongsTo('address')
});
// Address Model:
import DS from 'ember-data';
export default DS.Model.extend({
'address1': DS.attr(),
'address2': DS.attr(),
'city': DS.attr(),
'state': DS.attr(),
'zipCode': DS.attr(),
'country': DS.attr(),
'website': DS.attr(),
'phoneNumber1': DS.attr(),
'phoneExtension1': DS.attr(),
'phoneNumber2': DS.attr(),
'phoneExtension2': DS.attr(),
'email': DS.attr(),
});
// Adapter settings
import DS from 'ember-data';
export default DS.JSONAPIAdapter.extend({
namespace: 'api/json',
});
DS.JSONAPISerializer.reopen({
keyForAttribute(key) {
return key;
},
keyForRelationship(key) {
return key;
}
});
// Route
import Ember from 'ember';
export default Ember.Route.extend({
model() {
return Ember.RSVP.hash({
profile: this.store.findRecord('profile', 1)
});
}
});
// Template
{{model.profile.contactProfile.address1}}
和我得到的错误:
断言失败:您修改了“model.profile.contactProfile”两次
PS:我尝试使用 Ember.computed 方法来获取属性,这似乎有效。 这是必需的吗?
更新:我还发现数据在 {{#each}} 助手中加载得很好,但不是直接在模板上加载。
@lbarsukov
也许它与https://github.com/emberjs/data/issues/5023相关,其中回溯重新渲染是由具有links
属性集的 jsonapi 响应中的关系引起的。
这就是我的问题,它在 Ember Data 2.13.2 之后开始。 尝试使用 ember-data: 2.13.2 看看这是否能解决您的问题。
@daniel-de-wit 在这里为大师点赞 - 这确实有效。 它现在做我需要它做的事情,我对它很满意。
@lbarsukov @daniel-de-wit 我们发布了新版本的 ember data来解决这个问题。
@lbarsukov我认为这与您定义的关系有关。 belongsTo
一个(或一些)很有可能是hasMany
。
假设您有两个模型,问题和答案。 如果您返回问题的 10 个答案,但每个问题序列化程序都引用其答案,则您必须正确定义关系。
// Question Model:
export default DS.Model.extend({
'answers': DS.hasMany('answers'), // if you never reference question.answers you can omit this
...
});
// Answer Model:
export default DS.Model.extend({
'question': DS.belongsTo('question'),
...
});
当数据遇到定义多个问题/答案对时,期望没有的 1-1 关系,它推断问题在渲染过程中被修改。
从最初的帖子中,提到了一些钩子:
这是一个非常极端的例子,但它说明了问题。 除了 init,didInitAttrs、didReceiveAttrs、willInsertElement 和 willRender 在渲染过程中也会同步发生。 此外,回溯通常是由双向绑定属性的行为引起的问题。
为什么didInsertElement
和didRender
钩子像魅力一样工作,但其他钩子因twice render
错误而失败?
@BenjaminHorn @ Blackening999 @Dhaulagiri @DingoEatingFuzz @ Gaurav0 @GavinJoyce @Redsandro @TRMW @ Turbo87 @aklkv @alidcastano @backspace @bdiz @bgentry @bjornharrtell @bryanhickerson @buschtoens @caseklim @cbou @chancancode @danaoira @丹尼尔-德-机智@fivetanley @fotinakis @gabrielgrant @ghost @jakesjews @janmisek @joukevandermaas这仍然是一个问题,也许我们应该关闭,你怎么看?
我修复了我的应用程序中的任何错误实例。
每当我遇到这种情况时,我都能够重新安排事情,这样它就不会再发生了,所以我认为可以关闭。
是的,请关闭
一个时代的终结😬
👍
很抱歉提出一个老问题。
比如说我正在渲染
{{this.myBool}}
错误是Error: Assertion Failed: You modified "myBool" twice
我可以通过将其更改为:
{{if this.myBool true false}}
同样,如果类名绑定导致问题
我能改变:
classNameBindings: ['myBool']
到
classNameBindings: ['myBool:yes:no']
如果我可以像这样将警告静音,为什么 Ember 不能为我处理呢?
这是我使用的演示代码:
这些事情都没有“解决”问题。 要么你切换到生产模式,要么断言/检测代码中存在错误。 在任何情况下,您都需要修复分配/设置值的一侧,而不是消耗它的一侧。
嗯,我在玩弄时没有收到错误,我应该按什么顺序点击?
单击打开,然后单击关闭。 但是产生的子组件的关闭按钮,而不是父组件。
我明白了,问题是在拆卸过程中, focusOut
在组件上被调用,该组件在已经呈现的属性上调用set
。 我不是 100% 确定组件如何失去焦点和时序语义。 我很确定您尝试的变化只会混淆跟踪系统并掩盖潜在的问题。 让我们在一个新问题中跟踪这个? 我不确定这是否是一个有效的问题,但让我们在那里进行调查。
最有用的评论
这在 2.10 的博客文章中没有提到,这让我感到惊讶,因为之前的弃用警告说它将在 3.0 之前得到支持,如上所述。