如何强制 UI 在 Vue.js 中的事件循环周期中间更新?

Ada*_*ner 5 javascript vue.js

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

Vue.nextTick

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

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

HTML:

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

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';
  });
}
Run Code Online (Sandbox Code Playgroud)

vm.$forceUpdate

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

  1. 它似乎没有改变 的值vm.$el
  2. 它似乎没有更新用户界面。

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>
Run Code Online (Sandbox Code Playgroud)

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';
}
Run Code Online (Sandbox Code Playgroud)

v-bind:key

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

  1. 它似乎没有改变 的值vm.$el
  2. 它似乎没有更新用户界面。

代码笔: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>
Run Code Online (Sandbox Code Playgroud)

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';
}
Run Code Online (Sandbox Code Playgroud)

计算

正如这个流行的答案所推荐的那样,使用计算属性似乎也没有做任何事情:

  1. 它似乎没有改变 的值vm.$el
  2. 它似乎没有更新用户界面。

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

HTML:

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

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';
}
Run Code Online (Sandbox Code Playgroud)

承诺(在编辑中添加)

使用 Promise 也不起作用。

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

HTML:

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

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';
  });
}
Run Code Online (Sandbox Code Playgroud)

设置超时

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

  1. vm.$el 更新。
  2. 用户界面更新。

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

HTML:

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

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);
}
Run Code Online (Sandbox Code Playgroud)

问题

  1. 为什么Vue.nextTick, vm.$forceUpdate, v-bind:key, 或计算属性不起作用?
  2. 为什么setTimeout在延迟时工作不一致0
  3. setTimeout似乎哈克。是否有一种“适当”的方式来强制 UI 更新?

Ran*_*urn 5

概要

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

调查结果摘要

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

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

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

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

注意:上面的但似乎没有表示 Vue 正在做它应该做的事情,但没有出现预期的视觉输出。因此,代码不会产生准确的预期输出

讨论

前两个作品

证明前两个你想做的事情很容易。'B' 没有被放置在视图中的想法将被反驳。但需要进一步讨论以解决缺乏明显变化的问题。

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

当您逐步执行序列时,您会注意到 UI 更新为值“B”(无论超时长度如何)。驱散。

那么缺乏能见度怎么办?这是由 JavaScript 的Event Loop流程模型引起的,具体与一个称为 Run-to-Completion 的原则有关。该MDN事件循环文档状态:

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

或运行渲染/绘制浏览器进程。因此,当执行堆栈时,BC立即渲染,然后立即渲染,这似乎B从未渲染过。当使用带有 JavaScript 繁重任务(例如引导 SPA)的动画 GIF 时,可以看到这个确切的问题。动画 GIF 要么会断断续续,要么根本不会动画 - 运行到完成阻碍了。

所以 Vue 做它应该做的事情,而 JavaScript 做它应该做的事情。但是长时间运行的循环很麻烦。这就是 lodash_debounce或 simplesetTimout等工具有用的原因。

最后三个工作?

是的。使用相同的断点vue.js将显示 Vue 刷新其更新队列时发生的唯一中断。正如Vue 关于异步更新队列的文档中所讨论的,每个更新都会排队,并且只呈现每个属性的最后一次更新。因此,尽管在处理过程中message实际更改为B,但由于 Vue 异步队列的工作方式,它永远不会呈现:

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