【问题标题】:How can you force the UI to update in the middle of an event loop cycle in Vue.js?如何在 Vue.js 的事件循环周期中强制 UI 更新?
【发布时间】:2018-09-16 00:57:38
【问题描述】:

我想强制 UI 在事件循环周期中途更新。

Vue.nextTick

Vue.nextTick 似乎为您提供了vm.$el 的更新版本,但实际上并不会导致 UI 更新。

代码笔:https://codepen.io/adamzerner/pen/RMexgJ?editors=1010

HTML:

<div id="example">
  <p>Value: {{ message }}</p>
  <button v-on:click="change()">Change</button>
</div>

JS:

var vm = new Vue({
  el: '#example',
  data: {
    message: 'A'
  },
  methods: {
    change: change
  }
})

function change () {
  vm.message = 'B';
  // vm.$el.children[0].textContent === "Value: A"
  Vue.nextTick(function () {
    // vm.$el.children[0].textContent === "Value: B"
    // but the UI hasn't actually updated
    for (var i = 0; i < 10000000; i++) {}
    vm.message = 'C';
  });
}

vm.$forceUpdate

vm.$forceUpdate 似乎根本没有做任何事情。

  1. vm.$el 的值似乎没有改变。
  2. 似乎没有更新 UI。

CodePen:https://codepen.io/adamzerner/pen/rdqpJW?editors=1010

HTML:

<div id="example">
  <p>Value: {{ message }}</p>
  <button v-on:click="change()">Change</button>
</div>

JS:

var vm = new Vue({
  el: '#example',
  data: {
    message: 'A'
  },
  methods: {
    change: change
  }
})

function change () {
  vm.message = 'B';
  // vm.$el.children[0].textContent === "Value: A"
  vm.$forceUpdate();
  // vm.$el.children[0].textContent === "Value: A" still
  // and the UI hasn't actually updated
  for (var i = 0; i < 10000000; i++) {}
  vm.message = 'C';
}

v-bind:key

v-bind:key 似乎也没有做任何事情:

  1. vm.$el 的值似乎没有改变。
  2. 似乎没有更新 UI。

Codepen:https://codepen.io/adamzerner/pen/WzadKN?editors=1010

HTML:

<div id="example">
  <p v-bind:key="message">Value: {{ message }}</p>
  <button v-on:click="change()">Change</button>
</div>

JS:

var vm = new Vue({
  el: '#example',
  data: {
    message: 'A'
  },
  methods: {
    change: change
  }
})

function change () {
  // vm.$el.children[0].textContent === "Value: A"
  vm.message = 'B';
  // vm.$el.children[0].textContent === "Value: A" still
  // and the UI hasn't actually updated
  for (var i = 0; i < 10000000; i++) {}
  vm.message = 'C';
}

计算

使用计算属性as this popular answer recommends 似乎也没有做任何事情:

  1. vm.$el 的值似乎没有改变。
  2. 似乎没有更新 UI。

代码笔:https://codepen.io/adamzerner/pen/EEdoeX?editors=1010

HTML:

<div id="example">
  <p>Value: {{ computedMessage }}</p>
  <button v-on:click="change()">Change</button>
</div>

JS:

var vm = new Vue({
  el: '#example',
  data: {
    message: 'A'
  },
  computed: {
    computedMessage: function () {
      return this.message;
    },
  },
  methods: {
    change: change
  }
})

function change () {
  // vm.$el.children[0].textContent === "Value: A"
  vm.message = 'B';
  // vm.$el.children[0].textContent === "Value: A" still
  // and the UI hasn't actually updated
  for (var i = 0; i < 10000000; i++) {}
  vm.message = 'C';
}

承诺(在编辑中添加)

使用 Promise 也不起作用。

代码笔:https://codepen.io/adamzerner/pen/oqaEpV?editors=1010

HTML:

<div id="example">
  <p>Value: {{ message }}</p>
  <button v-on:click="change()">Change</button>
</div>

JS:

var vm = new Vue({
  el: '#example',
  data: {
    message: 'A'
  },
  methods: {
    change: change
  }
})

function change () {
  // vm.$el.children[0].textContent === "Value: A"
  vm.message = 'B';
  // vm.$el.children[0].textContent === "Value: A" still
  // and the UI hasn't actually updated
  var promise = new Promise(function (resolve, reject) {
    for (var i = 0; i < 10000000; i++) {}
    resolve();
  });
  promise.then(function () {
    vm.message = 'C';
  });
}

设置超时

setTimeout 是唯一可行的方法。但它只有在延迟为100 时才能始终如一地工作。当延迟为0 时,它有时会起作用,但不会始终如一地起作用。

  1. vm.$el 更新。
  2. UI 更新。

代码笔:https://codepen.io/adamzerner/pen/PRyExg?editors=1010

HTML:

<div id="example">
  <p>Value: {{ message }}</p>
  <button v-on:click="change()">Change</button>
</div>

JS:

var vm = new Vue({
  el: '#example',
  data: {
    message: 'A'
  },
  methods: {
    change: change
  }
})

function change () {
  // vm.$el.children[0].textContent === "Value: A"
  vm.message = 'B';
  setTimeout(function () {
    // vm.$el.children[0].textContent === "Value: B"
    // the UI has updated
    for (var i = 0; i < 10000000; i++) {}
    vm.message = 'C';
  }, 100);
}

问题

  1. 为什么Vue.nextTickvm.$forceUpdatev-bind:key 或计算属性不起作用?
  2. 为什么延迟为0setTimeout 工作不一致?
  3. setTimeout 看起来很老套。是否有“适当”的方式来强制更新 UI?

