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

      


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM