很重要的東西。
比較不一樣的 ref 過去在 Options API 裡要存取 DOM 元素的話,我們通常會這樣寫:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <template > <div class ="home" > <p ref ="p" > Hello Composition API</p > <button @click ="logRef" > Click Me</button > </div > </template > <script > export default { methods : { logRef ( ) { console .log(this .$refs.p) } } } </script >
之所以能夠這樣子做是因為我們有 this
,這個 this
代表的是 Vue 這個實體,而 Vue 實體上會自帶 $ref
來儲存 DOM 元素的資訊。
可是在 Composition API 中有一個很重要的區別,那就是:
setup
沒有 this
值
setup
沒有 this
值
setup
沒有 this
值
如果我試著在 setup
中存取 this
值,那只會得到 undefined
:
1 2 3 4 5 6 export default { setup ( ) { console .log(this ) } }
所以正確的作法會變成這樣:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <template > <div class ="home" > <p ref ="pTag" > Hello Composition API</p > <button @click ="logRef" > Click Me</button > </div > </template > <script > import { ref } from 'vue' export default { setup ( ) { const pTag = ref(null ) const logRef = () => console .log(pTag, pTag.value) return { pTag, logRef } } } </script >
附註:ref
儲存的值是一個 Object,其中 value
屬性就是我們儲存的值
這樣子我們就能在按下按鈕時拿到 <p>
的 DOM 節點。
這裡你可能會想說 ref 的初始值為什麼要設成 null
?難道沒辦法直接設為 <p>
嗎?
答案是沒有辦法。
我們先回憶一下 setup
的觸發時機。前面有說過 setup
是跑在最一開始的 hook,既然如此,在這個時機點有可能存取到 DOM 元素嗎?當然不可能,所以才需要透過這樣的方式來做:
把 pTag
return 到外面讓它跟 template 做繫結
透過 logRef
這種 callback 的形式來印出 pTag
的值
所以要在 setup
中去操作 DOM 元素也是不可能的,因為 setup
執行的當下並沒有任何 DOM 的存在。
總之這個觀念要一定要銘記在心。
ref 更大的用途(Reactive) ref
除了拿來存取 DOM 元素以外,更常被用來設置「Reactive」 的值。
前面我們有說過在 setup
中建立的變數在預設下沒有辦法「隨著更新反應到畫面上」,但是如果改用 ref
就可以改變這件事情,像這樣:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <template > <div class ="home" > <p > Hello, my name is {{ name }}, and I'm {{ age }} years old</p > <button @click ="changeName" > Click Me</button > <button @click ="age++" > Add 1 to age</button > </div > </template > <script > import { ref } from 'vue' export default { setup ( ) { const name = ref('PeaNu' ) const age = ref(24 ) const changeName = () => { name.value = 'PPB' } return { name, age, changeName } } } </script >
ref(...)
會建立一個物件,並且把我們儲存的值放在 value
屬性上,就跟 React 的 useRef
會儲存在 current
屬性上是一樣的概念。
接著當我們透過 changeName
來更新值的時候,神奇的事情就發生了,畫面上的 name
會確實會被同步更新為 PPB
,因為現在這個 name
是「Reactive」的。
另一種作法:reactive 要讓一個變數變成 Reactive 還有另一個方法,就是改用 reactive
來建立,像這樣:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <template > <div class ="home" > <p > Hello, my name is {{ user.name }}, and I'm {{ user.age }} years old</p > <button @click ="changeName" > Click Me</button > <button @click ="user.age++" > Add 1 to age</button > </div > </template > <script > import { reactive } from 'vue' export default { setup ( ) { const user = reactive({ name : 'PeaNu' , age : 24 }) const changeName = () => { user.name = 'PPB' } return { user, changeName } } } </script >
這樣子也可以達到一樣的效果,不過要注意 reactive
的值必須是一個「Object」 ,它沒辦法存「Primitive」的值。
所以我才會改成用 user
來建立物件,而不是像 ref
一樣用「純值」來儲存。
總之兩種方法都是 OK 的,不過你應該會比較常看到 reactive
的作法。
reactive 的好夥伴:toRefs 剛剛建立的 reactive
範例中有一個麻煩的點是「必須透過存取屬性的方式來取值」,像是
1 2 3 4 5 6 <template > <div class ="home" > <p > Hello, my name is {{ user.name }}, and I'm {{ user.age }} years old</p > </div > </template >
如果能夠像 ref
一樣直接填 name
的話就更好了對吧!所以 Vue 提供了 toRefs
(注意有 s)這個方法,用途就是把 reactive
的每個屬性轉成 ref
,像這樣:
1 2 3 4 5 6 7 8 9 10 11 12 <script > import { reactive, toRefs } from 'vue' export default { setup ( ) { const user = reactive({ name : 'PeaNu' , age : 24 }) return { ...toRefs(user) } } } </script >
我私心覺得這樣是最方便的作法,因為:
在 template 中不用再透過 obj.property
來存取
在 script 中不用再透過 ref.value
來存取資料
總結來說就是一舉兩得,真的是很棒的功能。