vue3.0API詳解

大鵬_yp發表於2021-05-20

Vue 3.0 於 2020-09-18 釋出了,使用了 Typescript 進行了大規模的重構,帶來了 Composition API RFC 版本,類似 React Hook 一樣的寫 Vue,可以自定義自己的 hook ,讓使用者更加的靈活。

為什麼推出3.0?

  • 對 TypeScript 支援不友好(所有屬性都放在了 this 物件上,難以推倒元件的資料型別)
  • 大量的 API 掛載在 Vue 物件的原型上,難以實現 TreeShaking
    • TreeShaking:當我引入一個模組的時候,我不引入這個模組的所有程式碼,我只引入我需要的程式碼
  • 架構層面對跨平臺 dom 渲染開發支援不友好
  • CompositionAPI。受 ReactHook 啟發
  • 更方便的支援了 jsx
  • Vue 3 的 Template 支援多個根標籤,Vue 2 不支援
  • 對虛擬 DOM 進行了重寫、對模板的編譯進行了優化操作

一、setup

為我們使用 vue3 的 Composition API 新特性提供了統一的入口, 有些文章說setup 函式會在 beforeCreate 之後、created 之前執行但實踐證明這個說法不正確如下圖列印順序, vue3 也是取消了這兩個鉤子,統一用 setup 代替, 該函式相當於一個生命週期函式,vue 中過去的 data,methods,watch 等全部都用對應的新增 api 寫在 setup()函式中。

1、setup引數

  1. props: 元件傳入的屬性
  2. context:attrs,emit,slots

props

  setup中接受的props是響應式的, 當傳入新的props 時,會及時被更新。由於是響應式的, 所以不可以使用ES6解構,解構會消除它的響應式。需要結構可以用toRefs()

context

  setup中不能訪問Vue2中最常用的this物件,所以context中就提供了this中最常用的三個屬性:attrsslot 和emit,分別對應Vue2.x中的 $attr屬性、slot插槽 和$emit發射事件。

簡單用法

 

2、reactive、ref與toRefs

 

在vue2.x中, 定義資料都是在data中, 但是Vue3.x 可以使用reactiveref來進行資料定義。

區別

  reactive用於處理物件的雙向繫結,ref則處理js基礎型別的雙向繫結,reactive不能代理基本型別,例如字串、數字、boolean等。

簡單用法

<template>
  <div>
    <span>{{ year }}</span
    ><span>{{ user.name }}</span
    ><span>{{ user.label }}</span>
  </div>
</template>

<script>
import { reactive, ref } from "vue";
export default {
  props: {
    data: {
      type: Object,
      default: {
        id: 1,
        name: "匹配",
      },
    },
  },
  components: {},

  setup(props, con) {
    const year = ref(10);
    const user = reactive({
      name: "夏利",
      label: "",
    });
  //這裡ref取值需要加value
if (year.value > 5) { user.label = "牛逼"; } else { user.label = "一般"; } return { year, user, }; }, }; </script>

  不能直接對user進行結構, 這樣會消除它的響應式, 這裡就和上面我們說props不能使用ES6直接解構就呼應上了。那我們就想使用解構後的資料怎麼辦,解決辦法就是使用toRefs

   return {
        year,
        // 使用reRefs
        ...toRefs(user)
    }

3、生命週期

 

  我們可以看到beforeCreatecreatedsetup替換了(但是Vue3中你仍然可以使用, 因為Vue3是向下相容的, 也就是你實際使用的是vue2的)。其次,鉤子命名都增加了on; Vue3.x還新增用於除錯的鉤子函式onRenderTriggeredonRenderTricked

<script>
import {
  defineComponent,
  onBeforeMount,
  onMounted,
  onBeforeUpdate,
  onUpdated,
  onBeforeUnmount,
  onUnmounted,
  onErrorCaptured,
  onRenderTracked,
  onRenderTriggered,
} from "vue";

