preface
We are all familiar with the principle of data responsiveness in Vue. In VUe2, object.defineProperty () is used to implement change detection. Vue3 uses the ES6 ProxyAPI instead of defineProperty to do this. Now that we know the principle of responsiveness, how do we implement a data interception method? Next, let’s implement a data interception library of our own step by step!
The basic concept
The MVVM framework
MVVM is short for model-view-viewModel. It’s essentially an improved version of MVC. MVVM abstracts the state and behavior of the View in it, and lets separate the View UI from the business logic. Of course, the ViewModel already does this for us, fetching the data from the Model and helping with the business logic involved in displaying content in the View.
The three elements of the MVVM framework are data responsiveness, template engine and its rendering
- Data responsiveness: Listens for data changes and updates in an attempt (data changes can be responded to in a view, that is, data responsiveness)
- Vue 2.x version: Object.defineProperty()
- Vue 3.x version: Proxy
- Template engine: Provides template syntax for describing views
- The interpolation: {{}}
- Instructions: V-bind, V-ON, V-model, V-for, V-if…
- Render: How to convert template to HTML-template => vnode => dom
Implement data detection
1. Basic method definition
// reactive.js
function defineReactive (obj, key, val) {
Object.defineProperty(obj, key, {
get() {
// Logs are generated for each value to facilitate debugging
console.log(`some data was get --- key: ${key}, val: ${val}`)
return val
},
set(newVal) {
if(newVal ! == val) {// Output log every time assignment, convenient debugging
console.log(`new data was set --- key: ${key}, val: ${newVal}`)
val = newVal
}
}
})
}
Copy the code
Now that we have basically implemented a primitive data interception function, let’s test it out
// reactive.js
let test = {}
defineReactive(test, 'foo'.'firstblood')
// take the value of foo
test.foo
// Set the value of foo
test.foo = 'foo'
Copy the code
Above, we defined an object, test, and tried to fetch the value of foo after processing it with the defineReactive function we wrote earlier.
When we run Node React.js, the console outputs the result
The value and assignment of the test object have been successfully intercepted!
Although the current simple version of defineReactive has basically implemented object interception, there are still many shortcomings, such as:
-
We need to manually handle every key of the object.
defineReactive(test, 'foo'.'foo') defineReactive(test, 'bar'.'bar') defineReactive(test, 'baz'.'baz') Copy the code
-
Cannot continue to detect properties of object properties when the object property is also an object
let test = { foo: { id: 1.name: 'foo', } } defineReactive(test, 'foo', {name: 'newFoo'}) test.foo.id // Node executes the current file and prints 'some data was get -- key: foo, val: [object object]' // Indicates that only foo is successfully detected, but foo's ID cannot be detected Copy the code
-
When assigned to an object, detection cannot continue
let test = {} defineReactive(test, 'foo', {name: 'newFoo'}) test.foo.name // Node executes the current file and prints 'some data was get -- key: foo, val: [object object]' // Only foo is successfully detected. Foo's name is not detected Copy the code
-
New attributes cannot be detected if objects are added/deleted
let test = {} defineReactive(test, 'foo'.'firstblood') / / foo values test.foo // node 执行后输出 'some data was get --- key: foo, val: firstblood' test.bar // Node just prints 'some data was get -- key: foo, val: firstblood' and does not check bar Copy the code
Based on these shortcomings, we need to continue to improve our object interception operations
2. Revamp defineReactive
– Iterates over objects to be reactified
// reactive.js
function observe (obj) {
// Check the type of the passed parameter
if (typeofobj ! = ='object' || obj === null) return
// Object responsivity: iterates over each key and defines getters and setters
Object.keys(obj).forEach(key= > {
// Call the previously written interceptor method
defineReactive(obj, key, obj[key])
})
}
Copy the code
With the Observe method, each property of the object is traversed and intercepted, so that all properties of the object can be intercepted automatically by submitting the object to Observe
const myData = {
foo: 'foo'.bar: 'bar'.baz: {
name: 'baz'
}
}
observe(myData)
// test
myData.foo
myData.bar = 'newBar'
myData.baz.name
Copy the code
The console output after Node executes the above code proves that the automatic object attribute interception function has been basically implemented, but nested objects are still problematic
– Resolve nested object issues
If the object property value is also an object, submit the object property value to Observe
// reactive.js
function defineReactive(obj, key, val) {
observe(val)
Object.defineProperty(obj, key, {
/ /...
})
/ /...
}
Copy the code
Test it out:
const myData = {
foo: 'foo'.bar: 'bar'.baz: {
name: 'baz'
}
}
observe(myData)
myData.baz.name
Copy the code
The console output after node execution is as follows, indicating that we have implemented data access detection for nested objects
– Resolves the problem that assignment is an object
If the value of an attribute of an object is an object, observe that attribute to make it an object
// reactive.js
function defineReactive(obj, key, val) {
observe(val)
Object.defineProperty(obj, key, {
get () {
// ...
},
set (newVal) {
// ...
observe(newVal) // The new value is the case of the object
// ...}})/ /...
}
Copy the code
– Resolve the problem of adding/removing new attributes
// reactive.js
// Add a set function to handle it
function set(obj, key, val) {
defineReactive(obj, key, val)
}
Copy the code
At this point, we have achieved a simple version of the data interception library!
The full version code is as follows:
/** * Convert objects to responsive data *@param {*} Obj requires a reactive object *@param {*} The key attribute *@param {*} Val values * /
function defineReactive (obj, key, val) {
// Solve object nesting problems such as test.baz.a
observe(val)
Object.defineProperty(obj, key, {
get() {
console.log(`get ${key}: ${val}`)
return val
},
set(newVal) {
if(newVal! ==val) {console.log(`set ${key}: ${newVal}`)
val = newVal
// Resolve the case where the assigned value is an object (e.g. Test. foo={f1:666})
observe(val)
}
}
})
}
/** * object reactivity: iterates over each key, defines getter, setter *@param {*} Data requires a reactive object */
function observe (data) {
if(typeofdata ! = ='object' || data === null) {
return
}
Object.keys(data).forEach(key= > {
defineReactive(data, key, data[key])
})
}
/** * Add a new attribute *@param {*} obj
* @param {*} key
* @param {*} val
*/
function $set (obj, key, val){
defineReactive(obj, key, val)
}
Copy the code
conclusion
Today we have basically implemented a simple version of the data interception library, so how can we use this library to achieve data responsiveness, so that data changes drive view response? What does it do in ve2. X? Space is limited, but listen to the next time to break down ~!