Vue3.0 series – Responsive

Vue3.0 series – rendering process

preface

There are many new features and optimizations in VUe3.0. In terms of optical performance, you gave the data when the live broadcast, update performance improved by 1.3-2 times, SSR improved by 2-3 times. This article summarizes some of the new features that stand out, along with their implementation principles

New response

Vue3 uses proxy to rewrite the reactive part, in addition to addressing the parts that Object. DefineProperty does not support, but also makes a lazy recursive optimization.

See VUe3.0 series – Responsive for details

tree-shaking

Vue3 source export is no longer a VUE, but a series of functions:

export { computed } from './apiComputed'
Copy the code

ES6 Module Features:

  • Can only appear as a statement at the top level of a module
  • The module name for import must be a string constant
  • Import binding is immutable

ES6 module dependencies are deterministic, independent of runtime state, and can be reliably statically analyzed, which is the basis for tree-shaking.

Using the above features, tree-shaking can be implemented with compilation tools

For more information on tree-shaking principles, check out tree-shaking Performance Optimization Practices – Principles

composition api

Composition API stands for composite API and is one of the most discussed features

Previously, all content was written in options, data in data, methods in methods, etc.

Now everything can be written in the setup function, for example:

setup() {
      // ToDOS code
      const state = reactive({
        todos: [{id: '1'.title: '111'.compoleted: true},
          {id: '2'.title: '222'.compoleted: false},
          {id: '3'.title : '333'.compoleted: false}],})function add() {
        state.todos = [
          {id: '2'.title: '222'.compoleted: false},
          {id: '1'.title: '111'.compoleted: true},
          {id: '3'.title : '333'.compoleted: false}}]//num code
      const num = ref(0);
      function addNum() {
       num.value++;
      }
      
      // Lifecycle related code
      onBeforeMount(function() {
        console.log(this.'app onBeforeMount')})return {state, num, add, addNum}
  }
Copy the code

As you can see, the code hierarchy is very clear, and if you want to find a part of the code, you don’t have to jump to other places. That is to solve the problem of repeated horizontal jump

At the same time, the withdrawal of public modules is more clear:

//addTodo.js
import {reactive} from 'vue';

export default function(state) {
    let addFlag = reactive({
        added: true
    })
    function addTodo() {
        state.todos.push({
            id: 4.title: '444'})}return {addTodo, addFlag}
}

// app.vue
import addTodo from './addTodo';

setup() {
      // ToDOS code
      const state = reactive({
        todos: [{id: '1'.title: '111'.compoleted: true},
          {id: '2'.title: '222'.compoleted: false},
          {id: '3'.title : '333'.compoleted: false}],})const {addTodo, addFlag} = addTodo();
      function add() {
        state.todos = [
          {id: '2'.title: '222'.compoleted: false},
          {id: '1'.title: '111'.compoleted: true},
          {id: '3'.title : '333'.compoleted: false}}]//num code
      const num = ref(0);
      function addNum() {
       num.value++;
      }
      
      // Lifecycle related code
      onBeforeMount(function() {
        console.log(this.'app onBeforeMount')})return {state, num, add, addNum, addTodo, addFlag}
  }
Copy the code

As opposed to vue2mixinIn this way, the source of the data can be seen more clearly

Realize the principle of

  • Unified entrance,setupThat’s the premise
  • In the implementationsetupPreviously, the current instance is cached and cleared after execution. sosetupThe function execution inside can fetch the component instance. Because lifecycle functions are asynchronous, they persist the current instance using the closure principle
  • willsetupExecute the result assignment ininstance.ctxOn:instance.ctx.setupState = new Proxy({state, num, add, addNum, addTodo, addFlag}, xxx)
  • configurationinstance.ctxResponsive proxy for. For example, getctx.num, the CTX agent will detect the value insetupStateOn, returns. The same goes for modifying values

Performance optimization

Vue3’s Update performance improved by 1.3-2 times, yu said. Mainly because of the following three points:

  • Compiler optimization
    • Add static tags at compile time
    • Dynamic props
  • Static ascension
  • Static cache

For example:

//template
<div>
  <div>222</div>
  <div @click="Click">{{name}}</div>
  <div :name="name" ss="sss">{{name}}</div>
</div>
Copy the code

Vue3 compilation results:

import { createVNode, renderList, Fragment, openBlock, createBlock, createCommentVNode } from "vue"

// Binding optimization for webpack code-split
const _createVNode = createVNode, _renderList = renderList, _Fragment = Fragment, _openBlock = openBlock, _createBlock = createBlock, _createCommentVNode = createCommentVNode

const _hoisted_1 = /*#__PURE__*/_createVNode("p".null."mmmm", -1 /* HOISTED */)
const _hoisted_2 = { key: 0 }
const _hoisted_3 = { key: 1 }

