Coposition 詳解

LifeCycle Hooks

在新版的生命週期函數,能夠按需導入到組件中,且只能在setup()函數中使用.javascript

import { onMounted, onUnmounted } from 'vue'; export default {  setup () {  onMounted(()=>{  //  });   onUnmounted(()=> {  //  });  } };

 

 

生命週期2.x與Composition之間的映射關係

  • beforeCreate -> use setup()
  • created -> use setup()
  • beforeMount -> onBeforeMount
  • mounted -> onMounted
  • beforeUpdate -> onBeforeUpdate
  • updated -> onUpdated
  • beforeDestroy -> onBeforeUnmount
  • destroyed -> onUnmounted
  • errorCaptured -> onErrorCaptured

 

setup

理解

setup()函數是vue3中專門新增的方法,能夠理解爲Composition Api的入口.html

執行時機

在beforecreate以後,create以前執行.vue

接收props數據

export default {  props: {  msg: {  type: String,  default: () => {}  }  },  setup(props) {  console.log(props);  } }

context:

setup()的第二個參數是一個上下文對象,這個上下文對象大體包含了這些屬性,注意:在setup()函數中沒法訪問thisjava

const MyComponent = {  setup(props, context) {  context.attrs  context.slots  context.parent  context.root  context.emit  context.refs  } }

 

reactive

reactive是用來建立一個響應式對象,等價於2.x的Vue.observable,具體能夠參考下面demo。react

<template>  <div>  <p @click="incment()">  click Me!  </p>  <p>  一:{{ state.count }} 二: {{ state.addCount }}  </p>  </div> </template>  <script> import { reactive } from 'vue'; export default {  setup () {  const state = reactive({//建立響應式數據  count: 0,  addCount: 0  });   function incment () {  state.count++;  state.addCount = state.count * 2;  }   return {  state,  incment  };  } }; </script>

 

ref

基本語法

ref()函數用來給定的值建立一個響應式的數據對象,ref()的返回值是一個對象,這個對象上只包含一個.value屬性.下面是基本數據類型建立步驟.vuex

import { ref } from 'vue'; export default {  setup () {  const valueNumber = ref(0);  const valueString = ref('hello world!');  const valueBoolean = ref(true);  const valueNull = ref(null);  const valueUndefined = ref(undefined);   return {  valueNumber,  valueString,  valueBoolean,  valueNull,  valueUndefined  };  } };

image.png

 

在template中訪問ref建立的響應式數據

import { ref } from 'vue'; export default {  setup () {  const value = ref(1);   return {  value,  msg: 'hello world!'  };  } };
<template>  <p>  {{ value }} {{ msg }}  </p> </template>

將ref響應式數據掛載到reactive中

當把ref()建立出來值直接掛載到reactive()中時,會自動把響應式數據對象的展開爲原始的值,不須要經過.value就能夠直接訪問到.api

import { ref, reactive } from 'vue'; export default {  setup () {  const count = ref(1);  const state = reactive({  count  });   console.log(state.count);//1 能夠直接訪問到,不須要經過.value就能夠直接訪問到  state.count++;  console.log(count.value);//2 咱們發現,最初count值也發生了改變   return {  count  };  } };

新的ref會覆蓋舊的ref,實例以下:數據結構

import { ref, reactive } from 'vue'; export default {  setup () {  const count = ref(1);  const state = reactive({  count  });  const newCount = ref(9);   state.count = newCount;  state.count++;   console.log(state.count, newCount, count);// 10 10 1   return {  count  };  } };

image.png

咱們發現,此次的count值卻沒有發生改變,仍是原始值1,是由於新建立的newCount替換並覆蓋了以前的count值,取代了它的位置.app

 

 

isRef

用來判斷某個值是否爲ref建立出來的對象,場景:當須要展開某個值多是ref()建立出來的對象時.dom

import { ref, isRef } from 'vue'; export default {  setup () {  const count = ref(1);  const unwrappend = isRef(count) ? count.value : count;   return {  count,  unwrappend  };  } };

 

toRefs

torefs()函數能夠將reactive()建立出來的響應式對象轉換爲普通的對象,只不過這個對象上的每一個屬性節點都是ref()類型的響應式數據

<template>  <p>  <!-- 能夠不經過state.value去獲取每一個屬性 -->  {{ count }} {{ value }}  </p> </template>  <script> import { ref, reactive, toRefs } from 'vue'; export default {  setup () {  const state = reactive({  count: 0,  value: 'hello',  })   return {  ...toRefs(state)  };  } }; </script>

 

toRef

概念:爲源響應式對象上的某個屬性建立一個ref對象,兩者內部操做的是同一個數據值,更新時兩者是同步的。至關於淺拷貝一個屬性.

區別ref: 拷貝的是一份新的數據單獨操做,更新時相互不影響,至關於深拷貝。

場景:當要將某個prop的ref傳遞給某個複合函數時,toRef頗有用.

import { reactive, ref, toRef } from 'vue'  export default {  setup () {  const m1 = reactive({  a: 1,  b: 2  })   const m2 = toRef(m1, 'a');  const m3 = ref(m1.a);   const update = () => {  // m1.a++;//m1改變時,m2也會改變  // m2.value++; //m2改變時m1同時改變  m3.value++; //m3改變的同時,m1不會改變  }   return {  m1,  m2,  m3,  update  }  } }

 

computed

computed()用來建立計算屬性,返回值是一個ref的實例。

 

建立只讀的計算屬性

import { ref, computed } from 'vue'; export default {  setup () {  const count = ref(0);  const double = computed(()=> count.value + 1);//1   double++;//Error: "double" is read-only   return {  count,  double  };  } };

 

建立可讀可寫的計算屬性

在使用computed函數期間,傳入一個包含get和set函數的對象,能夠額獲得一個可讀可寫的計算屬性

// 建立一個 ref 響應式數據 const count = ref(1)  // 建立一個 computed 計算屬性 const plusOne = computed({  // 取值函數  get: () => count.value + 1,  // 賦值函數  set: val => {  count.value = val - 1  } })  // 爲計算屬性賦值的操做,會觸發 set 函數 plusOne.value = 9 // 觸發 set 函數後,count 的值會被更新 console.log(count.value) // 輸出 8

 

watch

watch()函數用來監視某些數據項的變化,從而觸發某些特定的操做,看下面這個案例,會實時監聽count值的變化. 查看官方文檔API

import { ref, watch } from 'vue'; export default {  setup () {  const count = ref(1);   watch(()=>{  console.log(count.value, 'value');  })   setInterval(()=>{  count.value++;  },1000);  return {  count,  };  } };

 

監聽指定的數據源

監聽reactive的數據變化

import { watch, reactive } from 'vue'; export default {  setup () {  const state = reactive({  count: 0  })   watch(()=>state.count,(count, prevCount)=>{  console.log(count, prevCount);//變化後的值 變化前的值  })   setInterval(()=>{  state.count++;  },1000);   return {  state  };  } };

監聽ref類型的數據變化

import { ref, watch } from 'vue'; export default {  setup () {  const count = ref(0);   watch(count,(count, prevCount)=>{  console.log(count, prevCount);//變化後的值 變化前的值  })   setInterval(()=>{  count.value++;  },1000);   return {  count  };  } };

 

監聽多個指定數據變化

監聽reactive類型數據變化

import { watch, reactive } from 'vue'; export default {  setup () {  const state = reactive({  count: 0,  msg: 'hello'  })   watch([()=> state.count, ()=> state.msg],([count, msg], [prevCount, prevMsg])=>{  console.log(count, msg);  console.log('---------------------');  console.log(prevCount, prevMsg);  })   setTimeout(()=>{  state.count++;  state.msg = 'hello world';  },1000);   return {  state  };  } };

 

監聽ref類型數據變化

import { ref, watch } from 'vue'; export default {  setup () {  const count = ref(0);  const msg = ref('hello');   watch([count, msg],([count, name], [prevCount, prevname])=>{  console.log(count, name);  console.log('---------------------');  console.log(prevCount, prevname);  })   setTimeout(()=>{  count.value++;  msg.value = 'hello world';  },1000);   return {  count,  msg  };  } };

 

清除監視

在setup()函數內建立的watch監視,會在當前組件被銷燬的時候自動中止。若是想要明確的中止某個監視,能夠調用watch()函數的返回值便可

// 建立監視,並獲得 中止函數 const stop = watch(() => {  /* ... */ })  // 調用中止函數,清除對應的監視 stop()

 

清除無效的異步任務

有時候watch()監視的值發生了變化,咱們指望清除無效的異步任務,此時watch回調函數中提供了cleanup registrator function來執行清除工做

    • 場景
      • watch被重複執行了
      • watch被強制stop()
<template>  <p>  <input type="text" v-model="keyword">  </p> </template>  <script> import { watch, ref } from 'vue'; export default {  setup () {  const keyword = ref('');   const asyncPrint = val => {  return setTimeout(()=>{  console.log(val);  },1000);  }   watch(keyword, (keyword, prevKeyword, onCleanup) => {  const timeId = asyncPrint(keyword);   onCleanup(()=> clearTimeout(timeId));  }, {lazy: true})   return {  keyword  };  } }; </script>

 

watchEffect

vue3中新增的api,用於屬性監聽.

與watch有什麼不一樣?

      • watchEffect不須要指定監聽屬性,能夠自動收集依賴,只要咱們回調中引用了響應式的屬性,那麼這些屬性變動的時候,這個回調都會執行,而watch只能監聽指定的屬性而作出變動(v3中能夠同時監聽多個)
      • watch能夠獲取到新值和舊值,而watchEffect獲取不到
      • watchEffect會在組件初始化的時候就會執行一次與computed同理,而收集到的依賴變化後,這個回調纔會執行,而watch不須要,除非設置了指定參數。

 

基礎用法

import { watchEffect, ref } from 'vue' setup () {  const userID = ref(0)  watchEffect(() => console.log(userID))  setTimeout(() => {  userID.value = 1  }, 1000)   /*  * LOG  * 0  * 1  */   return {  userID  }  }

中止監聽

若是watchEffect是在setup或者生命週期裏面註冊的話,在取消掛在的時候會自動中止。

//中止監聽  const stop = watchEffect(() => {  /* ... */ })  // later stop()

使 side effect 無效

什麼是 side effect ,不可預知的接口請求就是一個 side effect,假設咱們如今用一個用戶ID去查詢用戶的詳情信息,而後咱們監聽了這個用戶ID, 當用戶ID 改變的時候咱們就會去發起一次請求,這很簡單,用watch 就能夠作到。 可是若是在請求數據的過程當中,咱們的用戶ID發生了屢次變化,那麼咱們就會發起屢次請求,而最後一次返回的數據將會覆蓋掉咱們以前返回的全部用戶詳情。這不只會致使資源浪費,還沒法保證 watch 回調執行的順序。而使用watchEffect咱們就能夠作到.

onInvalidate(fn)傳入的回調會在watchEffect從新運行或者watchEffect中止的時候執行。

watchEffect(() => {  // 異步api調用,返回一個操做對象  const apiCall = someAsyncMethod(props.userID)   onInvalidate(() => {  // 取消異步api的調用。  apiCall.cancel()  }) })

 

shallowReactive

概念:只處理對象最外層屬性的響應式(也就是淺響應式),因此最外層屬性發生改變,更新視圖,其餘層屬性改變,視圖不會更新.

場景:若是一個對象的數據結構比較深,但變化只是最外層屬性.

import { shallowReactive } from 'vue'  export default {  setup() {  const obj = {  a: 1,  first: {  b: 2,  second: {  c: 3  }  }  }   const state = shallowReactive(obj)   function change1() {  state.a = 7  }   function change2() {  state.first.b = 8  state.first.second.c = 9  console.log(state);  }   return { state }  } }

 

shallowRef

概念:只處理了value的響應式,不進行對象的reactive處理.

場景:若是有一個對象數據,後面會產生新的對象替換.

 

import { shallowRef } from 'vue'  export default {  setup () {  const m1 = shallowRef({a: 1, b: {c: 2}})   const update = () => {  m1.value.a += 1  }   return {  m1,  update  }  } }

 

customRef

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

場景:使用customRef實現輸入框防抖

<template>  <div>  <input v-model="keyword" placeholder="搜索關鍵字"/>  <p>{{keyword}}</p>  </div> </template>  <script> import { customRef } from 'vue'  export default {  setup () {  const keyword = useDebouncedRef('', 500)  console.log(keyword)   return {  keyword  }  }  } function useDebouncedRef(value, delay = 200) {  let timeout;  return customRef((track, trigger) => {  return {  get() {  // 告訴Vue追蹤數據  track()  return value  },  set(newValue) {  clearTimeout(timeout)  timeout = setTimeout(() => {  value = newValue  // 告訴Vue去觸發界面更新  trigger()  }, delay)  }  }  }) } </script>

 

自定義Hook函數

自定義hook的做用類型於vue2中的mixin技術。

優點:清楚知道代碼來源,方便複用

案例:收集用戶點擊的頁面座標

 

hook/useMousePosition.js

import { ref, onMounted, onUnmounted } from "vue";  export default function useMousePosition() {  // 初始化座標數據  const x = ref(-1);  const y = ref(-1);   // 用於收集點擊事件座標的函數  const updatePosition = e => {  x.value = e.pageX;  y.value = e.pageY;  };   // 掛載後綁定點擊監聽  onMounted(() => {  document.addEventListener("click", updatePosition);  });   // 卸載前解綁點擊監聽  onUnmounted(() => {  document.removeEventListener("click", updatePosition);  });   return { x, y }; }

 

模版中使用hook函數

<template>  <div>  <p>{{ x }}</p>  <p>{{ y }}</p>  </div> </template>  <script> import useMousePosition from '@/hook/useMousePosition' export default {  setup () {  const {x, y} = useMousePosition();  return {  x,  y  }  } } </script>

 

readonly與shallowReadonly

  • readonly:
    • 深度只讀數據
    • 獲取一個對象 (響應式或純對象) 或 ref 並返回原始代理的只讀代理。
    • 只讀代理是深層的:訪問的任何嵌套 property 也是隻讀的。
  • shallowReadonly
    • 淺只讀數據
    • 建立一個代理,使其自身的 property 爲只讀,但不執行嵌套對象的深度只讀轉換
  • 應用場景:
    • 在某些特定狀況下, 咱們可能不但願對數據進行更新的操做, 那就能夠包裝生成一個只讀代理對象來讀取數據, 而不能修改或刪除

 

<template>  <div @click="update()">  <p>{{ a }}</p>  <p>{{ b }}</p>  </div> </template>  <script> import { reactive, readonly, shallowReadonly } from 'vue' export default {  setup () {  const state = reactive({  a: 1,  b: {  c: 2  }  })   const m = readonly(state);  const m2 = shallowReadonly(state);   const update = () => {  m.a++ //沒法修改 只讀  m2.a++ //沒法修改  m2.b.c++ //能夠修改 視圖層數據改變  }   return {  ...toRefs(state),  update  }  }  } </script>

 

Template refs

經過ref()還能夠引用頁面上的元素或者組件.

 

元素引用

使用ref()函數建立DOM引用,需在onMounted中獲取.

<template>  <div>  <p ref="dom">hello</p>  </div> </template>  <script> import { ref, onMounted } from 'vue'; export default {  setup () {  const dom = ref(null);   onMounted(()=> {  console.log(dom.value)//當前dom元素  });   return {  dom  }  } }; </script>

 

組件引用

<template>  <div>  <Test ref="comRef"/>  </div> </template>  <script> import { ref, onMounted } from 'vue'; import Test from "./test2"; export default {  components: { Test },  setup () {  const comRef = ref(null);   onMounted(()=> {  comRef.value.coun;//獲取子組件值  comRef.value.Handle();//調用子組件函數  })   return {  comRef  }  } }; </script>

 

  • test2
<template>  <p>  {{ count }}  </p> </template>  <script> import { ref } from 'vue'; export default {  setup () {  const count = ref('count');   const Handle = (()=> {  console.log('hello');  })   return {  count,  Handle  }  } }; </script>

 

createComponent

這個函數不是必須的,除非你想完美結合TypeScript提供的類型推斷來進行項目開發

場景:這個函數僅僅提供了類型推斷,能爲setup()函數中的props提供完整的類型推斷.

import { createComponent } from 'vue'  export default createComponent({  props: {  foo: String  },  setup(props) {  props.foo // <- type: string  } })

 

getCurrentInstance

描述:能夠獲取當前組件的實例,而後經過ctx屬性獲取當前上下文,這樣咱們就能夠在steup中使用router和vuex了.

<script> import { getCurrentInstance } from 'vue' export default {  setup () {  const { ctx } = getCurrentInstance()  console.log(ctx.$router.currentRoute.value) //當前路徑  //與之前this獲取原型上東西同樣  //ctx.$parent 父組件  // ctx.$nextTick 組件更新完畢  // ctx.$store VueX  } } </script>

 

學習手冊

https://vue3js.cn/vue-composition-api

相關文章
相關標籤/搜索