Deep in simple language of vue3 (serial 3)

Time:2021-3-5

Hello, everyone. I’m Mokou. I’ve been working on vue3 related content recently, such as source code analysis and mini-vue3 development.

Review the content of the previous chapters, in the previous chapters mainly about the following content.

  1. New build toolsviteThe principle and implementation from scratch
  2. vue3Use a new pose
  3. New API:reactiveUse and source code analysis
  4. Tracking collectiontrackImplementation and source code analysis
  5. Trace triggertriggerImplementation and source code analysis
  6. Responsive coreeffectAndtrack、triggerWorking principle and source code analysis

OK, the goal of this chapter: complete a vue3 from scratch!

Pre knowledge that must be knowneffectAndtrack、triggerWorking principle, details, please see the official account >Front end advanced courseAn official account of a front end technology with temperature and no advertising.

Here is a simple analysis of the role of these three functions

  1. Track: Collection dependencytargetMap
  2. Trigger: trigger dependency, usetargetMap
  3. Treatment of side effects

For the source code of this chapter, uuz needs star to make a living.

The serial contents of the first two chapters are as follows:

  • Vue3. X easy to understand series (serial 1)
  • Vue3. X easy to understand series (Serial 2)

Hand in hand implementation of vue3

first. We have two global variables, which are used to store and locate tracking dependencies, that is, to givetrackandtriggerThe warehouse used.

let targetMap = new WeakMap();
let activeEffect;

So the first way to design is totrack, remember thattrackHow to call in vue3?

track(obj, 'get', 'x');

trackI’ll look for itobj.xIs it tracked? If not, put obj. X intargetMap(complete tracking task), willobj.xAs the key of the map, activeeffect is the value of the map.

Let’s forget about value exception handling and so on,trackOnly one thing will be doneactiveEffectCram intargetMap;

function track(target, key) {
  //First, find out if obj has been tracked
  let depsMap = targetMap.get(target);
  if (!depsMap) {
    //If not tracked, add one
        targetMap.set(target, (depsMap = new Map()));
  }
  //Then find out if obj. X is tracked
  let dep = depsMap.get(key);
    if (!dep) {
    //If not tracked, add one
    depsMap.set(key, (dep = new Set()));
  }
  //If active effect is not added, add one
  if (!dep.has(activeEffect)) {
        dep.add(activeEffect);
    }
}

And then write onetrigger, remembertriggerHow to call in Vue?

trigger(obj, 'set', 'x')

triggerI’ll just gotargetMapIn search ofobj.xTracking task, if found, to repeat, and then execute the task.

That is to say: put aside the value anomaly correlation,triggerOnly one thing has been done: fromtargetMapValue and then call the function value.

function trigger(target, key) {
  //Find tracking items
  const depsMap = targetMap.get(target);
  //Don't do anything until you find it
  if (!depsMap) return;
  //De duplication
  const effects = new Set()
  depsMap.get(key).forEach(e => effects.add(e))
  //Implementation
  effects.forEach(e => e())
}

Finally, it iseffectDo you remember how the worker’s API was called in vue3?

effect(() => {
  console.log('run cb')
})

effectReceives a callback function, which is then sent to thetrack. So we can do thiseffect

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

And inside_effectTwo things have also been done

  1. Assign itself toactiveEffect
  2. implementeffectCallback function

Good code is coming out.

function effect(fn) {
  //Define an internal_ effect 
  const _effect = function(...args) {
    //When executing, assign itself to activeeffect
    activeEffect = _effect;
    //Execute callback
    return fn(...args);
  };
  _effect();
  //Return closure
  return _effect;
}

All the antecedents have been completed. Now let’s finish onereactiveThat is, the object-oriented and responsive API. Remember how to use it in vue3reactiveIs that right?

<template>
  <button @click="appendName">{{author.name}}</button>
</template>

setup() {
  const author = reactive({
    name: 'mokou',
  })

  const appendName = () =>  author.name  +='excellent';

  return { author, appendName };
}

