Vue3 | Composition API 學習總結

爲了可以使用Composition API, 咱們須要有一個能夠實際使用它的地方。在vue組件中,咱們將此位置稱爲setuphtml

setup函數

setup函數是在組件建立以前執行的,setup函數中的props一旦被解析,就將做爲Composition API 的入口vue

須要注意的是: 咱們不能在setup函數中使用this, 由於它不會找到組件實例。 緣由是 雖然組件實例是在執行setup以前就會被建立出來了,可是 setup函數被調用以前,data,computed,methods等都沒有被解析,因此它們沒法在setup中被獲取。node

setup接收倆個參數(props,context)

props

做爲setup函數中的第一個參數,props是響應式的,當傳入新的prop時,它將會被更新react

其實就是父組件傳遞過來的屬性會被放到props對象中web

  • 對於定義props的類型,和Vue2同樣,在props選項對象中定義
  • 在template中的寫法依然是不變的
  • 由於props有直接做爲參數傳遞到setup函數中,因此咱們能夠直接經過參數來使用。
export default {
    props:{
        message:{
            type:String,
            default:"hello"
        }
    },
    setup(props){
        consloe.log(props.message)
    }
}
複製代碼

!! 因爲 props 是響應式的, 因此咱們不能用 解構,它會消除props的響應性。api

若是須要結構props,那麼須要用到 toRefs函數來完成操做。數組

import {toRefs} from 'vue'
setup(props){
    const message = toRefs(props,'message')
    console.log(message.value)
}
複製代碼

context

做爲setup函數的第二個參數, context是一個普通的JavaScript對象,它暴露組件的三個property安全

  • **attrs ** :全部的非prop的attribute
  • slots :父組件傳遞過來的插槽
  • emit : 當咱們組件內部須要發出事件時用到emit, 和vue2中 this.$emit用法一致

!!因爲context只是一個普通的JavaScript對象,因此它不是響應式的,這就意味着咱們能夠安全地對 context 使用 解構markdown

export default{
    setup(props,{attrs,slots,emit}){
        ...
    }
}
複製代碼

setup函數的返回值

因爲setup是一個函數,那麼它就會有返回值網絡

若是setup的返回值是一個對象,那麼該對象的property就能夠在模板中訪問到。

注意的是:從setup中返回的 refs在模板中是會自動淺解包的,因此沒必要在模板中用 .value

有關響應性的API

reactive

返回對象的響應式副本

若是想在setup中定義的數據是具備響應式的,那麼就可使用 reactive

const state = reactive({ name:'wangpf',age:18 })
複製代碼

爲何會經過 reactive 就能夠變成響應式的呢?

  • 由於當咱們使用reactvie函數處理咱們的數據以後,數據再次被使用時會進行以依賴收集
  • 當數據發生變化時,全部收集到的依賴都是進行對象的響應式的操做
  • 事實上,在vue2中,咱們編寫的 data選項,也是在內部交給了 reactive函數將其變成了響應式對象的

注意的倆點:

  • reactive 將解包全部深層的 refs,同時維持着 ref 的響應性

    const count = ref(0)
    const obj = reactive({ count })
    
    // ref 會被解包
    console.log(count.value === obj.value) // true
    
    // 會更新到 'obj.count'
    count.value++ 
    console.log(count.value) // 1
    console.log(obj.count) // 1
    
    // 也會更新到 ref 'count'
    obj.count++
    console.log(count.value) // 2
    console.log(obj,value) // 2
    複製代碼
  • 當把 ref 分配給 reactive 時,將會自動解包

    const count = ref(1)
    const obj = reactive({ })
    obj.count = count
    console.log(obj.count) // 1
    console.log(obj.count === count.value) // true
    複製代碼

可是因爲 reactive 對傳入的類型是有限制的,它要求咱們必須傳入的是一個對象或者數組

若是咱們傳入的是一個基本的數據類型(String,Number,Boolean)則會報警告.

reactive的限制.png 這時,咱們可使用另外一個API, ref

ref

接受一個內部值並返回一個響應式且可變的 ref 對象。 ref對象具備指向內部值的單個property (.vlaue)

ref 會返回一個可變的響應式對象,該對象做爲一個響應式的引用(reference) 維護着它內部的值。

內部的值是在 ref的 vlaue屬性中被維護的

