The introduction
Vue3 has been released for some time, and I have time to check it out recently, so I hereby summarize. This paper will be elaborated from the following perspectives:
- Full support for TypeScript
- New data responsiveness
- Performance optimization
- Tree Shaking
- Composition API
Full support for TypeScript
Vue2 is notoriously unfriendly to TypeScript support.
We usually need to install vue-class-Component and vue-property-decorator separately. If Vuex is introduced in a project, we also need to add third-party plug-ins such as vuex-class, plus Webpack file configuration. Making TypeScript too expensive to introduce in Vue2.
This has been greatly improved in Vue3.
-
On installation, the Vue Cli has TypeScript tool support built directly into it, and does not require a separate installation.
-
In the official announcement of the NPM package
As applications grow, the static typing system can help prevent many potential runtime errors, which is why Vue3 is written in TypeScript. This means that using TypeScript in Vue3 doesn’t require any additional tools — it’s supported as a first-class citizen.
-
Define Vue components
import { defineComponent, PropType } from 'vue'; interface Student { name: string, address: string, age: number, } const Component = defineComponent({ // Type inference is enabled props: { success: { type: String }, callback: { type: Function as PropType<() = > void>},student: { type: Object as PropType<Student>, required: true, }, }, data () { return { message: 'Vue3 code style'}; },computed: { reversedMessage(): string { return this.message.split(' ').reverse().join(' '); }},})Copy the code
New data responsiveness
I believe that students who have interviewed Vue will be asked this question by the interviewer. How does Vue2 complete the data response?
Object.defineproperty is a method of data hijacking that recursively converts all attributes of an Object to get and set methods, thus intercepting access and changes to attributes of the Object.
Have you considered the downsides to this?
- Performance problems with large data: In the Observer approach, all keys are looping and recursing due to the need to intercept every property of the object.
Object.defineProperty
Method bottlenecks: The API does not support arrays. Vue2 is a reactive support that is overridden by array methods (link).- Cannot detect dynamically adding or deleting object properties:
defineProperty
的setter
Methods can’t do that, so we often use instructions$set
Assign a value to an attribute.
Let’s take a look at Vue3:
The ES6 Proxy object is used to create a Proxy for an object, enabling interception and customization of basic operations (such as property lookup, assignment, enumeration, function calls, and so on).
This process is for all attributes of the current object, whether original or new, as long as external access to the object, must pass this layer of interception.
const obj = {
name: 'whisky'.age: '27'.alcohol:[
{ name: 'Macallan' },
{ name: Loehmann Lake},],}const p = new Proxy(obj, {
get(target, propKey, receiver) {
console.log('You visited' + propKey);
return Reflect.get(target, propKey, receiver);
},
set(target, propKey, value, receiver) {
console.log('You set it.' + propKey);
console.log('new' + propKey + '=' + value);
Reflect.set(target, propKey, value, receiver); }}); p.age ='20';
console.log(p.age);
// You set age
/ / new age = 20
// You accessed age
/ / 20
p.newPropKey = 'New properties';
console.log(p.newPropKey);
// You set newPropKey
// New newPropKey= new property
// You accessed newPropKey
/ / new properties
// There is no need to add reactive processing again, despite the new property newPropKey
Copy the code
The operation of Vue3 intercepts objects through Proxy, while defineProperty in Vue2 intercepts objects’ attributes.
Performance optimization
Conclusion: Compared with Vue2, the overall performance is improved twice. The optimization points are as follows:
Static flag PatchFlags
Optimization of diff algorithm — static markup
The DIff algorithm of Vue2 adopts the strategy of full comparison.
Original Strategy:
When the Virtual DOM node changes, a new VNode will be generated, and the VNode will be compared with the oldVNode node. If the difference is found, the new value will be directly modified on the real DOM, and the value of the oldVNode will be replaced with VNode.
<div id="app">
<h1>The diff Vue2</h1> // Static node
<p>The fund is green again today</p> // Static node
<div>{{ name }}</div> // Dynamic node
</div>
function render() {
with(this) {
return _c('div', {
attrs: {
"id": "app"
}
}, [_c('h1', [_v("Vue2的diff")]), _c('p', [_v("Today the fund is green again.")]), _c('div', [_v(_s(name))])])
}
}
Copy the code
This strategy of full comparison can no doubt be applied to all VNodes, but by full comparison, it means comparison of all nodes.
In our real business scenario, there are some static elements with tag names, class names and even tag contents that will not change. If these static elements also participate in the replacement process of full comparison, there is no doubt that a time consumption will occur.
Let’s see what Vue3 does:
<div id="app">
<h1>The diff Vue3</h1> // Static node
<p>The fund is green again today</p> // Static node
<div>{{ name }}</div> // Dynamic node
</div>
render(_ctx, _cache) {
return (_openBlock(), _createBlock("div", { id: "app" }, [
_createVNode("h1".null."Vue3的diff"),
_createVNode("p".null."Today the fund is green again."),
_createVNode("div".null, _toDisplayString(_ctx.name), 1 /* TEXT */)))}Copy the code
Above, we find that static tags are actually added according to whether each VNode changes or not during the first step of creating the Virtual DOM. In this way, in the process of comparing VNodes with OldvNodes, only the marked nodes need to be compared.
Where the reference frame of the node change type is an enumeration of PatchFlags:
export const enum PatchFlags {
TEXT = 1,
CLASS = 1 << 1,
STYLE = 1 << 2,
PROPS = 1 << 3,
FULL_PROPS = 1 << 4,
HYDRATE_EVENTS = 1 << 5,
STABLE_FRAGMENT = 1 << 6,
KEYED_FRAGMENT = 1 << 7,
UNKEYED_FRAGMENT = 1 << 8,
NEED_PATCH = 1 << 9,
DYNAMIC_SLOTS = 1 << 10,
HOISTED = -1,
BAIL = -2
}
Copy the code
Obviously, the value is 1 if only the text changes dynamically, 1 << 2 if the style changes dynamically, and so on. When the combination changes, the corresponding tag node codes are generated by bit operation combination.
Optimization of event binding
The event binding in Vue2 is treated as dynamic binding, but it actually performs the same thing every time the event is clicked, so in Vue3, the event is directly cached and reused to improve performance.
In PatchFlags mentioned above, the value of the dynamic attribute is 1 << 3 and the result is 8. Normally, clicking on the event will be compiled and statically marked according to this value. In this case, adding the event listening cache cacheHandlers will remove the original static flag.
export function render(_ctx, _cache) {
return (_openBlock(), _createBlock("div", _hoisted_1, [
_createVNode("button", {
onClick: _cache[1] || (_cache[1] = $event => (_ctx.confirmHandler($event)))
}, "Confirm"),
_createVNode("span".null, _toDisplayString(_ctx.vue3), 1 /* TEXT */)))}// The event is cached in _cache[1] for the first rendering
If there is an event directly from the cache, there is no need to create an update again to reduce consumption
Copy the code
Static promotion hoistStatic
In the static markup section, we learned that some static elements do not need to be updated, but they still need to be rendered after each creation. At this point, static promotion (for the _hostied element) allows the specified element to be created only once, saving overhead by reusing the first and only creation at render time.
const _hoisted_1 = /*#__PURE__*/_createVNode("div".null."Static lift", -1 /* HOISTED */)
Copy the code
Tree Shaking
What is Tree Shaking? A tree, literally, shakes off unwanted leaves.
Back in the code world, we removed unreferenced code from the JavaScript context during our front-end Webpack packaging phase.
We should all have written the following code in Vue2:
import Vue from 'vue';
Vue.nextTick(() = > {
// Some DOM related operations
});
Copy the code
TIPS: $nextTick in a single file is essentially the same as vue.nexttick.
Consider a question:
If you don’t use vue. nextTick, or if you prefer setTimeout instead, the nextTick part of the Vue will become dead code, which increases the size of the code but is never used. This is a major performance drag for client-rendered Web apps.
In Vue3, the official team refactored the organization of all global apis so that all apis support Tree Shaking.
import { nextTick } from 'vue';
nextTick(() = > {
// Some DOM related operations
});
Copy the code
Composition API and Practice
The cause of
In the days of Vue2, component reuse was an important part of team development, and the transfer of component content based on slot or prop helped abstract logic.
However, at high levels of data transfer, component content can become confusing. We often need to separate methods and data, which are responsible for adding and subtracting, in different parts of the code block. If you can see it ata glance in a widget, but in components with tens or hundreds of lines, the interaction between functions distributed in data, compute, and Method can become difficult to understand. Increased reading, mental, maintenance, revision burden.
experience
The Composition API gives the user the ability to flexibly organize blocks of component code.
I’ll use a ToDoList to compare the Options API with the Composition API in the following article.
The target
Functional requirements:
- ToDo adds, deletes, marks finished, and modifies items
- Legacy items
- Clear completion
implementation
Let’s take a look at how we would handle this requirement if we used the Options API:
<template>
<section class="todo-app">
<header class="header"></header>
<section class="main"></section>
<footer class="footer"></footer>
</section>
</template>
<script>
export default {
data() {
return {
todos: [].// Store an array of toDos
newTodo: ' '.// The todo item currently added
editTodo: ' '.// Todo item currently modified
};
},
computed: {
// Current legacy items
remaining: function () {
return this.todos.filter((todo) = >! todo.completed).length; },// Select all logic
allDone: {
get: function () {
return this.remaining === 0;
},
set: function (value) {
this.todos.forEach(function (todo) { todo.completed = value; }); }},},methods: {
addTodo () {},
deleteTodo () {},
editTodo () {},
doneEdit () {},
cancelEdit () {},
removeCompleted () {},
},
};
</script>
Copy the code
Above, Vue2 ToDoList has been basically completed. To get to Composition API:
// Leave template unchanged
<script>
import { ref, reactive, computed, toRefs } from "vue";
// ref is used to track normal data
// reactive is used to track objects or arrays
// toRefs handles reactive to ref
export default {
setup() {
/ / the data layer
const test = ref('this is test');
const state = reactive({
todos: [].// Store an array of toDos
newTodo: ' '.// The todo item currently added
editTodo: ' '.// Todo item currently modified
});
/ / computed layer
const remaining = computed(
() = > state.todos.filter(todo= >! todo.completed).length );const allDone = computed({});
/ / the methods
function addTodo () {},
function removeTodo () {},
function editTodo () {},...return {
...toRefs(state),
remaining,
allDone,
addTodo,
removeTodo,
editTodo,
... // Other methods are omitted
};
}
</script>
Copy the code
conclusion
By contrast, the Composition API is actually a style that favors hooks.
- The new function
setup
- It will be
created
Executed before the life cycle and acceptedprops
(for internal access),context
Two parameters (context object, can passcontext
To access an instancethis
).
ref
.reactive
.toRefs
ref
You can take an incoming value as a parameter and return a response based on that valueRef
Object in which values that are changed and accessed are tracked through modificationstest
Can trigger a re-rendering of the template to display the latest values.reactive
与ref
The difference is simply thatreactive
To modify an object or array.toRefs
Can bereactive
The created reactive object is converted toref
Common objects.
- The relevant
computed
.watch
.watchEffect
computed
Used to create the computed property. The return value is oneref
Object that needs to be introduced separately.watch
As with the method in Vue2, you need to listen for the data and execute its listening callback.watchEffect
The function passed in is executed immediately, listens for its dependencies in a responsive manner, and rerunts the function when its dependencies change.
-
Life cycle correlation
Vue3’s lifecycle hooks have been slightly changed:
Vue2--------------vue3 beforeCreate -> setup() created -> setup() beforeMount -> onBeforeMount mounted -> onMounted beforeUpdate -> onBeforeUpdate updated -> onUpdated beforeDestroy -> onBeforeUnmount destroyed -> onUnmounted activated -> onActivated deactivated -> onDeactivated errorCaptured -> onErrorCaptured Copy the code
As you can see, the original life cycle is basically there, just prefixed with on. Setup creates both beforeCreate and beforeCreate hooks, and creates both beforeCreate and created hooks.
<script> import { ref onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted } from "vue" export default { setup() { const count = ref(0); // Other life cycles are written here onBeforeMount (() = > { count.value++; console.log('onBeforeMount', count.value); }) onMounted (() = > { count.value++; console.log('onMounted', count.value); }) OnBeforeUpdate and onUpdated do not modify values. onBeforeUpdate (() = > { console.log('onBeforeUpdate', count.value); }) onUpdated (() = > { console.log('onUpdated', count.value); }) onBeforeUnmount (() = > { count.value++; console.log('onBeforeUnmount', count.value); }) onUnmounted (() = > { count.value++; console.log('onUnmounted', count.value); }) return{ count, }; }}; </script>Copy the code
Note that all of the life cycles are introduced separately, and we might not use as many hooks in daily development. Bringing in on demand reduces the volume, which is the benefit of Tree Shaking.
At this point, we have a basic understanding of the Composition API. The part about component calls is the same as Vue2, except that we might extract the ToDoList code mentioned above as a function component and reference it in the parent component.
Extract the logic useTodos and use the setup method to return all the data, so we can define the component useTodos:
// useTodos.js
const useTodos = () = > {
/ / the data layer
const state = reactive({
todos: [].// Store an array of toDos
newTodo: ' '.// The todo item currently added
editTodo: ' '.// Todo item currently modified
});
/ / computed layer
const remaining = computed(
() = > state.todos.filter(todo= >! todo.completed).length );const allDone = computed({});
/ / the methods
function addTodo () {},
function removeTodo () {},
function editTodo () {},...return {
...toRefs(state),
remaining,
allDone,
addTodo,
removeTodo,
editTodo,
...
};
}
Copy the code
Now, if we need toDOS components, we can:
<script>
import useTodos from './useTodos';
export default {
setup () {
const { remaining, allDone, state ... } = useTodos();
return {
remaining,
allDone,
state,
...
}
}
}
</script>
Copy the code
The last
Vue3 has been released for more than a year, I believe that many developers have realized the charm of it, the above views are combined with the official website and the opinions of the major platform developers for analysis and comparison, hoping to bring help to Vue3 enthusiasts on the road and have been on the road ~