admin管理员组文章数量:1395312
I would like to force the UI to update midway through an event loop cycle.
Vue.nextTick
Vue.nextTick seems to provide you with an updated version of vm.$el
, but doesn't actually cause the UI to update.
CodePen:
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 doesn't appear to do anything at all.
- It doesn't appear to change the value of
vm.$el
. - It doesn't appear to update the UI.
CodePen:
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 also doesn't appear to do anything at all:
- It doesn't appear to change the value of
vm.$el
. - It doesn't appear to update the UI.
Codepen:
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';
}
puted
Using a puted property, as this popular answer remends, also doesn't appear to do anything:
- It doesn't appear to change the value of
vm.$el
. - It doesn't appear to update the UI.
CodePen:
HTML:
<div id="example">
<p>Value: {{ putedMessage }}</p>
<button v-on:click="change()">Change</button>
</div>
JS:
var vm = new Vue({
el: '#example',
data: {
message: 'A'
},
puted: {
putedMessage: 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 (added in edit)
Using promises doesn't work either.
CodePen:
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
setTimeout
is the only thing that seems to work. But it only works consistently when the delay is 100
. When the delay is 0
, it works sometimes, but doesn't work consistently.
vm.$el
updates.- The UI updates.
CodePen:
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);
}
Questions
- Why don't
Vue.nextTick
,vm.$forceUpdate
,v-bind:key
, or puted properties work? - Why does
setTimeout
work inconsistently when the delay is0
? setTimeout
seems hacky. Is there a "propper" way to force a UI update?
I would like to force the UI to update midway through an event loop cycle.
Vue.nextTick
Vue.nextTick seems to provide you with an updated version of vm.$el
, but doesn't actually cause the UI to update.
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>
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 doesn't appear to do anything at all.
- It doesn't appear to change the value of
vm.$el
. - It doesn't appear to update the 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 also doesn't appear to do anything at all:
- It doesn't appear to change the value of
vm.$el
. - It doesn't appear to update the 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';
}
puted
Using a puted property, as this popular answer remends, also doesn't appear to do anything:
- It doesn't appear to change the value of
vm.$el
. - It doesn't appear to update the UI.
CodePen: https://codepen.io/adamzerner/pen/EEdoeX?editors=1010
HTML:
<div id="example">
<p>Value: {{ putedMessage }}</p>
<button v-on:click="change()">Change</button>
</div>
JS:
var vm = new Vue({
el: '#example',
data: {
message: 'A'
},
puted: {
putedMessage: 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 (added in edit)
Using promises doesn't work either.
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>
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
setTimeout
is the only thing that seems to work. But it only works consistently when the delay is 100
. When the delay is 0
, it works sometimes, but doesn't work consistently.
vm.$el
updates.- The UI updates.
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>
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);
}
Questions
- Why don't
Vue.nextTick
,vm.$forceUpdate
,v-bind:key
, or puted properties work? - Why does
setTimeout
work inconsistently when the delay is0
? setTimeout
seems hacky. Is there a "propper" way to force a UI update?
- 1 The change function is synchronous, so by definition blocks. Nothing else is going to happen. setTimeout doesn't work because the execution context is still the synchronous function. There is a simple solution, but it depends on your use case for counting to 10MM. – Randy Casburn Commented Apr 6, 2018 at 4:32
-
Would you mind elaborating on a few things @RandyCasburn? Regarding the execution context inside of
setTimeout
, I'm not accessingthis
so I don't see how that's relevant. And the execution context is the same regardless of whether the delay is0
or100
, yet changing the delay to100
causessetTimeout
to work. Suppose that my use case is simply to get the UI to show "B" immediately after clicking "Change", and then to "C" a few moments later. Can you provide the simple solution that you have in mind? – Adam Zerner Commented Apr 6, 2018 at 4:45 -
You have two options: 1: set a watch property and watch
message
: watch : { message: function(){} } Watchers; or 2: the $watch API method $watch. I remend #1 since it directly addresses your property. These simply inject an async capability into the synchronous function. – Randy Casburn Commented Apr 6, 2018 at 4:54 - Here you go: codepen.io/anon/pen/OvBQmV?editors=1010 – Randy Casburn Commented Apr 6, 2018 at 5:03
-
@RandyCasburn that isn't working for me.
watch
is only firing afterC
is assigned tomessage
. – Adam Zerner Commented Apr 6, 2018 at 5:14
3 Answers
Reset to default 5Synopsis
The illusion of B
not being updated/displayed in the UI is caused by a bination of Vue's Async Update Queue and JavaScript's Event Loop Process model. For details and proof read on.
#Summary of Findings#
These actually do what you want (but don't seem to)
Vue.nextTick
setTimeout
- (but doesn't seem to with short timeout)
These work as expected (but require explanation)
v-bind:key
vm.$forceUpdate
- Promise
Note: The but doesn't seem to above is an acknowledgment that Vue is doing what it is supposed to but the expected visual output does not appear. Therefore, the code doesn't produce the expected output is accurate.
Discussion
First Two Work
Proving the first two do what you want is quite easy. The idea of 'B' not being placed in the view will be disproved. But further discussion is required to address the lack of visible change.
- Open each of the Pens in Chrome
- In dev tools, set a break point in
vue.js
on line 1789 - Step through the sequence
While you step through the sequence you will notice the UI is updated with the value 'B' as it should (regardless of length of timeout). Dispelled.
So what about the lack of visibility? This is caused by JavaScript's Event Loop process model and is specifically related to a principle called Run-to-Completion. The MDN Event Loop Documentation states:
A downside of this model is that if a message takes too long to plete, the web application is unable to process user interactions like click or scroll.
or run the render/paint browser processes. So when the stack is executed, B
is rendered then C
immediately thereafter, which seems like B
is never rendered. One can see this exact problem when using an animated GIF with a JavaScript heavy task, such as bootstrapping a SPA. The animated GIF either will stutter or will not animate at all - the Run-to-Completion is in the way.
So Vue does what it is supposed to and JavaScript does what it is supposed to correctly. But the long running loop is troublesome. This is the reason tools like lodash _debounce
or simple setTimout
are helpful.
Last Three Work?
Yes. Using the same breakpoint in vue.js
will show the only break happens when Vue is flushing its queue of updates. As discussed in Vue's documentation about Async Update Queue each update is queued and only the last update for each property is rendered. So although message
is actually changed to B
during processing, it is never rendered because of the way the Vue Async Queue works:
In case you haven’t noticed yet, Vue performs DOM updates asynchronously. Whenever a data change is observed, it will open a queue and buffer all the data changes that happen in the same event loop.
I'm not sure what you want but try this...
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)
}
}
})
I would look at using Vue Instance Lifecycle Hooks.
Take a look at this Vue Instance Lifecycle Diagram as well.
Tying in with a Lifecycle Hook should give you the ability to disrupt something midway if you need to update it. Maybe do some sort of check with the beforeMount hook.
版权声明:本文标题:javascript - How can you force the UI to update in the middle of an event loop cycle in Vue.js? - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1744601834a2615113.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论