计算属性设置器导致 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 在日期选择器焦点上崩溃浏览器的主要内容,如果未能解决你的问题,请参考以下文章

如何在 vuejs 中的基于类的组件中编写计算设置器

TypeScript VueJS:使用具有计算属性的观察器

Vue JS通过v-model上的文件上传器设置图像应用空对象

对挂载中的属性的更改未触发在 VueJS 中计算

VUEJS 3 组合计算和 vuex 没有反应

vuejs 表单计算属性