Reactive Data in Vue
Preface
Since Vue3 was released, many people have shared that in interviews they are frequently asked: “What is the underlying principle of reactive data in Vue2 versus Vue3?”
A simple answer would be: “Vue2 uses getter and setter to implement reactive data, while Vue3 uses Proxy to achieve reactivity.” This kind of answer might be enough to handle the interviewer, showing that we are engineers who at least roughly understand new technology rather than just using it blindly. However, if the interviewer asks more deeply: “So what is the principle behind each of these?”, without some understanding of Virtual DOM, Side Effects, and ECMAScript, it would be hard to answer.
Therefore, this article borrows code snippets from 《Vue.js Design and Implementation》, a book by a member of the Vue3 open-source team, to reproduce Vue’s reactive data.
Virtual DOM
Frontend engineers using Vue or React more or less know that these frameworks complete page rendering based on the Virtual DOM. So what exactly is the Virtual DOM?
In short, it is just a Javascript object, for example:
const demoVDom = {
tag: 'div',
children:[
{tag : 'span', children : 'Hello world'}
]
}
Then a framework’s render function mounts the Virtual DOM onto the actual HTML. You can check this VDom codepen.
Side Effect Function
A side effect function, simply put, is a function that affects variables outside itself, such as:
let a = 1;
function sideEffectA(){
a = 2;
}
sideEffectA();
// after execution, the variable 'a' outside the function has been modified
In frontend web development, side effect functions are everywhere:
// html
<body>
<div class="demo">
<p>Hello Vue2</p>
</div>
</body>
// javascript
setTimeout(()=>{
document.querySelector('.demo').innerHTML = `<p>Hello Vue3</p>`
},3000)
Basically, any modification of an element on the page is a side effect.
Reactive Data
Back to the topic of reactive data, by combining the Virtual DOM, the render function, and side effect functions, we can roughly clarify the approach:
If we have a piece of data: let demoData = { text : 'Hello Data'}, and whenever we modify the text field, we re-execute the render function once, then we have implemented reactive data!
- Use a
new Set()to store all side effect functions - Define a side effect function so that the updated
textdata renders into the static HTML - Bind the data so that when the data changes, all side effect functions in the
Setare triggered
Reactive Data in Vue2
In the Vue2 era, the ES2015 spec was not yet available, so step 3 (binding the data) was implemented using Javascript’s Object.defineProperty function:
// save side-effect function
const bucket = new Set()
// init data
const dataVue2 = { text: '' }
// side effect function
function myVue2Effect() {
const vue2Node = document.querySelector('.vue2')
vue2Node.innerText = dataVue2.text
}
// define reactive
function vue2_defineReactive(obj, key, value) {
Object.defineProperty(obj, key, {
get() {
bucket.add(myVue2Effect)
return value
},
set(newValue) {
if (newValue !== value) {
value = newValue
bucket.forEach((fn) => fn())
}
},
})
}
You can see it in action on this codepen.
Reactive Data in Vue3
After the ES2015 spec, Vue3 can use the Proxy object to hijack data access:
// origin data
const dataVue3 = { text: 'Vue3: hello world' }
// proxy the origin data
const objVue3 = new Proxy(dataVue3, {
get(target, key) {
bucket.add(myVue3Effect)
return target[key]
},
set(target, key, newVal) {
target[key] = newVal
bucket.forEach((fn) => fn())
return true
},
})
You can see it in action on this codepen.
Conclusion
The above only implements very simple reactive data. In the actual Vue2/Vue3 frameworks, considering all kinds of data structures, other operations (such as for loops), and performance concerns, different data structures are used to handle reactive data.
Ref
ChangeLog
- 20221006 - init
- 20260501–translate by claude code