Last week sent 🎄 Vue3 official tutorial 】 【 word notes | synchronous tutorship video 1050 praise

🔥 THIS week I took a look at the mini version of Vue3 written by You Da Shen, please correct your notes as follows.

[Original video]

⚡️ pay attention to public number [front-end bus] reply [mini-vue] for complete code

First, the overall work process

  1. The compiler compiles the view template into a rendering function
  2. The data response module initializes a data object as a responsive data object
  3. The view to render
    1. RenderPhase: The render module uses render functions to generate the virtual Dom from initialization data
    2. MountPhase: Create view page Html using the virtual Dom
    3. PatchPhase: Once the data model changes, the rendering function will be called again to generate a new virtual Dom and then do Dom Diff to update the view Html

Two, three modules of the division of labor

  • Data responsive module
  • The compiler
  • Rendering function

1. Data responsive module

Provides methods for creating responsive objects where all data changes can be listened for.

2. Compile the module

Compile the HTML template into a rendering function

This compilation process can be performed at the following two moments

  • Browser Runtime
  • Compile time for Vue projects

3. Rendering functions

The render function renders the view to the page through the following three cycles

  • Render Phase
  • Mount Phase
  • Patch Phase

Mock MVVM

The MVVM framework essentially adds a VM layer between the original View and Model to do the following. Complete data and view listening. Let’s start by writing a Mock version. The idea is to implement listening for fixed views and data models.

1. Interface definition

The framework interface of our MVVM is exactly the same as Vue3.

Initialization needs to be determined

  • The view template
  • The data model
  • Model behavior – for example, we want the data model messages to be in reverse order when we click.
const App = {
  / / view
  template: `
<input v-model="message"/>
<button @click='click'>{{message}}</button>
`.setup() {
    // Data hijacking
    const state = new Proxy({message: "Hello Vue 3!!"}, {set(target, key, value, receiver) {
          const ret = Reflect.set(target, key, value, receiver);
          // Trigger the function response
          effective();
          returnret; }});const click = () = > {
      state.message = state.message.split("").reverse().join("");
    };
    return{ state, click }; }};const { createApp } = Vue;
createApp(App).mount("#app");
Copy the code

2. Program skeleton

The program execution process is roughly shown as follows:

const Vue = {
  createApp(config) {
    // The compilation process
    const compile = (template) = > (content, dom) = >{};// Generate the render function
    const render = compile(config.template);

    return {
      mount: function (container) {
        const dom = document.querySelector(container);
        
				// Implement the setup function
        const setupResult = config.setup();
				
        // Data responds to update views
        effective = () = >render(setupResult, dom); render(setupResult, dom); }}; }};Copy the code

3. Compile the render function

The rendering functions in the MVVM framework are created by compiling view templates.

