Motivation
In the official Vuex documentation, there is a quote.
Vuex stores are reactive. When Vue components retrieve state from it, They will be reactive and like to update if the store’s state changes.
Vuex claims that its Store data is responsive. When the Vue component retrieves the state from the Store, the Vue component effectively updates the view to reflect the state change.
We all know that in Vue, we can listen for responsive data using computed and Watch technologies. Similarly, we should be able to use computed and Watch to listen and call back Vuex’s state.
Vue data and view binding
Reading through the Vuex documentation, you can see that it only mentions using computed technology to observe state in the Store (or directly using store data in the template) to bind data to views. So why is there no mention of the Watch’s approach? Can we use Watch technology to bind data and view? Can you watch the vuex store data? How to watch? (You can directly refer to the watch Store data section later)
Computed and Watch technologies mentioned here include computed and Watch created by option, as well as computed and Watch created by composition API. The computed and watch methods in the Composition API are similar to the Option API, but only the Option API.
Code interpretation
Take a look at the Option API: Creating computed Watcher and Watch The watcher is created using initComputed and initWatch.
First, take a look at why, using computed technology, you can bind store State to views. Here is a simplified version of initComputed
const computedWatcherOptions = { lazy: true }
function initComputed (vm, computed) {
// computedWatchers object on the component
var watchers = vm._computedWatchers = Object.create(null);
// Iterate over computed objects in options
for (var key in computed) {
// Methods defined in computed, obtained by the key value in computed
var userDef = computed[key];
// Assign a value to the new variable getter
var getter = typeof userDef === 'function' ? userDef : userDef.get;
// Create wather, collect dependencies, and put the Watcher instance into the formed Wather queue
// The Watcher constructor accepts an input parameter:
// vm,expOrFn,cb,options,isRenderWatcher
watchers[key] = new Watcher(
vm,
getter || noop,
noop,
computedWatcherOptions
);
// Used to define a computed property as responsive.defineComputed(vm, key, userDef); }}Copy the code
Generally speaking, this code is divided into the following steps:
- Create computedWatchers object for the current component
- Next, you iterate over computed keys and create a Watcher for each key to observe the observable data that is “touched” (vUE speak) in the computed[key] method.
- Define getters for computed properties (such as vm.a) so that they can be relied on by the Watcher currently being created when it is’ touched ‘(so-called dependency collection).
Touch the data?
In the official vue documentation, there is a picture of ‘Reactivity in Depth’ that mentions render to touch data. What is Touch data? It’s just a getter method that calls the data. But the getter method has been hijacked in the defineReactive method to have the ability to be observed.
Object.defineProperty(obj, key, {
enumerable: true.configurable: true.get: function reactiveGetter () {
const value = getter ? getter.call(obj) : val
// Check if there is a global Watcher when the property is touched, i.e. when the getter is called for the property
// Rely on the collection logic
if (Dep.target) {
// If there is a global Watcher, let the Watcher collect the current data as its dependency
dep.depend()
if (childOb) {
childOb.dep.depend()
if (Array.isArray(value)) {
dependArray(value)
}
}
}
return value
}
)
Copy the code
Let’s go back to initComputed. In this function, the observed data used in computed[key] is touched by creating Watcher for each computed key. In our analysis, this is the data defined by Touch in the store. This way, Watcher will receive notification when the data in the Store is modified. However, since computedWatcherOptions = {lazy: True}, the computed Watcher created is lazy Watcher, that is, instead of executing Watcher’s callback immediately when the dependency watcher observed changes, watcher marks it as dirty, of course, In computed Watcher, there is no callback (there is actually an empty noOP function).
So far, the view is not updated when store data changes. We need to make this computed property touchable as well. That’s where the defineComputed method comes in.
The defineComputed method defines a new getter for each computed property that has the ability to be touched, at mount time, and relied on by the Watcher updating the view.
The following code is the new getter for a computed property to be defined. To check whether its computed watcher is dirty when it is touched (as we mentioned earlier, computed Watcher is lazy watcher) is to check whether the data observed by the computed Watcher has changed. If it changes, reevaluate it. Next, check global’s Watcher to see if a dependency collection is currently taking place, and if so, make the Watcher depend on our computed property (view update Watcher is based on computed property). Finally returns the value of watcher.
// Get the corresponding watcher
const watcher = this._computedWatchers && this._computedWatchers[key]
if (watcher) {
// Check whether watcher has changed. If it has changed, evaluate is called to obtain the new value
if (watcher.dirty) {
watcher.evaluate()
}
// Check whether watcher is currently collecting dependencies
if (Dep.target) {
watcher.depend()
}
// This return value is what is called a computed value in the cache (if watcher is not dirty)
return watcher.value
}
Copy the code
Use watch to view store data?
Can we bind vuex data and view in the way of Watch? The answer is yes, but it requires a different technology. First take a look at initWatcher
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
In initWatch, we first iterate through the Watch object in option, and then create one or more Watchers for each key value of the watch object (when the value of watch[key] is an array). The element of the array can be the name of a component method, an object containing a handler, or a function. CreateWatcher (); 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 encapsulates some of the input judgment logic, and finally, vue’s $watch method is called to observe the expOrFn changes. The expOrFn input type is string, which is the key of watch.
The biggest difference between the initialization of computed and that of Watch is the expOrFn and handler.
Computed Or expOrFn Imports computed[key] (function), and handler is NOOP. Note The expOrFn of watch is passed in the key (string) of watch, and the handler is Watch [key].
The essential difference
This is the essential difference between computed and Watch.
So, if we have code like this:
computed: {
a: () = > { store.state.count++ }
},
watch: {
b: () = > { this.a++ }
}
Copy the code
Computed Watcher observes changes in store.state.count, whereas watch Watcher observes changes in B.
Computed observations refer to observed data for which the getter is called in computed[key] functions. Therefore, if you call the getter for multiple observed data in computed[key] functions, you can observe multiple data. However, watch observes the unique key, so it can only observe the unique data. At the same time, computed has no handler, so computed Watcher does not call the callback function when observable data for computed changes, and it simply marks itself (Watcher) as dirty because of the lazy attribute. Handler (Watch [key]) is called when wach’s observable data changes.
Their relationship goes something like this:
Watch’s data in store
What if we wanted to directly watch the data in the Vuex Store without generating unnecessary computed properties? The red line in the picture below.
If you use the Option API watch, you can do this: however, the data in the Watch Vuex Store will not update the view.
watch: {
'$store.state.count': function (newval) {
console.log(newval); }},Copy the code
We can also do this with $watch. Of course, with this approach, the view is not updated.
created() {
// top-level property name
this.$watch('a'.(newVal, oldVal) = > {
// do something
})
// function for watching a single nested property
this.$watch(
() = > this.c.d,
(newVal, oldVal) = > {
// do something})// function for watching a complex expression
this.$watch(
// every time the expression `this.$store.state.a + this.$store.state.b` yields a different result,
// the handler will be called. It's as if we were watching a computed
// property without defining the computed property itself
() = > this.$store.state.a + this.$store.state.b,
(newVal, oldVal) = > {
// do something})}Copy the code
To update a view, you can assign a value to an observable in the handler and then use that value in the view layer to update the view after calling back to the handler.
conclusion
In summary, we know that using computed technology is the easiest way to track the data in the Store and keep the view up to date. We also learned about the use of Watch technology in the component to trace data in the Store to trigger callback functions. We also explore the similarities and differences between computed and Watch technologies by looking at the source code, and summarize the different ways to observe VUEX data in components.