一文了解Composition API新特性:ref、toRef、toRefs
在上一篇文章中,我們初步了解了vue3的新特性,今天,我們將延續Composition API的話題,來了解Composition API帶來的新特性: ref
、 toRef
和 toRefs
。
下面開始進入本文的講解?
一、??如何理解ref、toRef和toRefs
1、ref、toRef和toRefs是什么
(1)ref
1)ref是什么
-
ref
可以生成值類型
(即基本數據類型) 的響應式數據; -
ref
可以用于模板和reative; -
ref
通過.value
來修改值(一定要記得加上.value
); -
ref
不僅可以用于響應式,還可以用于模板的DOM
元素。
2)舉個例子??
假設我們定義了兩個值類型的數據,并通過一個定時器來看它響應式前后的效果。接下來我們用代碼來演示一下:
<template>
<p>ref demo {{ageRef}} {{state.name}}</p>
</template>
<script>
import { ref, reactive } from 'vue'
export default {
name: 'Ref',
setup(){
const ageRef = ref(18)
const nameRef = ref('monday')
const state = reactive({
name: nameRef
})
setTimeout(() => {
console.log('ageRef', ageRef.value,'nameRef', nameRef.value)
ageRef.value = 20
nameRef.value = 'mondaylab'
console.log('ageRef', ageRef.value,'nameRef', nameRef.value)
},1500)
return{
ageRef,
state
}
}
}
</script>
復制代碼
別眨眼,來看下此時瀏覽器的顯示效果:
大家可以看到,控制臺先后打印的順序是響應式前的數據和響應式后的數據。因此,通過 ref
,可以實現值類型的數據響應式。
值得注意的是, ref
不僅可以實現響應式,還可以用于模板的DOM元素。我們用一段代碼來演示一下:
<template>
<p ref="elemRef">今天是周一</p>
</template>
<script>
import { ref, onMounted } from 'vue'
export default {
name: 'RefTemplate',
setup(){
const elemRef = ref(null)
onMounted(() => {
console.log('ref template', elemRef.value.innerHTML, elemRef.value)
})
return{
elemRef
}
}
}
</script>
復制代碼
此時瀏覽器的顯示效果如下所示:
我們通過在模板中綁定一個 ref
,之后在生命周期中調用,最后瀏覽器顯示出該 DOM
元素。所以說, ref
也可以用來渲染模板中的DOM元素。
(2)toRef是什么
1)toRef是什么
toRef
可以響應對象Object
,其針對的是某一個響應式對象(reactive
封裝)的屬性prop
。toRef
和對象Object
兩者保持引用關系,即一個改完另外一個也跟著改。toRef
如果用于普通對象(非響應式對象),產出的結果不具備響應式。如下代碼所示:
//普通對象
const state = {
age: 20,
name: 'monday'
}
//響應式對象
const state = reactive({
age: 20,
name: 'monday'
})
復制代碼
2)舉個例子??
對于一個普通對象來說,如果這個普通對象要實現響應式,就用 reactive
。用了 reactive
之后,它就在響應式對象里面。那么在 一個響應式對象里面,如果其中有一個屬性要拿出來單獨做響應式的話,就用 toRef
。來舉個例子看一看:
<template>
<p>toRef demo - {{ageRef}} - {{state.name}} {{state.age}}</p>
</template>
<script>
import { ref, toRef, reactive, computed } from 'vue'
export default {
name: 'ToRef',
setup() {
const state = reactive({
age: 18,
name: 'monday'
})
// // toRef 如果用于普通對象(非響應式對象),產出的結果不具備響應式
// const state = {
// age: 18,
// name: 'monday'
// }
//實現某一個屬性的數據響應式
const ageRef = toRef(state, 'age')
setTimeout(() => {
state.age = 20
}, 1500)
setTimeout(() => {
ageRef.value = 25 // .value 修改值
}, 3000)
return {
state,
ageRef
}
}
}
</script>
復制代碼
此時我們來看下瀏覽器的顯示效果:
我們通過 reactive
來創建一個響應式對象,之后呢,如果只單獨要對響應式對象里面的某一個屬性進行響應式,那么使用toRef
來解決。用 toRef(Object, prop)
的形式來傳對象名和具體的屬性名,達到某個屬性數據響應式的效果。
(3)toRefs是什么
1)toRefs是什么
- 與
toRef
不一樣的是,toRefs
是針對整個對象的所有屬性,目標在于將響應式對象(reactive
封裝)轉換為普通對象 - 普通對象里的每一個屬性
prop
都對應一個ref
-
toRefs
和對象Object
兩者保持引用關系,即一個改完另外一個也跟著改。
2)舉個例子??
假設我們要將一個響應式對象里面的所有元素取出來,那么我們可以這么處理。代碼如下:
<template>
<p>toRefs demo {{state.age}} {{state.name}}</p>
</template>
<script>
import { ref, toRef, toRefs, reactive } from 'vue'
export default {
name: 'ToRefs',
setup() {
const state = reactive({
age: 20,
name: 'monday'
})
return {
state
}
}
}
</script>
復制代碼
此時瀏覽器的顯示結果如下:
但是這樣子好像有點略顯麻煩,因為在模板編譯的時候一直要 state.
,這樣如果遇到要取很多個屬性的時候就有點臃腫了。
既然太臃腫了,那我們換一種思路,把 state
進行解構。代碼如下:
<template>
<p>toRefs demo {{age}} {{name}}</p>
</template>
<script>
import { ref, toRef, toRefs, reactive } from 'vue'
export default {
name: 'ToRefs',
setup() {
const state = reactive({
age: 20,
name: 'monday'
})
return {
...state
}
}
}
</script>
復制代碼
此時瀏覽器的顯示結果如下:
效果是一樣的,看起來清晰了很多。但是呢……天上總不會有無緣無故的餡餅出現,得到一些好處的同時總要失去些原本擁有的東西。
對于解構后的對象來說,如果直接解構 reactive
,那么解構出來的對象會直接失去響應式。我們用一個定時器來檢驗下效果,具體代碼如下:
<template>
<p>toRefs demo {{age}} {{name}}</p>
</template>
<script>
import { ref, toRef, toRefs, reactive } from 'vue'
export default {
name: 'ToRefs',
setup() {
const state = reactive({
age: 20,
name: 'monday'
})
setTimeout(() => {
state.age = 25
}, 1500)
return {
...state
}
}
}
</script>
復制代碼
此時瀏覽器的顯示結果如下:
我們等了好幾秒之后,發現 age
遲遲不變成25,所以當我們解構 reactive
的對象時,響應式將會直接失去。
所以,就來到了我們的 toRefs
。 toRefs
在把響應式對象轉變為普通對象后,不會丟失掉響應式的功能。具體我們用代碼來演示一下:
<template>
<p>toRefs demo {{age}} {{name}}</p>
</template>
<script>
import { ref, toRef, toRefs, reactive } from 'vue'
export default {
name: 'ToRefs',
setup() {
const state = reactive({
age: 18,
name: 'monday'
})
const stateAsRefs = toRefs(state) // 將響應式對象,變成普通對象
setTimeout(() => {
console.log('age', state.age, 'name', state.name)
state.age = 20,
state.name = '周一'
console.log('age', state.age, 'name', state.name)
}, 1500)
return stateAsRefs
}
}
</script>
復制代碼
此時我們觀察瀏覽器的顯示效果:
大家可以看到,用了 toRefs
,普通對象的值成功被取出來了,并且還不會丟失響應式的功能,該改變的值一個也不少。
(4)合成函數返回響應式對象
了解了上面三種類型的使用,我們再來看一種場景:合成函數如何返回響應式對象。下面附上代碼:
function useFeatureX(){
const state = reactive({
x: 1,
y: 2
})
//邏輯運行狀態,……
//返回時轉換為ref
return toRefs(state)
}
復制代碼
export default{ setup(){
//可以在不失去響應性的情況下破壞結構
const {x, y} = useFeatureX()
return{
x,
y
}
}}
復制代碼
在第一段代碼中,我們定義了一個函數,并且用 toRefs
將 state
對象進行返回,之后在組件里面直接調用響應式對象。
通過這樣方式,讓代碼邏輯變得更加清晰明了,復用性更強。
2、最佳使用方式
通過上面的演示可以得出以下幾點結論:
- 用
reactive
做對象的響應式,用ref
做值類型的響應式。 -
setup
中返回toRefs(state)
,或者toRef(state, 'xxx')
。 - 為了防止誤會產生,
ref
的變量命名盡量都用xxxRef
,這樣在使用的時候會更清楚明了。 -
合成函數返回響應式對象時,使用
toRefs
3、深入理解
講完 ref
、 toRef
和 toRefs
,我們再來思考一個問題:為什么一定要用它們呢?可以不用嗎?
(1)為什么需要用ref
-
值類型(即基本數據類型)無處不在,如果不用
ref
而直接返回值類型,會丟失響應式。 - 比如在
setup
、computed
、合成函數等各種場景中,都有可能返回值類型。 -
Vue
如果不定義ref
,用戶將自己制造ref
,這樣反而會更加混亂。
(2)為何ref需要.value屬性
通過上面的分析我們知道, ref
需要通過 .value
來修改值。這看起來是一個很麻煩的操作,總是頻繁的 .value
感覺特別瑣碎。那為什么一定要 .value
呢?我們來揭開它的面紗。
-
ref
是一個對象,這個對象不丟失響應式,且這個對象用value
來存儲值。 - 因此,通過
.value
屬性的get
和set
來實現響應式。 - 只有當用于 模板 和 reactive 時,不需要
.value
來實現響應式,而其他情況則都需要。
(3)為什么需要toRef和toRefs
與 ref
不一樣的是, toRef
和 toRefs
這兩個兄弟,它們不創造響應式,而是延續響應式。創造響應式一般由 ref
或者 reactive
來解決,而 toRef
和 toRefs
則是把對象的數據進行分解和擴散,其這個對象針對的是響應式對象而非普通對象。總結起來有以下三點:
- 初衷: 在不丟失響應式的情況下,把對象數據進行 分解或擴散。
-
前提: 針對的是響應式對象(
reactrive
封裝的)而非普通對象。 - 注意: 不創造響應式,而是延續響應式。
二、??♀?Composition API實現邏輯復用
1、規則
先來了解幾條規則:
-
Composition API
指抽離邏輯代碼到一個函數; - 函數的命名約定為
useXxxx
格式(React hooks也是); - 在
setup
中引用useXxx
函數。
2、舉個例子??
引用一個非常經典的例子:獲取鼠標的定位。接下來我們用Composition API來進行封裝演示:
定義一個 js
文件,名字為 useMousePosition
,具體代碼如下:
import { reactive, ref, onMounted, onUnmounted } from 'vue'
function useMousePosition() {
const x = ref(0)
const y = ref(0)
function update(e) {
x.value = e.pageX
y.value = e.pageY
}
onMounted(() => {
console.log('useMousePosition mounted')
window.addEventListener('mousemove', update)
})
onUnmounted(() => {
console.log('useMousePosition unMounted')
window.removeEventListener('mousemove', update)
})
return {
x,
y
}
}
復制代碼
再定義一個 .vue
文件,命名為 index.vue
。具體代碼如下:
<template>
<p v-if="flag">mouse position {{x}} {{y}}</p>
<button @click="changeFlagHandler">change flag</button>
</template>
<script>
import { reactive } from 'vue'
import useMousePosition from './useMousePosition'
export default {
name: 'MousePosition',
return {
flag: true
},
setup() {
const { x, y } = useMousePosition()
return {
x,
y
}
},
changeFlagHandler() {
this.flag = !this.flag
},
}
</script>
復制代碼
此時瀏覽器的顯示效果如下:
了解完 ref
后,我們來實現這個功能看起來會清晰很多。我們先通過 ref
對 x
和 y
做響應式操作,之后通過 .value
來修改值,最終達到時刻獲取鼠標定位的效果。同時,如果我們時刻保持著鼠標移動時不斷改變值,這樣子是非常耗費性能的。所以,我們可以通過一個按鈕,來隨時控制它的出現與隱藏。
大家可以發現,當隱藏的時候,隨后會觸發 onUnmounted
生命周期,組件內容隨之被銷毀。也就是說,使用的時候調用,不使用的時候及時銷毀,這樣子可以很大程度上提升性能。
三、??♀?結束語
通過上文的學習,我們可以知道, ref
、 toRef
和 toRefs
是 vue3
中 Composition API
的新特性,且 vue3
一般通過 ref
、 toRef
和 toRefs
來實現數據響應式。有了這三個內容,實現數據響應式看起來方便許多,而不再像 vue2
中那種處理起來很困難。
到這里,關于 ref
、 toRef
和 toRefs
的內容就講完啦!希望對大家有幫助!
如有疑問或文章有誤歡迎評論區留言或私信我交流~