Vue source code is limited to the level, see a mess, just read some articles analysis some harvest, then remember.

Now that VUE3 is out, is it out of date to study VUe2. X? No and no and no.

Despite the fact that most vUE users are still using it, much of vue3’s thinking can be found in VUE2, so learning is still necessary.

This article will talk about the responsive principle, from Baicaoyuan to Sanwei Bookstore, oh no, from Vue2 to Vue3. Objet.defineproperty ⇲…

talk is cheap…..

Objet.defineProperty

If you’ve ever used vUE, you know that if you change firstName in vue, then you change everything that uses firstName, for example, computed properties (computed)

computed: {
    fullName: () = > this.firstName + this.lastName
}
Copy the code

Data Monitor

watch: {
    firstName: function (val, oldVal) {
      // dosomething}}Copy the code

Template render

<span>Surname: {{firstName}}<span>
Copy the code

These three places are equivalent to a dependency on firstName (Watcher), more on that later.

In addition, the data definition in Vue looks like this

new Vue({
	el: '#app'.data: { firstName: 'the king'}})Copy the code

So how can I listen for changes in fisrtName and update the corresponding data?

Before Object.defineProperty, there is mutationObserver-mdn ⇲, which does the following

The MutationObserver interface provides the ability to monitor changes made to the DOM tree. It is designed as a replacement for the older Mutation Events functionality

This is a dead end and I won’t go into more details, but vue2. X uses objet.defineProperty to listen for changes to data and trigger updates to various parts.

For a quick introduction, you can read more here at ⇲. Take the example above

const data = { firstName: 'li' };
const vm = {};
Object.defineProperty(vm, "firstName", {
  enumerable: true.configurable: true.get() { console.log('I've been taken! '); return data.firstName; },
  set(newValue) { 
    data.firstName = newValue;
    console.log('I've been changed, here's the new value:', newValue) 
  },
});
vm.firstName = 'li';
console.log(vm.firstName)
// I have been changed, this is the new value: Li
// I am captured!
/ / li
Copy the code

The firstName attribute (enumerable: true) and the configurable property (64x: true) are different. Changing firstName triggers the set method, and getting firstName triggers the GET method.

The responsive core implementation method said, and then to a vUE responsive implementation principle.

Vue responsive principle

First up, from the nuggets brochure (recommended!) Analyze the internal operating mechanism of vue.js ⇲

The diagram above is a classic overview of the Vue life cycle. Including initialization, compilation, mount, update… And Vue’s response also basically runs through the whole cycle.

Also note:

  • Pictured above,watcherNot watch in vUE in code, butRely on.
  • There are three types of watcher:computed watcher,watch watcherrender watcherSay something here and talk about it later.

A general comparison of the responsive principle is provided at 👍 (after reading the handwriting, I suggest coming back to this diagram). Whenever looking at good I use to take doctrine 🤪Figure come fromIllustrated Vue responsive principle ⇲

Write a vue2. X response by hand

This section mainly refer to (excerpted)😅 write a simple VUE response with you to understand the principle of response ⇲

(v-model, v-show, v-text {{}})

The original implementation

First, recall the use of Vue

new Vue({
  el: '#app'.data: {
      name: 'ethan'.text: 'text',}})Copy the code

Let’s start with the Vue class

Vue

First you need to define a Vue class that does the following:

  1. Change data to reactive.
  2. Ability to compile templates and identify bound data.

For the first point, using an Observer to make data responsive, In addition, to use {{firstName}} instead of {{data.firsName}} in template, you need to map data attributes to Vue for direct call, using _proxyData method. Write a Compiler implementation specifically for the second point.

class Vue {
  constructor(options) {
    this.$options = options || {}
    // Pass ID or Dom.
    this.$el = typeof options.el === 'string' ?
      document.querySelector(options.el) : options.el;
    this.$data = options.data;
    // Process attributes in data
    this._proxyData(this.$data);
    // Make data responsive
    new Observer(this.$data)  
    // Template compilation
    new Compiler(this)}// Register attributes in data to vUE
  _proxyData(data) {
    Object.keys(data).forEach(key= > {
      Object.defineProperty(this, key, {
        enumerable: true.configurable: true.get(){ return data[key]  },
        set (newValue) {
          if(newValue === data[key]) return; data[key] = newValue; }})})}}Copy the code

Observer

Implement an Observer, which does the following:

  1. Change each property of the object data and its children to reactive,

At this point, iterate over each property to make it reactive, using the walk method. The responsivity of a single attribute is handled with Object.defineProperty and encapsulated as the defineReactive method.

Note:

  • Use Walk recursion to make each property of data responsive, containing the newly changed value
  • Add dependencies every time you fetch.
  • Notify each time data is updatedDepNotification (notifyUpdate)

So here’s the code

/* Turn data into a responsive object */
class Observer {
  constructor(data) {
    this.walk(data);
  }
  walk(data) {
    if(! data ||typeofdata ! ='object') return;
    Object.keys(data).forEach(key= > {
      this.defineReactive(data, key, data[key]); })}defineReactive(obj, key, value) {
    // Recursively change the object's child properties to responsive
    this.walk(value);
    const self= this;
    let dep = new Dep()
    Object.defineProperty(obj, key, {
      enumerable: true.configurable: true.get(){
      	  // Target = Watcher
        Dep.target && dep.addSub(Dep.target)
        // Obj[key] = Obj[key]; (Endless loop)
        return value
      },
      set (newValue) {
        // If the old value is the same as the new value, it is not updated
        if(newValue === obj[key]) return;
        value = newValue;
        // If the new value is
        self.walk(newValue)
        // Update the view
        dep.notify()
      }
    })
  }
}
Copy the code

Compiler

The following is the implementation of the template Compiler

  1. parsingDomData bound in
  2. parsingDomInstruction (v-model.v-textEtc.)

All you need to do is treat the DOM as a string using regular parsing.

Pay attention to

  • Text nodes, element nodes have different parsing methods, targeted processing
  • For instructions, get the Dom custom attributes by getting JS
  • Listen for changes to the input node. usingchangeThe event

In addition to text nodes and element nodes, there are many nodes that need to be dealt with specifically. For more information, please refer to vue-Design – renderer mount ⇲

Here is the Compiler implementation

// Parse the template content into a DOM tree
class Compiler {
  constructor(vm) {
    this.vm = vm;
    this.el = vm.$el;
    this.compile(this.el)
  }
  compile(el) {
    let childrenNodes = [...el.childNodes]
    // Walk through the specific parsing for each node
    childrenNodes.forEach(node= > {
      if(this.isTextNode(node)){
        this.compileText(node)
      }else if(this.isElementNode(node)) {
        this.compileElement(node)
      }
      if(node.childNodes && node.childNodes.length) this.compile(node)
    })
  }
  // Text node compiles
  compileText(node){
    let reg  = / \ {\ {(. +?) \} \} /
    let val = node.textContent
    if(reg.test(val)){
      let key = RegExp.$1.trim()
      const value = this.vm[key];
      node.textContent = val.replace(reg, value)
      new Watcher(this.vm, key, (newVal) = > {
        node.textContent = newVal
      })
    }
  }
  // Element node compiles
  compileElement(node) {
    // https://developer.mozilla.org/zh-CN/docs/Web/API/Element/attributes! [...node.attributes].forEach(attr= > {
      let attrName = attr.name
      if(this.isDirective(attrName)){
        attrName = attrName.substring(2);
        let key = attr.value;
        this.update(node, key, attrName)
      }
    })
  }
  // Run different update methods dynamically
  update(node, key, attrName) {
    let updateFn = this[attrName+'Update']
    updateFn && updateFn.call(this, node, key, this.vm[key])
  }
  // The text node will be re-rendered if the value is updated. The essence is to modify textContent with JS
  textUpdate(node, key, content ){
    node.textContent = content
    new Watcher(this.vm, key, newVal= > { node.textContent = newVal })
  }
  / / v - model updating
  modelUpdate(node, key, value) {
    const typeAttr = node.getAttribute('type')
    // The input field is filtered
    if(typeAttr == "text") {
      node.value = value;
      new Watcher(this.vm, key, newVal= > { node.value = newVal})
      node.addEventListener('keyup'.() = > {
        this.vm.$data[key] = node.value
      })
    }
  }
  isDirective(attr) {
    return attr.startsWith('v-')}isTextNode(node){
    return node.nodeType === 3
  }
  isElementNode(node) {
    return node.nodeType === 1}}Copy the code

As you can see, Watcher has been added to the top three (we’ll talk about the implementation of Watcher later)

  • When the text node is updated (compileText), such as<span>{{ name }}</span>
  • Custom instruction V-text update when (textUpdate), such as<span v-text="name"></span>
  • Custom instruction V-modal update (modelUpdate), such as<input v-modal="name"></input>

Because in the above three cases there is a dependency on name (the render Watcher of the above mentioned three Watcher).

Dep

Let’s talk about the implementation of Dep, the main role

  • Collect and update dependencies

The main exposure methods are addSub and notify. See below 👇

// Collect dependencies and notify dependency updates
class Dep {
 constructor() {
    this.subs = []
  }
  addSub(sub) {
    if (sub && sub.update) {
      this.subs.push(sub)
    }
  }
  notify() {
    this.subs.forEach(sub= > {
      sub.update()
    })
  }
}
Copy the code

Watcher

Finally, Watcher is the most common dependency in the article! What does it do?

  • Update Dep target (value Watcher itself)
  • Use the callback functioncbTriggered update

The following code

class Watcher {
  constructor(vm, key, cb){
    this.vm = vm
    this.key = key
    this.cb = cb
    Dep.target = this
    this.oldVal = vm[key]
    Dep.target = null
  }
  update(){
    let newValue = this.vm.$data[this.key]
    if(newValue === this.oldVal) return;
    // Call the callback update
    this.cb(newValue)
  }
}
Copy the code

Remember? Earlier the Observer defined the response with this code 👇

class Observer{
	/ /...
    get(){
      Dep.target && dep.addSub(Dep.target)
      return value
    },
}
Copy the code

Add target to Dep every time Watcher is instantiated, and collect dependencies every time data[prop] is executed!

All right, finally, it’s all done. Test it out?

Test the

Sparrows may be small, but their execution order needs to be adjusted:

<body>
  <div id="app">
    <input type="text" v-model="name" /><br/>
    <b>Name:</b><span>{{ name }}</span> <br/>
    <b>Gender:</b><span>{{ sex }}</span>
    <hr />
    <div v-text="text"></div>
  </div>
  <script>
    // Rely on collection and update
    class Dep { / /... }
    // Dependencies, expose the update method notify
    class Watcher { / /... }
    // Resolve dependencies in the template Dom
    class Compiler { / /... }
    // Make Data responsive
    class Observer { / /.. }
    / / the Vue instance
    class Vue { / /.. }

    new Vue({
      el: '#app'.data: {
          name: 'ethan'.sex: 'male'.text: 'text',}})</script>
</body>
Copy the code

Of course,v-textAlso bidirectional binding, mainlycompileTextThe role of. Let’s try name instead

<body>
  <div id="app">
    <input type="text" v-model="name" /><br/>
    <b>Name:</b><span>{{ name }}</span> <br/>
    <b>Gender:</b><span>{{ sex }}</span>
    <hr />
    <div v-text="name"></div>
  </div>
  <script>
  	/ /...
  </script>
</body>
Copy the code

If can ah ~ ~ ~, wow 🤸 came ️ 🤸 came ️ 🤸 came ️, have to say I’m a bit 🤏 copying code, families, some a great 🤡? Don’t give? Nothing 😁 please continue to read.

Fixed original bugs and extended V-show

Bugs found

The above implementation, I have an idea, gender can be changed through selection can 🤷♂️? Sex = data.sex = data.sex = data.sex = data.sex = data.sex = data.sex = data.sex = data.sex = data.sex = data.sex

At that time thought strange simple, the result I toss about for a good while 🤣, forget over.

html

<div id="app">
  <input type="text" v-model="name" /><br/>
  <input id="male" name="sex" type="radio" v-model="sex" value="Male">
    <label for="male"></label>
  </input>
  <input id="female" name="sex" type="radio" v-model="sex" value="Female">
    <label for="female"></label>
  </input><br/>
  <b>Name:</b><span>{{ name }}</span> <br/>
  <b>Gender:</b><span>{{ sex }}</span>
  <hr />
  <div v-text="text"></div>
</div>
Copy the code

js

/ /...
class Compiler {
  constructor(vm) {}
  compile(el) {}
  compileText(node){}
  compileElement(node) {}
  update(node, key, attrName) {
    let updateFn = this[attrName+'Update']
    updateFn && updateFn.call(this, node, key, this.vm[key])
  }
  // The text node will be re-rendered if the value is updated. The essence is to modify textContent with JS
  textUpdate(node, key, content ){}
  modelUpdate(node, key, value) {
    const typeAttr = node.getAttribute('type')
    if(typeAttr == "text") {}// Add here
    else if(typeAttr === "radio") {
    	// You can use Watcher or Wather to change the class name, as shown in the next section
      const nameAttr = node.getAttribute('name')
      // There is no need for watch,
      node.addEventListener('change'.(ev) = > {
        this.vm.$data[key] = ev.target.value
      })
    }
  }
  isDirective(attr) {}
  isTextNode(node){}
  isElementNode(node){}}Copy the code

So you can see, the first time I switch female, it’s updated, and then it’s not updated, right? Why, everybody comrades can think about 💭 first.

This. OldVal => this. OldVal => this. OldVal => this. The last choose when to choose “male”, “female” Watcher of enclosing oldVal didn’t update, so the same value will not trigger the Watcher, the specific code within the Watcher of the update method.

When selecting “female” again, Watcher is triggered, but it doesn’t seem to change.

It’ll be easier if we find out why. Pass in the old value in notify, and receive and update this.oldVal in Watcher’s update

class Dep {
  / /...
  notify(oldValue){
  	// Accept the old value ↑ and pass ↓
    this.subs.forEach(sub= > {
      sub.update(oldValue)
    })
  }
}
class Watcher {
  / /...
  update(oldValue){
  	// Accept ↑ and update the old value ↓
    this.oldVal = oldValue;
    let newValue = this.vm.$data[this.key]
    if(newValue === this.oldVal) return;
    this.cb(newValue)
  }
}
class Observer {
  / /...
  defineReactive(obj, key, value) {
    / /...
    Object.defineProperty(obj, key, {
    	/ /...
      set (newValue) {
      	  / / to take the old value
        const oldval = obj[key]
        if(newValue === obj[key]) return;
        value = newValue;
        self.walk(newValue)
        // Update the view and pass the old value
        dep.notify(oldval)
      }
    })
  }
}
Copy the code

That will do.

Extend the v – show

With the previous implementation, v-show is easy to implement, listening for input and updating values.

css

<style>
  .show-txt {
    opacity: 1;/* For viewing purposes, do not display */
    transition: all .5s linear;
  }
  .show-txt.hidden {
    opacity: 0;
    color: #eee;
  }
</style>
Copy the code

html

<div id="app">
  <input type="text" v-model="name" /><br/>
  <input id="male" name="sex" type="radio" v-model="sex" value="Male">
    <label for="male"></label>
  </input>
  <input id="female" name="sex" type="radio" v-model="sex" value="Female">
    <label for="female"></label>
  </input><br/>
  <input name="show" type="checkbox" v-model="show" checked>Whether to show</input>
  <div class="show-txt" v-show="show">Show text examples</div><br/>
  <b>Name:</b><span>{{ name }}</span> <br/>
  <b>Gender:</b><span>{{ sex }}</span>
  <hr />
  <div v-text="text"></div>
</div>
Copy the code

js

/ /...
class Compiler {
	/ /..
  modelUpdate(node, key, value) {
    const typeAttr = node.getAttribute('type')
    if(typeAttr == "text") {}
    else if(typeAttr === "radio") {}
    else if(typeAttr === 'checkbox') { / / monitored value
      node.addEventListener('change'.(ev) = > {
        this.vm.$data[key] = ev.target.checked
      })
    }
  }
  // The v-show directive binds the value update callback
  showUpdate(node, key, value){
    const change = (val) = > { 
      constoperate = !! val ?'remove' : 'add';
      node.classList[operate]('hidden') 
    }
    change(value);
    new Watcher(this.vm, key, (newVal) = > { change(newVal) })
  }
  // ...
}
/ /...

/ / test
new Vue({
  el: '#app'.data: {
      name: 'ethan'.sex: 'male'.text: 'text'.show: true,}})Copy the code

Well, that’s it for now, Vue2’s response-based handwritten implementation comes to an end. If you prefer the source code to find the answer to recommend Vue source interpretation (3) – responsive principle ⇲

Of course, the above is only the update of the object attributes, Vue source array update detection using rewriting the prototype method. Here is a brief introduction.

Array detection

Here are eight array methods that vUE can detect for change,

push()
pop()
shift()
unshift()
splice()
sort()
reverse()
Copy the code

The idea is to intercept these methods in the array prototype, one or two of which can be seen in the source code.

var arrayProto = Array.prototype;
var arrayMethods = Object.create(arrayProto);
var methodsToPatch = [
  'push'.'pop'.'shift'.'unshift'.'splice'.'sort'.'reverse'
];
/** * Intercept mutating methods and emit events */
methodsToPatch.forEach(function (method) {
  // Cache the original method
  var original = arrayProto[method];
  def(arrayMethods, method, function mutator () {
    var args = [], len = arguments.length;
    while ( len-- ) args[ len ] = arguments[ len ];

    var result = original.apply(this, args);
    // Get the global Observer, equivalent to the above Observer
    var ob = this.__ob__;
    var inserted;
    switch (method) {
      case 'push':
      case 'unshift':
        inserted = args;
        break
      case 'splice':
        inserted = args.slice(2);
        break
    }
    // There is an insert, which listens for both attributes
    if (inserted) { ob.observeArray(inserted); }
    // Notification update
    ob.dep.notify();
    return result
  });
});
Copy the code

Extension: a responsive implementation of Vue3

Vue3 is a big upgrade over Vue2, and this article will only explore the principle of responsiveness, which is roughly summarized here.

Vue3 response principle

First, a comparison.

Vue2.x

  • Based on theObject.defineProperty, does not have the ability to listen on arrays, and needs to redefine the array prototype to achieve responsiveness.
  • Object.definePropertyObject attributes could not be added or removed.
  • Since Vue executes on properties when instantiatinggetter/setterAll attributes must exist on the data object for Vue to convert it to reactive.
  • Deep monitoring requires one-time recursion, which has a large impact on performance.

Vue3

  • Based on theProxyReflect, can listen on native arrays, can listen on object attributes added and removed.
  • You can significantly improve performance by not having to iterate through data’s properties at once.
  • Because Proxy is a new attribute in ES6, some browsers do not support it and can only be compatible with IE11.

For the response formula of Vue3,Online ⇲Find a picture and sum it up.

About the Proxy

Compared to Object.defineProperty, Proxy⇲ supports more comprehensive Object operations: GET, set, HAS, deleteProperty, ownKeys, defineProperty…… , etc.

Nuggets a lot of good articles about 🏸- proxy, no longer repeat, to give an example, you can take curiosity to proxy literacy.

let data = [1.2.3]
let p = new Proxy(data, {
    get(target, key, receiver) {
        console.log('get value:', key)
        return Reflect.get(target, key, receiver)
    },
    set(target, key, value, receiver) {
        console.log('set value:', key, value)
        Reflect.set(target, key, value, receiver)
        return true // In strict mode, if the set method returns false, a TypeError exception is raised.
    }
})
p.length = 4 // set value: length 4
console.log(data) // [1, 2, 3, empty]
p.shift()
// get value: shift
// get value: length
// get value: 0
// get value: 1
// set value: 0 2
// get value: 2
// set value: 1 3
// set value: length 3
p.push(4)
// get value: push
// get value: length
// set value: 3 4
// set value: length 4
p
// Proxy {0: 2, 1: 3, 3: 4}
p[3] = 0
// set value: 3 0
Copy the code

Handwritten response

I’m gonna do it! What? Start work? No, no, dear readers, I mean I’m gonna start coding.

The main functions are as follows:

Only the Composition API responsive principle implementation is involved, and the compilation part (Dom part) is not involved.

/* Dependency collection: create data & CB mapping */
function track(target, key){}

/* Trigger update: execute cb */ according to the mapping
function trigger(target, key){}

/* Create responsive data */
function reactive(obj){}

/* Declare the response function cb(dependent on responsive data) */
function effect(cb){}
Copy the code

Limited to space and code is not much, here directly to the code, please refer to the comments to read.

Code from (suggested intensive reading) : Lin Sanxin drew 8 pictures, the most easy-to-understand Vue3 response core principle analysis ⇲

const targetMap = new WeakMap(a)// Collect dependencies
function track(target, key) {
    // If activeEffect is null, the following is not executed
    // This is to avoid track being triggered by things like console.log(person.name)
    if(! activeEffect)return
    let depsMap = targetMap.get(target)
    if(! depsMap) { targetMap.set(target, depsMap =new Map()}let dep = depsMap.get(key)
    if(! dep) { depsMap.set(key, dep =new Set())
    }
    dep.add(activeEffect) // Add the activeEffect
}
// Trigger the update
function trigger(target, key) {
    let depsMap = targetMap.get(target)
    if (depsMap) {
        const dep = depsMap.get(key)
        if (dep) {
            dep.forEach(effect= > effect())
        }
    }
}
// Define reactive, hijack data
function reactive(target) {
    const handler = {
   			// reciver is a Proxy object
        get(target, key, receiver) {
            track(receiver, key) // Collect dependencies while accessing
            return Reflect.get(target, key, receiver)
        },
        set(target, key, value, receiver) {
            Reflect.set(target, key, value, receiver)
            trigger(receiver, key) // Automatically notifies updates when setting the value}}return new Proxy(target, handler)
}
let activeEffect = null
// A stack of dependencies can be used to store dependencies. The following implementation method is somewhat familiar. Yes, vue2 response implementation does this
function effect(fn) {
    activeEffect = fn
    activeEffect()
    activeEffect = null
}
Copy the code

If it is difficult to understand the above code, please refer to the original text 👆 and the article Vue3 Reactive principle + Written Reactive ⇲ (remember to come back after reading, I have some low-quality extensions to everyone 🤡), which is quite detailed.

The main use of Proxy hijacking data (instead of Object.defineProperty), other aspects such as dependency collection, triggered update implementation is basically the same as Vue2.

Test the

const data = { name: 'the second egg' }
const rData = reactive(data)
// There is a dependency on data.name
effect(() = > { 
    console.log('I rely on two eggs.', rData.name); 
})
rData.name = 'King two Eggs'
// I rely on two eggs
// I rely on two eggs king two eggs
Copy the code

In addition to reactive, there is also a REF that is designed to respond to a single variable. Reactive is an extension of reactive, and only value is reactive.

  • ref
function ref(initValue) {
 	return reactive({
      value: initValue
   })
}
Copy the code

Based on the above code, other responsive apis ⇲ computed, watchEffect, and Watch can also be implemented.

  • watchEffect
function watchEffect(fn) {
	effect(() = > fn())
}
const eData = ref(5);
    watchEffect(() = > { console.log('Effect test:', eData.value) })
    eData.value = Awesome!
}
// Effect test: 5
// Effect test: 666
Copy the code
  • Computed
function computed(fn) {
    const result = ref()
    effect(() = > result.value = fn())
    return result
}
const ref1 = ref(5);
const cRef1 = computed(() = > {
    console.log('Computed Test:', ref1);
    return ref1.value
});
ref1.value = Awesome!;
console.log('last: ', ref1, cRef1)
// computed test: Proxy {value: 5}
// Computed test: Proxy {value: 666}
Copy the code
  • watch

Support for listening on a single value and multiple values, and both old and new values are passed back. Also note that when listening on multiple sources, a callback to FN is triggered to update exactly (Map implementation). Of course, the previous code needs to be modified slightly, as follows:

function track(target, key) {
    if(! activeEffect.fn)return
    / /...
    dep.add(activeEffect.fn) // I put the original callback on fn
    // The deP is used as the update key
    activeEffect.key = dep;
}

function trigger(target, key, { oldValue }) {
    / /...
    // The old value is passed along with the deP (as key)
    dep.forEach(effect= > effect(oldValue, dep))
}

function reactive(target) {
    const handler = {
        / /...
        set(target, key, value, receiver) {
        	// Get the old value to return
            const oldValue = Reflect.get(target, key, receiver)
            if(value === oldValue) return;
            const result = Reflect.set(target, key, value, receiver)
            trigger(receiver, key, { newValue: value, oldValue}) // Automatically notifies updates when setting the value
            return result
        }
    }
    return new Proxy(target, handler)
}
let activeEffect = {fn: null.key: null}

function effect(fn) {
    activeEffect.fn = fn
    activeEffect.fn()
    activeEffect = {fn: null.key: null}}function watch(source, fn) {
    let oldValues = new Map(), newValues = new Map(), isArray = false; 
    function handleEffects(rValue){
        effect((oldValue, upKey = null) = > {
            // Here to trigger get collect dependency
            const newValue = typeof rValue === 'function' ? rValue().value : rValue.value;
            if(activeEffect.fn) {
                Activeeffect.key () activeEffect.key () activeEffect.key (
                oldValues.set(activeEffect.key, newValue)
                newValues.set(activeEffect.key, newValue)
            }else {// Update both old and new values
                oldValues.set(upKey, oldValue)
                newValues.set(upKey, newValue)
                // Source and multi-source are treated differently
                isArray 
                    ? fn([[...newValues.values()], [...oldValues.values()]]) 
                    : fn([...newValues.values()][0], [...oldValues.values()][0]); }})}// Listen on multiple sources
    if(Array.isArray(source)) {
        isArray = true;
        source.forEach(rValue= > {
            handleEffects(rValue)
        })
    }
    // Listen on a single source
    else 
        handleEffects(source)
}
Copy the code

Test the

const wRef = ref(5);
watch(wRef, (value, preValue) = > {
    console.log('Watch listens for single-source tests:', value, preValue)
})
wRef.value = 66
// Watch listens to single source test: 666 5
Copy the code
const wRef = ref(1);
const wRef1 = ref(2);
const wRef2 = ref(3);

watch([wRef, () = > wRef1, wRef2], (values, preValues) = > { 
    console.log('Watch listens to multi-source tests:', values, preValues)
})
wRef.value = 11;
// Watch monitor multisource test: [11, 2, 3]
wRef1.value = 22;
// Watch monitor multisource test: [11, 22, 3]
wRef2.value = 33
// Watch monitor multi-source test: [111, 22, 33] [11, 2, 3]
wRef.value = 111
// Watch monitor multi-source test: [11, 22, 33] [1, 2, 3]
wRef2.value = 333
// Watch Monitor multi-source test: [111, 22, 33] [11, 2, 33]
Copy the code

The simple version of Watch is basically implemented, and only the data supporting REF and Reactive will have problems. You can think about where the problems are.

conclusion

The basic idea of vuE2 and VUe3 responses is similar. Dependencies are collected and updated as the data source changes.

After writing, I feel more and more ignorant. My life is limited, and knowledge is limitless.

And it looks like it might be easy to understand, so let’s do it.

The paper come zhongjue shallow, must know this to practice!

Limited to technology and writing the level of the article, the article error welcome 👐 we point out.

reference

You can only see farther by standing on someone else’s shoulder. Thanks to the authors of all the references in the 💖💖💖 article.

Booklet 🔗 | the nuggets – analyze the Vue. Js internal operation mechanism

🔗 | write a simple vue response type belt you know reactive principle

🔗 | VUE source related interview questions

🔗 | Vue3 why two-way binding Proxy do?

🔗 | Vue 3 reactive principle and implementation

🔗 | Vue3 of reactive principle is analysed