Hi, my name is Mokou and I have been doing vuE3 related work recently, such as source code parsing and the development of Mini-VuE3.

Review the previous chapters, which mainly cover the following.

  1. New Build toolsviteThe principles and implementation from scratch
  2. vue3Use a new pose
  3. The new API:reactiveUse and source code analysis
  4. Track collectiontrackImplementation and source code analysis
  5. Tracking triggertriggerImplementation and source code analysis
  6. Responsive coreeffectTrack, the triggerWorking principle and source code analysis

Ok, the goal of this chapter: build a Vue3 from scratch!

The prerequisite knowledge that must be known is effect and the working principle of track and trigger. For details, please see the public number -> Front-end advanced class, a public number with temperature and no advertising front-end technology.

So let’s just do a quick analysis of what these three functions do

  1. Track: Collects dependencies and stores themtargetMap
  2. Trigger: indicates the use of the trigger dependencytargetMap
  3. Effect: Side effects

This chapter source see Uuz urgently need star to maintain a livelihood.

Contents of the first two chapters:

  • Vue3. X Series (Serial 1)
  • Vue3. X Series (Serial 2)

Hands touch hands to achieve Vue3

In the first place. We have two global variables that store and locate trace dependencies, which are repositories for track and trigger.

let targetMap = new WeakMap(a);let activeEffect;
Copy the code

So the first method that needs to be designed is track. Remember how track is called in VUe3?

track(obj, 'get'.'x');
Copy the code

Track will find out if obj.x is tracked. If not, put obj.x in targetMap, use obj.x as the key of the map, and activeEffect as the value of the map.

Aside from value exception handling and the like, track does one thing: plug activeEffect into targetMap;

function track(target, key) {
  // First find if obj is tracked
  let depsMap = targetMap.get(target);
  if(! depsMap) {// If not, add one
		targetMap.set(target, (depsMap = new Map()));
  }
  // Then find if obj.x is tracked
  let dep = depsMap.get(key);
	if(! dep) {// If not, add one
    depsMap.set(key, (dep = new Set()));
  }
  // If no activeEffect is added, add one
  if (!dep.has(activeEffect)) {
		dep.add(activeEffect);
	}
}
Copy the code

Then you write a trigger. Remember how trigger is called in Vue?

trigger(obj, 'set'.'x')
Copy the code

Trigger will only go to targetMap to find the obj.x tracking task, and if it finds one, it will undo it and execute the task.

That is: regardless of the value exception, trigger does only one thing: value from targetMap and then call the function value.

function trigger(target, key) {
  // Find the trace item
  const depsMap = targetMap.get(target);
  // Do nothing until you find it
  if(! depsMap)return;
  / / to heavy
  const effects = new Set()
  depsMap.get(key).forEach(e= > effects.add(e))
  / / execution
  effects.forEach(e= > e())
}
Copy the code

Finally, effect. Remember how the worker’s API is called in VUe3?

effect((a)= > {
  console.log('run cb')})Copy the code

Effect receives a callback function, which is then sent to Track. So we can do effect this way

  1. Define an internal function_effectAnd execute.
  2. Returns a closure

Internal _effect also does two things

  1. Assign itself toactiveEffect
  2. performeffectThe callback function

Good code is coming.

function effect(fn) {
  // Define an internal _effect
  const _effect = function(. args) {
    // Assign itself to activeEffect during execution
    activeEffect = _effect;
    // Perform the callback
    returnfn(... args); }; _effect();// Return the closure
  return _effect;
}
Copy the code

Now that all the prerequisites are complete, it’s time to start working on a Reactive, object responsive API. Remember how to use Reactive in VUE3?

<template>
  <button @click="appendName">{{author.name}}</button>
</template>Setup () {const author = reactive({name: 'mokou',}) const appendName = () => author.name += 'excellent '; return { author, appendName }; }Copy the code

With the excellent code above, it is easy to implement vuE3’s responsive operation. By reviewing the previous chapters, we know that Reactive is implemented by Proxy for data.

This allows us to invoke track and trigger via Proxy, hijacking getters and setters for reactive design

export function reactive(target) {
  // Proxy data
  return new Proxy(target, {
    get(target, prop) {
      // Perform tracing
      track(target, prop);
      return Reflect.get(target, prop);
    },
    set(target, prop, newVal) {
      Reflect.set(target, prop, newVal);
      / / triggering effect
      trigger(target, prop);
      return true; }})}Copy the code

All right. All set, let’s mount our fake Vue3

export function mount(instance, el) {
  effect(function() {
    instance.$data && update(el, instance);
  })
  instance.$data = instance.setup();
  update(el, instance);
}

function update(el, instance) {
  el.innerHTML = instance.render()
}
Copy the code

Write a demo with mini-vue3

Test it out. Refer to vue3. Define setup and render.