// Compile the function
// The input value is view template
const compile = (template) = > {
  // Render function
  return (observed, dom) = > {
  	// Render process}}Copy the code

Simply put, the view template is parsed and rendered.

There are three things to deal with

  • Determine which values need to be rendered according to the data model

    // <button>{{message}}</button>
    // Render data to view
    button = document.createElement('button')
    button.innerText = observed.message
    dom.appendChild(button)
    Copy the code
  • Bind model events

    // <button @click='click'>{{message}}</button>
    // Bind model events
    button.addEventListener('click'.() = > {
      return config.methods.click.apply(observed)
    })
    Copy the code
  • Determine which input items need bidirectional binding

// <input v-model="message"/>
// Create a keyup event to listen for input changes
input.addEventListener('keyup'.function () {
  observed.message = this.value
})
Copy the code

Complete code

const compile = (template) = > (observed, dom) = > {

    // re-render
    let input = dom.querySelector('input')
    if(! input) { input =document.createElement('input')
        input.setAttribute('value', observed.message)
      	
        input.addEventListener('keyup'.function () {
            observed.message = this.value
        })
        dom.appendChild(input)
    }
    let button = dom.querySelector('button')
    if(! button) {console.log('create button')
        button = document.createElement('button')
        button.addEventListener('click'.() = > {
            return config.methods.click.apply(observed)
        })
        dom.appendChild(button)
    }
    button.innerText = observed.message
}
Copy the code

4. Data response implementation

Data hijacking is a common approach to Vue. The difference is whether you use DefineProperty or Proxy. That is, one property at a time or one object at a time. Of course the latter sounds like a distinct advantage over the former. This is the responsivity principle of Vue3.

Proxy/Reflect was added in the ES2015 specification, Proxy can better intercept object behavior, Reflect can more gracefully manipulate objects. Advantage is

  • Customization for the entire object, rather than an attribute of the object, eliminates the need to iterate over keys.
  • Arrays are supported. This DefineProperty doesn’t have one. This eliminates the need for hacks like overloading array methods.
  • The second argument to Proxy can be intercepted in 13 ways, which is much richer than object.defineProperty ()
  • Proxy is a new standard that is getting a lot of attention and performance optimization from browser vendors, while Object.defineProperty() is an existing, older method
  • Nested objects can be easily nested by recursion.

With all that said let’s start with a little example

var obj = new Proxy({}, {
    get: function (target, key, receiver) {
        console.log(`getting ${key}! `);
        return Reflect.get(target, key, receiver);
    },
    set: function (target, key, value, receiver) {
        console.log(`setting ${key}! `);
        return Reflect.set(target, key, value, receiver);
    }
})
obj.abc = 132

Copy the code

So if you change the value in obj, it will print it out.

That is, if the object is modified, it will be responded to.

Of course, the response we need is to update the view again by re-running the Render method.

Start by making an abstract data response function

// Define the response function
let effective
observed = new Proxy(config.data(), {
  set(target, key, value, receiver) {
    const ret = Reflect.set(target, key, value, receiver)
    // Trigger the function response
    effective()
    return ret
  },
})
Copy the code

At initialization we set the response action to render view

const dom = document.querySelector(container)
// Set the response action to render view
effective = () = > render(observed, dom)
render(observed, dom)
Copy the code

1. Monitor view changes

Changes in the browser view are mainly monitored for input changes, so you only need to bind events to listen for them.

document.querySelector('input').addEventListener('keyup'.function () {
  data.message = this.value
})
Copy the code

2. Complete code

<html lang="en">
  <body>
    <div id="app"></div>
    <script>
      const Vue = {
        createApp(config) {
          // The compilation process
          const compile = (template) = > (content, dom) = > {
            // re-render
            dom.innerText = "";
            input = document.createElement("input");
            input.addEventListener("keyup".function () {
              content.state.message = this.value;
            });
            input.setAttribute("value", content.state.message);
            dom.appendChild(input);

            let button = dom.querySelector("button");
            button = document.createElement("button");
            button.addEventListener("click".() = > {
              return content.click.apply(content.state);
            });
            button.innerText = content.state.message;
            dom.appendChild(button);
          };
          
          // Generate the render function
          const render = compile(config.template);

          return {
            mount: function (container) {
              const dom = document.querySelector(container);
              const setupResult = config.setup();
              effective = () = >render(setupResult, dom); render(setupResult, dom); }}; }};// Define the response function
      let effective;
      const App = {
        / / view
        template: `   `.setup() {
          // Data hijacking
          const state = new Proxy({message: "Hello Vue 3!!"}, {set(target, key, value, receiver) {
                const ret = Reflect.set(target, key, value, receiver);
                // Trigger the function response
                effective();
                returnret; }});const click = () = > {
            state.message = state.message.split("").reverse().join("");
          };
          return{ state, click }; }};const { createApp } = Vue;
      createApp(App).mount("#app");
    </script>
  </body>
</html>

Copy the code

5. View rendering process

Dom => virtual DOM => render functions

1. What is Dom and Document Object Model

HTML is mapped to a list of nodes in the browser that we can call.

What is the virtual Dom

Dom has a large number of nodes, so the performance of directly querying and updating Dom is poor.

A way of representing the actual DOM with JavaScript Objects. Rerepresent the actual Dom with a JS object

3. What is a render function

In Vue we compile a view template into a render function and then convert it into a virtual Dom

4. Update views efficiently with DomDiff

5. To summarize

For example 🌰 the virtual Dom and Dom are like the relationship between a building and a building design.Suppose you want to add a kitchen on the 29th floor ❌ demolish the whole 29th floor and rebuild ✅ draw the design first, find out the differences between the old and new structures and then build

Implement the rendering function

In Vue we compile a view template into a render function and then convert it into a virtual Dom

The rendering process is usually divided into three parts:

vue-next-template-explorer.netlify.app/

  • RenderPhase: The render module uses render functions to generate the virtual Dom from initialization data
  • MountPhase: Create view page Html using the virtual Dom
  • PatchPhase: Once the data model changes, the rendering function will be called again to generate a new virtual Dom and then do Dom Diff to update the view Html
mount: function (container) {
    const dom = document.querySelector(container);
    const setupResult = config.setup();
    const render = config.render(setupResult);

    let isMounted = false;
    let prevSubTree;
    watchEffect(() = > {
      if(! isMounted) { dom.innerHTML ="";
        // mount
        isMounted = true;
        const subTree = config.render(setupResult);
        prevSubTree = subTree;
        mountElement(subTree, dom);
      } else {
        // update
        constsubTree = config.render(setupResult); diff(prevSubTree, subTree); prevSubTree = subTree; }}); },Copy the code

1.Render Phase

The render module uses render functions to generate the virtual Dom from initialization data

render(content) {
  return h("div".null, [
    h("div".null.String(content.state.message)),
    h(
      "button",
      {
        onClick: content.click,
      },
      "click"),]); },Copy the code

2. Mount Phase

Create view page Html using the virtual Dom

function mountElement(vnode, container) {
  // Render to a real DOM node
  const el = (vnode.el = createElement(vnode.type));

  / / processing props
  if (vnode.props) {
    for (const key in vnode.props) {
      const val = vnode.props[key];
      patchProp(vnode.el, key, null, val); }}// Handle children
  if (Array.isArray(vnode.children)) {
    vnode.children.forEach((v) = > {
      mountElement(v, el);
    });
  } else {
    insert(createText(vnode.children), el);
  }

  // Insert into the view
  insert(el, container);
}

Copy the code

3. Patch Phase(Dom diff)

Once the data model changes, the rendering function is called again to generate a new virtual Dom and then do Dom Diff to update the view Html

function patchProp(el, key, prevValue, nextValue) {
  // onClick
  // 1. If the first two values are on
  // 2. Consider it an event
  // 3. On is the corresponding event name
  if (key.startsWith("on")) {
    const eventName = key.slice(2).toLocaleLowerCase();
    el.addEventListener(eventName, nextValue);
  } else {
    if (nextValue === null) {
      el.removeAttribute(key, nextValue);
    } else{ el.setAttribute(key, nextValue); }}}Copy the code

Update views with DomDiff – Efficiently

function diff(v1, v2) {
  // 1. If the tag is different, just replace it
  // 2. If tag is the same
  // 1. Check whether the props are changed
  // 2. To detect children -
  const { props: oldProps, children: oldChildren = [] } = v1;
  const { props: newProps, children: newChildren = [] } = v2;
  if(v1.tag ! == v2.tag) { v1.replaceWith(createElement(v2.tag)); }else {
    const el = (v2.el = v1.el);
    / / contrast props
    // 1. The new node is not equal to the value of the old node -> direct assignment
    // 2. Delete all keys that do not exist in the new node
    if (newProps) {
      Object.keys(newProps).forEach((key) = > {
        if (newProps[key] !== oldProps[key]) {
          patchProp(el, key, oldProps[key], newProps[key]);
        }
      });

      // Go through the old node - "new node does not have, then delete all
      Object.keys(oldProps).forEach((key) = > {
        if(! newProps[key]) { patchProp(el, key, oldProps[key],null); }}); }/ / the children

    // newChildren -> string
    // oldChildren -> string oldChildren -> array

    // newChildren -> array
    // oldChildren -> string oldChildren -> array
    if (typeof newChildren === "string") {
      if (typeof oldChildren === "string") {
        if (newChildren !== oldChildren) {
          setText(el, newChildren);
        }
      } else if (Array.isArray(oldChildren)) {
        // Replace all the previous elementsv1.el.textContent = newChildren; }}else if (Array.isArray(newChildren)) {
      if (typeof oldChildren === "string") {
        // Clear the previous data
        n1.el.innerHTML = "";
        // Mount all the children out
        newChildren.forEach((vnode) = > {
          mountElement(vnode, el);
        });
      } else if (Array.isArray(oldChildren)) {
        // a, b, c, d, e -> new
        // a1,b1,c1,d1 -> old
        // If there are too many new, create a new one

        // a, b, c -> new
        // a1,b1,c1,d1 -> old
        // If there are many old ones, delete all the old ones
        const length = Math.min(newChildren.length, oldChildren.length);
        for (let i = 0; i < length; i++) {
          const oldVnode = oldChildren[i];
          const newVnode = newChildren[i];
          // Can be very complex
          diff(oldVnode, newVnode);
        }

        if (oldChildren.length > length) {
          // Indicates that there are many old nodes
          // Delete all of them
          for (leti = length; i < oldChildren.length; i++) { remove(oldChildren[i], el); }}else if (newChildren.length > length) {
          // Indicates that new has many nodes
          // Then you need to create the corresponding node
          for (let i = length; i < newChildren.length; i++) {
            mountElement(newChildren[i], el);
          }
        }
      }
    }
  }
}
Copy the code

Principle of compiler

This is a place where Yoshitami is not implemented and I will give you a very concise version of it in the future. In this chapter, we will focus on the compile function.

We’ve already seen what a compiler function can do

// Compile the function
// The input value is view template
const compile = (template) = > {
  // Render function
  return (observed, dom) = > {
  	// Render process}}Copy the code

The short answer is

  • Enter: View template
  • Output: Render function

It can be broken down into three small steps

  • Parse template string -> AST(Abstract Syntax Treee) Abstract Syntax tree

  • Transform conversion tags such as v-bind V-if V-for conversion

  • Generate AST -> render function

    // Template string -> AST(Abstract Syntax Treee) Abstract Syntax tree
    let ast = parse(template)
    // Conversion processing such as v-bind v-if v-for conversion
    ast = transfer(ast)
    // AST -> render function
    return generator(ast)
    Copy the code

    You can get a feel for this with the online version of VueTemplateExplorer

    vue-next-template-explorer.netlify.com/

Compiler function parsing

1. Parse

The way a parser works is a series of regular matches.

Such as:

Matching of tag attributes

  • class=”title”

  • class=’title’

  • class=title

const attr = /([a-zA-Z_:][-a-zA-Z0-9_:.]*)=("([^"]*)"|'([^']*)'|([^\s"'=<>`]+)/

"class=abc".match(attr);
// output
(6) ["class=abc"."class"."abc".undefined.undefined."abc".index: 0.input: "class=abc".groups: undefined]

"class='abc'".match(attr);
// output
(6) ["class='abc'"."class"."'abc'".undefined."abc".undefined.index: 0.input: "class='abc'".groups: undefined]

Copy the code

We’ll talk more about that when we implement it. You can refer to the article.

AST parser in action

So for our project we could write it like this

// <input v-model="message"/>
// <button @click='click'>{{message}}</button>
// The transformed AST syntax tree
const parse = template= > ({
    children: [{
            tag: 'input'.props: {
                name: 'v-model'.exp: {
                    content: 'message'}},}, {tag: 'button'.props: {
                name: '@click'.exp: {
                    content: 'message'}},content:'{{message}}'}],})Copy the code

2. Transform processing

The previous section did the abstract syntax tree, and this is where the special transformation to the Vue3 template takes place.

For example, vFor and vOn

In Vue, the three will be carefully divided into two levels for processing

  • Compile -core Core compilation logic

    • AST-Parser

    • Basic types are resolved v-for and V-ON

  • Compile-dom compile logic for the browser

    • v-html

    • v-model

    • v-clock

const transfer = ast= > ({
    children: [{
            tag: 'input'.props: {
                name: 'model'.exp: {
                    content: 'message'}},}, {tag: 'button'.props: {
                name: 'click'.exp: {
                    content: 'message'}},children: [{
                content: {
                    content: 'message'},}]}],})Copy the code

3. Generate Generate renderer

The generator simply generates the rendering function from the transformed AST syntax tree. Of course you can render different results for the same syntax tree. For example, if you want to render a button or an SVG block, it is up to you. This is called a custom renderer. Here we simply write a fixed Dom renderer placeholder. When I get to the next implementation, I’m unpacking.

const generator = ast= > (observed, dom) = > {
    // re-render
    let input = dom.querySelector('input')
    if(! input) { input =document.createElement('input')
        input.setAttribute('value', observed.message)
        input.addEventListener('keyup'.function () {
            observed.message = this.value
        })
        dom.appendChild(input)
    }
    let button = dom.querySelector('button')
    if(! button) {console.log('create button')
        button = document.createElement('button')
        button.addEventListener('click'.() = > {
            return config.methods.click.apply(observed)
        })
        dom.appendChild(button)
    }
    button.innerText = observed.message
}

Copy the code

🔥 concern public number [front-end bus] reply [mini-vue] for complete code

Pay attention to the full stack

Recent articles (thanks for your encouragement and support 🌹🌹🌹)

  • 🔥 🎄 Vue3 official tutorial 】 【 word notes | synchronous learning video 1050 praise
  • 🔥 39 exits run | Denver annual essay 100 praise
  • 🔥 Element3 Development Insider – Vue CLI plug-in development 167 likes
  • 🔥 make wheel series every day 500+ praise
  • 🔥 Vue3.0 global launch summary of dry goods 267 thumbs up

Welcome to clap brick, discuss more elegant implementation together