The runtime of the mini – vue – core

Hello, everyone, I am a self-taught front end of the small rookie, the current work less than a year, still trying to learn, I hope that THE content I write, helpful to you, also hope that you have any opinions and suggestions can tell me, let me less detours, thank you!!

Through the implementation of a mini-vue3 to deepen their understanding of VUE, slowly learn, record what you have learned, there are detailed notes

This Mini-vue3 is learned through The Git warehouse of Cui Xiaorui. Teacher Ruan Yifeng recommended to learn Vue3

Address of this warehouse: Mini-VUe-IMPl

Source: the mini – vue

Pure own learning, if there are mistakes, but also hope you correct, including

In the study-every-day warehouse, there are big boss brain map, need to take oh ~

The previous article on responsive systems is here

1.Responsive system

2. Core runtime

Speaking of the core runtime, again, take a look at the vue architecture schema diagram to see what it does

From the figure, we can see that the Run-time core is responsible for connecting the run-time DOM and the reactivity responsive system in our runtime, so as to achieve the function of cross-platform. Why cross-platform?

This is because VUe3 provides a custom renderer, and we can provide different renderer interfaces according to different platforms to achieve cross-platform rendering. The main reason for this is also that virtual nodes are used to render real nodes, and stable APIS are needed to help us render nodes on the page

For example: browser DOM

// Create a node and add it to the HTML body node
const div = document.createElement('div')
div.textContent = 'Hello World'
document.body.append(div)
Copy the code

In this example we use the browser’s DOM API to create a node and add it to the view

There are other platforms in WXML for applets that provide stable apis to turn our virtual nodes into real nodes

At this point, we know what run-time Core does. ~

  1. Providing virtual nodesVNodeIs used to create real nodes anddiffMore updates
  2. Provides a custom renderer interface that allows users to create renderers based on the platform
  3. callreactivityThe module is triggered by the dependency collection of a responsive systemdiffUpdate our view

1. Provide virtual nodesvirtual DOM

In fact, I have always been very confused about the difference between virtual nodes and component instances, are objects, have corresponding attributes, what is the difference, until this time I through the implementation of a simple version, finally can distinguish

1.1 Virtual Nodes

The virtual node is a mirror image of the real DOM, and we use this mirror to create the real DOM and optimize more of the above, for example

By marking static nodes, we can update them with finer granularity. Static nodes will never move. Where do I write to die

Why do you need the virtual DOM?

  1. cross-platform
  2. Avoid repeated manipulation of realityDOMUnnecessary performance drain
  3. Description by virtual nodeDOMTree, can go throughdiffKnow exactly which point is updated and make updates to avoid unnecessary backflow redrawing

The core of the virtual DOM is to use efficient JS operations to reduce low performance DOM operations, so as to improve web page performance

Other platforms, similarly, use DOM as a description point

Look at the code

Vue3 also has many attributes, such as static node tags, which can be skipped in diff
export function createVNode(type, props? , children?) {
  const vnode = {
    type.// Node type
    props, / / property
    children, / / child nodes
    shapeFlag: getShapeFlag(type), // Virtual node type
    el: null // Real DOM references, accurate DOM updates
  }

  if (typeof children === 'string') {
    // If the child component is text, mark the current node as element + text
    vnode.shapeFlag |= ShapeFlag.TEXT_CHILDREN
  } else if (Array.isArray(children)) {
    // If the subcomponent is an array, it may be a new component, or the element's child node is element + child element
    vnode.shapeFlag |= ShapeFlag.ARRAY_CHILDREN
  }

  // Whether there is a slot
  if (
    vnode.shapeFlag & ShapeFlag.STATEFUL_COMPONENT &&
    typeof children === 'object'
  ) {
    vnode.shapeFlag |= ShapeFlag.SLOT_CHILDREN
  }

  return vnode
}
export function createTextVnode(text: string) {
  return createVNode(Text, {}, text) / / text
}

/ * * *@description Gets the type identifier * of the current child node@param Type Current vNode type */
function getShapeFlag(type: any) {
  return typeof type= = ='string'
    ? ShapeFlag.ELEMENT / / element
    : ShapeFlag.STATEFUL_COMPONENT / / component
}
Copy the code

In the virtual node, there’s a bit operation, which I’m sure you’ve all heard of, to mark the node type with a binary switch, which is really high, and normally we would use a map to determine the type, like this

const vnodeType = {
  element: false.stateful_component: false.text_children: false.array_children: false
}

// If the component is an element, the child node is text
vnodeType.element = true
vnodeType.text_children = true

// The result will be this
if (vnodeType.element && vnodeType.text_children) {
  // Related operations
}
Copy the code

Actually such readability is very high, but performance is poor, we all need a mapping object each virtual node, and then create the marked, and the reference object storage, determine the consumption is high, on the contrary we use binary switch, will be much faster, digital, consume the space is little, the binary is more suitable for computer analysis

