正在加载……
专注、离线、切勿分心

Vue3.0 亮点

  1. Performance:性能比Vue 2.x快1.2~2倍
  2. Tree shaking support:按需编译,体积比Vue2.x更小
  3. Composition API: 组合API(类似React Hooks)
  4. Better TypeScript support:更好的 Ts 支持
  5. Custom Renderer API:暴露了自定义渲染API
  6. Fragment, Teleport(Protal), Suspense:更先进的组件

Vue3.0是如何变快的

diff算法优化

https://vue-next-template-explorer.netlify.app/
+ Vue2中的虚拟dom是进行全量的对比,内存中新建一个虚拟dom树,每一个节点去对比,然后更新。
+ Vue3新增了静态标记(PatchFlag),在与上次虚拟节点进行对比时候,只对比带有patch flag的节点,并且可以通过flag的信息得知当前节点要对比的具体内容。_toDisplayString方法告诉算法具体要对比的内容,后面的就是 PatchFlag。
// diff算法内部原理
<div>
    <p>第一行</p>
    <p>{{msg}}}</p>
</div>

export function render(_ctx, _cache, $props, $setup, $data, $options) {
  return (_openBlock(), _createBlock("div", null, [
    _createVNode("p", null, "第一行"),
    _createVNode("p", null, _toDisplayString(_ctx.msg) + "}", 1 /* TEXT */)
  ]))
}

// 附录: PatchFlags
export const enum PatchFlags {
  TEXT = 1,// 动态文本节点
  CLASS = 1 << 1, // 2  // 动态 class
  STYLE = 1 << 2, // 4 // 动态 style
  PROPS = 1 << 3, // 8 // 动态属性,但不包含类名和样式
  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 比较
  DYNAMIC_SLOTS = 1 << 10, // 1024 // 动态 slot
  HOISTED = -1, // 静态节点
  // 指示在 diff 过程应该要退出优化模式
  BAIL = -2
}

hoistStatic静态提升

+ Vue2中无论元素是否参与更新, 每次都会重新创建, 然后再渲染
+ Vue3中对于不参与更新的元素, 会做静态提升, 只会被创建一次, 在渲染时直接复用即可
<div>
    <p>第一行</p>
    <p>{{msg}}}</p>
</div>
// 静态提升之前:
export function render(_ctx, _cache, $props, $setup, $data, $options) {
  return (_openBlock(), _createBlock("div", null, [
    _createVNode("p", null, "第一行"),
    _createVNode("p", null, _toDisplayString(_ctx.msg) + "}", 1 /* TEXT */)
  ]))
}
// 静态提升之后:
const _hoisted_1 = /*#__PURE__*/_createVNode("p", null, "第一行", -1 /* HOISTED */)

export function render(_ctx, _cache, $props, $setup, $data, $options) {
  return (_openBlock(), _createBlock("div", null, [
    _hoisted_1,
    _createVNode("p", null, _toDisplayString(_ctx.msg) + "}", 1 /* TEXT */)
  ]))
}

cacheHandlers事件侦听器缓存

+ 默认情况下绑定的click事件会被视为动态绑定,所以每次都会去追踪它的变化,但是因为是同一个函数,所以没有追踪变化,直接缓存起来复用即可。
<div>
  <button @click="onClick">按钮</button>
</div>

// 开启事件监听缓存之前:
export function render(_ctx, _cache, $props, $setup, $data, $options) {
  return (_openBlock(), _createBlock("div", null, [
    _createVNode("button", { onClick: _ctx.onClick }, "按钮", 8 /* PROPS */, ["onClick"])
  ]))
}
// 开启事件监听缓存之后:
export function render(_ctx, _cache, $props, $setup, $data, $options) {
  return (_openBlock(), _createBlock("div", null, [
    _createVNode("button", {
      onClick: _cache[1] || (_cache[1] = (...args) => (_ctx.onClick(...args)))
    }, "按钮")
  ]))
}
// 注意点:转换之后的代码,观察有没有静态标记.在Vue3的 diff 算法中,只有有静态标记的才会进行比较,才会进行追踪.

ssr渲染

+ 当有大量静态的内容时候,这些内容会被当做纯字符串推进一个buffer里面,即使存在动态的绑定,会通过模板插值嵌入进去。这样会比通过虚拟dom来渲染的快上很多很多。
+ 当静态内容大到一定量级时候,会用_createStaticVNode方法在客户端去生成一个static node,这些静态node,会被直接innerHtml,就不需要创建对象,然后根据对象渲染。