Through the above excellent code, it is easy to realize the responsive operation of vue3. By reviewing the previous chapters, we know thatreactiveIt is implemented by proxy proxy data.

So we can get throughProxyTo calltrackandtrigger, hijackinggetterandsetterComplete the responsive design

export function reactive(target) {
  //Proxy data
  return new Proxy(target, {
    get(target, prop) {
      //Execution tracking
      track(target, prop);
      return Reflect.get(target, prop);
    },
    set(target, prop, newVal) {
      Reflect.set(target, prop, newVal);
      //Trigger effect
      trigger(target, prop);
      return true;
    }
  })
}

okay. Everything is ready, so let’s Mount ourfake vue3bar

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()
}

Write a demo with mini-vue3

Test it. Refer to the writing of vue3. Definesetupandrender

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

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

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

mount(App, document.body)

Execute it. It’s really excellent code. Responsive normal execution, every timesetIntervalAfter execution, the pages are rewritten and refreshedcount.numThe data of the project.

Source code please see uuz, PS: July 23, the source code already supports JSX.

The above is approved50+Line of code, easy to achievevue3The response of the system. But is that the end?

There are also the following questions

  1. ProxyYou must pass in an object
  2. renderFunction sumhThe function is not correct (vue3’s H function is now two, not beforecreateElement(it’s over)
  3. Recursion of virtual DOM
  4. Stop talking about it- -!I won’t listen.

ref

One drawback of using reactive is that proxy can only proxy objects, but not underlying types.

If you call this codenew Proxy(0, {}), the browser will give you feedbackUncaught TypeError: Cannot create proxy with a non-object as target or handler

So, for the underlying type of proxy. We need a new way, and in the futurevue3The new API for base types isref

<button >{{count}}</button>

export default {
  setup() {
    const count = ref(0);
    return { count };
  }
}

The implementation of ref is very simple: it can be realized by using the getter of JS object

Take chestnuts

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

ref.value ; // print get
ref.value  =3; // Print Set

So it is realized through the previous chapterstrackandtriggerIt can be easily realizedref

Code completed 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;
}

computed

So how to achieve itcomputed

First of all: referencevue3OfcomputedUsage

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

Blind guess can get an idea, through the transformation of the nexteffectCan be achieved, that is, in theeffectThe moment of the call is not executedrunmethod. So we can add onelazyParameters.

function effect(fn, options = {}) {
  const _effect = function(...args) {
    activeEffect = _effect;
    return fn(...args);
  };

  //Add this code
  if (!options.lazy) {
    _effect();
  }

  return _effect;
}

thatcomputedYou can write that

  1. Internal executioneffect(fn, {lazy: true})ensurecomputedCallbacks are not triggered during execution.
  2. By objectgetterProperties, incomputedThe callback is executed when it is used.
  3. adoptdirtyPrevent memory overflow.

Excellent code is coming out:

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;
      }
      return value;
    }
  }
  return _computed;
}

So here comes the question…dirtySet to after the first executionfalseHow to reset?

herevue3The solution is to giveeffectAdd oneschedulerTo deal with side effects.

function effect(fn, options = {}) {
  const _effect = function(...args) {
    activeEffect = _effect;
    return fn(...args);
  };
  if (!options.lazy) {
    _effect();
  }

  //Add this line
  _effect.options = options;

  return _effect;
}

Now that we have itschedulerThat needs to be changedtriggerTo deal with the newscheduler

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();
  }
}

Then, merge the above code,computedIt’s 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');
      return value;
    }
  }
  return _computed;
}

summary

  1. The core of reactive istrack + trigger + Proxy
  2. Refs are owned by objectsgetterandsettercoordinationtrack + triggerRealized
  3. Computed is actually aeffectImprovement on the basis of

The content of the next chapter is as followsvue3How to combinejsx

last

It’s not easy to be original. Please comfort my brother.

  1. Source code, please see uuz
  2. This article is from https://github.com/zhongmeizhi/FED-note
  3. Welcome to official account “front-end advanced class” to learn the front-end step by step. replyWhole stackorVueGood gifts

Deep in simple language of vue3 (serial 3)