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 };
  }

      

posted @ 2021-08-26 15:16  眼里有激光  阅读(639)  评论(0编辑  收藏  举报