Vue team released Vue 3.0 version on September 18, 2020 at 11:30 PM. For me, who likes to play around with the source code, my restless heart began to be restless. The source code of Vue2. The story of Vue3.0 is like an up-and-down suspense drama that makes people scratch their heads and drool. It’s not like yoda’s style.

Vue 3.0 — The One Piece was released with Releases of vUE 3.0

In this article we will start by comparing the creation of Vue2 and Vue3 application examples, finish by comparing the two architectural styles, and start with a line-by-line analysis in the next article.

Create an instance

Creating a new Vue application instance provides an existing DOM element on the page as a mount target for the Vue instance.

Vue2.x

var app = new Vue({ el: '#app', data: { message: 'Hello Vue! '}})Copy the code

Vue3.0

Vue.createApp(/* options */)
Vue.createApp(/* options */).mount('#app')
Copy the code

As you can see, vue.js 3.0 initializes the application in the same way as vue.js 2.x, essentially mounting the component to the DOM node with the ID app.

Let’s take a look at the internal implementation of Vue2. X:

(function(global, factory) { typeof exports === 'object' && typeof module ! == 'undefined' ? module.exports = factory() : typeof define === 'function' && define.amd ? define(factory) : (global.Vue = factory()); })(this, function() { function initMixin(Vue) { Vue.prototype._init = function(options) { //... / / option to merge the vm $options = mergeOptions (resolveConstructorOptions (vm) constructor), the options | | {}, vm); / /... $options.el) {vm.$mount(vm.$options.el); } } } var Vue = function(options) { this._init(options); Function mountComponent(vm, el, hydrating) {//... Function () {vm._update(vm._render(), hydrating); }; New Watcher(vm, updateComponent, noop, {before: function before() {if (vm._isMounted &&! vm._isDestroyed) { callHook(vm, 'beforeUpdate'); } } }, true /* isRenderWatcher */ ); / /... } function lifecycleMixin(Vue) { Vue.prototype._update = function(vnode, hydrating) { //... if (! prevVnode) { // initial render vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */ ); } else { // updates vm.$el = vm.__patch__(prevVnode, vnode); } / /... } } function createPatchFunction() { //... / / initial render | | data update diff algorithm return function patch () {/ /... Var patch = createPatchFunction({nodeOps: nodeOps, modules: modules}); Vue.prototype.__patch__ = inBrowser ? patch : noop; $prototype.$prototype.$prototype = function(); hydrating) { el = el && inBrowser ? query(el) : undefined; return mountComponent(this, el, hydrating) }; var mount = Vue.prototype.$mount; Prototype.$mount = function(el, hydrating) {//... Var ref = compileToFunctions(template, {shouldDecodeNewlines: shouldDecodeNewlines, shouldDecodeNewlinesForHref: shouldDecodeNewlinesForHref, delimiters: options.delimiters, comments: options.comments }, this); / /... return mount.call(this, el, hydrating) } initMixin(Vue); reurn Vue; });Copy the code

New Vue(/*options*****/) creates a program instance that passes the options object to _init for a series of initialization operations. Mount (vm.mount(vm.mount(vm. mount(vm.options.el)).

Template is compiled to Render Function during component mount and then mountComponent is called to compileToFunctions. MountComponent is called if the render function is written directly to the component.

The two key methods in the mountComponent function are the _render function, which returns the generated virtual node VNode, and the _update function, which renders the virtual node vm._render into the DOM.

_update calls __patch__ initial render & updates. Initial render refers to the initial render of a VNode object onto a real DOM tree.

When the data changes, a new VNode object is generated, the __Patch__ method is called, the newly generated VNode is compared with the old VNode, and the differences (changed nodes) are updated to the real DOM tree.

By default, reactive systems such as render() calls are shielded for dependency collection. Watcher render function observer tutorial.

Vue3 imports a createApp entry function, which is a function exposed by vue.js. Let’s look at its internal implementation: Vue.

const createApp = ((... args) => { const app = ensureRenderer().createApp(... args); / /... const { mount } = app; app.mount = (containerOrSelector) => { const container = normalizeContainer(containerOrSelector); if (! container) return; const component = app._component; if (! isFunction(component) && ! component.render && ! component.template) { component.template = container.innerHTML; } // clear content before mounting container.innerHTML = ''; const proxy = mount(container); / /... return proxy; }; return app; });Copy the code

As you can see from the code, createApp does two main things: create an app object and override the app.mount method. This approach is similar to the $mount design of Vue2. X. Let’s look at them in detail.

1. The app object

First we create an app object with ensureRenderer().createApp() : ensureRenderer();

const app = ensureRenderer().createApp(... args);Copy the code

EnsureRenderer () is used to create a renderer object that is “ensureRenderer” to its internal code:

let renderer; function createRenderer(options) { return baseCreateRenderer(options); } function baseCreateRenderer(options, createHydrationFns) {const patch = (/*... */) => {//... } function render(vnode, container) {return {render, createApp: createAppAPI(render) } } function ensureRenderer() { return renderer || (renderer = createRenderer(rendererOptions)); } function createAppAPI(render) {// createApp Prop Return function createApp(rootComponent, rootProps = null) {const app = {_component: rootComponent, _props: RootProps, mount(rootContainer) {// createVNode of rootComponent const vnode = createVNode(rootComponent, RootProps) // Vnode render(vnode, rootContainer) app._container = rootContainer return vnode.component.proxy } } return app } }Copy the code

Vue. CreateApp (/* options */) returns the app object in Vue3. Note that in createApp the mount method of the object returned by createAppAPI is redone, which is why we’ll talk about it later.

Why is it so complicated, you might ask? The last time I saw encapsulation using closures and function currification techniques was in ve2. X compileToFunctions.

CompileToFunctions, compiler entry functions, The purpose of this cumbersome design is to use createCompilerCreator to create platform-specific compileToFunctions that don’t contain any platform-specific logic. Vue2.5.1 source: github.com/vuejs/vue/b…

Back to the complexity of Vue3 createApp. We know from the code that a renderer can be created with a call to the ensureRenderer() function and that the renderer will not be created if the user relies only on the responsive package, so the code associated with the core rendering logic can be removed in a tree-shaking manner.

EnsureRenderer calls are actually triggered by the user when ensureRenderer is executed with createApp. If you import the Reactivity related API only from Vue without executing createApp, that is not the ensureRenderer implementation. No renderer is created, and the renderer-related code is removed by tree-shaking during packaging.

Some changes to the mount function

The mount method is overridden in Vue2 because Runtime+Compiler is required, while Runtime−only does not. The run-time only Compiler does not need a Compiler. The Runtime−only Compiler does not need a Compiler. The first call to the $mount method is “public mount method”. The second call to the $mount method is to add a compiler entry function for template compilation.

//"public mount method" Vue.prototype.$mount = function(el, hydrating) { el = el && inBrowser ? query(el) : undefined; return mountComponent(this, el, hydrating) }; var mount = Vue.prototype.$mount; Var ref = compileToFunctions(template, {//... var ref = compileToFunctions(template, {//... }, this); var render = ref.render; var staticRenderFns = ref.staticRenderFns; options.render = render; options.staticRenderFns = staticRenderFns; return mount.call(this, el, hydrating) };Copy the code

There are some changes to the mount method in Vue3, but for compatibility with Vue2 we still see some echoes of Vue2.

Vue3 mount methods focus is no longer the Runtime – only | | the Runtime + Compiler but to provide cross-platform rendering support, createApp function within the app. Mount method is a standard can be cross-platform component rendering process:

function createApp(rootComponent, rootProps = null) { const app = { _component: rootComponent, _props: RootProps, mount(rootContainer) {// createVNode of rootComponent const vnode = createVNode(rootComponent, RootProps) // Vnode render(vnode, rootContainer) app._container = rootContainer return vnode.component.proxy } } }Copy the code

The standard cross-platform rendering process is to create a VNode and then render a VNode. The rootContainer parameter can also be a different type of value, for example, a DOM object on the Web platform and a different type of value on other platforms, such as Weex and applets. Therefore, the code should not contain any platform-specific logic, which means that the execution logic of the code is platform-independent. So we need to rewrite this method externally to improve the rendering logic of the Web platform.

App. Mount rewrite:

const createApp = ((... args) => { const app = ensureRenderer().createApp(... args); / /... const { mount } = app; App. mount = (containerOrSelector) => {const container = normalizeContainer(containerOrSelector); if (! container) return; const component = app._component; if (! isFunction(component) && ! component.render && ! component.template) { component.template = container.innerHTML; } // clear content before mounting container.innerHTML = ''; // True const proxy = mount(container); container.removeAttribute('v-cloak'); container.setAttribute('data-v-app', ''); return proxy; }; return app; });Copy the code

Normalize the container with normalizeContainer, and then do an if judgment. If the component object does not define the render function and template template, take the innerHTML of the container as the component template content. It then empties the contents of the container before mounting it, and finally calls app.mount to follow the standard component rendering process.

In this case, the rewrite logic is Web platform-specific, so it is implemented externally. In addition, the goal is to give the user more flexibility in using the API and also to be compatible with the Vue. Js 2.x writing style, such as app.mount’s first argument supports both selector string and DOM object types.

Since app.mount is the start of the component rendering process, let’s focus on two things the core rendering process does: create a VNode and render a VNode.