Vue3.0-快速上手

创建Vue3的3种方式
		Vue-CLI
  	Webpack
  	Vite

什么是Vite

ViteVue作者开发的一款意图取代webpack的工具,其实现原理是利用ES6import会发送请求去加载文件的特性,拦截这些请求,做一些预编译,省去webpack冗长的打包时间。

安装Vite

npm install -g create-vite-app 利用Vite创建Vue3项目 create-vite-app projectName

// vite vue3 中 main.js 使用vue的区别
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')
/*
    new Vue({
      el: '#app',
      store: store,
      router: router,
      render: c => c(App)
    })
    new Vue({
      store: store,
      router: router,
      render: c => c(App)
    }).$mount("#app");
*/

组合API

基本用法

import { ref } from 'vue';
export default {
  name: 'App',
  setup(){ // setup函数是组合API的入口函数
    // 定义了一个名称叫做count变量, 这个变量的初始值是0;这个变量发生改变之后, Vue会自动更新UI
    let count = ref(0); // 相当于 let count = 0;
    // 在组合API中, 如果想定义方法, 不用定义到 methods 中, 直接定义即可
    function myFn() {
      count.value += 1; // count是一个对象
    }
    // 在组合API中定义的变量/方法, 要想在外界使用, 必须通过return {xxx, xxx}暴露出去
    return{count, myFn}
  }
}

抽取

// 解决 Vue2 中业务逻辑和数据分散的问题
import {reactive} from 'vue';
export default {
  name: 'App',
  setup() {
/*
    let state = reactive({ // ref函数只能监听简单类型的变化, 不能监听复杂类型的变化(对象/数组)
      stus:[
        {id:1, name:'zs', age:10},
        {id:2, name:'ls', age:20},
      ]
    });
    function remStu(index) {
      state.stus = state.stus.filter((stu, idx) => idx !== index);
    }
*/
    let { state, remStu } = useRemoveStudent();
    return { state1, remStu }
  }
}
function useRemoveStudent() { // 把业务逻辑和数据放在一起
  let state = reactive({
    stus:[
      {id:1, name:'zs', age:10},
      {id:2, name:'ls', age:20},
    ]
  });
  function remStu(index) {
    state.stus = state.stus.filter((stu, idx) => idx !== index);
  }
  return { state, remStu };
}

组合

方法和数据可以放在一起,提取到单独的js文件。

Composition APIOption API(Vue2.X定义数据方法的风格)可以混合使用。

Composition API 本质 (组合API/注入API),会将组合的数据和方法提取后变成Option API的方式。


setup函数执行时机和注意点

1.setup执行时机
setup
beforeCreate: 表示组件刚刚被创建出来, 组件的data和methods还没有初始化好
Created     : 表示组件刚刚被创建出来, 并且组件的data和methods已经初始化好

2.setup注意点
- 由于在执行setup函数的时候, 还没有执行Created生命周期方法, 所以在setup函数中, 是无法使用data和methods
- 由于我们不能在setup函数中使用data和methods,所以Vue为了避免我们错误的使用,它直接将setup函数中this修改成了undefined
- setup函数只能是同步的不能是异步的
// Composition API 和 Option API 可以混用
data: function(){
  return {
    name: 'lnj',
  }
},
methods:{
  myFn1(){
    alert('abc');
  },
},
setup() {
  let age = ref(18);  
  // console.log(this); // undefined
  return {age}
}

reactive

reactiveVue3中提供的实现响应式数据的方法,在Vue2中响应式数据是通过defineProperty来实现的,而在Vue3中响应式数据是通过ES6Proxy来实现的。

reactive注意点:

reactive参数必须是对象(json/arr),如果给reactive传递了其它对象( eg:Date ),默认情况下修改对象,界面不会自动更新,如果想更新,可以通过重新赋值的方式。

创建一个响应式数据本质:就是将传入的数据包装成一个Proxy对象


let state = reactive(123);
let state = reactive({age: 123});
let state = reactive([1, 3, 5]);

ref

refreactive一样,也是用来实现响应式数据的方法,由于reactive必须传递一个对象,如果只想让某个变量实现响应式的时候会非常麻烦,所以Vue3就提供了ref方法,实现对简单值的监听。

ref本质:

ref底层的本质其实还是reactive,系统会自动根据我们给ref传入的值将它转换成,ref(xx) -> reactive({value:xx})

