VUE3.0 总结

Vue3.0的六大亮点:

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

Vue3.0变的更快:

  1. diff 方法优化:

    • Vue2.x中的虚拟DOM是进行全量对比
    • Vue3.0新增了静态标记(PatchFlag),虚拟DOM对比时,只对比带有patch flag 的节点
      export function render(_ctx, _cache, $props, $setup, $data, $options) {
        return (_openBlock(), _createBlock("div", null, [
          _createVNode("p", null, "你好,小明"),
          _createVNode("p", null, "你好,小明"),
          _createVNode("p", null, "你好,小明"),
          _createVNode("p", null, "你好," + _toDisplayString(_ctx.name), 1 /* TEXT */)
        ]))
      }
      最后一个_createVNode中有个数字1,就是静态标记
      
  2. hoistStatic 静态提升:

    • Vue2.x中无论元素是否参与更新,每次都会重新创建
    • Vue3.0中对于不参与更新的元素,只会被创建一次,之后会在渲染时被复用
      静态提升之前:
      
      export function render(_ctx, _cache, $props, $setup, $data, $options) {
        return (_openBlock(), _createBlock("div", null, [
          _createVNode("p", null, "你好,小明"),
          _createVNode("p", null, "你好,小明"),
          _createVNode("p", null, "你好,小明"),
          _createVNode("p", null, "你好," + _toDisplayString(_ctx.name), 1 /* TEXT */)
        ]))
      }
      
      静态提升之后:
      
      const _hoisted_1 = /*#__PURE__*/_createVNode("p", null, "你好,小明", -1 /* HOISTED */)
      const _hoisted_2 = /*#__PURE__*/_createVNode("p", null, "你好,小明", -1 /* HOISTED */)
      const _hoisted_3 = /*#__PURE__*/_createVNode("p", null, "你好,小明", -1 /* HOISTED */)
      
      export function render(_ctx, _cache, $props, $setup, $data, $options) {
        return (_openBlock(), _createBlock("div", null, [
          _hoisted_1,
          _hoisted_2,
          _hoisted_3,
          _createVNode("p", null, "你好," + _toDisplayString(_ctx.name), 1 /* TEXT */)
        ]))
      }
      
  3. cacheHandlers 事件侦听器缓存:

    • 默认情况下onClick会被视为动态绑定,所以每次都会追踪它的变化。
      但因为是同一个函数,所以没有追踪变化,直接缓存服用。
    <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 && _ctx.onClick(...args)))
        }, "按钮")
      ]))
    }
    
  4. ssr 渲染:

    • 当有大量静态的内容时候,这些内容会被当做纯字符串推进一个buffer里面,即使存在动态的绑定,会通过模板插值嵌入进去。这样会比通过虚拟DOM来渲染的快上很多很多。

    • 当静态内容大到一定量级时候,会用 _createStaticVNode 方法在客户端去生成一个static node,这些静态的node,会被直接innerHtml,就不需要创建对象,然后根据对象渲染。

创建Vue3.0项目

  • Vue-CLI的方式:

    • npm install -g @vue/cli
    • vue create projectName
    • cd projectName
    • npm install
    • vue add vue-next (如果VUCLI的版本比较低,会安装失败,安装最新版本VUECLI即可) 现在vue cli 默认支持 vue3.0,所以可以省略该步骤
    • npm run serve
  • Vite方式:

    • npm install -g create-vite-app
    • create-vite-app projectName(项目名称)
    • cd projectName
    • npm install
    • npm run serve

组合API (composition API)

  setup() 函数

  • 该方法是在 beforecreate 钩子之前完成的

  • 是组合API的入口,该函数执行时尚未创建组件实例,所以没有this。

  • 在组合API中定义的变量/方法,想在外界使用,必须通过 return 暴露。

  • 如果函数返回对象,对象中的变量/方法,可以直接在模板中使用。

  • 函数只能监听简单类型的变化,不能监听复杂类型的变化(对象/数组)

  • 函数只能是同步的,不能是异步的

  ref

  • 作用:定义一个响应式数据。
  • 语法:const xxx = ref(初始值)
  • 在js中使用ref的值必须通过value获取
  • 在模板中使用不用通过value获取
  • 修改响应式数据(( 值类型 / 对象的属性 / 数组的某个值 ))是不会影响到原始数据的
