计算属性设置器导致 vuejs / javascript 在日期选择器焦点上崩溃浏览器
Posted
技术标签:
【中文标题】计算属性设置器导致 vuejs / javascript 在日期选择器焦点上崩溃浏览器【英文标题】:computed property setter cause vuejs / javascript to crash the browser on the datepicker focus 【发布时间】:2018-08-12 06:19:38 【问题描述】:当我专注于开始日期日期选择器(它挂起浏览器)时,您能否建议为什么 javascript / vuejs 在我的JSFiddle 上崩溃。 尝试取消注释 endDate 计算属性并注释当前它可以正常工作,但是功能消失了。
endDate:
get()
return moment(this.startDate).add(this.interval * this.periods, 'days')
,
set(value)
this.interval = (moment(value).diff(this.startDate, 'days')+1) / this.periods
,
我尝试实现的原始post重新功能
【问题讨论】:
因为存在循环依赖。设置this.endDate
会触发this.interval
的重新计算,这会触发this.endDate
的重新计算,这会触发this.interval
的重新计算,这会触发this.endDate
的重新计算,这会触发this.interval
的重新计算,即。 ..
我不认为这是真的,如果您设置 this.interval = 8 或任何数字,应用程序仍然可以工作。我真的真的真的认为它的方式时刻正在被使用。 Moment 很可能会吞下正在传递的值,并且不会离开,而是从某种内部机制触发 vue,因为它被包装在计算属性中。
@acdcjunior 应用程序功能齐全,直到我修改 startDate endDate 工作没问题
@sksallaj 循环依赖:jsfiddle.net/ghLmnm2c/204 打开控制台然后聚焦输入。
@acdcjunior,我一直在测试这个,代码中有非常奇怪的结果,说明什么有效,什么无效。他没有按预期处理的插件。这不是循环引用。更新 this.interval 不会触发重新计算 this.endDate。我在一些测试中更新了 this.interval,但没有触发。
【参考方案1】:
新的小提琴答案:https://jsfiddle.net/ghLmnm2c/483/
我对此进行了更多思考,并采用了我第一次尝试帮助您时提到的旧策略,并删除了计算属性,因为这可能与组件的呈现方式有关。因此,我使用插件的事件发射器复制了该功能,并在间隔插件上添加了一个观察器,以获得相同的功能。
这是 html 的样子:
Start Date: <date-picker v-model="startDate" :config="config" @dp-change="updateEndDateInterval"></date-picker>
End Date: <date-picker v-model="endDate" :config="config" @dp-update="updateInterval"></date-picker>
Interval: <input class="form-control" type="text" v-model="interval" @keyup="updateEndDate">
我认为唯一会改变 endDate 的输入是 startdate 和间隔。
以下是方法:
methods:
updateEndDateInterval: function()
this.updateEndDate(); //this automatically hits updateInterval because of @dp-change="updateInterval" on the enddate component
,
updateEndDate: function()
this.endDate = moment(this.startDate).add(this.interval * this.periods, 'days');
,
updateInterval: function(v)
if (this.periods > 0)
var dateVal = ""
if (v.target)
dateVal = v.target.value;
else
dateVal = v;
this.interval = (moment(dateVal).diff(this.startDate, 'days')+1) / this.periods;
updateInterval 接受一个v 参数,v 会从updateEndDateInterval 中获取传递过来的moment 对象,或者是改变interval 输入文本框值时的事件对象。如果从间隔文本框中传入 this.endDate,则会遇到循环引用,其中间隔将更新结束日期,结束日期将更新间隔等等。
另外,您需要设置 endDate 以具有反应属性
data:
interval: 7,
startDate: moment(),
endDate: '' // leave this empty
当您加载组件时,将 endDate 属性更新为任何 startdate 值(在 updateEndDate 中定义):
mounted: function()
this.updateEndDate();
与您的旧小提琴相比,我检查了所有功能,并检查了反应性。就个人而言,我更喜欢这种策略,因为您使用的是插件中提到的首选方法,我并没有真正看到任何使用计算属性的示例。这也使事情变得更加模块化,因为您可以在必要时进行调整,而不会意外修改其他组件。
我打算给间隔文本框一个 vue 观察器,并将其设置为 updateEndDate,如下所示:
//don't do this
watch:
interval: function()
this.updateEndDate()
但由于循环引用问题,此代码不起作用。这将使您的浏览器崩溃。所以这就是我参加@keyup 活动的原因。当您从 startdate 或 enddate 更改间隔时,间隔会更改,并且因为上面有手表,它会在不断更新自身时创建循环引用。这就是为什么通过 keyup 事件触发 updateEndDate 是基于键输入触发的,因此选择日期不会干扰间隔的更新。
解决根本问题(已修复)。
将此答案用作参考:
Passing event and argument to v-on in Vue.js
我设置了一个标志,以防止您在更改开始日期时更新间隔,并在设置结束日期时防止间隔自行更改。
<date-picker v-model="endDate" :config="config" @dp-change="updateInterval($event, intervalUpdate)"></date-picker>
然后将响应属性添加到数据:
data:
intervalUpdate: true
更新方法:
updateEndDateInterval: function()
this.intervalUpdate = false; //do not update interval
...
,
updateEndDate: function()
this.intervalUpdate = false;
...
,
updateInterval: function(v, shouldUpdate)
console.log(shouldUpdate);
if (shouldUpdate)
this.intervalUpdate = shouldUpdate;
if (this.intervalUpdate)
....
this.intervalUpdate = true; //reset the flag
(已修复) 现在,您遇到的根本问题。 使用 startdate 更新结束日期和间隔日期。使用计算机属性时看不到它。这就是您无法正确编辑间隔的原因。
如果您只更改结束日期,则计算间隔的公式可以正常工作,因为您只是更改了间隔。但是,更改 startdate 会更改 enddate AND 间隔。
在选择startdate时,都需要更新,但它不能因为彼此依赖而不是,如果切换执行顺序,则会获得陈旧的数据:
因此,如果您尝试先更新 enddate,则不能,因为 this.interval 已过时,因为这会给您错误的 this.endDate 值,并在下一行使用错误的 enddate 值计算间隔会给你不正确的结果。
//stale this.interval value as it isn't updated before
this.endDate = moment(this.startDate).add(this.interval * this.periods, 'days').format('DD MMM YYYY');
//incorrect this.endDate to give incorrect this.interval
this.interval = (moment(this.endDate).diff(this.startDate, 'days')+1) / this.periods;
如果您尝试颠倒顺序,则不能,因为 this.endDate 已过时,未更新,因此您的间隔值不正确。而当你执行下一行时,由于this.interval的值不正确,this.endDate变得不正确
//stale this.endDate value as it isn't updated before
this.interval = (moment(this.endDate).diff(this.startDate, 'days')+1) / this.periods;
//incorrect this.interval used to give incorrect this.endDate
this.endDate = moment(this.startDate).add(this.interval * this.periods, 'days').format('DD MMM YYYY');
【讨论】:
【参考方案2】:分析 这不是已发布问题的答案。它是为了展示寻找浏览器崩溃的根本原因的指南,以及如何解决它的可能解决方案。
https://jsfiddle.net/ghLmnm2c/227/
我遵循了我在第四个结论中提到的策略(在下面的旧答案部分中提到)。我创建了一个名为 endDateProxy 的反应性代理变量,并将 endDate 设置为该 endDateProxy,然后在 getter 中更新该值。我仍然相信我的其他结论也会有所帮助。但是这个答案可以为您提供所需的内容。
<date-picker v-model="endDateProxy" :config="config"></date-picker>
...
data:
.... // other stuff
endDateProxy: '' //proxy reactive variable
,
....
get()
//set the reactive endDateProxy property
this.endDateProxy = moment(this.startDate).add(this.interval * this.periods, 'days');
return this.endDateProxy
旧答案
实际上,您所处的情况非常奇怪。我真的尽了最大努力,它只是在折磨我。但幸运的是,我想出了可能的解决方案,可以帮助您解决问题或为您指明正确的方向。
结论
我的意思是你必须放弃使用 VueBootstrapDatetimePicker,并创建自己的 Moment js 包装器组件到 vue js 中。以下是他们如何使用 select2 的示例:https://vuejs.org/v2/examples/select2.html
或者一起放弃momentjs,使用javascript的老式Date对象。
或者您可以保留您的代码,并使用 Math.random() 包装您的 setter,但不确定这是否能解决您的问题。我发现在下面的分析中有效。
我的最终结论可能是您不应该使用绑定到插件的 v-model 的计算属性。您必须像创建 startdate 一样创建一个名为“endDate”的反应性数据变量,将 v-model 绑定到该变量,然后显式创建 setEndDate vue 方法,并将事件侦听器添加到直接设置 endDate 数据的所有控件.因此,例如,您可以在 startdate 控件上发布“onchange”或“onfocus”事件,并将其绑定到 setEndDate 方法,对于周期和间隔控件也是如此。
分析:
我尝试进行完整的分析,有大量的浏览器崩溃:-D
当我看到您的代码时,我正忙着找出导致浏览器崩溃的原因。然后我检查是否涉及循环依赖,发现不是这种情况,浏览器会意识到这一点并结束调用堆栈大小。如果将计算的 setter 公式更改为:
set(value)
this.interval = 8
这行得通。
我试过这样做(删除除数--“/this.periods”):
set(value)
this.interval = (moment(value).diff(this.startDate, 'days')+1)
这也奏效了。
我检查了如果你加回除数会是什么值:
set(value)
console.log((moment(value).diff(this.startDate, 'days')+1) / this.periods)
我发现值是 7.25(我在公式上使用了 console.log)
然后我做了这样的事情:
set(value)
this.interval = 7.25
这也奏效了!
然后我想看看是否有办法保留公式中的所有变量,并使用 Math.round 公式(我还考虑了 this.periods 是否等于零,因为你不想除以零):
set(value)
if (this.periods > 0)
this.interval = Math.round((moment(value).diff(this.startDate, 'days')+1) / this.periods)
这也有效
我还发现存在不推荐使用的值并且不遵循 ISO 8601 或 RFC 2822,因此我更改了代码以反映这一点,并且浏览器仍然冻结,但您不再遇到不推荐使用的错误:
format: 'YYYY-MM-DD'
set(value)
var newEndDateMomentObj = moment(value);
var startDateMomentObj = moment(this.startDate);
this.interval = (newEndDateMomentObj.diff(startDateMomentObj, 'days')+1) / this.periods
【讨论】:
感谢您的全面分析@sksallaj 我还尝试了另一种情况,即我没有日期选择器但按钮和方法来更改 startDate,结果相同,所以我会排除它必须的日期选择器问题成为时刻或自我竞争【参考方案3】:编辑:此解决方案失去了 endDate 的反应性,但这证明插件不必要地渲染超过它需要的,并且因为有无限数量的日期,它将为 datepicker 渲染所有日期.有关正确答案,请参阅有关删除计算属性的新帖子。
我再次查看了您发布的原始代码,并添加了一个修复程序。我不知道为什么我没有早点想到这个。
请看这个jsfiddle:https://jsfiddle.net/ghLmnm2c/301/
为了防止您的代码出现任何新的重新渲染,您可以在您正在使用的插件指令之上添加 v-once。
<date-picker v-model="startDate" :config="config" v-once></date-picker>
<date-picker v-model="endDate" :config="config" v-once></date-picker>
注意我是如何放置 v-once 指令的。所以你的代码确实没有什么问题,只是 vue 重新渲染了 datepicker,你不想继续渲染,因为 datepicker 有自己的渲染功能。
https://vuejs.org/v2/api/#v-once
另外,我注意到你也可以使用一个有用的东西,如果你查看下面的参考,你可以向下滚动到事件部分。您正在使用的插件可以发出更改、更新和隐藏:
https://github.com/ankurk91/vue-bootstrap-datetimepicker
所以你可以这样做:
<date-picker v-model="date" @dp-hide="doSomethingOnHide" @dp-change="doSomethingOnChange"></date-picker>
【讨论】:
更新导致我们失去了对不同日期的 endDate 选择的反应性。 是的,我认为这是不利的一面,你失去了反应性,我怀疑它确实与日期选择器有关,其中有一些东西会导致不必要的重新渲染。 v-once 证明渲染一次可以让你的代码工作,取消它意味着渲染迭代。我不认为它是循环的,我认为它是无限的,因为有无限的日子可以循环。您可能想请插件的开发人员帮助解决这个问题。在我的解决方案中,两个日期选择器都会更新 enddate.. 的计算属性,这与您想要的非常接近。 我发布了一个新策略,我删除了计算属性,并使用插件的事件发射器重新构建了您的功能。 我注意到 jsfiddle 上的 rev 396 是最新的,你能确认它是正确的吗?我将它发布在 Datepicker github 上,但是我采取了另一种方法并一起删除了 datepicker,但问题仍然存在,所以我怀疑它可能是 momentjs 或 vue 反应性 angine 本身 jsfiddle.net/863tpnxo 你可以看到 reactivnes 在那里工作,这可能是计算问题,如果你放 950000 周期而不是 reactivnes,它将被卡住一段时间但然后解冻,我现在怀疑 datepicker 和 momentjs :)跨度>以上是关于计算属性设置器导致 vuejs / javascript 在日期选择器焦点上崩溃浏览器的主要内容,如果未能解决你的问题,请参考以下文章