See what VUE3 does

/** * Virtual node type identifier * Each bit represents a type */
export const enum ShapeFlag {
  ELEMENT = 1./ / 0001 elements
  STATEFUL_COMPONENT = 1 << 1./ / 0010 components
  TEXT_CHILDREN = 1 << 2.// 0100 child nodes are text
  ARRAY_CHILDREN = 1 << 3.// 1000 child nodes are arrays
  SLOT_CHILDREN = 1 << 4 // 1 0000 component uses a slot
}

/ / about the types of judgment, the use of | and & to operate
/ / create the node, to open the corresponding identifier bit by | =
// Use ampersand (1 &) to indicate the bit identifier
Copy the code

2. Provide renderer interface

The renderer interface is not difficult to provide, passing in the API for manipulating nodes as a closure. The effect is to call the passed stable API when the virtual node needs to create the real node

/ * * *@description Create renderers through external definitions to render different platform elements *@param The Options element node handles the provided interface */
export function createRenderer(options) {
  // Provides a custom rendering interface
  const {
    createElement: hostCreateElement,  // Create operation
    patchProp: hostPatchProp,          // Attribute manipulation
    insert: hostInsert,                // Insert operations
    createTextNode: hostCreateTextNode // Text manipulation
  } = options

  / /... Operations on virtual nodes and components

  return {
    // Provide exposure to the entrance
    createApp: createAppAPI(render)
  }
}

export function createAppAPI(render) {
  return function createApp(rootComponent) {
    return {
      mount(rootContainer) {
        // 1. Convert the component or 'Dom' into a virtual node
        const vnode = createVNode(rootComponent)

        // 2. Process 'vnode'
        render(vnode, rootContainer)
      }
    }
  }
}
Copy the code

3. Component instance object

As I mentioned earlier, I didn’t know the difference between a component object and a virtual node object. I said virtual nodes. Now let’s look at component instances

Component instance objects, it is my usual operation. The vue file export the entire object, we can normal operation that the API documentation on the component instance has defined, is our operational view of interaction and rendering of an abstract, because components needs to be done within a lot, we can abstract out an object, to help us do these things

Take a look at the code:

export function createComponentInstance(vnode, parent) {
  const component: any = {
    vnode, // The component corresponds to a virtual node
    type: vnode.type, // Component type => is the configuration information we provide to the component, such as: {name: 'App, setup... }
    setupState: {}, // The status information returned by setup
    props: {}, // Component property information, which is passed by the parent component
    slots: {}, / / slots
    provides: parent ? parent.provides : {}, / / dojo.provide/inject function
    parent, // The parent of the component
    isMounted: false.// Indicates whether to mount
    subTree: null.// Component node tree for diff
    emit: () = > {} // Implementation of emit functionality
  }

  component.emit = emit

  return component
}
Copy the code

It should be clear from these functional apis that a component instance is a state store where we write components and provide operations, recording the current state of our component, while a virtual node is a mapping of the current real DOM

4. Invoke a responsive system

function setupRenderEffect(instance, container) {
  // Call the component's render function with 'effect'
  Dependency collection is triggered when we use reactive objects like 'ref/reactive' in the component
  // When the dependency changes, the current function will be called again, which will trigger 'patch' to perform 'diff' algorithm comparison and update to the view
  // Component-level dependency collection to avoid performance waste
  effect(() = > {
    // @tips: First load
    if(! instance.isMounted) {// 1. Call the render function, get the virtual node tree of the component, bind 'this' as the proxy object, implement the' render 'function to access the component state
      const subTree = instance.render.call(instance.proxy, h)

      // 2. Continue 'patch' and mount components recursively
      patch(null, subTree, container, instance)

      // 3. Component instance binding 'EL' is used for precise update of 'patch'
      instance.vnode.el = subTree.el

      // 4. Store the old virtual node tree, and then call 'patch'
      instance.subTree = subTree

      // 5. Indicates that the vm is mounted
      instance.isMounted = true
    }
    / / @ Tips: update
    else {
      // 1. Obtain a new virtual node tree
      const subTree = instance.render.call(instance.proxy, h)

      2. Obtain the old virtual node tree
      const prevSubTree = instance.subTree

      // 3. Update page of 'patch'
      patch(prevSubTree, subTree, container, instance)

      // 4. New tree becomes old tree after comparison
      instance.subTree = subTree
    }
  })
}
Copy the code

That’s it. Thank you for watching

Warehouse address: Mini-VUe-IMPl

Welcome to correct: study together

You can also implement a simple version, or go to my warehouse to have a look, the commit record is the corresponding, this week I will tag the corresponding easy to read and review, hope you like, if you like to give a star, thank you