Vue-Composition API(Ref & Reactive)

很重要的東西。

比較不一樣的 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() {
// 印出 DOM 節點資訊
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() {
// undefined
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">
<!-- bind ref -->
<p ref="pTag">Hello Composition API</p>
<button @click="logRef">Click Me</button>
</div>
</template>

<script>
// 把 ref 從 Vue 的實體中取出
import { ref } from 'vue'

export default {
setup() {
// 初始值為 null
const pTag = ref(null)
// reference Object & Node <p>
const logRef = () => console.log(pTag, pTag.value)

return { pTag, logRef }
}
}
</script>

附註:ref 儲存的值是一個 Object,其中 value 屬性就是我們儲存的值

這樣子我們就能在按下按鈕時拿到 <p> 的 DOM 節點。

這裡你可能會想說 ref 的初始值為什麼要設成 null?難道沒辦法直接設為 <p> 嗎?

答案是沒有辦法。

我們先回憶一下 setup 的觸發時機。前面有說過 setup 是跑在最一開始的 hook,既然如此,在這個時機點有可能存取到 DOM 元素嗎?當然不可能,所以才需要透過這樣的方式來做:

  1. pTag return 到外面讓它跟 template 做繫結
  2. 透過 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() {
// 建立 reactive 物件
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">
<!-- 得透過 user 來取得 name -->
<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 })

// toRefs 會回傳 { ref1: ..., ref2: ... },所以這邊可以解構
return { ...toRefs(user) }
}
}
</script>

我私心覺得這樣是最方便的作法,因為:

  1. 在 template 中不用再透過 obj.property 來存取
  2. 在 script 中不用再透過 ref.value 來存取資料

總結來說就是一舉兩得,真的是很棒的功能。

JavaScript-Immutable.JS Vue-Composition API 前言
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×