在新版的生命週期函數,能夠按需導入到組件中,且只能在setup()函數中使用.javascript
import { onMounted, onUnmounted } from 'vue'; export default { setup () { onMounted(()=>{ // }); onUnmounted(()=> { // }); } };
setup()函數是vue3中專門新增的方法,能夠理解爲Composition Api的入口.html
在beforecreate以後,create以前執行.vue
export default { props: { msg: { type: String, default: () => {} } }, setup(props) { console.log(props); } }
setup()的第二個參數是一個上下文對象,這個上下文對象大體包含了這些屬性,注意:在setup()函數中沒法訪問thisjava
const MyComponent = { setup(props, context) { context.attrs context.slots context.parent context.root context.emit context.refs } }
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()的返回值是一個對象,這個對象上只包含一個.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 }; } };
import { ref } from 'vue'; export default { setup () { const value = ref(1); return { value, msg: 'hello world!' }; } };
<template> <p> {{ value }} {{ msg }} </p> </template>
當把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 }; } };
咱們發現,此次的count值卻沒有發生改變,仍是原始值1,是由於新建立的newCount替換並覆蓋了以前的count值,取代了它的位置.app
用來判斷某個值是否爲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()函數能夠將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 {
概念:爲源響應式對象上的某個屬性建立一個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()用來建立計算屬性,返回值是一個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()函數用來監視某些數據項的變化,從而觸發某些特定的操做,看下面這個案例,會實時監聽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來執行清除工做
<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>
vue3中新增的api,用於屬性監聽.
與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,假設咱們如今用一個用戶ID去查詢用戶的詳情信息,而後咱們監聽了這個用戶ID, 當用戶ID 改變的時候咱們就會去發起一次請求,這很簡單,用watch 就能夠作到。 可是若是在請求數據的過程當中,咱們的用戶ID發生了屢次變化,那麼咱們就會發起屢次請求,而最後一次返回的數據將會覆蓋掉咱們以前返回的全部用戶詳情。這不只會致使資源浪費,還沒法保證 watch 回調執行的順序。而使用watchEffect咱們就能夠作到.
onInvalidate(fn)傳入的回調會在watchEffect從新運行或者watchEffect中止的時候執行。
watchEffect(() => { // 異步api調用,返回一個操做對象 const apiCall = someAsyncMethod(props.userID) onInvalidate(() => { // 取消異步api的調用。 apiCall.cancel() }) })
概念:只處理對象最外層屬性的響應式(也就是淺響應式),因此最外層屬性發生改變,更新視圖,其餘層屬性改變,視圖不會更新.
場景:若是一個對象的數據結構比較深,但變化只是最外層屬性.
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 } } }
概念:只處理了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 } } }
建立一個自定義的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的做用類型於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>
<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 {
經過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>
<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>
這個函數不是必須的,除非你想完美結合TypeScript提供的類型推斷來進行項目開發
場景:這個函數僅僅提供了類型推斷,能爲setup()函數中的props提供完整的類型推斷.
import { createComponent } from 'vue' export default createComponent({ props: { foo: String }, setup(props) { props.foo // <- type: string } })
描述:能夠獲取當前組件的實例,而後經過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>