Problem reading

Recently, I used the Tree component for a project using ElementUI, but because of the amount of customization required, the native Tree component was not able to meet the requirements of the project, so I decided to look at the ElementUI code implementation logic, and then adjust it for the parts that can be customized.

Among them, when I saw the Watch part of the Tree component, I was full of doubts at first sight. I admitted that it was probably due to the weak knowledge of JavaScript that I could not understand the logic of some things in the first time. Fortunately, after the manual test, I understood the details and thus formed this article to record the whole idea.

Let’s start by looking at the Watch option of the Tree component in ElementUI.

watch: { defaultCheckedKeys(newVal) { this.store.setDefaultCheckedKey(newVal); }, defaultExpandedKeys(newVal) { this.store.defaultExpandedKeys = newVal; this.store.setDefaultExpandedKeys(newVal); }, data(newVal) { this.store.setData(newVal); }, checkboxItems(val) { Array.prototype.forEach.call(val, (checkbox) => { checkbox.setAttribute('tabindex', -1); }); }, checkStrictly(newVal) { this.store.checkStrictly = newVal; }},Copy the code

To be honest, the first time I looked at this code, I didn’t understand, I thought, “A listener is not supposed to listen for specific properties, why is it a handler?” Where is Key?

So I just went to the console, and I rubbed out the code

a = { b(c) {console.log(c)}}
Copy the code

As the result of the output, I suddenly clear, do not believe you look

To tell the truth, this piece of content for me, is a violent blow ah, really is to pay for their JS.

But that’s far from the end of the story, because we haven’t seen the watch yet

The basic concept

Vue provides a more general way to observe and respond to changes in data on Vue instances: listening properties. It’s easy to abuse Watch when you have some data that needs to change with other data — especially if you’ve used AngularJS before. However, it is often better to use computed properties rather than imperative Watch callbacks.

While computing properties is more appropriate in most cases, sometimes a custom listener is required. That’s why Vue provides a more generic way to respond to changes in data with the Watch option. This approach is most useful when asynchronous or expensive operations need to be performed when data changes.

Here’s a simple example:

<div id="watch-example"> 
    <p> Ask a yes/no question: 
        <input v-model="question"> 
    </p> 
    <p>{{ answer }}</p> 
</div>
Copy the code
<script> var watchExampleVM = new Vue({ el: '#watch-example', data: { question: '', answer: 'I cannot give you an answer until you ask a question! '}, watch: {// If 'question' changes, this function runs question: function (newQuestion, oldQuestion) { this.answer = 'Waiting for you to stop typing... 'this.debouncedGetAnswer()}}, created: function () {//' _. Debounce 'is a function that uses Lodash to limit the frequency of operations. // In this example, we want to limit the frequency of access to yesNo. WTF/API // AJAX requests will not be sent until the user has entered. To learn more about the // '_.debounce' function (and its relatives' _.throttle '), // see: https://lodash.com/docs#debounce this.debouncedGetAnswer = _.debounce(this.getAnswer, 500) }, methods: { getAnswer: function () { if (this.question.indexOf('?') === -1) { this.answer = 'Questions usually contain a question mark. ; -)' return } this.answer = 'Thinking... ' var vm = this axios.get('https://yesno.wtf/api') .then(function (response) { vm.answer = _.capitalize(response.data.answer) }) .catch(function (error) { vm.answer = 'Error! Could not reach the API. ' + error }) } } }) </script>Copy the code

The above code is an example in the document, which well explains the functions and application scenarios of Watch. Very good, you can go to Vue official documents to see, to experience the real case of Watch.

And that’s not all I’m going to say

The vue watch

I don’t know if you have seen the source code of VUe2, I have been looking at this content recently, because I am curious, for example, the watch option.

Of course, I will not explain how to read the source code, different people have different views, I will start with initState function, okay

export function initState (vm: Component) { vm._watchers = [] const opts = vm.$options if (opts.props) initProps(vm, opts.props) if (opts.methods) initMethods(vm, opts.methods) if (opts.data) { initData(vm) } else { observe(vm._data = {}, true /* asRootData */) } if (opts.computed) initComputed(vm, opts.computed) if (opts.watch && opts.watch ! == nativeWatch) { initWatch(vm, opts.watch) } }Copy the code

This is a uniform initialization based on the different options we defined in the VUE instance, such as props, Data, Methods, computed, and watch options.

In this article I’m just going to study Watch. So, in the last section, if you define the Watch option in the Vue instance, then a Watch listener is created here. How?

Firefox also has a watch function in Object. Prototype.

// Firefox has a "watch" function on Object.prototype...
export const nativeWatch = ({}).watch
Copy the code

Continue to look at the watch.


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

We typically define the Watch option on vue instances using Object. In Object, use properties in the Data Object or props Object as keys, and define a function to handle any changes to the corresponding keys.

So in initWatch, we iterate over the defined Watch property and createWatcher, respectively

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

The functionality of createWatcher can be seen in the last line of code

vm.$watch( expOrFn, callback, [options] )
Copy the code

Observe a change in the evaluation result of an expression or function on a Vue instance. The callback takes new and old values. The expression accepts only simple key paths. For more complex expressions, replace them with a function.

Vm.$watch returns a cancel watch function to stop triggering the callback:

Var unwatch = vm.$watch('a', cb)Copy the code

To detect changes in values within an object, specify deep: true in the option argument. Note that listening for array changes does not need to do this.

vm.$watch('someObject', callback, { 
    deep: true 
}) 
vm.someObject.nestedValue = 123 
// callback is fired
Copy the code

Specifying immediate: true in the options argument triggers the callback immediately with the current value of the expression:

$watch('a', callback, {immediate: true}) // Trigger the callback immediately with the current value of 'a'Copy the code

Note that with the immediate option, you cannot cancel listening for a given property on the first callback.

Var unwatch = vm.$watch('value', function () {doSomething() unwatch()}, {immediate: true})Copy the code

If you still want to call an unlistening function inside a callback, you should check the availability of the function first:

var unwatch = vm.$watch( 'value', function () { 
    doSomething() 
    if (unwatch) { 
        unwatch() 
    } 
}, { immediate: true })
Copy the code

review

Now to look at the problem in the guide, you look at the code in ElementUI

watch: { defaultCheckedKeys(newVal) { this.store.setDefaultCheckedKey(newVal); }, defaultExpandedKeys(newVal) { this.store.defaultExpandedKeys = newVal; this.store.setDefaultExpandedKeys(newVal); }, data(newVal) { this.store.setData(newVal); }, checkboxItems(val) { Array.prototype.forEach.call(val, (checkbox) => { checkbox.setAttribute('tabindex', -1); }); }, checkStrictly(newVal) { this.store.checkStrictly = newVal; }},Copy the code

First of all, the names of these functions will be used as keys to listen, because keys are defined in the data or props options. I could have written that. And the arguments to those functions, the arguments to the callback function, the new values and the old values.