The opening

As a front-end engineer, you’ve heard the question, “What’s the difference between a Vue and a React?” Indeed, Both Vue and React are currently the most popular and ecological frameworks, and their overall functionality is very similar.

In this regard, we organized a diff theme sharing to discuss the similarities and differences between Vue and React based on the same function. This month’s theme will give you a better understanding of the VUE and React frameworks from the following perspectives: component and logic reuse, diff algorithms, build and run, and render.

React and VUE both promote the concept of component development, so we will start with components in the first part of this article, without saying too much, directly into the main topic: VUE components and logical reuse of source code parsing.

The body of the

The component and logic reuse of VUE is described in three functional points:

  • Component reuse using V-slot, through the source code analysis of its working principle
  • Logic reuse mixins, four merge strategies for source code parsing
  • A better Composition API for components: Composition

Source code parsing – Master the full picture of Vue

In order to have a deeper understanding of the design idea of VUE, let’s first understand the overall picture of VUE:

In 2013, Yu, who works at Google, developed a lightweight framework inspired by Angular, originally named Seed.

October 26, 2015, 1.0.0 Evangelion was the first milestone in the history of Vue. Vue-router, Vuex, and VUe-CLI were released in the same year, marking vUE’s evolution from a view-layer library to an incremental framework.

10.01, 2016, 2.0.0 was the second major milestone, incorporating the React virtual Dom scheme and supporting server-side rendering. Since the release of Vue 2.0, Vue has become a hot topic in the front-end world.

Vue2 source code:

Vue2 source code structure

Vue2 Logic:

Vue2 logical relationship

2019.02.05, Vue released 2.6.0, which is a continuation of the previous version, after which 3.0.0 will be released.

On December 5, 2019, Amid much anticipation, Yu Yu Creek released the source code for Vue 3, which is currently in Alpha.

Vue3 source code structure

Vue3 source code structure

Vue3 Logical relationship

Vue3 Logical relationship

At this point, we have an idea of the source code structure and logical relationships, and we will analyze the source code.

Components reuse v-slot

The essence of a component is an AST tree. Vue components reuse V-slots, including anonymous slots, named slots, and scoped slots. How do they operate in the Vue ecosystem?

I summarize it in the following three steps:

  • First, proceedinitRenderInitialize the
  • Then, proceedrederSlotmount
  • Finally, proceedtemplatecompile

Working principle of V-slot

Init (vue2) init (vue2) init (vue2) init (vue2) init (vue2)

