• 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
        }
      })
    }
    
  • 相关阅读:
    BZOJ1057:[ZJOI2007]棋盘制作——题解
    洛谷4147:玉蟾宫——题解
    洛谷1578:[WC2002]奶牛浴场——题解
    BZOJ1926:[SDOI2010]粟粟的书架——题解
    BZOJ3123:[SDOI2013]森林——题解
    BZOJ1834:[ZJOI2010]网络扩容——题解
    BZOJ2668:[CQOI2012]交换棋子——题解
    BZOJ1070:[SCOI2007]修车——题解
    BZOJ1877:[SDOI2009]晨跑——题解
    在阿里,我们如何管理测试环境
  • 原文地址:https://www.cnblogs.com/aloneer/p/14322006.html
Copyright © 2020-2023  润新知