<template>
  <div class="home">
    <p>{{count}}</p>
    <button @click="myFn">按钮</button>
  </div>
</template>

<script>
import { ref } from "vue";
export default {
  name: 'Home',
  setup(){
    // 定义一个count变量,初始值为0
    let count = ref(0);
    // 在组合API中,如果想定义方法,不用定义到methods中,直接定义即可。
    function myFn(){
      count.value += 1;
    }
    //注意点:在组合API中定义的变量/方法,要想在外界使用,必须通过return {xxx,xxx} 暴露出去
    return {count,myFn}
  }
}
</script>

  reactive

  • 是Vue3中提供的实现响应式数据的方法,本质:将传入的数据包装成一个Proxy对象

  • Vue2.x中响应式数据是通过defineProperty来实现的

  • Vue3中响应式数据是通过ES6的Proxy来实现的

  • 参数必须是对象(json/array)

  • 如果给reactive传递了其它对象(其它类型数据):

    1. 默认情况下修改对象,界面不会自动更新
    2. 如果想更新,可以通过重新赋值的方式
<template>
  <div class="home">
    <p>{{stateage.time}}</p>
    <button @click="myFn">按钮</button>
    <ul>
      <li v-for="(stu,index) in state.stus" :key="stu.id" 
      @click="removeStu(index)">{{stu.name}} -- {{stu.age}}</li>
    </ul>
  </div>
</template>

<script>
import { reactive } from "vue";
export default {
  name: 'Home',
  // setup函数是组合API的入口函数
  setup(){
    // 创建一个相应式数据
    // 本质:将传入的数据包装成一个 Proxy 对象。

    // let stateage = reactive(123);      // 普通数据类型

    // let stateage = reactive([1,2,3])  // 数组

    // let stateage = reactive({         // json 对象
    //   age:10
    // });

    let stateage = reactive({
     time:new Date()
    })  // 其它对象 
    function myFn(){
      // stateage = 666     // 由于创建时不是一个对象,所以无法实现响应式。

      // stateage[0] += 1;  // 可以实现响应式 

      // stateage.age += 1; // 可以实现响应式
      
      // 修改以前的,界面不会更新
      stateage.time.setDate(stateage.time.getDate() + 1);
      // 重新赋值
      const newTime = new Date(stateage.time.getTime());
      newTime.setDate(stateage.time.getDate() + 1);
      stateage.time = newTime;
      console.log(stateage.time)
    }
    
    // 第一种写法:
    // let state = reactive({
    //   stus:[
    //     {id:1,name:"张三",age:10},
    //     {id:2,name:"李四",age:20},
    //     {id:3,name:"王五",age:30},
    //   ]
    // });
    // function removeStu(index){
    //   state.stus = state.stus.filter((stu,idx) => idx !== index);
    // }

    // 第二种写法:
    let {state,removeStu} = useRemoveStudent();
    return {state,removeStu,stateage,myFn}
  }
}
function useRemoveStudent(){
  let state = reactive({
    stus:[
      {id:1,name:"张三",age:10},
      {id:2,name:"李四",age:20},
      {id:3,name:"王五",age:30},
    ]
  });
  function removeStu(index){
    state.stus = state.stus.filter((stu,idx) => idx !== index);
  }
  return {state, removeStu}
}
</script>

  isRef 与 isReactive

  • isRef 方法可以判断数据是否是 ref 对象
  • isReactive 方法可以判断数据是否是 reactive 对象
  // 判断是否是 ref 对象
  function isRef(obj){

    // 如果是 ref 对象 返回 true ,不是返回 false
    // return Boolean(obj && obj.__v_isRef === true);

    // 如果是 ref 对象 返回 true ,不是返回 undefined
    return obj && obj.__v_isRef;
  }

  // 判断是否是 reactive 对象
  function isReactive(obj){

    // 如果是 reactive 对象 返回 true ,不是返回 false
    // return Boolean(obj && obj.__v_isReactive === true);

    // 如果是 reactive 对象 返回 true ,不是返回 undefined
    return obj && obj.__v_isReactive;
  }

  // 测试
  console.log(isRef(ref(0)))             // true   
  console.log(isRef(reactive({})))       // false
  console.log(isReactive(ref(0)))        // false 
  console.log(isReactive(reactive({})))  // true
  <template>
    <div class="home">
      <div>{{age}}</div>
      <div>{{name.value}}</div>
      <button @click="myFn">按钮</button>
    </div>
  </template>

  <script>
  import {isRef,isReactive, reactive,ref} from "vue";
  export default {
    name: 'Home',
    setup(){

      let age = ref(18);
      let name = reactive({
        value:"小明"
      })
      function myFn(){
        console.log(isRef(age));       // true
        console.log(isRef(name));      // false
        console.log(isReactive(age));  // false
        console.log(isReactive(name)); // true
        age.value += 1;
        name.value = "小张";
      }
      return { age, name, myFn }

    }
  }
  </script>

  递归监听

  • 默认情况下,无论是通过ref和reactive都是递归监听
  • 问题:数据量较大时,非常耗性能