export default defineComponent({
  // beforeCreate和created是vue2的
  beforeCreate() {
    console.log("------beforeCreate-----");
  },
  created() {
    console.log("------created-----");
  },
  setup() {
    console.log("------setup-----");

    // vue3.x生命週期寫在setup中
    onBeforeMount(() => {
      console.log("------onBeforeMount-----");
    });
    onMounted(() => {
      console.log("------onMounted-----");
    });
    // 除錯哪些資料發生了變化
    onRenderTriggered((event) => {
      console.log("------onRenderTriggered-----", event);
    });
  },
});
</script>

 4、全域性定義屬性 globalProperties

  在專案中往往會全域性定義公共屬性或方法,方便我們元件間呼叫。

import { createApp } from 'vue'
import App from './App.vue'
import utils from './utils'

// vue2.0寫法
let vue=new Vue()
vue.prototype.utils=utils
// vue3.0寫法
const app=createApp(App)
app.config.globalProperties.utils=utils
app.mount(
'#app')

vue3.0中使用全域性定義的屬性

<script>
import { getCurrentInstance } from "vue";
export
default { components: {}, setup(props, con) {
const { ctx } = getCurrentInstance(); console.log(ctx.utils);
}, };
</script>

5、use與plugin

定義一個元件

 通過use引入

 6、readonly 只讀屬性

 

在使用readonly後重新進行復制是不允許修改的,這個api不常用

7、computed計算api

這裡引入了proxy內的類似寫法,有了set與get的內容寫法,該函式用來創造計算屬性,和過去一樣,它返回的值是一個 ref 物件。裡面可以傳方法,或者一個物件,物件中包含 set()、get()方法。

7.1 建立只讀的計算屬性

<template>
  <div>
    <p>refCount: {{refCount}}</p>
    <p>計算屬性的值computedCount : {{computedCount}}</p>
    <button @click="refCount++">refCount + 1</button>
  </div>
</template>

<script>
import { computed, ref } from '@vue/composition-api'
export default {
  setup() {
    const refCount = ref(1)
    // 只讀
    let computedCount = computed(() => refCount.value + 1) //2
    console.log(computedCount)
    return {
      refCount,
      computedCount
    }
  }
};
</script>

7.2 通過 set()、get()方法建立一個可讀可寫的計算屬性

<template>
  <div>
    <p>refCount: {{refCount}}</p>
    <p>計算屬性的值computedCount : {{computedCount}}</p>
    <button @click="refCount++">refCount + 1</button>
  </div>
</template>

<script>
import { computed, ref } from '@vue/composition-api'
export default {
  setup() {
    const refCount = ref(1)
    // 可讀可寫
    let computedCount = computed({
      // 取值函式
      get: () => refCount.value + 1,
      // 賦值函式
      set: val => {
        refCount.value = refCount.value -5
      }
    })
  //觸發get函式 console.log(computedCount.value)
// 為計算屬性賦值的操作,會觸發 set 函式 computedCount.value = 10 console.log(computedCount.value) // 觸發 set 函式後,count 的值會被更新 console.log(refCount.value)
return { refCount, computedCount } } }; </script>

8、watch與watchEffect

8.1watch

  watch 函式用來偵聽特定的資料來源,並在回撥函式中執行副作用。預設情況是惰性的,也就是說僅在偵聽的源資料變更時才執行回撥。

    *   1、需要一個明確的資料資源
         *   2、需要有一個回撥函式
         *   3、等到改變才會執行函式
<script>
import { ref, watch } from "vue";
export default {
  components: {},
  setup(props, con) {
    const count = ref(0);
    const name = ref("iuuggu");
    setTimeout(() => {
      (count.value = 1), (name.value = "這一行山");
    }, 200);
    watch(
      () => {
        /*
         *   1、需要一個明確的資料資源
         *   2、需要有一個回撥函式
         *   3、等到改變才會執行函式
         *
         */
        return count.value;
      },
      () => {}
    );
    // 4、ref簡寫 ,reactive不可以
    watch(count, () => {});
    // 5、監聽多個
    watch(
      () => {
        return [count, name];
      },
      ([newv, oldv], [newv, oldv]) => {}
    );
    // 6、onTrack,onTrigger
    watch(
      () => {
        return [count, name];
      },
      ([newv, oldv], [newv, oldv]) => {},
      {
        onTrack(e) {},
        onTrigger(e) {},
      }
    );
  },
};
</script>

