Vue framework, how computed and watch work (unfinished)

computed and watch definition

1.computed is a calculated attribute, similar to a filter, to process the data bound to the view. Examples from the official website:

<div id="example">
  <p>Original message: "{{ message }}"</p>
  <p>Computed reversed message: "{{ reversedMessage }}"</p>
</div>
var vm = new Vue ({
  the: '#example' ,
  data: {
    message: 'Hello'
  },
  computed: {
    // Calculate attribute getter 
    reversedMessage: function () {
       // `this` points to vm instance 
      return  this .message.split (''). Reverse (). Join ('' )
    }
  }
})

result:

Original message: "Hello"
Computed reversed message: "olleH"

Calculated attributes are cached based on their dependencies. They will only be re-evaluated when the relevant dependencies change.

It is worth noting that "reversedMessage" cannot be defined in the props and data of the component, otherwise an error will be reported.

2.watch is a listening action, used to observe and respond to data changes on the Vue instance. Examples on the official website:

<div id="watch-example">
  <p>
    Ask a yes/no question:
    <input v-model="question">
  </p>
  <p>{{ answer }}</p>
</div>
<!-Because the ecology of AJAX libraries and common tools is already quite rich, Vue core code is not repeated->
<!-Provide these features to keep it streamlined. This also allows you to freely choose the tools you are more familiar with. ->
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/axios.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/lodash.min.js"></script>
<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 will run 
    question: function (newQuestion, oldQuestion) {
       this .answer = 'Waiting for you to stop typing ...'
       this .debouncedGetAnswer ()
    }
  },
  created: function () {
     // `_.debounce` is a function that limits the operating frequency through Lodash. 
    // In this example, we want to limit the frequency of access yesno.wtf/api 
    // AJAX request will be issued until the user input is completed. To learn more about the 
    // `_.debounce` function (and its close relative` _.throttle`), 
    // please refer to: 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>

In this example, using the watch option allows us to perform an asynchronous operation (access an API), limits how often we perform the operation, and sets the intermediate state before we get the final result. These are impossible to calculate attributes.

Similarities and differences between computed and watch usage

The following summarizes the similarities and differences between the two usages:

Same : both computed and watch play the role of monitoring / relying on one data and processing

Similarities and differences : They are all Vue implementations of listeners, but computed is mainly used to process synchronized data, and watch is mainly used to observe the change of a certain value to complete a complex business logic with a large cost . When calculated can be used, it is preferred to use calculated to avoid the embarrassing situation of multiple calls to watch when multiple data affects one of the data.

Advanced usage of watch 

1.handler method and immediate attribute

<div id="demo">{{ fullName }}</div>
var vm = new Vue ({
  the: '#demo' ,
  data: {
    firstName: 'Foo',
    lastName: 'Bar',
    fullName: 'Foo Bar'
  },
  watch: {
    firstName: function (val) {
      console.log ( 'Not executed for the first time ~' )
       this .fullName = val + '' + this .lastName
    }
  }
})

As you can see, the watch will not be executed during initialization. Looking at the example above, only when the value of firstName changes will the monitoring calculation be performed.

But what if you want to execute it the first time it is bound? At this time we have to modify our example:

  watch: {
    firstName: {
      trades (val) {
        console.log ( 'First time executed ~' )
         this .fullName = val + '' + this .lastName
      },
      // On behalf of the firstName method declared in the watch, immediately execute the handler method 
      immediately: true
    }
  }

Open the console and you can see that 'the first time executed ~' is printed.

Have you noticed the handler? We bind a handler method to firstName. The watch method we wrote before actually writes this handler by default. Vue.js will handle this logic, and it will actually be compiled into this handler.

And immediate: true means that if the firstName is declared in wacth, it will immediately execute the handler method inside. If it is false, it will have the same effect as our previous one, and will not be executed when it is bound.

2.deep attribute

There is also a deep attribute in the watch, which indicates whether to enable deep monitoring. The default is false. Let's look at an example:

<div id="app">
  <div>obj.a: {{obj.a}}</div>
  <input type="text" v-model="obj.a">
</div>
var vm = new Vue ({
  the: '#app' ,
  data: {
    obj: {
        a: 1
    }
  },
  watch: {
    obj: {
      trades (val) {
       console.log('obj.a changed')
      },
      immediate: true
    }
  }
})
When we entered data in the input input box to change the value of obj.a, we found that 'obj.a changed' was not printed on the console.
Due to the limitations of modern JavaScript (and the obsolescence of Object.observe), Vue cannot detect the addition or deletion of object properties.
Since Vue performs the getter / setter conversion process on the property when it initializes the instance, the property must exist on the data object to allow Vue to convert it and make it responsive.

By default, in the handler method, it only listens to changes in its reference to the obj property. We only listen to it when we assign a value to obj. For example, we reassign obj in the mounted event hook function:

mounted() {
  this.obj = {
    a: '123'
  }
}

This will execute the handler and print out 'obj.a changed'.

But what if we need to monitor the attribute value in obj? At this time, the deep attribute comes in handy. We only need to add deep: true to deeply monitor the value of the attribute in obj.

  watch: {
    obj: {
      trades (val) {
       console.log('obj.a changed')
      },
      immediate: true,
      deep: true
    }
  }

The deep attribute means deep traversal, which will traverse down the object layer by layer, and add listeners to each layer.

The embodiment in the source code is defined in src / core / observer / traverse.js:

/* @flow */

import { _Set as Set, isObject } from '../util/index'
import type { SimpleSet } from '../util/index'
import VNode from '../vdom/vnode'

const seenObjects = new Set()

/**
 * Recursively traverse an object to evoke all converted
 * getters, so that every nested property inside the object
 * is collected as a "deep" dependency.
 */
export function traverse (val: any) {
  _traverse(val, seenObjects)
  seenObjects.clear()
}

function _traverse (val: any, seen: SimpleSet) {
  let i, keys
  const isA = Array.isArray(val)
  if ((!isA && !isObject(val)) || Object.isFrozen(val) || val instanceof VNode) {
    return
  }
  if (val.__ob__) {
    const depId = val.__ob__.dep.id
    if (seen.has(depId)) {
      return
    }
    seen.add(depId)
  }
  if (isA) {
    i = val.length
    while (i--) _traverse(val[i], seen)
  } else {
    keys = Object.keys(val)
    i = keys.length
    while (i--) _traverse(val[keys[i]], seen)
  }
}

If this.deep == true, that is, there is deep, then trigger the dependence of each deep object and track its changes.

The traverse method recurses each object or array, triggering their getter, so that each member of the object or array is collected by dependency, forming a "deep" dependency relationship.

This function implementation also has a small optimization. During the traversal process, the sub-responsive objects will be recorded to seenObjects through their dep.id to avoid repeated access in the future.

 

However, using the deep attribute will add a listener to each layer, and the performance overhead may be very large. This way we can optimize in the form of strings:

  watch: {
    'obj.a': {
      trades (val) {
       console.log('obj.a changed')
      },
      immediate: true
      // deep: true
    }
  }

Until the 'obj.a' attribute is encountered, a listener function will be set for this attribute to improve performance.

 

The essence of computed-computed watch 

We know that _init method will be called when new Vue (), this method will initialize the life cycle, initialize events, initialize render, initialize data, computed, methods, wacther and so on.

Today we mainly look at the implementation of the following initialization watch (initWatch), defined in src / core / instance / state.js:

// An object used to pass in a Watcher instance, namely calculated watcher 
const computedWatcherOptions = {computed: true }

function initComputed (vm: Component, computed: Object) {
   // $ flow-disable-line 
  // Declare a watchers and mount them on the VM instance at the same time 
  const watchers = vm._computedWatchers = Object.create ( null )
   // In SSR In the mode, the computed property can only trigger the getter method 
  const isSSR = isServerRendering ()

  // Traverse the passed computed method 
  for (const key in computed) {
     // Remove each method in the computed object and assign it to userDef 
    const userDef = computed [key]
    const getter = typeof userDef === 'function' ? userDef : userDef.get
    if (process.env.NODE_ENV !== 'production' && getter == null) {
      warn(
        `Getter is missing for computed property "${key}".`,
        vm
      )
    }

    // If it is not SSR server rendering, create a watcher instance 
    if (! IsSSR) {
       // create internal watcher for the computed property. 
      Watchers [key] = new Watcher (
        vm,
        getter || noop,
        noop,
        computedWatcherOptions
      )
    }

    // component-defined computed properties are already defined on the 
    // component prototype. We only need to define computed properties defined 
    // at instantiation here. 
    if (! (key in vm)) {
       // if the
 key in computed is not set Go to vm, mount it through defineComputed function       defineComputed (vm, key, userDef)
    } else  if (process.env.NODE_ENV! == 'production' ) {
       // If data and props have the same name as the key in the calculated, warning 
      if (key in vm. $ data) {
        warn(`The computed property "${key}" is already defined in data.`, vm)
      } else if (vm.$options.props && key in vm.$options.props) {
        warn(`The computed property "${key}" is already defined as a prop.`, vm)
      }
    }
  }
}

Through the source code, we can find that it first declared an empty object named watchers, and also mounted the empty object on the vm.

Then iterate over the calculated attributes, and assign the method of each attribute to userDef. If userDef is a function, assign it to the getter. Then determine whether it is server-side rendering. If not, create a Watcher instance.

However, it should be noted that in the newly created instance, we pass in the fourth parameter, which is calculatedWatcherOptions. const computedWatcherOptions = {computed: true}, this object is the key to realize the calculated watcher. At this time, the logic in Watcher has changed:

Guess you like

Origin www.cnblogs.com/magicg/p/12667252.html