let state = reactive({
  age: 18
})
// 等价于
let age = ref(18);
// 修改时
function myFn() {
  // age = 666;
  age.value = 666; // 必须这样修改 <p>{{age}}</p> 
}

ref注意点:

​ 在Vue中使用ref的值不用通过value获取,因为Vue会自动给我们添加.value,在JS中使用ref的值必须通过value获取。

ref获取渲染界面元素

<div ref="box">我是div</div>
import {ref, onMounted} from 'vue';
setup() {
	let box = ref(null); // reactive({value: null})
  onMounted(()=>{
  	console.log('onMounted',box.value); // 等同于vue2中的 mounted, 拿到界面元素
  });
  console.log(box.value); // null 先执行
}

ref和reactive区别

  • 如果在template里使用的是ref类型的数据,那么Vue会自动帮我们添加.value,如果在template里使用的是reactive类型的数据,那么Vue不会自动帮我们添加.value
  • Vue在解析数据之前,会自动判断这个数据是否是ref类型的,如果是就自动添加.value,如果不是就不自动添加.value
  • Vue是通过当前数据的__v_ref来判断的当前的数据是否是ref类型的,如果有这个私有的属性, 并且取值为true, 那么就代表是一个ref类型的数据
import {reactive, ref, isRef, isReactive} from 'vue';
let age = ref(18);       let age = reactive({value: 18});
isRef(age)               isReactive(age)

递归监听

默认情况下,无论是通过ref还是reactive都是递归监听。

递归监听存在的问题:

如果数据量比较大, 非常消耗性能。

非递归监听

shallowRef / shallowReactive

如果是shallowRef类型数据(针对ref), 可以通过triggerRef来触发监听对象某一层数据的变化,默认只能监听到 .value的第一层变化。

如果是shallowReactive类型数据(针对reactive), 只会监听数据的第一层变化。

// 注意点: Vue3只提供了triggerRef方法, 没有提供triggerReactive方法, 所以如果是reactive类型的数据, 那么是无法主动触发界面更新的.
let state = shallowRef({
    a:'a',
    gf:{
      b:'b',
    }
});
state.value = {
  a:'1',
  gf:{
    b:'2',
  }
}
state.value.gf.b = '4'; // 修改第二层的数据
triggerRef(state); // 调用方法更新

//  注意点: 如果是通过shallowRef创建数据, 那么Vue监听的是 .value 的变化, 并不是第一层的变化.
console.log(state); // shallowReactive将第一层 包装成proxy对象,可以监听到。对象里key对应的值还是对象监听不到
console.log(state.value); // shallowRef 包装成proxy对象,可以监听到

一般情况下我们使用 ref 和 reactive 即可,只有在需要监听的数据量比较大的时候,我们才使用shallowRef/shallowReactive


shallowRef本质

shallowRef底层是 shallowReactive实现的,shallowRef(10) -> shallowReactive({value: 10}) ;所以如果是通过shallowRef创建的数据,它监听的是.value的变化,因为底层本质上value才是第一层。


toRow

ReactiveRef 中得到原始数据的方法。toRaw作用是做一些不想被监听的事情(提升性能)。

ref/reactive数据类型,每次修改都会被追踪,都会更新UI界面,但是这样其实是非常消耗性能的;所以如果我们有一些操作不需要追踪,不需要更新UI界面,那么这个时候我们就可以通过toRaw方法拿到它的原始数据,对原始数据进行修改,这样就不会被追踪,这样就不会更新UI界面,这样性能就好了。

import {reactive, toRaw} from 'vue';
let obj = {name:'zs', age:18};
let state = reactive(obj);
let obj2 = toRaw(state); // obj !== state, obj === obj2
// state 和 obj 是引用关系, state的本质是一个Proxy对象, 在这个Proxy对象中引用了obj。

ref本质: ref(obj) -> reactive({value: obj}),如果想通过toRaw拿到ref类型的原始数据(创建时传入的那个数据),那么就必须明确的告诉toRaw方法, 要获取的是.value的值。

import {ref, toRaw} from 'vue';
let obj = {name:'zs', age:18};
let state = ref(obj);
let obj2 = toRaw(state.value);

markRaw

将数据标记为永远不能追踪的数据,一般在编写自己的第三方库时使用。这样就不会被Vue监听到。

import {reactive, markRaw} from 'vue';
let obj = {name: 'zs', age: 18};
obj = markRaw(obj);
let state = reactive(obj);  // 失效,修改obj也不会触发更新

toRef

创建一个ref类型数据,并和以前的数据关联。

reftoRef区别:

ref - 复制,创建出来的数据和以前无关(复制); 数据变化会自动更新界面
toRef - 引用,创建出来的数据和以前的有关(引用); 数据变化不会自动更新界面

如果利用ref将某一个对象中的属性变成响应式的数据,我们修改响应式的数据是不会影响到原始数据的。

let obj = {name:'zs'};
let state = ref(obj.name);  // 相当于 reactive({value: zs})
let state = toRef(obj, 'name');
state.value = 'ls'; // 修改

toRefs

批量创建ref类型数据, 并和以前数据关联。

toRefref区别:

toRef - 创建一个ref类型数据, 并和以前的数据关联
toRefs - 批量创建ref类型数据, 并和以前数据关联
let obj = {name:'zs', age:18};
let name = toRef(obj, 'name');
let age = toRef(obj, 'age');
// 等价于
let state = toRefs(obj);
state.name.value = 'ls'; // 修改
state.age.value = 666;

customRef 异步获取监听数据

返回一个ref对象,可以显式地控制依赖追踪和触发响应,用来自己实现ref功能。

<template>
  <div>
    <p>{{age}}</p>  
  </div>
</template>
import {ref, customRef} from 'vue';
function myRef(value) {
  return customRef((track, trigger)=>{
    return {
      get(){ // 界面渲染会执行一次get方法
        track(); // 告诉Vue这个数据是需要追踪变化的
        return value;
      },
      set(newValue){ // 修改值会执行一次set法
        value = newValue;
        trigger(); // 告诉Vue触发界面更新
      }
    }
  });
}
// let age = ref(18); // reactive({value: 18})
let age = myRef(18);
age.value += 1;

利用customRef实现异步获取数据更新界面,setup() 不能执行异步代码,所以可以通过这种方式。

import {ref, customRef} from 'vue';
function myRef(value) {
  return customRef((track, trigger) => {
    fetch(value) // 利用es6的fetch获取json数据
            .then((res)=>{
              return res.json();
            })
            .then((data)=>{
              console.log(data);
              value = data;
              trigger();
            })
            .catch((err)=>{
              console.log(err);
            })
    return {
      get(){
        track(); // 告诉Vue这个数据是需要追踪变化的
        // 注意点: 不能在get方法中发送网络请求
        // 渲染界面 -> 调用get -> 发送网络请求
        // 保存数据 -> 更新界面 -> 调用get
        return value;
      },
      set(newValue){
        value = newValue;
        trigger(); // 告诉Vue触发界面更新
      }
    }
  });
}
// setup() 方法中
let state = myRef('../public/data.json');

readonly & shallowReadonly

readonly用于创建一个只读的数据,并且是递归只读。shallowReadonly用于创建一个只读的数据,但是不是递归只读的。

import {readonly, isReadonly, shallowReadonly} from 'vue'
let state = readonly({name:'zs', attr:{age:18, height: 1.88}});
state.name = 'ls'; // 修改失败
let state = shallowReadonly({name:'lnj', attr:{age:18, height: 1.88}}); // 只能保护第一层,也就是na me,attr一级
state.name = 'ls'; // 修改失败
state.attr.age = 666; // 修改成功
console.log(isReadonly(state)); // true
const value = {name:'zs', age:123};
value.name = 'ls'; // 修改成功

💡 constreadonly区别:

  • const 赋值保护, 不能给变量重新赋值
  • readonly 属性保护, 不能给属性重新赋值

Vue3响应式数据本质

Vue2.x中是通过defineProperty来实现响应式数据的,在Vue3.x中是通过Proxy来实现响应式数据的。

let obj = {name:'zs', age:18};
let state = new Proxy(obj, {
    get(obj, key){
        console.log(obj, key); // { name: 'zs', age: 18 } name
        return obj[key];
    },
    set(obj, key, value){
        console.log(obj, key, value); // { name: 'zs', age: 18 } name ls
        obj[key] = value;
        console.log('更新UI界面');
    }
});

// console.log(state.name); // lnj
state.name = 'ls';
console.log(state); // { name: 'ls', age: 18 }

Proxy注意点

set方法必须通过返回值告诉Proxy此次操作是否成功,proxy才会继续向下执行后面的set工作。