readonly

咱們經過 reactive 或者 ref 能夠獲取到一個響應式的對象,可是某些狀況下, 咱們傳入給其餘地方(組件) 的這個響應式對象但願在另一個地方(組件)被使用,可是不能被修改,這個時候就能夠用 readonly 了

readnoly 會返回原生對象的只讀代理 原理在於: 利用 proxy 中的set,在set方法中將其劫持,而且設置該值不能修改

關於reactive 判斷 的API

isProxy

檢查對象是否由 reactivereadonly 建立的 proxy

const state = reactive({ count : 0 })
const count = 0
isProxy(state) // true
isProxy(readonly(state)) // true
isProxy(count) // false
複製代碼

isReactive

檢查對象是否由 reactive 建立處理的相應手機代理

但若是代理是由 readonly建立的,但包裹了由 reactive建立 另外一個代理,也會返回 true

const state = reactive({ count : 0 })
isReactive(state) // true
isReactive(readonly(state)) // true
複製代碼

isReadOnly

檢查對象是否由 readonly建立的只讀代理

toRaw

返回 reactive 或 readonly 代理的原始對象 (不建議保留對原始對象的持久化引用,謹慎使用)

shallowReactive

shallow(淺層)

建立一個響應式代理,他跟蹤其自身property的響應性,但不執行嵌套對象的深層響應式轉換 (深層仍是原生對象)

相似於淺拷貝,只把第一層轉爲響應式了,深層仍是原始對象

shallowReadonly

建立一個 proxy , 使其自身的 property 爲只讀, 但不執行嵌套對象的深度只讀轉換

就是說第一層是隻讀的,可是深層仍是可讀,可寫的

toRefs

因爲咱們使用ES6的解構語法對 reactive 返回的對象進行解構賦值,那麼解構後的數據是不具備響應式的

而使用 toRefs 能夠將 reative 返回的對象中的屬性都轉成 ref 這樣咱們再次解構出來的數據都是 ref的。

const state = reactive({ name:'wangpf' , age:18 });
const { name, age } = state; // 這樣解構出來的數據是沒有響應式的

const { name, age } = toRefs(state) // 這樣的解構出來的數據轉換成了ref的,是響應式的
// 上述的作法, 至關於在 state.name 和 name.value 之間創建了鏈接, 修改任何一個都會引發另一個變化
複製代碼

toRef

若是咱們但願轉換一個 reactive 對象中的屬性爲 ref ,那麼可使用 toRef 的方法

const state = reactive({ name:'wangpf' , age:18 });
const name = toRef(state,"name"); // 該 name 是ref的, 
// 一樣的, name.value 和 state.name 之間創建了鏈接, 修改會互相影響
複製代碼

ref其餘的API

unref

若是想要獲取一個ref引用中的value,能夠經過 unref 方法:

  • 若是參數是一個 ref, 則返回內部值,不然返回參數本事

  • 實際上是一個語法糖:

    • val = isRef(val) ? val.value : val
      複製代碼
function useFoo(x: number | Ref<number>) {
  const unwrapped = unref(x) // unwrapped 如今必定是數字類型
}
複製代碼

isRef

判斷值是不是一個ref對象

shallowRef

建立一個淺層的ref對象

const info = shallowRef({ name: "wangpf" })

const changeInfo  = () => {
    info.value.name = 'wpf'  // 此時的修改是不可以實現響應式的
}
複製代碼

triggerRef

手動觸發和 shallowRef 相關聯的反作用

const info = shallowRef({ name: "wangpf" })

const changeInfo  = () => {
    info.value.name = 'wpf'  // 此時的修改是不可以實現響應式的
    // 使用 triggerRef 能夠觸發
    triggerRef(info)
}
複製代碼

customRef

建立一個自定義的 ref ,並對其依賴項跟蹤和更新觸發進行顯示控制

  • 它須要一個工廠函數,該函數接受 track 和 trigger 函數 做爲參數
  • 應該返回一個帶有 get 和 set 的對象
用 customRef 作的一個防抖案例
import { customRef } from "vue";

export default function (value, delay = 300) {
  let timer = null;
  return customRef((track, trigger) => {
    return {
      get() {
        track();
        return value;
      },
      set(newValue) {
        clearTimeout(timer);
        timer = setTimeout(() => {
          value = newValue;
          trigger();
        }, delay);
      },
    };
  });
}