Function.prototype._init = function(){... vm._self = vm initLifecycle(vm) initEvents(vm) initRender(vm) callHook(vm, 'beforeCreate') initInjections(vm) // resolve injections before data/props initState(vm) initProvide(vm) // resolve provide after data/props callHook(vm, 'created') ... }Copy the code
  1. Initialization: ininitRenderIn, we see$slotswith$scopedSlotsMounted to vDOM and into slotchildrenI’m going to iterate, and I’m going to combineslotsObject and return.
// initRender initializes initRender (vm: Component) {... vm.$slots = resolveSlots(options._renderChildren, renderContext) vm.$scopedSlots = emptyObject ... }Copy the code
// resolveSlots is iterated and grouped into slots objects and returns function resolveSlots (children:? Array<VNode>, context: ? Component ): { [key: string]: Array<VNode> } { const slots = {} for (let i = 0, l = children.length; i < l; i++) { const child = children[i] ... if ((child.context === context || child.fnContext === context) && data && data.slot ! = null) {/ / named slot const name = data. The slot const slot = (slots [name] | | (slots [name] = [])) if (the child) tag = = = 'template')  { slot.push.apply(slot, Child. Children | | [])} else {/ / anonymous slot slot. Push (child)}} else {(slots. The default | | (slots. Default = [])). Push (child)} . return slotsCopy the code
  1. Mount: Step 1 generates the Slots object, and then passesrenderSlotFunction, which handles objects or functions, respectively, and generates Nodes.
Export function renderSlot (renderSlot name, anonymous, default name: string, fallbackRender:? ((() => Array<VNode>) | Array<VNode>), props: ? Object, bindObject: ? Object ): ? Array<VNode> { ... const target = props && props.slot if (target) { return this.$createElement('template', { slot: target }, nodes) } else { return nodes } ... }Copy the code

3. Compilation: Compile and render nodes to the page, which will be explained in the later compilation and rendering section, not expanded here.

In Vue, V-slot can be used for component reuse. For logical reuse, mixins are used.

Mixins four merge strategies

Mixins define some common methods or calculation properties, and then mix them into components for easy management and unified modification to achieve logical reuse. So when did it start merging? How did they merge?

Read the source code, draw a picture, we have a sneak peek.

Next, we go to src-core with these two questions in mind. We see that initGlobalAPI(Vue) is executed in the first line of the entry file, and that some global objects and methods are handled and mounted to Vue in this function.

export function initGlobalAPI (Vue: GlobalAPI) {
  ...
  initUse(Vue)
  initMixin(Vue)
  initExtend(Vue)
  ...
Copy the code

We can see initMixin(Vue) above. The first problem is solved and minxin is initialized here:

export function initMixin (Vue: GlobalAPI) {
  Vue.mixin = function (mixin: Object) {
    this.options = mergeOptions(this.options, mixin)
    return this
  }
}
Copy the code

How will minxin merge internally next? Let’s go to mergeOptions:

  • First judge whether there are mixins hanging in mixins, then recursively merge, and finally form a layer of nesting relationship.
  • Priority traversalparentKey, iterate againchildThe key of the
  • Call the correspondingstrats[XXX]Method to merge
export function mergeOptions ( parent: Object, child: Object, vm? : Component ): Object { ... If (child.mixins) {for (let I = 0, l = child.mixins.length; i < l; i++) { parent = mergeOptions(parent, child.mixins[i], vm) } } ... Const options = {} let key for (key in parent) {mergeField(key)} for (key in child) {if (! hasOwn(parent, Key)) {mergeField (key)}} / / call the corresponding staras [key] the function mergeField (key) {const strat = strats [key] | | defaultStrat options[key] = strat(parent[key], child[key], vm, key) } return options ... }Copy the code

Strats [key] : strats[key] : strats[key] : strats[key] : strats[key]

  1. strats.data, if there is no value in data, it will be re-set, if there is, it will be merged, and finally return the processed data object.
strats.data = function(parentVal, childVal, vm) { return mergeDataOrFn( parentVal, childVal, vm ) }; // mergeDataOrFn resets if (! to.hasOwnProperty(key)) { set(to, key, fromVal); } else if (typeof toVal =="object" && typeof fromVal =="object") {mergeData(toVal, fromVal); }Copy the code
  1. strats.watchLIFECYCLE_HOOKSThe lifecycle execution logic is the same, storing the functions in a new array queue and traversing them in order. The order of execution is global mixin -> mixin of component mixin -> component mixin -> component options
LIFECYCLE_HOOKS. ForEach (hook => {strats[hook] = mergeHook}) const res = childVal? parentVal ? parentVal.concat(childVal) : Array.isArray(childVal) ? childVal : [childVal] : parentVal return resCopy the code
  1. ASSET_TYPESIncluded in theComponent, directive, filterThey are superimposed by a chain of prototypes.
ASSET_TYPES. ForEach (function (type) {strats[type + 's'] = mergeAssets}) // mergeAssets var res = Object.create(parentVal || null); if (childVal) { for (var key in childVal) { res[key] = childVal[key]; } } return resCopy the code
  1. strats.props = strats.methods = strats.inject = strats.computedStudent: Direct substitution.
if (! parentVal) return childVal const ret = Object.create(null) extend(ret, parentVal) if (childVal) extend(ret, childVal) return retCopy the code

As we all know, with the complexity of a project’s structure, referring to a large number of mixins can introduce some usage drawbacks, such as implicit dependencies, naming conflicts, and invasions.

As a result, a better composite API emerged in VUE3, making component and logic reuse clearer and more readable.

A Better composite API: A Primer on Composition

In my opinion, the biggest changes of VUe3 compared to VUe2 are constructors, reactive expressions, and the concept of Composition Api.

  1. Let’s start with constructors, which are required in VUe2new Vue()To create the root instance and initialize the data and methods; Vue3 uses it directlycreateAppTo create objects, remove the Vue constructor, avoid unnecessary functionality integration, and reduce volume.
// vue2
const app = new Vue(options);
app.$mount("#app");
Vue.use();
// vue3
const app = createApp(app);
app.$mount("#app");
app.use();
Copy the code
  1. Vue3 uses Proxy to replace Object. DefineProperty hijacking of Vue2, and the responsive module is removed and can be used independently, that isreactivityThis will be covered in a later chapter, but I won’t expand it here.
  2. The main changes to the Composition Api are:

Composition Api makes componentization more cohesive, combining disparate logic together, and splitting individual functional logic into separate files. In writing, it is closer to the pure function idea of React.

Setup is a new function added to vue3, which is the entry point for vue3 component instantiation. Data and methods cannot be used in this function, so this cannot be used either, depending on when it is called. Setup accepts two parameters, props and context, to receive the values passed in by the external component. So when is setup registered?

By executing the createApp unit test, let’s go to the source package-Runtime-core

// 1- ensureRenderer to createApp objects const createApp = ((" ensureRenderer ")) args) => { const app = ensureRenderer().createApp(... The args)} / / 2 - ensureRenderer createRenderer rendering function called function ensureRenderer () {return the renderer | | (= the renderer createRenderer<Node, Element>(rendererOptions))} // 3- createRenderer function createRenderer(options: RendererOptions<HostNode, HostElement>) { return baseCreateRenderer<HostNode, HostElement>(options)} // 4-basecreaterenderer diff and patch the vNode to the container. Function baseCreateRenderer(options: RendererOptions, createHydrationFns? : typeof createHydrationFunctions ): any { ... return { render, hydrate, createApp: createAppAPI(render, hydrate) } ... // 5- patch calls processComponent, mountComponent, setupComponent, attributes, slots, and setup. function setupComponent() { const propsOptions = Comp.props resolveProps(instance, initialVNode.props, propsOptions) resolveSlots(instance, initialVNode.children) setupStatefulComponent(instance, HandleSetupResult, if it is a function, assigns to the instance object render, if it is an object, creates a response. export function handleSetupResult( instance: ComponentInternalInstance, setupResult: unknown, parentSuspense: SuspenseBoundary | null ) { if (isFunction(setupResult)) { instance.render = setupResult as RenderFunction } else if (isObject(setupResult)) { instance.renderContext = reactive(setupResult) } finishComponentSetup(instance, parentSuspense) }Copy the code

Finally, let’s take a look at the comparison of Vue2 and Vue3 in use:

// vue2 // mixins.js export default { data() { return { x:0, y:0 } }, methods: { update(e) => { this.x = e.pageX this.y = e.pageY } }, mounted() { window.addEventListener('mousemove', update) }, unmounted() { window.removeEventListener('mousemove', update) }, } ... // Component usage: where does XYZ come from? import appMixin from './mixins.js' export default { data() { return { x:0, y:0, z:10 } } } template: `<div>{{ x }} {{ y }} {{ z }}</div>`Copy the code
// vue3 function useMouse() { const x = ref(0) const y = ref(0) const update = e => { x.value = e.pageX y.value = e.pageY } onMounted(() => { window.addEventListener('mousemove', update) }) onUnmounted(() => { window.removeEventListener('mousemove', update) }) return { x, y } } ... // Use this function in the Component const {x, Const {z} = useOtherLogic() return {x, y, z}}, template: `<div>{{ x }} {{ y }} {{ z }}</div>` }Copy the code

For an intuitive feeling of the change in writing, let’s go straight to the figure above:

The options with omponsition diff

conclusion

To review, this article introduces Vue’s component multiplexing V-slot to logical multiplexing mixins, and then to the Compositon API in VUe3. Through the analysis of the source code, I learned that Vue2 was transformed into Vue3, and Vue and React are more and more similar, because the underlying logic of the technology is the same, and the iterative upgrade of the framework is also to better serve the developers.

I have read such a metaphor, I think it is very interesting to share with you:

Vue is automatic transmission; React is a manual shift

Self-interpretation: The encapsulation idea of Vue makes developers get started faster; React is closer to the bottom and requires a few more steps. They are all our means of transportation, taking us away from primitive walking at a faster pace and more efficiently.

The next post will cover logic reuse in React.

Refer to the article

  • Vue history introduction: blog.csdn.net/h64257772/a…
  • Mixins Vue source 】 【 source code parsing: zhuanlan.zhihu.com/p/95838174
  • Vue3.0 new features and experience summary: juejin.cn/post/694045…