let arr = [1, 3, 5]; // [1, 3, 5, 7]
let state = new Proxy(arr, {
    get(obj, key){
        console.log(obj, key);
        return obj[key];
    },
    set(obj, key, value){
        // [ 1, 3, 5 ] 3 7
        // [ 1, 3, 5, 7 ] length 4
        console.log(obj, key, value); // [ 1, 3, 5 ] 3 7 key是3,表示要设置的是索引为3的元素
        obj[key] = value;
        console.log('更新UI界面');
        return true; // 返回true告诉proxy上一步push(7)的操作成功,接下来会继续执行set方法,修改数组的length [ 1, 3, 5, 7 ] length 4 
    }
});

// console.log(state[1]); // [ 1, 3, 5 ] 1
state.push(7);

手写shallowRef/shallowReactive

function shallowRef(val) {
    return shallowReactive({value:val}); // 本质上监听的是value
}
function shallowReactive(obj) {
    return new Proxy(obj, {
        get(obj, key){
            return obj[key];
        },
        set(obj, key, val){
            obj[key] = val;
            console.log('更新UI界面');
            return true;
        }
    })
}
let obj = { a:'a', gf:{b:'b'} };
/*
let state = shallowReactive(obj);
state.a = '1'; // 更新UI界面
state.gf.b = '2'; // 无效
*/
let state = shallowRef(obj);
// state.value.a = '1'; // 无效,原因就是shallowRef只能监听value
// state.value.gf.b = '2';
state.value = obj = { a:'a', gf:{b:'b'} };
// state.value.a = '1';  // 更新UI界面
// state.value.gf.b = '2'; // 无效

手写ref/reactive

function reactive(obj) {
    if(typeof obj === 'object') {
        if(obj instanceof Array) {
            // 如果是一个数组,那么取出数组中的每一个元素,判断每一个元素是否又是一个对象,
            // 如果是一个对象,那么也需要包装成Proxy
            obj.forEach((item, index) => {
                if(typeof item === 'object') {
                    obj[index] = reactive(item)
                }
            })
        } else {
            // 如果是一个对象,那么取出对象属性的值,判断对象属性的取值是否又是一个对象,
            // 如果又是一个对象,那么也需要包装成Proxy
            for(let key in obj) {
                let item = obj[key]
                if(typeof item === 'object') {
                    obj[key] = reactive(item)
                }
            }
        }
    } else {
        console.log(`${obj} is not object`)
    }
    return new Proxy(obj, {
        get(obj, key){
            return obj[key];
        },
        set(obj, key, val){
            obj[key] = val;
            console.log(obj, key , val)
            console.log('更新UI界面');
            return true;
        }
    })
}
// ref实现
function ref(val) {
    return shallowReactive({value:val});
}
let obj = { a:'a', gf:{b:'b'} };
let state  = reactive(obj)
state.gf.b = '4'; // 更新UI

let arr = [{id: 1, name: 'zs'}, {id: 2, name: 'ls'}]
let state2  = reactive(arr)
state2[0].name = 'ww' // 更新UI

手写shallowReadonly/readonly

function shallowReadonly(obj) {
  // readonly只需要在这里递归遍历obj并调用自身readOnly方法
  return new Proxy(obj, {
      get(obj, key){
          return obj[key];
      },
      set(obj, key, val){
        console.warn(`${obj[key]}是只读的,不能修改`)
      }
  })
}
let obj = { a:'a', gf:{b:'b'}};
let state  = shallowReadonly(obj)
state.a = '1'; // a是只读的,不能修改
state.gf.b = '2'; // 无输出
function readonly(obj) {
  if(typeof obj === 'object') {
    if(obj instanceof Array) {
        // 如果是一个数组,那么取出数组中的每一个元素,判断每一个元素是否又是一个对象,
        // 如果是一个对象,那么也需要包装成Proxy
        obj.forEach((item, index) => {
            if(typeof item === 'object') {
                obj[index] = readonly(item)
            }
        })
    } else {
        // 如果是一个对象,那么取出对象属性的值,判断对象属性的取值是否又是一个对象,
        // 如果又是一个对象,那么也需要包装成Proxy
        for(let key in obj) {
            let item = obj[key]
            if(typeof item === 'object') {
                obj[key] = readonly(item)
            }
        }
    }
  }
  return new Proxy(obj, {
      get(obj, key){
          return obj[key];
      },
      set(obj, key, val){
        console.warn(`${obj[key]}是只读的,不能修改`)
      }
  })
}
let obj = { a:'a', gf:{b:'b'}};
let state  = readonly(obj)
state.a = '1'; // a是只读的,不能修改
state.gf.b = '2'; // 无输出
posted on 2021-07-29 11:01  正在加载……  阅读(442)  评论(0编辑  收藏  举报