zoukankan      html  css  js  c++  java
  • vue3--相对于vue2的改变-T0档次

    前言

      vue3相对于vue2做了不少的改变,个人对其进行了一些整理。并结合在日常中的使用,对这些新特性在开发时的影响度进行了分级。

    T0档次

      setup

        介绍:setup(props,content) objet

          官方文档给出的解释是,在目前vue在编写复杂组件的时候(组件内代码量的庞大),会导致本来是一个逻辑块的代码,分散开来。这样会增加后面接手的人阅读上的难度。所以提供了一个组合api,可以把相同逻辑的代码块放在一个地方,这样便于阅读。不过这个东西仁者见仁智者见智,本人就觉得把一个个逻辑块分块放好也挺好的,重点开发人员编码习惯,一个邋遢的开发人员,就算你给他提供再好的api,照样会给你写出奇形怪状的代码来。但话说回来,你可以不用,但我不能没有。vue3充分尊重了开发人员的选择,setup你可以用,也可以不用。就像vue2一样,在钩子函数和methods中编写逻辑块,也是一种正确的选择

        使用细节:

          相对于vue2的执行周期

             setup(beforeCreate,created)凡是在原来beforeCreate和create中执行的代码,你都可以放在setup中,而且setup执行的时机还在brforeCreate之前

          一些限制

            1.不能访问$data,this

            2.必须返回一个{},

          返回值

            如果存在返回值,那么它将会合并到$data中,如果$data已经存在相同的key,那么将会覆盖原有key值。值得一提的是,如果你返回的是一个普通对象,那么即便合并到$data之后,改变对应的值,也不会具有响应性。你需要使用vue3提供的响应式方法,将普通对象转化成响应式对象,这样合并在$data上的属性才会是响应式的

         

    响应式原理

      变化:

        vue3将原来vue2中对数据响应式处理方法,由es5的Object.defineProperty改为es6的Proxy

      不同的点:

        defineProperty:是对对象的属性定义拦截,如果被定义的obj对象存在多个属性或者是多嵌套结构,那么就要便利递归它所有的属性,挨个去使用Object.defineProperty定义属性拦截

        let value = "obj1 - prop1";
        let obj = {};
        Object.defineProperty(obj, "prop1", {
          get() {
            return value + " suffix";
          },
        });
    
        console.log(obj); //obj1-name suffix

        Proxy:是对obj整个对象进行代理,它返回一个新的代理对象,这意味着:

          1.当我们改变数组中某一项的值的时候再也不需要使用$set了,直接this.arr[1]=new value即可触发视图更新。

          2.我们在响应式对象上新增的属性时,依然会触发也会触发视图更新

        let obj = { prop1: "obj1 - prop1" };
        let objProxy = new Proxy(obj, {
          get(obj, prop) {
            return obj[prop] + " suffix";
          },
        });
        console.log(obj.prop1); //obj1-prop1
        console.log(objProxy.prop1); //obj1-prop1 suffix
        return reactive({ data: "9999" });

    生命周期函数钩子

      介绍:vue3新增一批生命周期钩子函数,其目的是为了解决在使用setup的时候,因为setup的执行周期是在beaforeCreate和ceated之间,一些需要在其他周期时机内执行的一些逻辑,比如获取dom。无法setup中执行。所以为了使在setup中的代码,能够在指定的声明周期内执行,vue3补充了一些周期钩子函数,它们的作用和vue的声明周期钩子完全一样。

      

      举例使用

    import { onMounted } from "vue"; //引入钩子函数,其他的声明周期钩子函数也是如此引入
    export default {
      setup(props, content) {
        //因为在setup中无法访问this,所以也无法访问到$data,代码中需要依赖到$data中的字段的话,可以在setup中定义data,setup return之后,会合并至$data
        const data = reactive({
          data: 1,
        });
        //所有的钩子函数都是接收一个方法作为参数
        onMounted(function() {
          console.log("参数方法,在对应的生命周期内执行 mounted", data.data);
        });
        console.log("在setup中的代码,在beforeCreate至created 生命周期内执行");
        return data;
      },

    响应式api

      简介vue3提供的响应式api可以使普通对象转化为响应式对象,这也是为了配合setup的使用提供的一个方法,在setup的执行时机是beforeCreate和created,并且无法访问到this和$data,而使用setup可能会使用到一些响应式的变量(比如说,请求一个接口,在接口响应后将数据渲染到页面上,因为接口响应是异步的,所以就必须要一个响应式的对象接收数据发生改变,从而触发视图更新),而setup中不能读取this,自然就无法获取到data(){}项中定义的响应式属性,所以只能通过return的方式,把一个响应式的对象合并到data中。

    <template>
      <div class="hello">
        {{ prop1 }}
      </div>
      <button class="but" @click="changeVal">改变值</button>
    </template>
    
    <script>
    export default {
      setup(props, content) {
        //setup中将一个普通对象return合并到$data上之只会在初次渲染的时候作用在视图上,后续再对prop1进行更改this.prop1=new value,是不会触发视图更新的.因为data中的prop1只是一个普通的对象,而非响应式对象
        const data = {
          prop1: "old value",
        };
        return data; //return对象中所有属性都会合并到$data上
      },
      methods: {
        changeVal() {
          this.data = "new value"; //不会触发视图更新
        },
      },
    };
    </script>
    
    //使用响应式api //引入响应式api import { ref } from "@vue/reactivity"; export default { //要想使setup中return的对象具有响应式那么只需将return的对象使用响应式钩子处理即可 setup(props, content) { const data = { prop1: ref("old value"),//使prop1属性变成响应式对象 }; return data; }, methods: { changeVal() { this.prop1 = "new value";//视图发生改变 }, }, };

     响应式api分类:

        1.reactive 将引用类型对象转化成响应式对象,(这样的好处是,遇到复杂的数据类型,不需递归去使用ref,不然又回到了vue2)

    reactive({} || []); //针对引用类型,将引用类型转化为响应式对象

        2.ref 适合将基本类型转化成响应式对象,但ref同样也能对引用类型进行转化,与reactive不同的是,你需要通过ref.value来使用属性。对于基本类型来说这是实现响应式的基本条件,但是对于引用类型来说已经可以通过Proxy来实现代理,没有必要在进行过渡封装本来可以prop直接使用,没有必要value.prop,所以为什么说,ref适合基本类型。值得一提的是ref在对引用类型处理的时候,也是将其转化成Proxy

    const obj = ref(1 || "1"); //针对基本类型,接受一个基本类型返回响应式对象
    console.log(obj.value); //返回的响应式对象,通过value获取值

    响应式api拓展

      为了让开发者在使用响应式api时,能够便捷,全面的适用场景,vue3,对响应式api进行了一些拓展,围绕reactive和ref提供了一些功能性的api

      1.reactive

        readonly:创建一个只读的响应式对象。ps:(都只读了还需要响应式吗?)

     import {readonly} from "vue";//引入方法

     const copy = readonly({ prop: "测试属性", level: { prop: "level 测试属性" }, }); //将普通对象的副本转化成一个只读的响应式对象 copy.prop = "1234"; //warning;在这里改变只读的copy响应式对象时,会出现警告,不允许改变响应式对象copy的prop属性; copy.level.prop = "9999"; //warning 只读属性是深层次的,不管嵌套了多少层,只要是属于copy内部的属性,都无发更改 },

        isProxy:判断响应式对象是否是由reactive或者readonly创建的Proxy

      import {readonly} from "vue" //引入方法

       const copy = reactive({ prop: "测试属性", }); const copyReadonly = readonly({ prop: "only 属性" }); console.log(isProxy(copy)); //true   console.log(isProxy(copyReadonly)); //true
    console.log(isProxy({copyReadonly,copy})); //false

        isReactive:判断响应式对象是否是由reactive创建响应式对象

      import {isReactive} from "vue"  

      const copy = reactive({ prop: "测试属性", }); const copyReadonly = readonly({ prop: "only 属性" }); console.log(isReactive(copy)); //true console.log(isReactive(copyReadonly)); //true console.log(isReactive(readonly(copy))); //true 如果readonly依赖与reactive创建,那么也返回true console.log(isReactive(readonly({ copy }))); //false

        isReadonly:判断是否是由readonly创建的响应式对象

      import {isReadonly} from "vue";//引入方法

      const copy = reactive({ prop: "测试属性", }); const copyReadonly = readonly({ prop: "only 属性" }); console.log(isReadonly(copy)); //false console.log(isReadonly(copyReadonly)); //true console.log(isReadonly(readonly(copy))); //true 如果readonly依赖于reactive创建,同样返回ture

        toRaw:返回 reactivereadonly 代理的原始对象。这是一个转义口,可用于临时读取而不会引起代理访问/跟踪开销,也可用于写入而不会触发更改。不建议保留对原始对象的持久引用。请谨慎使用。

      const copy = reactive({
          prop: "测试属性",
        });
    
        const copyReadonly = readonly({ prop: "only 属性" });
        console.log(toRaw(copy)); //{prop:"测试属性"}
        console.log(toRaw(copyReadonly)); //{prop:"only 属性"}
        console.log(toRaw(readonly(copy))); //{prop:"测试属性"} 多重嵌套同样可以获取到原始值

         markRaw:标记一个对象,使其永远不会被转还成代理

       const base = markRaw({ prop: "base prop属性" });
        const reactiveBase = reactive(base);
        const readonlyeBase = readonly(base);
        const proxyBase = reactive({ reactiveBase });
        const proxyBase2 = reactive({ reactiveBase, prop2: "1245" }); //这里多出一个普通属性prop2
        console.log(isReactive(reactiveBase)); //false
        console.log(isReadonly(readonlyeBase)); //false
        console.log(isReactive(proxyBase)); //true 但是改变属性reactiveBase中的prop,仍不会触发视图更新
        console.log(isReactive(proxyBase2)); //true 只改动属性reactiveBase中的prop不会触发视图更新,但同时改变prop2属性,因为prop2属性正常的属性,所以会触发视图更新,视图更新的时候发现reactiveBase中的prop也发生了改变,
    //所以即便它被设置成了markRaw,也一样会在视图上呈现改动后的内容,

        shallowReactive:只对自身的属性进行响应式的追踪,对于嵌套的属性,不做响应式处理

      const obj = {
          level1: "测试属性level1",
          sub: {
            subProp1: "测试属性subProp1",
          },
        };
        const shallowReactiveObj = shallowReactive(obj);
    const reactiveObj
    = reactive(obj);//reactive console.log(isReactive(shallowReactiveObj)); //true console.log(isReactive(shallowReactiveObj.sub)); //false console.log(isReactive(reactiveObj.sub)); //true // ... //改动自身属性,触发视图更新 shallowReactiveObj.level1 = "new level1"; // ... //改动嵌套属性,不触发视图更新 shallowReactiveObj.sub.subProp1 = "new sub subProp1"; // ... //同时改变嵌套属性和自身属性 shallowReactiveObj.level1 = "new level1"; shallowReactiveObj.sub.subProp1 = "new sub subProp1"; //和markRaw一样,因为改变自身属性时会造成视图更新,而更新的时候发现嵌套熟悉sub.subProp1也改变了,所以改变后的sub.subProp1也会渲染至最新视图 return shallowReactiveObj;

        shallowReadonly:使其自身的 property 为只读,但不执行嵌套对象的深度只读转换 (暴露原始值)。

    const state = shallowReadonly({
      foo: 1,
      nested: {
        bar: 2
      }
    })
    
    // 改变状态本身的property将失败
    state.foo++
    // ...但适用于嵌套对象
    isReadonly(state.nested) // false
    state.nested.bar++ // 适用

      2.ref

        unref:如果参数为 ref,则返回内部值,否则返回参数本身

        let baseVal = 1;
        console.log(unref(ref(1))); //返回内部置 1
        console.log(unref(baseVal)); //number 1

        toRef:可以为响应式对象的其中property创建一个ref,然后将其独立使用

       let baseVal = {
          prop1: "baseVal.prop1",
        };
        let prop1 = toRef(reactive(baseVal), "prop1");
        ...
        prop1 = "new prop1"; //触发视图更新

        toRefs:将响应式对象转换为普通对象,其中结果对象的每个 property 都是指向原始对象相应 property 的

      let state = {
          prop1: "baseVal.prop1",
          prop2: "baseVal.prop2",
          sub: {
            prop1: "axsxsxs",
          },
        };
        let stateRef = toRefs(reactive(state)); //将一个rective转化成普通对象,并将reactive的property转化成ref
        // ...
        stateRef.prop1 = "new value"; //触发试图更新
    
        console.log(stateRef.sub); //Proxy 如果reactive的peoperty中存在{}||[],那么则不做处理,只是简单的保留,换句话说,就是toRefs只对基本类型property处理,非基本类型的proper,就地保留

        isRef:检查变量是否是一个ref,使用方法和isReactive一致

        const state = "test";
        const refState = ref(state);
        console.log(isRef(refState)); //true;
        console.log(isRef(state)); //false;

        cutomRef:创建一个自定义的 ref,并对其依赖项跟踪和更新触发进行显式控制。它需要一个工厂函数,该函数接收 tracktrigger 函数作为参数,并应返回一个带有 getset 的对象。

        let state = "test";
        const refState = customRef((track, trigger) => {
          let timeout;
          return {
            get() {
              track(); //track方法在get必须要执行,它是用来搜集依赖元素(订阅者),
              return state;
            },
            set(newValue) {
              clearTimeout(timeout);
              //这里表示延迟两秒通知订阅者更新,使用延迟函数能够更好的体现变化的异步效果,在学习过程中更便于理解
              timeout = setTimeout(() => {
                state = newValue;
                trigger(); //trigger是必须要执行的,他表示通知vue,refState已经发生改变,通过track() 收集到的订阅者需要更新了
              }, 1000);
            },
          };
        });

        shallowRef:创建一个 ref,它跟踪自己的 .value 更改,但不会使其值成为响应式的。在改变ref.value的时候会发生试图更新,在改变ref.value.prop的时候则不会触发试图更新

     setup() {
        let stateRef = shallowRef({ prop: "old val" }); //这里之所以使用引用类型,是因为对于基本类型ref.value直接可以访问值,无法演示ref.value.prop
        setTimeout(() => {
          stateRef.value = { prop: "new val" }; //触发视图更新(副作用之一)
        }, 2000);
        setTimeout(() => {
          stateRef.value.prop = "new val2"; //无法触发视图更新
        }, 3000);
        return {
          stateRef,
        };
      }

        triggerRef:手动触发与shallowRef的关联效果

    setup() {
        const shallow = shallowRef({
          greet: "Hello, world",
        });
    
        // 第一次运行时记录一次 "Hello, world"
        watchEffect(() => {
          console.log(shallow.value.greet);
        });
    
        // 这不会触发作用,因为 ref 很浅层
        shallow.value.greet = "Hello, universe";
    
        // 记录 "Hello, universe"
        triggerRef(shallow);
        return { shallow };
      }

          

  • 相关阅读:
    准备 FRM 考试——方法、工具与教训
    930. 和相同的二元子数组 前缀和
    1906. 查询差绝对值的最小值 前缀和
    剑指 Offer 37. 序列化二叉树 二叉树 字符串
    815. 公交路线 BFS
    518. 零钱兑换 II dp 完全背包
    1049. 最后一块石头的重量 II dp
    5779. 装包裹的最小浪费空间 二分
    5778. 使二进制字符串字符交替的最少反转次数 字符串 滑动窗口
    474. 一和零 dp
  • 原文地址:https://www.cnblogs.com/wrhbk/p/14700231.html
Copyright © 2011-2022 走看看