【问题讨论】:

  • 更改函数是同步的,因此按定义块。没有其他事情会发生。 setTimeout 不起作用,因为执行上下文仍然是同步函数。有一个简单的解决方案,但它取决于您的用例是否计数到 10MM。
  • 您介意详细说明@RandyCasburn 的一些事情吗?关于setTimeout 内部的执行上下文,我没有访问this,所以我看不出这有什么关系。无论延迟是0 还是100,执行上下文都是相同的,但是将延迟更改为100 会导致setTimeout 起作用。假设我的用例只是让 UI 在单击“更改”后立即显示“B”,然后在片刻之后显示“C”。您能提供您想到的简单解决方案吗?
  • 你有两个选择:1:设置一个watch属性,watch message: watch : { message: function(){} } Watchers;或 2:$watch API 方法$watch。我推荐#1,因为它直接针对您的财产。这些只是将异步功能注入到同步函数中。
  • @RandyCasburn 这对我不起作用。 watch 仅在将 C 分配给 message 后才会触发。

标签: javascript vue.js


【解决方案1】:

概要

B 没有在 UI 中更新/显示的错觉是由 Vue 的 Async Update Queue 和 JavaScript 的 Event Loop Process 模型的组合造成的。有关详细信息和校对,请继续阅读。

调查结果摘要

这些实际上你想要的(但似乎没有)

  • Vue.nextTick
  • setTimeout - (但似乎没有短暂的超时)

这些按预期工作(但需要解释)

  • v-bind:key
  • vm.$forceUpdate
  • 承诺

注意:上面的 but doesn't seem to 承认 Vue 正在做它应该做的事情,但没有出现预期的视觉输出。因此,代码没有产生预期的输出是准确的。

讨论

前两项工作

证明前两个你想要的很容易。不将“B”放置在视图中的想法将被推翻。但需要进一步讨论以解决缺乏明显变化的问题。

  • 在 Chrome 中打开每支笔
  • 在开发工具中,在第 1789 行的 vue.js 中设置断点
  • 逐步完成序列

当您逐步执行该序列时,您会注意到 UI 已按照应有的值更新为“B”(无论超时长度如何)。被驱散了。

那么缺乏可见性怎么办?这是由 JavaScript 的 Event Loop 流程模型引起的,具体与称为 Run-to-Completion 的原理有关。 MDN Event Loop Documentation 声明:

这种模式的一个缺点是,如果一条消息需要很长时间才能 完成,Web 应用程序无法处理用户交互 比如点击或滚动。

或运行渲染/绘制浏览器进程。所以当堆栈被执行时,B 被渲染然后C 紧接着,这似乎B 永远不会被渲染。当使用带有 JavaScript 繁重任务(例如引导 SPA)的动画 GIF 时,可以看到这个确切的问题。动画 GIF 要么会卡顿,要么根本不会动画 - Run-to-Completion 会阻碍。

所以 Vue 做它应该做的,而 JavaScript 做它应该做的正确。但是长时间运行的循环很麻烦。这就是像 lodash _debounce 或简单的 setTimout 这样的工具很有帮助的原因。

最后三件作品?

是的。在vue.js 中使用相同的断点将显示当 Vue 刷新其更新队列时发生的唯一中断。正如Vue's documentation about Async Update Queue 中所讨论的,每次更新都会排队,并且只呈现每个属性的最后一次更新。所以虽然message在处理过程中实际上变成了B,但由于Vue Async Queue的工作方式,它永远不会被渲染:

如果你还没有注意到,Vue 会执行 DOM 更新 异步。每当观察到数据变化时,它将打开一个 排队并缓冲同一事件中发生的所有数据更改 循环。

【讨论】:

  • 另外:$forceUpdate() 很奇怪。即使看起来应该更新,它也不会立即更新。调试显示它为 data 属性异步运行。但是,对于同步绑定的props,它可以同步运行。我觉得很奇怪。
  • 我觉得有点愚蠢......问题在于 JavaScript/事件循环,而不是 Vue.js。感谢您解释这一点。
  • 很高兴!这是一个有趣的问题,它以多种方式影响着我们所有人。当我第一次发布我的解决方案时,我运行了调试器并看到了“B”。你告诉我它不起作用 - 呃,我当时就明白了。
【解决方案2】:

我不确定你想要什么,但试试这个...

var vm = new Vue({
      el: '#example',
      data: function(){
        return {
          message: 'A'
        }
      },
      methods: {
        change: function(){
          this.message = 'B';
          let el = this;
          setTimeout(()=>{
            el.$nextTick(function () {
              el.message = 'C';
            })
          },2000)
        }
      }
    })

【讨论】:

  • 我知道setTimeout 可以工作(顺便说一句,它不需要$nextTick 工作)。有关我正在寻找答案的明确问题,请参阅我的 OP 中标题为“问题”的最后一部分。
  • Async update que 你试过vm.$nextTick().then(()=&gt;{/*your code*/}) 还是试试Vue.nextTick().then(()=&gt;{/*your code*/}) 使用断点查看它正在更新a、b、c。
【解决方案3】:

我会考虑使用Vue Instance Lifecycle Hooks

也看看这个Vue Instance Lifecycle Diagram

如果你需要更新它,使用生命周期钩子应该可以让你在中途中断一些东西。也许对beforeMount hook 进行某种检查。

【讨论】:

  • 您介意提供该方法成功运行的代码示例吗?
  • 我自己并没有真正做太多与生命周期相关的工作,但我熟悉它的工作原理。 Alligator.io 很好地解释了它的工作原理。我会假设它看起来像这样:&lt;script&gt; export default { beforeMount() { if(test_condition){ // Restart the mounting process of the Instance } } } &lt;/script&gt;
猜你喜欢
  • 1970-01-01
  • 2018-04-09
  • 2019-10-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多