Please run the worked demo:
let myCheckbox = Vue.component('my-checkbox', { template: `<div> <input type="checkbox" id="check1" :checked="checked" @change="change">{{checked}} </div>`, props: ['checked'], methods: { change() { this.$emit('change', event.target.checked); } } }) new Vue({ el: '#app', data: { checked: true, count: 1 }, methods: { change(isChecked) { this.count++ this.checked = isChecked if (this.count % 2 === 0) { // this.checked = !isChecked setTimeout(() => { this.checked = !isChecked }, 10); } } }, components: { myCheckbox } })
span { background: pink; }
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <div id="app"> <my-checkbox :checked="checked" @change="change"></my-checkbox> When <span>{{count}}</span> is even your check will fail </div>
however, if i change the synchronous operation to asynchronous operation in change
which means change
setTimeout(() => { this.checked = !isChecked }, 10);
to
this.checked = !isChecked
the demo is not going work because checked
in the check1
is not updated.
My question is
I don’t know why synchronous operation is not work!
I guess there is a promise
after the change
to do the underlying operation and that’s why setTimeout
works.However, I am not sure and I didn’t find any explanation about that.
Advertisement
Answer
When you tick the checkbox, the (native DOM) checkbox changes its appearance by adding the v
(tick symbol).
When that tick happens, it emits the change
event.
Notice that, at this point, the checked
property from Vue has not been changed yet. This change will happen when “someone” picks up the change
event and sets the checked
accordingly. That’s what the line 4 below does:
methods: { // 1 change(isChecked) { // 2 this.count++; // 3 this.checked = isChecked // 4
Now, when the this.count
is even, in the code without setTimeout
, you set right away this.checked
to its previous value.
So, since Vue sees that this.checked
did not change after the execution of the method, Vue understands there is no updating (no repainting) to do. That’s why the ticked symbol (v
above) remains (wrongly) ticked: Vue did not repaint the component.
As a proof, try the demo below. Notice the updated
lifecycle handler never executes:
let myCheckbox = Vue.component('my-checkbox', { template: `<div> <input type="checkbox" id="check1" :checked="checked" @change="change">{{checked}} </div>`, props: ['checked'], methods: { change() { this.$emit('change', event.target.checked); } }, // not executed because `checked` never changes (is always true) updated() { console.log('my-checkbox updated') } }) new Vue({ el: '#app', data: { checked: true, count: 1 }, methods: { change(isChecked) { this.count++; this.checked = isChecked if (this.count % 2 === 0) { this.checked = !isChecked } } }, components: { myCheckbox } })
span { background: pink; }
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <div id="app"> <my-checkbox :checked="checked" @change="change"></my-checkbox> When <span>{{count}}</span> is even your check will fail. - checked: <span>{{checked}}</span> - it never changes, always <b>true</b>, so my-checkbox <b>never</b> has to be updated/repainted. </div>
Now, when you use the setTimeout
, at the moment the change(isChecked)
method has ended its execution, the this.checked
has changed! Which triggers the repaint.
The thing is, shortly after the setTimeout
handler executes, setting this.checked
back to its original value. This triggers another repaint. Notice in the demo below, the updated hook is executed twice when this.count
is even.
Finally, as a last note, the idiomatic way of doing that in Vue is using Vue.nextTick()
, not a setTimeout
:
let myCheckbox = Vue.component('my-checkbox', { template: `<div> <input type="checkbox" id="check1" :checked="checked" @change="change">{{checked}} </div>`, props: ['checked'], methods: { change() { this.$emit('change', event.target.checked); } }, updated() { console.log('my-checkbox updated') }, }) new Vue({ el: '#app', data: { checked: true, count: 1 }, methods: { change(isChecked) { this.count++; this.checked = isChecked if (this.count % 2 === 0) { Vue.nextTick(() => { this.checked = !isChecked }); //setTimeout(() => { // this.checked = !isChecked //}, 10); } } }, components: { myCheckbox } })
span { background: pink; }
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <div id="app"> <my-checkbox :checked="checked" @change="change"></my-checkbox> When <span>{{count}}</span> is even the checked will be overridden. checked: <span>{{checked}}</span> </div>