递归监听
<template>
  <div class="home">
    <p>{{stateref.a}}</p>
    <p>{{stateref.gf.b}}</p>
    <p>{{stateref.gf.f.c}}</p>
    <p>{{stateref.gf.f.s.d}}</p>
    <p>------------------------</p>
    <p>{{statereact.a}}</p>
    <p>{{statereact.gf.b}}</p>
    <p>{{statereact.gf.f.c}}</p>
    <p>{{statereact.gf.f.s.d}}</p>
    <p>--------------------</p>
    <button @click="myFn">按钮</button>
  </div>
</template>

<script>
import {reactive,ref} from "vue";
export default {
  name: 'Home',
  setup(){
    let stateref = ref({
      a:"a",
      gf:{
        b:"b",
        f:{
          c:"c",
          s:{
            d:"d"
          }
        }
      }
    });
    let statereact = reactive({
      a:"a",
      gf:{
        b:"b",
        f:{
          c:"c",
          s:{
            d:"d"
          }
        }
      }
    });
    function myFn(){
      stateref.value.a = 1;
      stateref.value.gf.b = 2;
      stateref.value.gf.f.c = 3;
      stateref.value.gf.f.s.d = 4;

      statereact.a = "1";
      statereact.gf.b = "2";
      statereact.gf.f.c = "3";
      statereact.gf.f.s.d = "4";
    }

    return { stateref, statereact, myFn}
  }
}
</script>

  shallowReactive 和 shallowRef、triggerRef

  • shallowReactive 和 shallowRef 可以实现非递归监听,只监听数据的第一层的变化

  • shallowRef 的本质:shallowRef -> shallowReactive。即:shallowRef(10) -> shallowReactive({ value:10 })

  • 注意点:如果通过 shallowRef 创建的数据,vue监听的是 .value 的变化,并不是数据的第一层的变化,因为本质上 value 才是第一层。

  • triggerRef 可以实现更改 shallowRef 中非第一层的数据并更新页面。

  • 注意点:vue3 只提供了 triggerRef 方法,没有提供 triggerReactive 方法,所以如果是 reactive 类型的数据,那么是无法主动触发界面更新的。

  toRaw 和 markRaw

  • toRaw:从 Reactive 或 Ref 中得到原始数据。

  • 作用:做一些不想被监听的事情 ( 提升性能 )

  • 注意点:如果想通过 toRaw 拿到 ref 类型的原始数据(创建传入的数据),就必须明确告诉 toRaw 方法,要获取的是 .value 的值,因为经过vue处理之后,.value 中保存的才是当初创建时传入的原始数据。

  import  {  toRaw, reactive, ref } from "vue";

  export default {
    setup(){

      let obj = {name:'lnj',age:18};
      let state = reactive(obj);
      let obj2 = toRaw(state);

      console.log(obj2 == obj)       // true
      console.log(obj === state);    // false
      console.log(obj == state);     // false


      // let state = ref(obj);
      
      // let obj2 = toRaw(state);
      // console.log(obj2 === obj)   // false

      // let obj2 = toRaw(state.value)
      // console.log(obj2 === obj)      // true
  
    }
  }
  • state和obj的关系:引用的关系,state的本质是一个 Proxy 对象,在这个 Proxy 对象中引用了 obj。
  • 如果直接修改obj,是无法触发界面更新的。只有通过包装之后的对象来修改,才会触发界面的更新。
  • markRaw:使 Reactive 或 Ref 无法将数据创建(转化)为响应式数据
  • 作用:将数据变为不可被追踪监听的非响应式数据。

  toRef 和 toRefs

  • ref:如果利用 ref 将定义的数据( 值类型 / 对象的属性 / 数组的某个值 )变成响应式的数据,修改响应式数据是不会影响到原始数据的。会触发UI界面更新。

  • toRef:如果利用 toRef 将定义的数据( 值类型 / 对象的属性 / 数组的某个值 )变成响应式数据,修改响应式数据是会影响到原始数据的。但是如果响应式数据是通过 toRef 创建的,那么修改了数据并不会触发UI界面的更新。

  • toRefs:可以同时监听整个对象的属性 / 数组,改变响应式数据时,会改变原始数据,不会触发UI更新。

  customRef

  • 返回一个 ref 对象,可以显示地依赖和触发响应

  • 注意点:不能在get方法中发送网络请求

