Vue3.0的几大亮点
- 性能比vue2.x 快1.2~2倍
- 按需编译,体积比vue2.x更小
- composition Api (组和api类似React Hooks)
- 本身就是ts从写的更好的支持ts
- custom Renderer api 暴露了自定义渲染的api
- Teleport(Protal),Suspense 更先进的组件
Vue.3.0是如何变快的
1.优化了diff算法
- Vue2.x中的虚拟dom是进行全量的对比
- Vue3.x新增了动态标记位(PatchFlag),在与上次虚拟节点进行比较的时候,只对比带有patchflag的节点,并且可以通过flag的信息得知当前节点要对比的具体内容
Vue2.x中首先 div会和div比较h1和h1比较,p和p比较,这就是全量比较,其中只有p标签改变了值,比较的时候是每个节点对比,其中h1的值是写死的根本不会变,按道理就不应该去比较,所以vue3.0做了优化
Vue3.0的比较只比较和追踪带有标记位的
vue-next-template-explorer.netlify.app/
代码示例:
<div>
<h1>这是一个测试</h1>
<h1>这是一个测试</h1>
<h1>这是一个测试</h1>
<p>{{msg}}</p>
</div>
import { createElementVNode as _createElementVNode, toDisplayString as _toDisplayString, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
export function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createElementBlock("div", null, [
_createElementVNode("h1", null, "这是一个测试"),
_createElementVNode("h1", null, "这是一个测试"),
_createElementVNode("h1", null, "这是一个测试"),
_createElementVNode("p", null, _toDisplayString(_ctx.msg), 1 /* TEXT */)
]))
}
// Check the console for the AST
在创建虚拟Dom节点的时候,只有p标签插入了一个静态标记位1
下列为标记值所代表的含义:
export const enum PatchFlags {
TEXT = 1, *//动态文字内容*
CLASS = 1 << 1, *// 2 动态 class*
STYLE = 1 << 2, *//4 动态样式*
PROPS = 1 << 3, *//8 动态属性 props (不包含类名和样式)*
FULL_PROPS = 1 << 4, *//16 有动态的key属性,当key改变时需要进行完整的diff比较*
HYDRATE_EVENTS = 1 << 5, *//32 带有监听事件的节点*
STABLE_FRAGMENT = 1 << 6, *//64 一个不会改变子节点顺序的 fragment*
KEYED_FRAGMENT = 1 << 7, *// 128 带有key的节点的fragment或者部分子节点带有key*
UNKEYED_FRAGMENT = 1 << 8, *//256 子节点没有key的fragment*
NEED_PATCH = 1 << 9, *//512 一个节点只会进行非props比较,比如`ref`*
*// 动态的插槽*
DYNAMIC_SLOTS = 1 << 10,
*// SPECIAL FLAGS -------------------------------------------------------------*
*// 以下是特殊的flag,不会在优化中被用到,是内置的特殊flag*
*// 表示他是静态节点,他的内容永远不会改变,对于hydrate的过程中,不会需要再对其子节点进行diff*
HOISTED = -1,
*// 用来表示一个节点的diff应该结束*
BAIL = -2,
}
2. hoistStatic (静态提升)
这个是没有添加静态提升的代码,无论是更新还是不需要更新的每次都要从新生成节点 列如:3个p标签 开启静态提升之前:
点击右侧的options选中
开启静态提升以后:
import { createElementVNode as _createElementVNode, toDisplayString as _toDisplayString, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
const _hoisted_1 = /*#__PURE__*/_createElementVNode("h1", null, "这是一个测试", -1 /* HOISTED */)
const _hoisted_2 = /*#__PURE__*/_createElementVNode("h1", null, "这是一个测试", -1 /* HOISTED */)
const _hoisted_3 = /*#__PURE__*/_createElementVNode("h1", null, "这是一个测试", -1 /* HOISTED */)
export function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createElementBlock("div", null, [
_hoisted_1,
_hoisted_2,
_hoisted_3,
_createElementVNode("p", null, _toDisplayString(_ctx.msg), 1 /* TEXT */)
]))
}
// Check the console for the AST
选中静态提升的时候,h1的虚拟节点被从render方法中提升到全局变量中,这样就只创建一次,每次render的时候直接复用
3.事件侦听缓存
随便搞个栗子,开启事件侦听缓存之前:
<div>
<button @click='clickEvent'></button>
</div>
import { createElementVNode as _createElementVNode, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
const _hoisted_1 = ["onClick"]
export function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createElementBlock("div", null, [
_createElementVNode("button", { onClick: _ctx.clickEvent }, null, 8 /* PROPS */, _hoisted_1)
]))
}
// Check the console for the AST
可以看到这个标记位的值是8,被当成了动态属性,这样就会被对比,这里每次都是同一个方法没必要去进行对比
开启事件侦听缓存之后:
标记位没有了,没有被追踪就没有产生对比了
Vue3.0的diff算法中只有有静态标记位的才会对比才会追踪。
Vue中ref和reactive的对比:
reactive
- reactive 是vue3.0提供的响应式数据的方法
- reactive 是通过es6中的proxy来实现的,接收的参数必须是对象(json/arr)传入其他的值不会被监听
- 如果给reactive传递其他对象,默认情况下修改对象,视图不会更新,想要更新可以通过从新赋值的方式.如传入的是时间对象,值其实更新了,视图并灭有改变,想要改变就从新赋值
这样视图是没有更新的 **从新赋值以后视图更新
setup(){
const state=reactive({time:new Date()})
const change=()=>{
//时间从新赋值
let newTime=new Date(state.time.getTime());
newTime.setDate(state.time.getDate()+1);
state.time=newTime;
console.log(state.time)
}
return {
...toRefs(state),
change
}
}
Ref
- 1.Ref底层的本质还是 reactive 系统会自动根据我们给ref传入的值将它转化为
- Ref(xx)------->reactive({value:xxx})
- 2. 在模板中获取ref 的值不用通过value,在js中需要,那为什么reactive就不用.value去修改值呢,它是怎么判断的呢,vue3.0的底层会把ref包装的数据加入一个私有属性__v_isRef,我们是没法访问私有属性的,想判断是ref还是reactive包装的数据类型,vue3.0提供了isRef()和isReactive()
- 3.之所会有ref是因为reactive只监听对象或者数组,想监听某个变量比较繁琐,所以就有了ref可以单独监听某个变量
- 4.Ref和reactive创建的数据都是递归监听的,它们会将每一层都包装成proxy对象,在修改大体量的数据的时候就会比较消耗性能,所以Vue3.0就衍生了shallowReactive和shallowRef以及triggerRef
shallowReactive、shallowRef、triggerRef的使用
shallowReactive和shallowRef属于非递归监听,shallowReactive只监听了第一层的数据,且只将第一层包装成了proxy对象
shallowReactive
<template>
<div class="home">
<div>{{a}}</div>
<div>{{gf.b}}</div>
<div>{{gf.f.c}}</div>
<div>{{gf.f.s.d}}</div>
<button @click='change'>改变</button>
<div></div>
</div>
</template>
<script>
import {reactive,toRefs,ref,shallowReactive} from 'vue'
export default {
name: 'Home',
setup(){
const state=shallowReactive({
a:"a",
gf:{
b:'b',
f:{
c:'c',
s:{
d:'d'
}
}
}
})
const change=()=>{
state.a=1;
state.gf.b=2;
state.gf.f.c=3;
state.gf.f.s.d=4;
console.log(state)
console.log(state.gf)
console.log(state.gf.f)
console.log(state.gf.f.s)
}
return {
...toRefs(state),
change
}
}
}
</script>
之所以子集的值也发生改变是因为第一层的值发生改变了会被监听,视图会被更新,如果注释掉 state.a=1;视图将不再更新,因为监听不到
shallowRef监听的是.value的变化并不是第一层的变化
<template>
<div class="home">
<div>{{state.a}}</div>
<div>{{state.gf.b}}</div>
<div>{{state.gf.f.c}}</div>
<div>{{state.gf.f.s.d}}</div>
<button @click='change'>改变</button>
</div>
</template>
<script>
import {reactive,toRefs,ref,shallowReactive,shallowRef} from 'vue'
export default {
name: 'Home',
setup(){
// const state=shallowReactive({
const state= shallowRef({
a:"a",
gf:{
b:'b',
f:{
c:'c',
s:{
d:'d'
}
}
}
})
const change=()=>{
// state.a=1;
// state.gf.b=2;
// state.gf.f.c=3;
// state.gf.f.s.d=4;
state.value.a=1;
state.value.gf.b=2;
state.value.gf.f.c=3;
state.value.gf.f.s.d=4;
console.log(state)
console.log(state.value)
console.log(state.value.gf)
console.log(state.value.gf.f)
console.log(state.value.gf.f.s)
}
return {
state,
change
}
}
}
</script>
修改的值并没有发生变化,当修改value值的时候,值发生了变化
如果我只想修改第4层级的值,这样修改整个对象是不是有点扯,vue3.0就提供了triggerRef()函数,这个类似强制视图更新的意思,不加这个函数视图不会更新,有点像2.0中那个
$emit()这个函数,修改一个数组或者嵌套多级的对象,2.0中视图不会更新,我们就是用$emit()去更新的视图
手动实现一个reactive
reactived的底层就是用递归将对象的每一层都包装成Proxy对象
function reactive(obj){
if(typeof obj==='object'){
if(obj instanceof Array){//如果是一个数组
obj.forEach((item,index)=>{//取出数组的每一个元素包装
if(typeof item ==='object'){//判断每一个元素是否是一个对象,
obj[index]=reactive(item)//如果是一个对象也需要包装成Proxy
}
})
}else{//如果是一个对象----
for(let key in obj){//取出对象属性的值
let item=obj[key];
if(typeof item ==='object'){//判断对象的取值是否又是一个对象
obj[key]=reactive(item)//如果是一个对象也需要包装成Proxy
}
}
}
return new Proxy(obj,{
get(obj,key){
return obj[key]
},
set(obj,key,value){
obj[key]=value;
console.log('更新ui')
return true;
}
})
}else{
console.warn(`${obj} 不是 object`)
}
}
let obj={
a:"a",
gf:{
b:'b',
f:{
c:'c',
s:{
d:'d'
}
}
}
}
let state=reactive(obj);
state.a=1;
state.gf.b=2;
state.gf.f.c=3;
state.gf.f.s=4;
我们要注意在set中一定要return true,我们在set的时候可能会很多次要告知当前这这一次set值是成功的,比如push一个数组,除了在数组的末尾改变一个值以外,还会将数组的length进行改变,这里set就会执行2次。
Pinia在vue2.x中和vue3.0中具体的使用,以及注意点
比Vuex更轻量更友好,所以拥抱吧,真香 官网还没出汉语版的 pinia.esm.dev/cookbook/op…
yarn add pinia
# 或者使用npm
npm install pinia
在main.js中
// 使用pinia
import { createPinia } from 'pinia'
app.use(createPinia())
建立store.js文件
import { defineStore } from 'pinia'
export const useMainStore = defineStore({
// store
// 它用于 devtools 并允许恢复状态
id: 'main',
// 一个返回新状态的函数
state: () => ({
ishow:true,
list:[]
}),
// getters
getters: {
getIsshow(state) {
return this.ishow
},
},
// actions
actions: {
getdata() {
fetch('http://jsonplaceholder.typicode.com/posts')
.then(response => response.json())
.then(json =>this.list=json)
},
},
})
actions将vuex中mutations合并到了actions,写起来更直观,组件中使用:
import {useMainStore} from '../store/pinia'
import { storeToRefs } from 'pinia'
setup(){
const store=useMainStore();
const { list, ishow } = storeToRefs(store);
//这里如果用解构就必须用storeToRefs包裹,否则数据不会是响应式
const state=reactive({
ishow,
//ishow:computed(()=>store.ishow)
list
//list:computed(()=>store.list)
})
return {
...toRefs(state)
}
//actions的使用
const change=()=>{
store.getdata()
}
}
如果你不使用组合API,而使用computed、methods,Pinnia也提供了和Vuex一样的,mapState(),mapActions() 在main.js/ts中 记得安装 1:yarn add pinia@0.5.2 用@next版本会报错,vue2.x官网也提示用v1版本 2:yarn add @vue/composition-api
pinia.js //仓库
import { defineStore } from 'pinia'
export const useMainStore = defineStore({
// store
// 它用于 devtools 并允许恢复状态
id: 'main',
// 一个返回新状态的函数
state: () => ({
ishow:false,
test:true,
listData:[]
}),
// getters
getters: {
getIsshow(state) {
return this.ishow
},
},
// actions
actions: {
getData() {
fetch('http://jsonplaceholder.typicode.com/posts')
.then(response => response.json())
.then(json =>this.listData=json)
},
changValue(){
//等价于 this.ishow=true; this.test=1113123;
//$patch可以一次修改多个值
this.$patch({
ishow:true,
test: 1113123,
})
}
},
})
main.js/ts
import { createPinia,PiniaPlugin } from 'pinia'
import VueCompositionApi from '@vue/composition-api'
Vue.use(VueCompositionApi)
Vue.use(PiniaPlugin)
const pinia = createPinia()
new Vue({
router,
pinia,
render: h => h(App)
}).$mount('#app')
组件中使用:
<div @clcik='getData'>
{{ishow}}
</div>
import { mapState } from 'pinia'
import { mapState,mapActions } from 'pinia'
computed: {
// 在组件中可以是用this.counter获取
// 和使用store.counter获取一样
...mapState(useMainStore, ['ishow','test'])
}),
},
methods:{
//...mapActions(useMainStore, ['getData','changValue'])
//或者给函数起个别名
...mapActions(useMainStore,{newGetData:'getData'})
}
歇会。。。持续更新