複製代碼
<template>
  <input type="text" v-model="message" />
  <p>{{ message }}</p>
</template>

<script>
import useDebounceRef from "../hooks/useDebounceRef";

export default {
  name: "Demo2",
  setup() {
    const message = useDebounceRef("hello", 300);
    return {
      message,
    };
  },
};
</script>
複製代碼

computed

該API 的方法 和 vue2 的同樣, 只不過寫的地方是在setup函數中了, 但還需注意的點是 computed 返回的值是一個 ref

  • 方式一: 接收一個 getter 函數,併爲 getter 函數返回的值,返回一個不變的 ref 對象
const firstName = ref("wangpf");
    const lastName = ref("ok");
	const fullName = computed(()=> `${firstName.value} ${lastName.value}`)
複製代碼
  • 方式二 : 接收一個具備 get 和 set 的對象,返回一個可變的(可讀寫的)ref對象
const firstName = ref("wangpf");
    const lastName = ref("ok");
    const fullName = computed({
      get() {
        return `${firstName.value} ${lastName.value}`;
      },
      set(newValue) {
        const names = newValue.split(" ");
        firstName.value = names[0];
        lastName.value = names[1];
      },
    });
    const changeName = () => {
      fullName.value = "wpf err";
      console.log(fullName.value);
    };
複製代碼

偵聽數據的變化 (watch watchEffect)

在 Composition API 中, 咱們可使用 watchEffect 和 watch 來完成響應式數據的偵聽

  • watch 須要手動指定偵聽的數據源
  • watchEffect 用於自動收集響應式數據的依賴

watchEffect

響應式計算和偵聽 | Vue.js (vuejs.org)

當偵聽到某些響應式數據變化時,咱們但願執行某些操做,這個時候就能夠用 watchEffect

來看一個案例:

const name = ref('wangpf')
const age = ref(18)

watchEffect(() => {
    console.log('watchEffect執行了',name.value,age.value)
})
複製代碼
  • 經過以上代碼,首先 watchEffect 傳入的函數會當即被執行一次,而且在執行的過程當中會收集依賴
    • (爲何須要當即執行一次的緣由就是須要去收集依賴)
  • 其次,只有收集的依賴發生變化時,watchEffect 傳入的函數纔會被執行
watchEffect 的中止偵聽
  • 若是在發生某些狀況下,咱們但願中止偵聽,這個時候咱們能夠獲取 watchEffect 的返回值函數, 調用它便可

看一個案例:

const stopWatch = watchEffect(() => {
    console.log('watchEffect執行了',name.value,age.value)
})
const changeAge = () => {
    age.value++;
    if(age.value > 20){
        stopWatch() // 掉用 watchEffect 的返回值就能夠中止偵聽了
    }
}
複製代碼
watchEffect 清除反作用

用途: 好比在開發中咱們須要在偵聽函數中執行網絡請求,可是在網絡請求尚未達到的時候,咱們中止了偵聽器,或者偵聽器偵聽函數被再次執行了,這時咱們須要把上一次的網絡請求 取消掉, 就能夠用到該方法了

看了一個案例:

watchEffect((onInvalidate) => {
    console.log('watchEffect執行了',name.value,age.value)
    const timer = setTimeout(() => {
        console.log('1s後執行的操做')
    },1000)
    onInvalidate(() => {
        // 在這裏操做一些清除工做
        clearTimeour(timer)
    })
})
複製代碼

在上述代碼中,咱們給 watchEffect 傳入的函數被回調時,能夠獲取到一個參數:onInvalidate (該參數是一個函數),能夠在這個參數中,執行一些清除工做。

watchEffect 的執行時間(刷新時機)

在默認狀況下 , 組件的更新會在 watchEffect (反作用函數) 以前執行

const title = ref(null);  // 該title 已和 div 標籤綁定了
    watchEffect(() => {
      console.log(title.value);
    });
    return { title };
複製代碼

watchEffect 的執行時間.png 那麼,當咱們在 watchEffect (反作用函數) 獲取元素時, 第一次執行是確定是個null, 是不能夠的。只有當DOM掛載完畢後,纔會給 title 賦新的值,watchEffect (反作用函數)纔會再次執行, 打印出對應的元素