import {customRef} from "vue";

function myRef(value){
  return customRef((track,trigger)=>{
    return {
      get(){
        track(); // 告诉 vue 这个数据是需要追踪变化的
        console.log('get',value);
        return value
      },
      set(newvalue){
        console.log('set',newvalue);
        value = newvalue;
        trigger(); // 告诉vue触发界面更新
      }
    }
  })
}
export default {
  setup(){
    let age = myRef(18);
    function myFn(){
      age.value += 1;
    }

    return {state,myFn}
  }
}

  ref 获取元素

  • vue2.x 中我们可以通过给元素添加 ref='xxx' 然后在代码中通过 refs.xxx 的方式来获取元素
  • 在 vue3.x 中我们也可以通过 ref 来获取元素
<template>
  <div class="ref_get">
    <div ref="box">我是div</div>
  </div> 
</template>

<script>

import {ref,onMounted} from "vue";
// onMounted 监听dom是否渲染完成
export default {
  setup(){
    let box = ref(null);
    onMounted(()=>{
      console.log('onMounted',box.value);
    })
    console.log(box.value);
    return {box}
  }
}
</script>

  readonly 和 isReadonly、shallowReadonly

  • readonly:用于创建一个只读数据,并且是递归只读

  • shallowReadonly:用于创建一个只读的数据,但不是递归只读,只是数据第一层只读。

  • isReadonly:判断数据是否是只读的。

  • constreadonly 区别:

    • const:赋值保护,不能给变量重新赋值

    • readonly:属性保护,不能给属性重新赋值。

<template>
  <div class="readonly">
    <button @click="myFn">按钮</button>
  </div>
</template>

<script>
import {readonly, isReadonly, shallowReadonly} from "vue"
export default {
  setup(){
    let state = readonly({
      name:'lng',
      attr:{
        age:18,
        height:180
      }
    });
    let shallstate = shallowReadonly({
      name:'lng',
      attr:{
        age:18,
        height:180
      }
    });
    console.log(isReadonly(state));
    function myFn(){
      // state.name = "zs";   // 修改失败,警告
      // state.attr.age = 20; // 修改失败,警告
      // shallstate.name = "zs"; // 修改失败,警告
      shallstate.attr.age = 20;  // 修改成功
      console.log(shallstate.attr.age);
    }
    return { state,shallstate,myFn }
  }
}
</script>

  手写组合API

  • reactive
<script>
export default {
  setup(){

    function ref(val){
      return reactive({value:val})
    }

    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 (const key in obj) {
            let item = obj[key];
            if(typeof item == 'object'){
              obj[key] = reactive(item);
            }
          }
        }
        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(`message:${obj} is not object`)
      }
    }
  }
}
</script>
  • shallowReadonly
function shallowReadonly(obj){
  return new Proxy(obj,{
    get(obj,key){
      return obj[key]
    },
    set(obj,key,val){
      console.warn(`message:${obj} 的 ${key} is onlyread`)
    }
  })
}
  • shallowRef 和 shallowReactive
function shallowRef(val){
  return shallowReactive({value:val})
}


function shallowReactive(obj){
  return new Proxy(obj,{
    get(obj,key){
      return obj[key]
    },
    set(obj,key,value){
      obj[key] = value;
      return true
    }
  })
}
原文地址:https://www.cnblogs.com/aloneer/p/14322006.html