export function render(_ctx, _cache, $props, $setup, $data, $options) {
  return (_openBlock(), _createBlock("div".null, [
    _hoisted_1,
    (_openBlock(true), _createBlock(_Fragment, null, _renderList(_ctx.list, (item) = > {
      return (_openBlock(), _createBlock("p", { key: item }, "M"))}),128 /* KEYED_FRAGMENT */)),
    (_ctx.show)
      ? (_openBlock(), _createBlock("p", _hoisted_2, "M"))
      : (_openBlock(), _createBlock("p", _hoisted_3, "mmmm"))))}// Check the console for the AST
Copy the code

The general steps are as follows:

  • openBlockTo create acurrentBlockAn array of
  • Execute the child nodecreateVNodeMethod to createvnodeAnd the incomingpatchFlag(-1 is a static node, pacTH is not required) anddynamicProps(dynamic props). If is a dynamic node, this parameter is configuredvnodedepositcurrentBlock;dynamicPropsIn thevnode
  • The last executioncreateBlock, the callcreateVNodeOf the parent nodevnodeThat will becurrentBlockAssigned to itvnodethedynamicChildren

In the follow-up update, only patch vnode dynamicChildren is needed. Similarly, in the case of patchProps, only the values of dynamicProps need to be processed

Here’s the code:

openBlock

//openBlock
function openBlock(disableTracking = false) {
  blockStack.push((currentBlock = disableTracking ? null:)} [])Copy the code

closeBlock

//closeBlock
function closeBlock() {
  blockStack.pop()
  currentBlock = blockStack[blockStack.length - 1] | |null
}
Copy the code

createVNode

//createVNode removes unnecessary code
function _createVNode(
  type: VNodeTypes | ClassComponent | typeof NULL_DYNAMIC_COMPONENT,
  props: (Data & VNodeProps) | null = null,
  children: unknown = null,
  patchFlag: number = 0,
  dynamicProps: string[] | null = null,
  isBlockNode = false
) :VNode {

  // encode the vnode type information into a bitmap
  const shapeFlag = isString(type)
    ? ShapeFlags.ELEMENT
    : __FEATURE_SUSPENSE__ && isSuspense(type)
      ? ShapeFlags.SUSPENSE
      : isTeleport(type)
        ? ShapeFlags.TELEPORT
        : isObject(type)
          ? ShapeFlags.STATEFUL_COMPONENT
          : isFunction(type)
            ? ShapeFlags.FUNCTIONAL_COMPONENT
            : 0

  const vnode: VNode = {
    __v_isVNode: true,
    [ReactiveFlags.SKIP]: true,
    type,
    props,
    key: props && normalizeKey(props),
    ref: props && normalizeRef(props),
    scopeId: currentScopeId,
    children: null.component: null.suspense: null.ssContent: null.ssFallback: null.dirs: null.transition: null.el: null.anchor: null.target: null.targetAnchor: null.staticCount: 0,
    shapeFlag,
    patchFlag,
    dynamicProps,
    dynamicChildren: null.appContext: null
  }
  normalizeChildren(vnode, children)
  if (
    shouldTrack > 0 &&
    // avoid a block node from tracking itself! isBlockNode &&// has current parent block
    currentBlock &&
    // presence of a patch flag indicates this node needs patching on updates.
    // component nodes also should always be patched, because even if the
    // component doesn't need to update, it needs to persist the instance on to
    // the next vnode so that it can be properly unmounted later.
    (patchFlag > 0 || shapeFlag & ShapeFlags.COMPONENT) &&
    // the EVENTS flag is only for hydration and if it is the only flag, the
    // vnode should not be considered dynamic due to handler caching.patchFlag ! == PatchFlags.HYDRATE_EVENTS ) {// This will put vNode into currentBlock
    currentBlock.push(vnode)
  }
  return vnode
}
Copy the code

createBlock

//createBlock
function createBlock(type: VNodeTypes | ClassComponent, props? : Record<string, any> |null, children? : any, patchFlag? : number, dynamicProps? : string[]) :VNode {
  const vnode = createVNode(
    type,
    props,
    children,
    patchFlag,
    dynamicProps,
    true /* isBlock: prevent a block from tracking itself */
  )
  // Cache dynamic nodes to dynamicChildren
  vnode.dynamicChildren = currentBlock || (EMPTY_ARR as any)
  // This will return to the currentBlock of the previous parent node, combined with the following code, which refers to
  // If the node has dynamic children, then it must be a dynamic node itself, so it will change
  // This node is placed in the blCOK of its parent, which is a dynamic child of its parent
  closeBlock()
  if (shouldTrack > 0 && currentBlock) {
    currentBlock.push(vnode)
  }
  return vnode
}
Copy the code

The diff algorithm

The diff algorithm was also rewritten in VUe3, which changed from the double-pointer traversal of VUe2 to the single-pointer traversal, and the algorithm of increasing subsequence was used to get the optimal move step

See: Diff algorithm

teleport

Meaning to transmit, the child node is transmitted to the corresponding node (node specified by to)

Adding disabled renders in the current document stream

// MMMMM will render in the body
<div id="parent">
    <teleport to="body">mmmmm</teleport>
</div>

// MMMMM will render in parent
<div id="parent">
    <teleport to="body" disabled>mmmmm</teleport>
</div>
Copy the code

suspense

Meaning suspense, used with asynchronous components, rendering content changes with the asynchronous loading process

The contents of fallback will be rendered before AsyncComponent loading is complete, and the contents of default will be rendered after loading

const AsyncComponent = defineAsyncComponent(() = > import('./AsyncComponent'));

<template>
  <Suspense>
    <template #default>
      <AsyncComponent />
    </template>
    <template #fallback>
      Loading ...
    </template>
  </Suspense>
</template>
Copy the code