咱們但願第一次就打印出該元素的話,這時候須要給 watchEffect 傳入第二個參數, 該參數是一個 對象,對象中的 flush 可取三個值 : 'pre' (默認的) , 'post' , 'async' (不建議使用)

// 在組件更新後觸發,這樣你就能夠訪問更新的 DOM。
// 注意:這也將推遲反作用的初始運行,直到組件的首次渲染完成。
watchEffect(
  () => {
    /* ... */
  },
  {
    flush: 'post' // 在 DOM元素掛載或更新以後執行, "pre" 當即執行 (默認的)
  }
)
複製代碼

watch

watch API 的功能和 vue 2 的 option API 中的 watch 功能同樣, 默認狀況下, watch 只有當被偵聽的源發送改變時纔會去回調

與 watchEffect 比較,不一樣的是:

  • watch 是 懶執行反作用
  • 更具體地說明了什麼狀態下應該觸發偵聽器從新運行
  • 能夠訪問到偵聽狀態變化先後的值
偵聽單個數據源

想要偵聽單個數據源的話, 有倆種方法: 傳入 getter 函數 或 ref 對象

// 偵聽 getter 
const state = reactive({ name : 'wangpf' })
watch(() => state.name , (newVal,oldVal) => { 
	/* ... */
})

// 偵聽 ref
const name = ref('wamgpf')
watch(name,(newVal,oldVal) => { 
  /* ... */
  // 這裏的 newVal,oldVal 的值是 是返回的 ref.value 的值
})
複製代碼
偵聽多個數據源

方法: 傳入數組

const firstName = ref("AAA");
    const lastName = ref("bbb");
    const changeName = () => {
      firstName.value = "A";
      lastName.value = "b";
    };
    watch([firstName, lastName], (newVal, oldVal) => {
      console.log("newVal:", newVal, "oldVal:", oldVal);
    });
// newVal:  ["A", "b"] oldVal:  ["AAA", "bbb"]
複製代碼
偵聽響應式對象

就是偵聽 reactive 對象

const numbers = reactive([1, 2, 3, 4])

watch(
  () => [...numbers],
  (numbers, prevNumbers) => {
    console.log(numbers, prevNumbers)
  }
)

numbers.push(5) // logs: [1,2,3,4,5] [1,2,3,4]
複製代碼

想要深度偵聽嵌套對象或數組時,須要 deep 設爲 true,

const state = reactive({ 
  id: 1,
  attributes: { 
    name: '',
  }
})

watch(
  () => state,
  (state, prevState) => {
    console.log(
      'not deep',
      state.attributes.name,
      prevState.attributes.name
    )
  }
)

watch(
  () => state,
  (state, prevState) => {
    console.log(
      'deep',
      state.attributes.name,
      prevState.attributes.name
    )
  },
  { deep: true }
)

state.attributes.name = 'Alex' // 日誌: "deep" "Alex" "Alex"
複製代碼

可是會發現 新的值和舊的值是同樣的。這時候爲了徹底偵聽,須要使用深拷貝了

其餘API

生命週期函數

em.... 去看文檔吧,

生命週期鉤子 | Vue.js (vuejs.org)

Provide / Inject

功能和以前同樣,

咱們能夠經過 provide 來提供數據

  • 經過 provide 方法來定義每一個 property
  • 傳入倆個參數: name(提供的屬性名稱), value(提供的屬性值)

咱們能夠經過 jnject 來注入須要的內容

  • 要 inject 的 property 的 name
  • 默認值 (可選)
let count = ref(100)
let info = { name : "wangpf" , age : 18 }
provide("count",readonly(count))
provide("info",readonly(info))   // 這裏建議使用 readonly 對值進行包裹,防止傳遞的數據不會被 inject 的組件更改

// 在後代組件中 經過 inject 來獲取
const count = inject("count")
const info = inject("info")
複製代碼

h函數

vue在生成真實DOM以前,會將咱們的節點轉換成VNode,而VNode組合起來會造成一顆樹結構,即 虛擬DOM

在 template 中的 html 是使用渲染函數生成的對應的VNode

若是咱們想要利用 JavaScript 來編寫 createVNode 函數,生成對應的VNode 那麼就可使用 h() 函數