8.1.1 監聽資料來源

reactive

setup(props, context) {
    const state = reactive<Person>({ name: 'vue', age: 10 })

    watch(
      () => state.age,
      (age, preAge) => {
        console.log(age); // 100
        console.log(preAge); // 10
      }
    )
    // 修改age 時會觸發watch 的回撥, 列印變更前後的值
    state.age = 100
    return {
      ...toRefs(state)
    }
  }

ref

setup(props, context) {
    const age = ref<number>(10);

    watch(age, () => console.log(age.value)); // 100

    // 修改age 時會觸發watch 的回撥, 列印變更後的值
    age.value = 100
    return {
      age
    }
  }

8.1.2監聽多個

setup(props, context) {
    const state = reactive<Person>({ name: 'vue', age: 10 })

    watch(
      [
      ()
=> state.age,
      () => state.name
    ], ([newName, newAge], [oldName, oldAge])
=> { console.log(newName); console.log(newAge); console.log(oldName); console.log(oldAge); } ) // 修改age 時會觸發watch 的回撥, 列印變更前後的值, 此時需要注意, 更改其中一個值, 都會執行watch的回撥 state.age = 100 state.name = 'vue3' return { ...toRefs(state) } }

8.1.3stop 停止監聽

在 setup() 函式內建立的 watch 監視,會在當前元件被銷燬的時候自動停止。如果想要明確地停止某個監視,可以呼叫 watch() 函式的返回值即可,語法如下:

setup(props, context) {
    const state = reactive<Person>({ name: 'vue', age: 10 })

    const stop =  watch(
      [() => state.age, () => state.name],
      ([newName, newAge], [oldName, oldAge]) => {
        console.log(newName);
        console.log(newAge);

        console.log(oldName);
        console.log(oldAge);
      }
    )
    // 修改age 時會觸發watch 的回撥, 列印變更前後的值, 此時需要注意, 更改其中一個值, 都會執行watch的回撥
    state.age = 100
    state.name = 'vue3'

    setTimeout(()=> {
      stop()
      // 此時修改時, 不會觸發watch 回撥
      state.age = 1000
      state.name = 'vue3-'
    }, 1000) // 1秒之後講取消watch的監聽

    return {
      ...toRefs(state)
    }
  }

8.2 watchEffect

 該函式有點像update函式,但他執行在update與beforeuodate之前。

       *    1、首次載入會立即執行
         *    2、響應的最終所有依賴監聽變化(資料改變)
         *    3、在解除安裝onUnmounte時自動停止
         *    4、執行stop就會停止監聽,否則一直監聽
         *    5、非同步函式先執行再去監聽改變
<script>
import { watchEffect, ref, onMounted } from "vue";
export default {
  components: {},
  //con==context(attrs,emit,slots)
  setup(props, con) {
    const count = ref(0);
    setTimeout(() => {
      count.value = 1;
    }, 2000);
    const stop = watchEffect(
      () => {
        /*
         *    1、首次載入會立即執行
         *    2、響應的最終所有依賴監聽變化(資料改變)
         *    3、在解除安裝onUnmounte時自動停止
         *    4、執行stop就會停止監聽,否則一直監聽
         *    5、非同步函式先執行再去監聽改變
         */
      },
      {
        // 6、在update之後執行
        flush: "post",
        // 同步執行
        flush: "async",
      }
    );
    setTimeout(() => {
      stop();
    }, 4000);

    // 7、元件掛在ref
    const myRef = ref(null);
    // 避免監聽時先見聽到null 在監聽到h1
    onMounted(() => {
      watchEffect(() => {
        console.log(myRef.value);
      });
    });
    // 8、debugging  開發模式使用
    watchEffect(
      () => {
        console.log(count.value);
      },
      {
        onTrack(e) {
          // 監聽到count和改變count
        },
        onTrigger(e) {
          // count改變了會觸發
        },
      }
    );
    return {
      myRef,
      count,
    };
  },
};
</script>

 9、ref,torefs,isref,unref

9.1ref

ref() 函式用來根據給定的值建立一個響應式的資料物件,ref() 函式呼叫的返回值是一個物件,這個物件上只包含一個 .value 屬性:

ref資料

<template>
    <div class="mine">
        {{count}} // 10
    </div>
</template>

<script >
import {ref } from 'vue';
export default {
  setup() {
    const count = ref(10)
    // 在js 中獲取ref 中定義的值, 需要通過value屬性
    console.log(count.value);
    return {
       count
    }
   }
}
</script>

ref 訪問dom

通過 refs 來回去真實 dom 元素, 這個和 react 的用法一樣,為了獲得對模板內元素或元件例項的引用,我們可以像往常一樣在 setup()中宣告一個 ref 並返回它

  1.  還是跟往常一樣,在 html 中寫入 ref 的名稱
  2.  在steup 中定義一個 ref
  3.  steup 中返回 ref的例項
  4.  onMounted 中可以得到 ref的RefImpl的物件, 通過.value 獲取真實dom

<template>
  <!--第一步:還是跟往常一樣,在 html 中寫入 ref 的名稱-->
  <div class="mine" ref="elmRefs">
    <span>1111</span>
  </div>
</template>

<script >
import { set } from 'lodash';
import { onMounted, ref } from 'vue';
export default{
  setup(props, context) {
    // 獲取真實dom
    const elmRefs = ref(null);
    onMounted (() => {
      console.log(elmRefs.value); // 得到一個 RefImpl 的物件, 通過 .value 訪問到資料
    })

    return {
      elmRefs
    }
  }
}
</script>

 

 

 在 reactive 物件中訪問 ref 建立的響應式資料

當把 ref() 建立出來的響應式資料物件,掛載到 reactive() 上時,會自動把響應式資料物件展開為原始的值,不需通過 .value 就可以直接被訪問,例如:

<template>
    <div class="mine">
        {{count}} -{{t}} // 10 -100
    </div>
</template>

<script >
import  reactive, ref, toRefs } from 'vue';
export default {
  setup() {
    const count = ref(10)
    const obj = reactive({
      t: 100,
      count
    })
    // 通過reactive 來獲取ref 的值時,不需要使用.value屬性
    console.log(obj.count);
    return {
       ...toRefs(obj)
    }
   }
}
</script>

