Background narrative

background

Reading Vue3’s trigger update trigger function has such a hack code for adding a key index to the array.

switch (type) {
            case "add" /* ADD */:
                if(! isArray(target)) { add(depsMap.get(ITERATE_KEY));if(isMap(target)) { add(depsMap.get(MAP_KEY_ITERATE_KEY)); }}else if (isIntegerKey(key)) {
                    // new index added to array -> length changes
                    add(depsMap.get('length'));
                }
                break;
            case "delete" /* DELETE */:
                if(! isArray(target)) { add(depsMap.get(ITERATE_KEY));if(isMap(target)) { add(depsMap.get(MAP_KEY_ITERATE_KEY)); }}break;
            case "set" /* SET */:
                if (isMap(target)) {
                    add(depsMap.get(ITERATE_KEY));
                }
                break;
        }
Copy the code

Let’s talk a little bit about this code in V3, which is essentially doing some hacks to trigger updates. Take a look here:

                case "add" /* ADD */:
                if(! isArray(target)) { add(depsMap.get(ITERATE_KEY));if(isMap(target)) { add(depsMap.get(MAP_KEY_ITERATE_KEY)); }}// Focus on this code
                else if (isIntegerKey(key)) {
                    // new index added to array -> length changes
                    add(depsMap.get('length'));
                }
                break;
Copy the code

The way you can think about this code is when we define a responsive array in the page

import { reactive } from 'vue' 
const a = reactive({
    arr: [1.2.3]})// Suppose the template already uses a.rr to collect dependencies
// When I change its value, add an index to it
a.arr[5] = 'wang.haoyu'
Copy the code

** We know that Vue already supports reactive support for changing array indices in V3. ** will then enter the logic above.

When the satisfy type is ADD and an index of the array is added. Add (depsmap.get (‘length’)); add(depsmap.get (‘length’)); Add the update effect function. There’s nothing wrong with that.

The problem

But have you ever wondered when we use this in a template?

<template>
  <div>
      {{ obj.arr }}
  </div>
</template>
<script>
import { reactive } from 'vue'
export default {
  setup() {
    const obj = reactive({
      arr: [1.2.3]})setTimeout(() = > {
      obj.arr[10] = 'wang.haoyu'
    }, 2000);
    return {
      obj
    }
  }
}
</script>
Copy the code

We call obj.arr directly in the template, which is an array. When we add an index to the array ARR, we first collect the dependencies from vue3. It will collect dependencies on the entire array.

When we add an index to the ARR, the above logic will trigger here

                else if (isIntegerKey(key)) {
                    // new index added to array -> length changes
                    add(depsMap.get('length'));
                }
Copy the code

He will look for the length attribute in depsMap (which is a map of the array that depends on the collection object) and add the effect of the length collection to trigger the update.

But I added an index to the array, and I used the entire array object obj. Arr in the template. Why is the length property collected by the dependency?

This actually comes from Symbol. ToPrimitive

Symbol. ToPrimitive method

Methods to introduce

In javascript engines, when performing a specific operation, it is common to attempt to convert an object to the corresponding raw value. For example, comparing a string to an object, if the double equals == operator is used, the object will be converted to a raw value before the comparison operation is performed. Which raw value to use is previously determined by internal operations. In ES6, the value of that exposure trigger can be changed by means of the Symbol. ToPrimitive method.

The Symbol. ToPrimitive method is defined on the prototype of each standard type and specifies the operation to be performed when the object is converted to its original value. The Symbol. ToPromitive method is always called to hint every time a primitive value conversion is performed, passing a value as an argument. This value is called a type hint in the specification. Symbol. ToPrimitve returns either a number, a string, or an untyped value.

For most standard objects, numeric patterns have the following properties, sorted in order of priority:

  1. callvalueOfMethod and return if the result is the original value.
  2. Otherwise, calltoString()Defense, returns if it is the original value.
  3. If there is no more value to choose, an error is thrown.

Also, for most standard objects, the string pattern has the following precedence:

  1. calltoString()Method and return if the result is the original value.
  2. Otherwise, callvalueOfMethod, if the result is the original value.
  3. If there is no more value to choose, an error is thrown.

These default compulsions can be overridden if you customize the Symbol. ToPrimitive method.

Associated problems

At this point, you should get the idea that when we call obj. Arr to access the entire array in the template, vue first calls the Symbol. ToPrimitive method of the array to convert it to a string, which is called toString() method of the array, and then collects dependencies on the result.

What happens when the array’s toString() method is called. We’re looking at

const arr = [1.2];

const proxy = new Proxy(arr, {
 get: (target, key, receiver) = > {
   console.log(key);
   return Reflect.get(target, key, receiver); }}); proxy.toString()// Print the result
toString
join
length
0
1

Copy the code

You can see that inside the array’s toString method, we actually call the join method first, then we access the length and every element in the array, and we get 1,2.

It’s actually very clear at this point.

  1. Template used in Vueobj.arrAccess to an array
  2. callobj.arr.prototype[Symbol.toPrimitive]Try toobj.arrConvert to string
  3. Internal callstoStringmethods
  4. arr.toString()The array_length () method accesses each element in the array and its lengthjoin.

Back to the start

At this point we can see that while accessing the entire array in the template for dependency collection, vue3 essentially converts the entire array to a string type by calling the internal Symbol. ToPrimitive method. Thus, the dependency collection on each item and the corresponding length of the array is carried out in the phone, when the array is added an index. V3 manually calls length in the array to trigger the corresponding update.

Adding an index will definitely change the length of the array. When the template accesses the entire array to turn it into a String, it will rely on the length of the and collection. So when an update is triggered, adding an index triggers an update to the array.

legacy

{{obj}} (const obj = {name:wang,haoyu});

If we go directly to app.innerhtml = obj, the page div displays [Object Object], and the vUE template displays name:wang.haoyu. I don’t know if you noticed this little detail.

Does the interior have something to do with Vue overwriting Symbol. ToPrimitive? You can take a guess