Review the use of watch
Watch is a method in Vue to listen for changes in data. Let’s review the use of Watch before reading the source code
Listen for basic data types
<div>
{{ name }}
<button @click="changeName">Change the name</button>
</div>
Copy the code
export default {
data() {
return {
name: 'maoxiaoxing',}},watch: {
name(val, oldval) {
console.log(val, oldval)
}
},
methods: {
changeName() {
this.name = this.name === 'maoxiaoxing' ? 'yangxiaoA' : 'maoxiaoxing'}}}Copy the code
Watch can accept two parameters, one after the change and one before the change, and you can process some logic based on these two values
Listening to the object
<div>
{{ obj.name }}
<button @click="changeName">Change the name</button>
</div>
Copy the code
export default {
data() {
return {
obj: {
name: 'maoxiaoxing',}}},watch: {
obj: {
handler(val, oldval) {
console.log(val, oldval)
},
deep: true.immediate: true,}},methods: {
changeName() {
this.obj.name = this.obj.name === 'maoxiaoxing' ? 'yangxiaoA' : 'maoxiaoxing'}},created() {
console.log('created')}}Copy the code
When the listening object changes, add the “deep” attribute to deep listening object data; If you want to execute the watch method when the page comes in, just add immediate. It is important to note that the watch with the immediate property is executed before the created life cycle
Watch takes an array as an argument
When I look at the Vue source code, I find it interesting that if the properties that watch listens to receive an array instead of setting a method, I can pass multiple methods to the currently listening properties
export default {
data() {
return {
name: 'jack',}},watch: {
name: [{handler: function() {console.log(1)}, immediate: true },
function(val) {console.log(val, 2)}}],methods: {
changeName() {
this.name = this.name === 'maoxiaoxing' ? 'yangxiaoA' : 'maoxiaoxing'}}}Copy the code
The array can take different forms of arguments, either methods or objects, in the same way as a normal watch. Can receive data as parameters this point is not found in the official documentation, as to why can write so, the following source code will be mentioned.
Initialize the watch
initState
// src\core\instance\state.js
export function initState (vm: Component) {
vm._watchers = []
const opts = vm.$options
if (opts.props) initProps(vm, opts.props) // If there are props, initialize them
if (opts.methods) initMethods(vm, opts.methods) // If there are methods, initialize the methods in the methods
if (opts.data) { // Initialize, data, if any; Otherwise it responds with an empty object
initData(vm)
} else {
observe(vm._data = {}, true /* asRootData */)}if (opts.computed) initComputed(vm, opts.computed) // If computed, initialize computed
if(opts.watch && opts.watch ! == nativeWatch) {// Initialize the watch if there is one
initWatch(vm, opts.watch)
}
}
Copy the code
First initialize the watch in initState, passing the watch property to the initWatch method if it has one
initWatch
// src\core\instance\state.js
function initWatch (vm: Component, watch: Object) {
for (const key in watch) {
const handler = watch[key]
if (Array.isArray(handler)) {
for (let i = 0; i < handler.length; i++) {
createWatcher(vm, key, handler[i])
}
} else {
createWatcher(vm, key, handler)
}
}
}
Copy the code
InitWatch iterates over the watch and determines whether each value is an array. If it is an array, it iterates over the array and creates multiple callbacks. This explains why the watch listens to an array. If it’s not an array, create the callback function directly. Here we can also see the benefits of learning the source code. By learning the source code, we can learn some writing methods that are not mentioned in official documents.
createWatcher
function createWatcher (
vm: Component,
expOrFn: string | Function, handler: any, options? :Object
) {
if (isPlainObject(handler)) {
options = handler
handler = handler.handler
}
if (typeof handler === 'string') {
handler = vm[handler]
}
return vm.$watch(expOrFn, handler, options)
}
Copy the code
CreateWatcher will determine if handler is an object. If handler is an object, mount it to the options property and extract the handler property from the object. If handler is a string, this method will be found from the Vue instance and assigned to handler. As you can see here, watch also supports writing strings. Execute the $watch method on the Vue instance.
$watch
Vue.prototype.$watch = function (
expOrFn: string | Function, cb: any, options? :Object
) :Function {
// Get the Vue instance this
const vm: Component = this
if (isPlainObject(cb)) {
// Execute createWatcher if cb is an object
return createWatcher(vm, expOrFn, cb, options)
}
options = options || {}
// Mark user watcher
options.user = true
// Create user watcher object
const watcher = new Watcher(vm, expOrFn, cb, options)
// Judge immediate if true
if (options.immediate) {
// Immediately execute the cb callback and pass in the current value
try {
cb.call(vm, watcher.value)
} catch (error) {
handleError(error, vm, `callback for immediate watcher "${watcher.expression}"`)}}// Return the method to cancel listening
return function unwatchFn () {
watcher.teardown()
}
}
Copy the code
The watch function is an instance method of Vue, which means that we can use Vue. The watch function is an instance method of Vue, which means that we can use Vue. The official documentation is very detailed. $watch creates a Watcher object. This also involves the reactive principle. Data changed in the watch can be changed in a responsive way. It also determines if the immediate attribute exists and, if so, calls the callback directly.
Watcher
export default class Watcher {
vm: Component;
expression: string;
cb: Function;
id: number;
deep: boolean;
user: boolean;
lazy: boolean;
sync: boolean;
dirty: boolean;
active: boolean;
deps: Array<Dep>;
newDeps: Array<Dep>;
depIds: SimpleSet;
newDepIds: SimpleSet;
before: ?Function;
getter: Function;
value: any;
constructor (
vm: Component,
expOrFn: string | Function,
cb: Function,
options?: ?Object, isRenderWatcher? : boolean) {
this.vm = vm
if (isRenderWatcher) {
vm._watcher = this
}
vm._watchers.push(this)
// options
if (options) {
this.deep = !! options.deepthis.user = !! options.userthis.lazy = !! options.lazythis.sync = !! options.syncthis.before = options.before
} else {
this.deep = this.user = this.lazy = this.sync = false
}
this.cb = cb
this.id = ++uid // uid for batching
this.active = true
this.dirty = this.lazy // for lazy watchers
this.deps = []
this.newDeps = []
this.depIds = new Set(a)this.newDepIds = new Set(a)this.expression = process.env.NODE_ENV ! = ='production'
? expOrFn.toString()
: ' '
// parse expression for getter
if (typeof expOrFn === 'function') {
this.getter = expOrFn
} else {
// expOrFn is a string, for example watch: {'person.name': function... }
// parsePath('person.name') returns a function that gets the value of person.name
this.getter = parsePath(expOrFn)
if (!this.getter) {
this.getter = noop process.env.NODE_ENV ! = ='production' && warn(
`Failed watching path: "${expOrFn}"` +
'Watcher only accepts simple dot-delimited paths. ' +
'For full control, use a function instead.',
vm
)
}
}
this.value = this.lazy
? undefined
: this.get()
}
/** * Evaluate the getter, and re-collect dependencies. */
/* Get the value of the getter and redo the dependency collection */
get () {
/* Set your own watcher observer instance to dep. target to rely on the collection. * /
pushTarget(this)
let value
const vm = this.vm
try {
value = this.getter.call(vm, vm)
} catch (e) {
/* Getters are performed to render, but dependencies are collected. After setting dep. target to its own observer instance, the getter operation is performed. For example, there may be a, B, and C data in the current data, and the getter rendering needs to depend on a and C, so the getter function of a and C data will be triggered when the getter is executed. In the getter function, the dep. target can be determined whether the Dep. Target exists and then the dependency collection is completed. Place the observer object in the subs of Dep in the closure. * /
if (this.user) {
handleError(e, vm, `getter for watcher "The ${this.expression}"`)}else {
throw e
}
} finally {
// "touch" every property so they are all tracked as
// dependencies for deep watching
/* If deep exists, trigger the dependency of each deep object, tracking its changes */
if (this.deep) {
/* Recurse each object or array, triggering their getters so that each member of the object or array is collected by dependencies, forming a "deep" dependency */
traverse(value)
}
popTarget()
this.cleanupDeps()
}
returnvalue } ... Other methods}Copy the code
I left out some of the other methods in the Watcher above, except for the get function, which we can see in the get function that if there is a deep property, it will recursively process every property in the object to achieve the effect of deep listening. Here is the end of the explanation of the use and principle of Watch. By reading the source code, we can not only understand how the Vue framework is implemented internally, but also see some usages not mentioned in the official documents, which is very helpful to us.