Small knowledge, big challenge! This article is participating in the creation activity of “Essential Tips for Programmers”.
preface
I hope this article will help you deepen your understanding of Vue and can confidently say that you are proficient in Ve2 2/3. In addition, I also hope that friends passing by can help me to fill the gap 🤞.
Content mixed usage + principle + use carefully, suggest collection, slowly look.
The difference between
Life cycle changes
Overall, there is little change, but most of the names need + ON, and the function is similar. The Vue3 composite API needs to be introduced first. The Vue2 option API can be called directly, as shown below.
// vue3
<script setup>
import { onMounted } from 'vue'
onMounted(() = >{... })// Can split different logic into multiple onMounted, still execute sequentially, not overwrite
onMounted(() = >{... })</script>
// vue2
<script>
export default {
mounted(){... }},</script>
Copy the code
Common life cycle tables are shown below.
Vue2.x | Vue3 |
---|---|
beforeCreate | Not needed* |
created | Not needed* |
beforeMount | onBeforeMount |
mounted | onMounted |
beforeUpdate | onBeforeUpdate |
updated | onUpdated |
beforeDestroy | onBeforeUnmount |
destroyed | onUnmounted |
Tips: Setup runs around beforeCreate and Created lifecycle hooks, so it doesn’t need to be explicitly defined.
More than a root node
Vue3 supports multiple node components, also known as fragments.
In Vue2, when writing a page, we need to wrap the component in
<template>
<div>
<header>.</header>
<main>.</main>
<footer>.</footer>
</div>
</template>
Copy the code
Vue3, we can component contains multiple root nodes, can write one less layer, niceeee!
<template>
<header>.</header>
<main>.</main>
<footer>.</footer>
</template>
Copy the code
Asynchronous components
Vue3 provides Suspense components that allow applications to render content at the bottom of the loop while waiting for asynchronous components, such as loading, for a smoother user experience. To use it, declare it in the template and include two naming slots: default and fallback. Suspense ensures that the default slot is displayed when asynchronous content is finished loading and the fallback slot is used as the loading state.
<tempalte>
<suspense>
<template #default>
<todo-list />
</template>
<template #fallback>
<div>
Loading...
</div>
</template>
</suspense>
</template>
Copy the code
If you want to invoke asynchronous requests in setup, you need to add the async keyword before setup. Async setup() is used without a suspense boundary.
Solution: Wrap a layer of Suspense components around the current component in the parent page.
Teleport
Vue3 provides a Teleport component to move part of the DOM to a location outside the Vue app. For example, Dialog components are common in projects.
<button @click="dialogVisible = true">Click on the</button>
<teleport to="body">
<div class="dialog" v-if="dialogVisible">
</div>
</teleport>
Copy the code
Modular API
Vue2 is an Option API. A logic is scattered in different parts of the file (data, props, computed, watch, lifecycle functions, etc.), which makes the code less readable and requires jumping up and down the file. Vue3’s Composition API solves this problem by writing together the contents of the same logic.
In addition to improving code readability and cohesion, the composite API provides a perfect solution for logical reuse, such as 🌰, as shown in the common mouse coordinate example below.
// main.vue
<template>
<span>mouse position {{x}} {{y}}</span>
</template>
<script setup>
import { ref } from 'vue'
import useMousePosition from './useMousePosition'
const {x, y} = useMousePosition()
}
</script>
Copy the code
// useMousePosition.js
import { ref, onMounted, onUnmounted } from 'vue'
function useMousePosition() {
let x = ref(0)
let y = ref(0)
function update(e) {
x.value = e.pageX
y.value = e.pageY
}
onMounted(() = > {
window.addEventListener('mousemove', update)
})
onUnmounted(() = > {
window.removeEventListener('mousemove', update)
})
return {
x,
y
}
}
</script>
Copy the code
This solution solves the naming conflict hidden danger of Vue2 Mixin, resulting in unclear dependency relationship and inflexible configuration and use among different components.
Response principle
The principle of Vue2 response is based on Object.defineProperty; Vue3 is based on Proxy.
Object.defineProperty
Basic usage: Directly define new properties or modify existing properties on an object and return the object. Writable and Value do not coexist with getters and setters.
let obj = {}
let name = 'jin line'
Object.defineProperty(obj, 'name', {
enumerable: true.// enumerable (whether to pass for... In or object.keys ()
configurable: true.// Configurable (can delete using delete, can set the property again)
// value: ", // Any type of value, default undefined
// writable: true, // writable
get: function() {
return name
},
set: function(value) {
name = value
}
})
Copy the code
Carry Vue2 core source code, slightly deleted.
function defineReactive(obj, key, val) {
// dep with one key
const dep = new Dep()
// Get the property descriptor for key and return if it is not configurable
const property = Object.getOwnPropertyDescriptor(obj, key)
if (property && property.configurable === false) { return }
// Get getters and setters, and get val values
const getter = property && property.get
const setter = property && property.set
if((! getter || setter) &&arguments.length === 2) { val = obj[key] }
// Make sure all keys in the object are observed
let childOb = observe(val)
Object.defineProperty(obj, key, {
enumerable: true.configurable: true.// get hijacks obj[key] for dependency collection
get: function reactiveGetter() {
const value = getter ? getter.call(obj) : val
if(Dep.target) {
// Rely on collection
dep.depend()
if(childOb) {
// For nested objects, rely on collection
childOb.dep.depend()
// Trigger array responsiveness
if(Array.isArray(value)) {
dependArray(value)
}
}
}
}
return value
})
// set update obj[key]
set: function reactiveSetter(newVal) {...if(setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
// Set the new value to be responsive
childOb = observe(val)
// Rely on notification updates
dep.notify()
}
}
Copy the code
So why did Vue3 abandon it? There must be some flaws.
Main cause: Cannot listen for new or deleted elements of an object or array. Vue2 scheme: The common array prototype methods push, POP, shift, unshift, splice, sort, reverse hack processing; Vue. Set listens to new object/array properties. Object add/delete response, you can also new a new object, new will merge the new properties and the old object; Delete deeply copies the deleted object to the new object.
DefineOProperty allows you to listen on existing elements of an array, but Vue2 does not provide it because of performance issues.
Proxy
Proxy is a new feature in ES6. It intercepts the behavior of the target object through the second parameter handler. Limitations are eliminated by providing language-wide responsiveness compared to Object.defineProperty. But abandoned on compatibility (below Internet Explorer 11)
limitations
- Add and delete objects/arrays.
- Monitor.length changes.
- WeakMap, Set, WeakMap, WeakSet support.
Basic usage: Create proxies for objects to intercept and customize basic operations.
const handler = {
get: function(obj, prop) {
return prop in obj ? obj[prop] : ' '
},
set: function() {},... }Copy the code
Move the Vue3 source react. ts file
function createReactiveObject(target, isReadOnly, baseHandlers, collectionHandlers, proxyMap) {...// collectionHandlers: Handles Map, Set, WeakMap, WeakSet
// baseHandlers: handles arrays and objects
const proxy = new Proxy(
target,
targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
)
proxyMap.set(target, proxy)
return proxy
}
Copy the code
In the basehandlers.ts example, reflect.get is used instead of target[key] because the receiver parameter points this to the object in the getter call instead of the object in the Proxy construct.
// Rely on collection
function createGetter(isReadonly = false, shallow = false) {
return function get(target: Target, key: string | symbol, receiver: object) {...// Array type
const targetIsArray = isArray(target)
if(! isReadonly && targetIsArray && hasOwn(arrayInstrumentations, key)) {return Reflect.get(arrayInstrumentations, key, receiver)
}
// Non-array type
const res = Reflect.get(target, key, receiver);
// The object is called recursively
if (isObject(res)) {
return isReadonly ? readonly(res) : reactive(res)
}
return res
}
}
// Send updates
function createSetter() {
return function set(target: Target, key: string | symbol, value: unknown, receiver: Object) {
value = toRaw(value)
oldValue = target[key]
// The ref data is dependent on the set value, so it can be assigned to return
if(! isArray(target) && isRef(oldValue) && ! isRef(value)) { oldValue.value = valuereturn true
}
// Whether the object has a key There is a key set, but no key add
const hadKey = hasOwn(target, key)
const result = Reflect.set(target, key, value, receiver)
if (target === toRaw(receiver)) {
if(! hadKey) { trigger(target, TriggerOpTypes.ADD, key, value) }else if (hasChanged(value, oldValue)) {
trigger(target, TriggerOpTypes.SET, key, value, oldValue)
}
}
return result
}
}
Copy the code
Virtual DOM
Compared with Vue2, patchFlag fields are added to the virtual DOM of Vue3. Let’s see with the help of the Vue3 Template Explorer.
<div id="app">
<h1>Technology to touch the fish</h1>
<p>It's a nice day today</p>
<div>{{name}}</div>
</div>
Copy the code
The rendering function is as follows.
import { createElementVNode as _createElementVNode, toDisplayString as _toDisplayString, openBlock as _openBlock, createElementBlock as _createElementBlock, pushScopeId as _pushScopeId, popScopeId as _popScopeId } from "vue"
const _withScopeId = n= > (_pushScopeId("scope-id"),n=n(),_popScopeId(),n)
const _hoisted_1 = { id: "app" }
const _hoisted_2 = /*#__PURE__*/ _withScopeId(() = > /*#__PURE__*/_createElementVNode("h1".null."Technology", -1 /* HOISTED */))
const _hoisted_3 = /*#__PURE__*/ _withScopeId(() = > /*#__PURE__*/_createElementVNode("p".null."It's a beautiful day.", -1 /* HOISTED */))
export function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createElementBlock("div", _hoisted_1, [
_hoisted_2,
_hoisted_3,
_createElementVNode("div".null, _toDisplayString(_ctx.name), 1 /* TEXT */)))}Copy the code
Note that the patchFlag field type is the fourth parameter of the third _createElementVNode, as shown below. 1 indicates that the node is a dynamic text node, so in the diff process, only text alignment is required, and class, style, etc., need not be concerned. In addition, all static nodes are found to be saved as a variable for static promotion, which can be referenced directly during re-rendering without re-creation.
export const enum PatchFlags {
TEXT = 1.// Dynamic text content
CLASS = 1 << 1.// Dynamic class name
STYLE = 1 << 2.// Dynamic style
PROPS = 1 << 3.// Dynamic attributes, without class name or style
FULL_PROPS = 1 << 4.// Has a dynamic key property. When the key changes, a full diff comparison is required
HYDRATE_EVENTS = 1 << 5.// A node with a listening event
STABLE_FRAGMENT = 1 << 6.// Fragment that does not change the order of child nodes
KEYED_FRAGMENT = 1 << 7.// Fragment or partial element node with key attribute
UNKEYED_FRAGMENT = 1 << 8.// The child node does not have a key fragment
NEED_PATCH = 1 << 9.// Only non-props comparisons will be made
DYNAMIC_SLOTS = 1 << 10.// Dynamic slots
HOISTED = -1.// Static node, the diff phase ignores its children
BAIL = -2 // means diff should end
}
Copy the code
Event caching
Vue3’s cacheHandler caches our event after the first rendering. Compared to Vue2, you don’t need to pass a new function every time you render. Add a click event.
<div id="app">
<h1>Technology to touch the fish</h1>
<p>It's a nice day today</p>
<div>{{name}}</div>
<span onCLick="() = > {}"><span>
</div>
Copy the code
The rendering function looks like this
import { createElementVNode as _createElementVNode, toDisplayString as _toDisplayString, openBlock as _openBlock, createElementBlock as _createElementBlock, pushScopeId as _pushScopeId, popScopeId as _popScopeId } from "vue"
const _withScopeId = n= > (_pushScopeId("scope-id"),n=n(),_popScopeId(),n)
const _hoisted_1 = { id: "app" }
const _hoisted_2 = /*#__PURE__*/ _withScopeId(() = > /*#__PURE__*/_createElementVNode("h1".null."Technology", -1 /* HOISTED */))
const _hoisted_3 = /*#__PURE__*/ _withScopeId(() = > /*#__PURE__*/_createElementVNode("p".null."It's a beautiful day.", -1 /* HOISTED */))
const _hoisted_4 = /*#__PURE__*/ _withScopeId(() = > /*#__PURE__*/_createElementVNode("span", { onCLick: "() = > {}"},/*#__PURE__*/_createElementVNode("span"] -1 /* HOISTED */))
export function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createElementBlock("div", _hoisted_1, [
_hoisted_2,
_hoisted_3,
_createElementVNode("div".null, _toDisplayString(_ctx.name), 1 /* TEXT */),
_hoisted_4
]))
}
Copy the code
The Diff optimization
Vue3 patchChildren source code Based on the above and the source code, patchFlag helps distinguish static nodes and different types of dynamic nodes in diFF. Reduce the comparison of nodes and their attributes to a certain extent.
function patchChildren(n1, n2, container, parentAnchor, parentComponent, parentSuspense, isSVG, optimized) {
// Get old and new child nodes
const c1 = n1 && n1.children
const c2 = n2.children
const prevShapeFlag = n1 ? n1.shapeFlag : 0
const { patchFlag, shapeFlag } = n2
// Process patchFlag greater than 0
if(patchFlag > 0) {
if(patchFlag && PatchFlags.KEYED_FRAGMENT) {
/ / is the key
patchKeyedChildren()
return
} els if(patchFlag && PatchFlags.UNKEYED_FRAGMENT) {
// There is no key
patchUnkeyedChildren()
return}}// The match is a text node (static) : Remove the old node and set the text node
if(shapeFlag && ShapeFlags.TEXT_CHILDREN) {
if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {
unmountChildren(c1 as VNode[], parentComponent, parentSuspense)
}
if(c2 ! == c1) { hostSetElementText(container, c2as string)
}
} else {
// Match old and new vNodes as arrays, then full comparison; Otherwise, remove all current nodes
if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {
if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
patchKeyedChildren(c1, c2, container, anchor, parentComponent, parentSuspense,...)
} else {
unmountChildren(c1 as VNode[], parentComponent, parentSuspense, true)}}else {
if(prevShapeFlag & ShapeFlags.TEXT_CHILDREN) {
hostSetElementText(container, ' ')}if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
mountChildren(c2 asVNodeArrayChildren, container,anchor,parentComponent,...) }}}}Copy the code
PatchUnkeyedChildren source code below.
function patchUnkeyedChildren(c1, c2, container, parentAnchor, parentComponent, parentSuspense, isSVG, optimized) {
c1 = c1 || EMPTY_ARR
c2 = c2 || EMPTY_ARR
const oldLength = c1.length
const newLength = c2.length
const commonLength = Math.min(oldLength, newLength)
let i
for(i = 0; i < commonLength; i++) {
// Clone a new Vnode if it is already mounted. Otherwise, create a new Vnode
const nextChild = (c2[i] = optimized ? cloneIfMounted(c2[i] as Vnode)) : normalizeVnode(c2[i])
patch()
}
if(oldLength > newLength) {
// Remove redundant nodes
unmountedChildren()
} else {
// Create new nodes
mountChildren()
}
}
Copy the code
PatchKeyedChildren source code is as follows, using the longest increasing sequence algorithm idea.
function patchKeyedChildren(c1, c2, container, parentAnchor, parentComponent, parentSuspense, isSVG, optimized) {
let i = 0;
const e1 = c1.length - 1
const e2 = c2.length - 1
const l2 = c2.length
If the new and old nodes are the same, execute patch to update the difference. Otherwise, break out of the loop
while(i <= e1 && i <= e2) {
const n1 = c1[i]
const n2 = c2[i]
if(isSameVnodeType) {
patch(n1, n2, container, parentAnchor, parentComponent, parentSuspense, isSvg, optimized)
} else {
break
}
i++
}
If the new and old nodes are the same node, execute patch to update the difference. Otherwise, break out of the loop
while(i <= e1 && i <= e2) {
const n1 = c1[e1]
const n2 = c2[e2]
if(isSameVnodeType) {
patch(n1, n2, container, parentAnchor, parentComponent, parentSuspense, isSvg, optimized)
} else {
break
}
e1--
e2--
}
// Only nodes need to be added
if(i > e1) {
if(i <= e2) {
const nextPos = e2 + 1
const anchor = nextPos < l2 ? c2[nextPos] : parentAnchor
while(i <= e2) {
patch(null, c2[i], container, parentAnchor, parentComponent, parentSuspense, isSvg, optimized)
}
}
}
// Only nodes need to be deleted
else if(i > e2) {
while(i <= e1) {
unmount(c1[i], parentComponent, parentSuspense, true)}}// The old and new nodes are not traversed
// [i ... e1 + 1]: a b [c d e] f g
// [i ... e2 + 1]: a b [e d c h] f g
// i = 2, e1 = 4, e2 = 5
else {
const s1 = i
const s2 = i
{e: 2, d: 3, c: 4, h: 5}
const keyToNewIndexMap = new Map(a)for (i = s2; i <= e2; i++) {
const nextChild = (c2[i] = optimized
? cloneIfMounted(c2[i] as VNode)
: normalizeVNode(c2[i]))
if(nextChild.key ! =null) {
if (__DEV__ && keyToNewIndexMap.has(nextChild.key)) {
warn(
`Duplicate keys found during update:`.JSON.stringify(nextChild.key),
`Make sure keys are unique.`
)
}
keyToNewIndexMap.set(nextChild.key, i)
}
}
}
let j = 0
// Record the number of new VNodes to be patched
let patched = 0
// Length of the remaining Vnode
const toBePatched = e2 - s2 + 1
// Whether to move the identifier
let moved = false
let maxNewindexSoFar = 0
// Initialize the correspondence between old and new nodes (for subsequent maximum-incrementing sequence algorithms)
const newIndexToOldIndexMap = new Array(toBePatched)
for (i = 0; i < toBePatched; i++) newIndexToOldIndexMap[i] = 0
// Traverses the remaining nodes of the old Vnode
for (i = s1; i <= e1; i++) {
const prevChild = c1[i]
// Indicates that all new VNodes have been patched. Remove the remaining vNodes
if (patched >= toBePatched) {
unmount(prevChild, parentComponent, parentSuspense, true)
continue
}
let newIndex
// If the old Vnode has a key, obtain it from keyToNewIndexMap
if(prevChild.key ! =null) {
newIndex = keyToNewIndexMap.get(prevChild.key)
// If the old Vnode does not have a key, the new Vnode is iterated to obtain the key
} else {
for (j = s2; j <= e2; j++) {
if (newIndexToOldIndexMap[j - s2] === 0 && isSameVNodeType(prevChild, c2[j] as VNode)){
newIndex = j
break}}}// Delete and update the node
// The new Vnode has no current node
if (newIndex === undefined) {
unmount(prevChild, parentComponent, parentSuspense, true)}else {
// The subscript position of the old Vnode + 1 is stored in the Map of the new Vnode
// The + 1 processing is used to prevent the first index of the array from being 0, which means that a new node needs to be created
newIndexToOldIndexMap[newIndex - s2] = i + 1
// If it is not continuously incremented, it means that it needs to move
if (newIndex >= maxNewIndexSoFar) {
maxNewIndexSoFar = newIndex
} else {
moved = true
}
patch(prevChild,c2[newIndex],...)
patched++
}
}
NewIndexToOldIndexMap = {0:5, 1:4, 2:3, 3:0}
// Create or move a node
const increasingNewIndexSequence = moved
// Get the longest increment sequence
? getSequence(newIndexToOldIndexMap)
: EMPTY_ARR
j = increasingNewIndexSequence.length - 1
for (i = toBePatched - 1; i >= 0; i--) {
const nextIndex = s2 + i
const nextChild = c2[nextIndex] as VNode
const anchor = extIndex + 1 < l2 ? (c2[nextIndex + 1] as VNode).el : parentAnchor
// 0 Create a Vnode
if (newIndexToOldIndexMap[i] === 0) {
patch(null,nextChild,...)
} else if (moved) {
// Move the node
if (j < 0|| i ! == increasingNewIndexSequence[j]) { move(nextChild, container, anchor, MoveType.REORDER) }else {
j--
}
}
}
}
Copy the code
Packaging optimization
Tree-shaking: Concepts in module packaging webpack, rollup, etc. Remove unreferenced code in the JavaScript context. It relies primarily on import and export statements to detect whether code modules are exported, imported, and used by JavaScript files.
Taking nextTick as an example, in Vue2 the global API is exposed on the Vue instance and cannot be removed by tree-shaking even if it is not used.
import Vue from 'vue'
Vue.nextTick(() = > {
// Something DOM related
})
Copy the code
Vue3 has been refactored for both global and internal apis with tree-shaking support in mind. As a result, the global API can now only be accessed as named exports of ES module builds.
import { nextTick } from 'vue'
nextTick(() = > {
// Something DOM related
})
Copy the code
With this change, unused apis in Vue applications will be eliminated from the final bundle for optimal file size, as long as the module binder supports tree-shaking. The global apis affected by this change are as follows.
- Vue.nextTick
- Ue. Observable (replaced by ue. Reactive)
- Vue.version
- Vue.compile (full build only)
- Vue.set (build only)
- Vue.delete (build only)
The internal API also has tags and instructions such as Transition and V-model exported with names. Only when the program is actually used will it be bundled.
According to UVU live, today’s Vue3 is only 22.5 KB packed with all operating functions, which is much lighter than Vue2.
Custom render API
The createApp provided by Vue3 maps template to HTML by default. However, if you want to generate a canvas, you need to customize the render generator using the Custom Renderer API.
// Custom run-time render function
import { createApp } from './runtime-render'
import App from './src/App'
createApp(App).mount('#app')
Copy the code
TypeScript support
Vue3 is rewritten by TS and has better TypeScript support than Vue2.
- Vue2
Option API
In option is a simple object, while TS is a type system, object-oriented syntax, not particularly matching. - Vue2 need
vue-class-component
Enhanced VUE native components are also requiredvue-property-decorator
Add more decorators combined with Vue features, which are cumbersome to write.
The surrounding
For details about the new syntax of the Vue3 Composition API, see the official migration documentation for links to ~.
Vue - cli 4.5.0
Vue Router 4.0
Vuex 4.0
Element plus
Vite
reference
Vue3.0 performance optimization to rewrite virtual Dom remember a question to think about: Vue3 is a Virtual Dom Diff array, and Vue 2 is a Virtual Dom Diff array. Vue3 is a Virtual Dom Diff array