preface
The reason why I write this article is because I wrote an article about how to synchronize views after data changes in VUE. The previous one was vue2, but now VUe3 comes out, so I write an article about VUe3.
To be clear in advance, the contents of the article can be understood, never use in a production environment.
It is well known that in VUE, when data is changed, the DOM is updated in a microtask, which is an asynchronous operation. This is shown in Run Micrortasks.
So, what we’re going to do is, we’re going to kill the microtask, and we’re going to put the DOM update into the macro task and make it a synchronous operation.
How to do
First, let’s take a look at the update process of VUE
After we modify a piece of data, the setter is triggered, the corresponding update task is derived into the queue, and the queueFlush function is called, which calls flushJobs in a Promise. At this point, vue does the update component, updating the DOM.
In this case, if you don’t let Vue update the component in this Promise.
We’re going to do two things
- Manually update components as data changes
- Data changes are not triggered
reactive
The setter. The expectation is that we control the flow from data changes to DOM updates.
Manually update components as data changes
How do I update components manually? Let’s look at this in VUe3 and print this at this location in the Vue component
// xxx.vue export default {setup() {return function () {console.log(this) Cannot use arrow function return <div></div>}}}Copy the code
There are so many properties in there that, at first glance, there seems to be no API to update the component.
However, we noticed that there was a $attribute, and when we clicked on it, we could see that there was an update. This is what we were looking for, and we can call it to update the component. The problem of manually updating components is solved.
Setters for vUE are not fired after data changes
Once you solve the problem of component updates, you solve the problem of setters. In that case, we can’t use the REACTIVE API anymore, so I need to encapsulate the data myself.
Looking directly at the code, I have implemented a selfReactive. The principle is simple: Proxy all objects in the incoming selfReactive, detect its changes, and update the view immediately if any changes occur.
In vue, the value of number is set to +1 (reactive) every time we click the button. Reactive normally we write it before the first return because we need to manually call the $. Update method, depending on this, */ export default {setup() {return function () {const number = selfReactive({value: 0}, this); return <div> <button onClick={() => { number.value ++ }}>btn</button> <div> {number.value} </div> </div> } } }Copy the code
However, vue calls render every time it is updated, and the selfReactive function will be called as well, normally generating a new number object. The result is that the value of number.value is 0 each time, and 0 is rendered to the page.
How to solve this problem? Which is exactly like “useState” in hooks.
In the following selfReactive implementation, we use a global Array of States to hold all objects passed into selfReactive in order. Each time selfReactive is called, the index is checked to see if the object already exists in states.
- Why have you used it
index
Can be judgedstates
Does the object already exist in? Because in render, every time you callselfReactive
They are ordered, and can be determined by using the order of calls as an index, provided that they are not used in conditional statementsselfReative
, likeuseState
The same cannot be used in conditional statements.
function isObject(obj) { return Object.prototype.toString.call(obj) === '[object Object]' } function isArray(arr) { return Array.isArray(arr) } const states = []; let index = 0; Function setProxy (obj, instance) {const handler = {set: const handler = {setProxy (obj, instance) {const handler = {set: function(obj, prop, value) { obj[prop] = value; index = 0; instance.$.update() return true; }}; Object.keys(obj).forEach(key => { if (isObject(obj[key]) || isArray(obj[key])) { obj[key] = setProxy(obj[key], instance); }}); return new Proxy(obj, handler) } function selfReactive (obj, instance) { if (! isObject(obj) && ! IsArray (obj)) {throw new Error(' obj must be Object ')} You need to do a proxy agent layer const curState = states [index] | | setProxy (obj, instance) states [index++] = curState return curState; }Copy the code
With that done, let’s see if our view can now be updated synchronously. In the figure below, we can see that without Run Micrortasks, the microtasks generated by Promise are gone, and diff and other processes are all completed in the current macro task. And you’re done!
I put all the code in CodeSandbox, click on the link to experience. If the link does not open, copy the above code into your VUE3 project.
To reiterate
Just read this article, but never, ever do this when you’re actually writing code.