SoFunction
Updated on 2025-04-04

A detailed explanation of the simple implementation of the renderer in Vue

1. Renderer

Renderers are used to complete rendering operations, such as on browser platforms, the renderer can convert virtual DOM to real DOM.

2. A simple example to understand the working process of the renderer in Vue

const { effect, ref } = VueReactivity

function renderer(domString, container) {
   = domString
}

const count = ref(1)

effect(() => {
  renderer(`<h1>${}</h1>`, ('app'))
})

++

We passed@vue/reactivityRefer to vue's responsive API, usingeffect(Side Effects Function) andref(generate responsive data) makescountandrendererBuild connections, like thiscountWhen changes are made, it will be calledrendererProcess the rendering andh1Tag mounted to idappon the element. This is a simple renderer implementation.

3. Several operations involved in the renderer: mount, update, and uninstall

Suppose we already have a rendererrenderer

const renderer = createRenderer()

There is a data that describes the nodes of the elementvnode: Similar to the following structure

// type is type, children is child nodeconst vnode = { 
    type: 'h1', 
    children: 'hello' 
}

You can see that this is a hello text inside<h1>node

Then there will be several situations in the actual working process of the renderer:

(vnode, ('#app'))

The operation involved at this time isMount, just change the vnode to the DOM element and put it inside the app element

(newVNode, ('#app'))

becauseThere are already old nodes, so it cannot be simply mounted directly, it needs to beComparison of old and new nodes, find the part that needs to be updated and then change it. The operation at this time isrenew, In order to handle updates, there needs to be corresponding logic in the renderer.

(null, ('#app')) 

The new node is nullThis means that the renderer needs to be clearedappThe contents in the element, the operation involved at this time isuninstall

According to the processing of the above three situations, the renderer can be written as:

function createRenderer() {

  function patch(n1, n2, container) {

  }

  function render(vnode, container) {
    if (vnode) {
      // The new vnode exists, pass it to the patch function together with the old vnode for patching      patch(container._vnode, vnode, container)
    } else {
      if (container._vnode) {
        // The old vnode exists, and the new vnode does not exist, which means it is an unmount operation        // Just clear the DOM in the container         = ''
      }
    }
    //Storing vnode to container._vnode, that is, the old vnode in subsequent rendering    container._vnode = vnode
  }
  
  return {
    render
  }
}

Use createRenderer to repeat the above three renderings and analyze the process:

// First rendering(vnode, ('#app'))

// The second render(newVNode, ('#app'))

//The third render(null, ('#app'))
  • First rendering:vnodeBeing rendered and existscontainer._vnodemiddle;
  • Second rendering: old and newvnodeAll exist, need to bepatchUpdates in the function;
  • The third rendering: newvnodefornullJudge old nodescontainer._vnodeDoes it exist, if the old onevnodeIf present, the processing is uninstall;

4. How to implement a platform-independent renderer:

A general renderer should be platform-independent.That is, the rendering function of the renderer cannot be only effective in a certain platform.. To achieve this goal, we first implement a browser-based renderer and observe in which steps the browser-related API is used. Then we can consider it.Extract these APIs as configuration items, this allows you to implement a general renderer.

1. Implement a renderer that depends on the browser API

From the previous onevnodeAs an example:

const vnode = { 
    type: 'h1', 
    children: 'hello' 
}
  • typefor label type,
  • childrenis a child element.
    Based on the above implementation, we first improvepatchFunctions used to handle node mounts and updates
patch(container._vnode, vnode, container) 

Through the previous code, you can see thatpatchThe function will accept three parameters, namelyOld nodes, new nodes, and containers,We judge the old node parameters in it, so as to handle different operations: At present, only the mount situation is analyzed:

  function patch(n1, n2, container) {
    if (!n1) {
      mountElement(n2, container)
    } else {
      //
    }
  }

As shown in the above code:n1That is, when the old node does not exist, it is proved to be a mount operation, then it is called directlymountElementMounting the new node

  function mountElement(vnode, container) {
    const el = createElement()
    if (typeof  === 'string') {
       = 
    }
    (el)
  }

existmountElementWe have simply processed the mount logic:

1. UsecreateElementAccording to the vnodetypeto create the corresponding DOM type:like='h1'Then el is<h1></h1>

2. Make judgments:ifIt is a string type to prove that the child node is a text node, and the element is directly used.textContentAttribute setting element.

3. Call appendChild to add the processed elements to the target container. This mount is completed.

2. Implement a general renderer

The renderer implemented aboveThere is a dependency on the browser's API, among themappendChildcreateElementtextContentThese are APIs that require a browser environment to execute. If you want the renderer to be universal, you need to remove dependencies on these APIs.

Solution: Pass these APIs as configuration items into the renderer to create functions, so we can define it ourselvescreateRendererWhich APIs are used to perform rendering operations so that the renderer's work no longer depends on a certain platform.

const renderer2 = createRenderer({
  //Create an element  createElement(tag) {
    return { tag }
  },
  //Set the element text content  setElementText(el, text) {
    (`set up ${(el)} Text content:${text}`)
     = text
  },
  //Insert the element into the target node  insert(el, parent, anchor = null) {
     = el
  }
})

We put the logic and API of creating elements intocreateElementIn, put the API to set text content insetElementTextIn, put the API procedure of mounting the element to the containerinsertmiddle. Then use the incoming configuration to callmountElement

  function mountElement(vnode, container) {
    const el = createElement()
    if (typeof  === 'string') {
      setElementText(el, )
    }
    insert(el, container)
  }

This way we can pass increateRendererFunctionaloptionsConfiguration, what methods are used in different platforms to perform elements creation, processing of element content, and mounting of elements.
The entire rendering function is as follows:

function createRenderer(options) {

  const {
    createElement,
    insert,
    setElementText
  } = options

  function mountElement(vnode, container) {
    const el = createElement()
    if (typeof  === 'string') {
      setElementText(el, )
    }
    insert(el, container)
  }

  function patch(n1, n2, container) {
    if (!n1) {
      mountElement(n2, container)
    } else {
      //
    }
  }

  function render(vnode, container) {
		if (vnode) {
      // The new vnode exists, pass it to the patch function together with the old vnode for patching      patch(container._vnode, vnode, container)
    } else {
      if (container._vnode) {
        // The old vnode exists, and the new vnode does not exist, which means it is an unmount operation        // Just clear the DOM in the container         = ''
      }
    }
    //Storing vnode to container._vnode, that is, the old vnode in subsequent rendering    container._vnode = vnode
  }
  
  return {
    render
  }
}

Therefore, a simple renderer has been implemented, but in actual cases, the mount and update of elementsThere are more details that need to be processed, such as attributes, classes, and how to correctly mount events, how to improve the efficiency of renderer updates (diff algorithm), etc., these will be discussed in subsequent articles.

The above is a detailed explanation of the simple implementation of the renderer in Vue. For more information about Vue implementing the renderer, please pay attention to my other related articles!