const App = {
  $data: null,
  setup () {
    let count = reactive({ num: 0 })

    setInterval((a)= > {
      count.num += 1;
    }, 1000);

    return {
      count
    };
  },
  render() {
    return `<button>The ${this.$data.count.num}</button>`
  }
}

mount(App, document.body)
Copy the code

Let’s do it. It’s good code. After each setInterval, the page is rewritten to refresh the count.num data.

Source code see uuz, PS: July 23 the source code has support for JSX.

With 50+ lines of code, vuE3’s responsiveness is easily implemented. But is that the end of it?

There are also the following questions

  1. ProxyYou must pass in objects
  2. renderFunctions andhFunction and correct (Vue3 h function is now 2 not beforecreateElementA)
  3. Recursion of the virtual DOM
  4. Don’t say anymore- -!I won’t listen.

ref

One drawback to using Reactive is that proxies can only Proxy objects, not underlying types.

Uncaught TypeError: Cannot create Proxy with a non-object as target or handler if you call this code new Proxy(0, {}), Uncaught TypeError: Cannot create Proxy with a non-object as target or handler

So, for the base type of proxy. We need a new approach, and in VUe3, the new API for the base type is REF

<button >{{count}}</button> export default { setup() { const count = ref(0); return { count }; }}Copy the code

Implementing a REF is pretty simple: you can do it using the getters that come with the JS object

Here’s an example:

let v = 0;
let ref = {
    get value() {
        console.log('get')
        return v;
    },
    set value(val) {
        console.log('set', val)
        v= val;
    }
}

ref.value; / / print the get
ref.value = 3; / / print the set
Copy the code

The ref can be easily implemented through the track and trigger implemented in the previous chapters

Code done directly on

function ref(target) {
  let value = target

  const obj = {
    get value() {
      track(obj, 'value');
      return value;
    },
    set value(newVal) {
      if(newVal ! == value) { value = newVal; trigger(obj,'value'); }}}return obj;
}
Copy the code

computed

So how do you implement computed?

First: see vuE3 for computed use

let sum = computed((a)= > {
  return count.num + num.value + '! '
})
Copy the code

Blind guessing leads to an idea that can be implemented by modifying Effect so that the run method is not executed at the moment effect is called. So we can add a lazy argument.

function effect(fn, options = {}) {
  const _effect = function(. args) {
    activeEffect = _effect;
    returnfn(... args); };// Add this code
  if(! options.lazy) { _effect(); }return _effect;
}
Copy the code

So computed can be written this way

  1. Internal implementationeffect(fn, {lazy: true})ensurecomputedExecute without triggering a callback.
  2. Pass objectgetterProperties,computedThe callback is executed when it is used.
  3. throughdirtyPrevent memory overflow.

Good code coming up:

function computed(fn) {
  let dirty = true;
  let value;
  let _computed;

  const runner = effect(fn, {
    lazy: true
  });
  
  _computed = {
    get value() {
      if (dirty) {
        value = runner();
        dirty = false;
      }
      returnvalue; }}return _computed;
}
Copy the code

So the question is how do you reset dirty when it’s set to false after the first execution?

Vue3’s solution at this point is to add a scheduler to Effect to handle side effects.

function effect(fn, options = {}) {
  const _effect = function(. args) {
    activeEffect = _effect;
    returnfn(... args); };if(! options.lazy) { _effect(); }// Add this line
  _effect.options = options;

  return _effect;
}
Copy the code

Now that we have a Scheduler, we need to change the trigger to handle the new scheduler.

function trigger(target, key) {
  const depsMap = targetMap.get(target);
  if(! depsMap)return;
  const effects = new Set()
  depsMap.get(key).forEach(e= > effects.add(e))

  // Change this line
  effects.forEach(e= > scheduleRun(e))
}

// Add a method
function scheduleRun(effect) {
  if(effect.options.scheduler ! = =void 0) {
    effect.options.scheduler(effect);
  } else{ effect(); }}Copy the code

Then, by combining the above code, computed is done

function computed(fn) {
  let dirty = true;
  let value;
  let _computed;

  const runner = effect(fn, {
    lazy: true.scheduler: (e) = > {
      if(! dirty) { dirty =true;
        trigger(_computed, 'value'); }}}); _computed = { get value() {if (dirty) {
        value = runner();
        dirty = false;
      }
      track(_computed, 'value');
      returnvalue; }}return _computed;
}
Copy the code

conclusion

  1. The core of Reactive istrack + trigger + Proxy
  2. Ref is owned by the objectgettersetterCooperate withtrack + triggerImplementation of the
  3. Computed is actually a problem ineffectOn the basis of improvement

Next chapter: How does VUE3 combine with JSX?

The last

Original is not easy, give a three even comfort next brother.

  1. See uuz for the source code
  2. This article is from github.com/zhongmeizhi…
  3. Welcome to pay attention to the public number “front-end advanced class” seriously learn the front end, step up together. replyThe whole stackVueI got a nice gift for you