h() 函數是一個用於常見 VNode 的一個函數, 其實更準確的命名是 createVNode() 函數,但爲了簡便 vue 將其簡化爲 h() 函數

h() 函數接收三個參數: (標籤,屬性,後代)

h(
  // {String | Object | Function} tag
  // 一個 HTML 標籤名、一個組件、一個異步組件、或
  // 一個函數式組件。
  //
  // 必需的。
  'div',

  // {Object} props
  // 與 attribute、prop 和事件相對應的對象。
  // 咱們會在模板中使用。
  //
  // 可選的。
  {},

  // {String | Array | Object} children
  // 子 VNodes, 使用 `h()` 構建,
  // 或使用字符串獲取 "文本 Vnode" 或者
  // 有插槽的對象。
  //
  // 可選的。
  [
    'Some text comes first.',
    h('h1', 'A headline'),
    h(MyComponent, {
      someProp: 'foobar'
    })
  ]
)
複製代碼

注意:若是沒有props,能夠將 children 做爲第二個參數傳入, 可是會產生歧義,因此通常會將 null 做爲第二個參數傳入,將 children 做爲第三個參數傳入

h函數的基本使用

  • 能夠在 render 函數選項中使用
  • 能夠在 setup 函數選項中使用
export default {
    render(){
        return h('div', { class:'app' }, 'hello app')
    }
}

export default {
    setup(){
        return () => h('div', { class:'app' }, 'hello app')
    }
}
複製代碼

這樣寫代碼,不只慢並且閱讀性通常, 因此 推薦使用 jsx, 語法和 react同樣, 這裏不細說了。可看文檔

jsx | Vue.js (vuejs.org)

自定義指令

自定義指令 | Vue.js (vuejs.org)

在 Vue 中,代碼複用和抽象的主要形式是組件。然而,有的狀況下,你仍然須要對普通 DOM 元素進行底層操做,這時候就會用到自定義指令。

自定義指令分爲倆種:

  • 自定義局部指令: 組件中經過 directives 選項,只能在當前組件中使用。
  • 自定義全局指令: app的 directive 方法, 能夠在任意組件中被使用。

指令的生命週期

一個指令定義的對象,Vue提供了以下的幾個鉤子函數:

  • created:在綁定元素的 attribute 或事件監聽器被應用以前調用;
  • beforeMount:當指令第一次綁定到元素而且在掛載父組件以前調用;
  • mounted:在綁定元素的父組件被掛載後調用;
  • beforeUpdate:在更新包含組件的 VNode 以前調用;
  • updated:在包含組件的 VNode 及其子組件的 VNode 更新後調用;
  • beforeUnmount:在卸載綁定元素的父組件以前調用;
  • unmounted:當指令與元素解除綁定且父組件已卸載時,只調用一次

這幾個鉤子函數中可傳入四個參數:elbindingvnodeprevNnode

案例:時間格式化的指令

時間格式化的指令.png

Teleport

Teleport | Vue.js (vuejs.org)

瞬移組件, 能夠將該組件轉移到其餘dom元素上。

一般用於 封裝模態框、土司之類的,將它放在Body元素上和 div#app 元素平級

倆個屬性

  1. to: 指定將其中的內容移動到的目標元素,可使用選擇器
  2. disabled: 是否禁用 teleport 的功能
<teleport :to='#demo'>
    <h2>hello</h2>
</teleport>

// 該元素就會被轉移到 id 爲 demo 元素上
複製代碼

Vue插件

插件 | Vue.js (vuejs.org)

一般狀況下,咱們向Vue全局去添加一些功能時,會採用插件的模式

倆種編寫方式

  1. 對象類型
    • 一個 對象,可是必須包含一個 install 的函數,該函數會在安裝插件時執行
  2. 函數類型
    • 一個 function,這個函數會在 安裝插件時自動執行
// plugin_obect.js
export default {
  install(app) {
    app.config.globalProperties.$name = "wangpf";
  },
};

// main.js
import plugin_object from "./plugins/plugin_object";
app.use(plugin_object);

// app.vue 
import { getCurrentInstance } from "vue";
 setup() {
    const Instance = getCurrentInstance();
    console.log("Instance", Instance.appContext.config.globalProperties.$name);
 }
複製代碼
相關文章
相關標籤/搜索