9.2 toRefs() 函式

toRefs() 函式可以將 reactive() 建立出來的響應式物件,轉換為普通的物件,只不過,這個物件上的每個屬性節點,都是 ref() 型別的響應式資料

<template>
  <div>
    <p>{{ count }} - {{ name }}</p>
    <button @click="count += 1">+1</button>
    <button @click="add">+1</button>
  </div>
</template>

<script>
import { reactive, toRefs } from "@vue/composition-api";
export default {
  setup() {
    // 響應式資料
    const state = reactive({ count: 0, name: "zs" });
    // 方法
    const add = () => {
      state.count += 1;
    };
    return {
      // 非響應式資料
      // ...state,
      // 響應式資料
      ...toRefs(state),
      add
    };
  }
};
</script>

9.3 isref

isRef() 用來判斷某個值是否為 ref() 建立出來的物件

 setup(props, context) {
    const name: string = 'vue'
    const age = ref(18)
    console.log(isRef(age)); // true
    console.log(isRef(name)); // false

    return {
      age,
      name
    }
  }

9.4 unref

unRef() 用來判斷某個值是否為 ref() 建立出來的物件有丟擲該物件

 setup(props, context) {
    const name: string = 'vue'
    const age = ref(18)
    console.log(unRef(age)); // 18
    console.log(unRef(name)); // vue

    return {
      age,
      name
    }
  }