# Vue3 - 入門篇
###### tags: `Vue` `HiSKIO`
:information_source: 內容著重在 composition API 的使用
## 安裝
1. CDN 引入
2. 下載後引入
3. npm install
4. Vue CLI
## setup 組件選項
```htmlmixed=
<body>
<div id="app">...</div> // 受 Vue 影響
<div id="noVue">...</div> // 不受 Vue 影響
<script src="./js/vue.js"></script>
<script>
const App = {
setup () {
return {
}
}
}
Vue.createApp(App).mount('#app')
</script>
</body>
```
`createApp()` : 創建 Vue 實體
`mount()`:掛載到 DOM
`body` 之下用一個 `div` 把全部的內容包起來
`setup` 在 `props` 進來的資料被解析後,就會作為 composition API 的入口點。
在 `setup` 被 return 出去的任何內容都可以被組件其他地方使用。
:::warning
`setup` 在組件創建前就執行了,因此沒有 `this`。能使用的只有 `props` 進來的資料。
:::
## 響應式變數 - `ref`
利用 `ref` 綁定變數,進行響應式改變。
```htmlmixed=
<div id="app">
<h1>{{ text }}</h1>
</div>
```
錯誤範例:
```javascript=
<script>
const App = {
setup () {
let text = 'Hello Vue'
setTimeout(() => {
text = 'Hello world'
console.log(text)
}, 1000)
return {
text
}
}
}
Vue.createApp(App).mount('#app')
</script>
```
![](https://i.imgur.com/ARJ9zsM.png)
`ref` 把接受的參數放進 `value` 這個 property 中,並以物件的形式返回,即可響應式地改變資料。
:warning: DOM 中直接寫 `{{ text }}`,不需要把 `.value` 寫出來。
正確範例:
```javascript=
<script>
const App = {
setup () {
const text = Vue.ref('Hello Vue')
setTimeout(() => {
text.value = 'Hello world'
console.log(text)
}, 1000)
return {
text
}
}
}
Vue.createApp(App).mount('#app')
</script>
```
也可以使用解構的方式使用:
```javascript=
<script>
const { ref } = Vue
const App = {
setup () {
const text = ref('Hello Vue')
setTimeout(() => {
text.value = 'Hello world'
console.log(text)
}, 1000)
return {
text
}
}
}
Vue.createApp(App).mount('#app')
</script>
```
![](https://i.imgur.com/kEnGcKr.png)
其原理:
**call by reference** v.s. **call by value**
![](https://blog.penjee.com/wp-content/uploads/2015/02/pass-by-reference-vs-pass-by-value-animation.gif)
## 響應式變數 - `reactive`
`reactive` 也可以對變數進行綁定,並達到響應式改變的功能。
:::warning
`reactive` 只接受**物件**或**陣列**的型別變數。
:::
```htmlmixed=
<div id="app">
<h1>{{ message.text }}</h1>
</div>
```
```javascript=
<script>
const { reactive } = Vue
const App = {
setup () {
const message = reactive({ text: 'Hello Vue' })
setTimeout(() => {
message.text = 'Hello world'
console.log(message)
}, 1000)
return {
message
}
}
}
Vue.createApp(App).mount('#app')
</script>
```
![](https://i.imgur.com/DbKvfeu.png)
### `ref` v.s `reactive` :pushpin:
在大多數情況下兩者可以互相替換使用,主要取決於個人喜好或者團隊習慣,根據具體情況去決定要使用哪個就好。
* `ref( )` : 可以接受任何型態的資料,但是不會對物件或陣列內部的屬性變動做監聽。
* `reactive( )` : 只能接受物件或陣列,可以做深層的監聽,以及訪問資料不需要 `.value`。
```htmlmixed=
<div id="app">
<h1>{{ refMessage }}</h1>
<h1>{{ reactiveMessage.text }}</h1>
</div>
```
```javascript=
<script>
const { ref, reactive } = Vue
const App = {
setup () {
const refMessage = ref('Hello Vue')
const reactiveMessage = reactive({ text: 'Hello Vue' })
setTimeout(() => {
refMessage.value = 'Hello ref'
reactiveMessage.text = 'Hello reactive'
console.log(refMessage)
console.log(reactiveMessage)
}, 1000)
return {
refMessage,
reactiveMessage
}
}
}
Vue.createApp(App).mount('#app')
</script>
```
![](https://i.imgur.com/UHtIqAV.png)
## `readonly`
`readonly` 讓 `reactive` 或 `ref` 的資料變成只能讀取不能修改。適合在傳遞參數時避免修改到資料
```htmlmixed=
<div id="app">
<h1>{{ number.index }}</h1>
<button @click="addNumber">Add</button>
<button @click="addNumberReadonly">Add ReadOnly</button>
</div>
```
```javascript=
<script>
const { reactive, readonly } = Vue
const App = {
setup () {
const number = reactive({ index: 0 })
const numberReadonly = readonly(number)
const addNumber = () => {
number.index++
console.log('1.number', number)
console.log('1.numberReadonly', numberReadonly)
}
// 會報 warning
const addNumberReadonly = () => {
numberReadonly.index++
console.log('2.number', number)
console.log('2.numberReadonly', numberReadonly)
}
return {
number,
numberReadonly,
addNumber,
addNumberReadonly
}
},
};
Vue.createApp(App).mount("#app")
</script>
```
![](https://i.imgur.com/VVu0MZc.png)
## `v-for` `key` 的重要性
當改變 `v-for` 中的資料時,因為有綁定 `key`,所以只會針對那幾筆資料進行變動,而不會對整個陣列重新渲染。所以 `key` 要是唯一值。
:::danger
不要拿 `index` 當 `key`
:::
## `v-if` v.s `v-show`
`v-if`:一開始就決定要不要渲染出來。
`v-show`:先渲染出來,再用 `display: none / block` 來隱藏或顯示。
## `computed`
```htmlmixed=
<div id="app">
<a @click="handleListShow" class="title">課程列表</a>
<ul class="box" :style="{height: listHeight}">
<li v-for="(list, index) in listArray" :key="list">{{ list.name }}</li>
</ul>
</div>
```
```javascript=
<script>
const { ref, reactive, computed } = Vue
const App = {
setup () {
const isOpen = ref(true)
const listArray = reactive([
{ name: "2020 Vue3 專業職人 | 入門篇", money: 3200 },
{ name: "2020 Vue3 專業職人 | 加值篇", money: 100 },
{ name: "2020 Vue3 專業職人 | 進階篇", money: 500 },
{ name: "現代 JavaScript 職人之路|入門篇", money: 300 },
{ name: "現代 JavaScript 職人之路|中階實戰篇", money: 1600 },
{ name: "職人必修的RWD 網頁入門班", money: 900 },
{ name: "HTML5+Animate CC 網頁動畫與遊戲互動", money: 2000 },
{ name: "現代 JavaScript 職人之路|面試篇", money: 1800 },
])
const listHeight = computed(() => {
return isOpen.value ? `${listArray.length * 40}px` : '0px'
})
const handleListShow = () => {
isOpen.value = !isOpen.value
}
return {
isOpen,
listArray,
listHeight,
handleListShow,
}
},
};
Vue.createApp(App).mount("#app")
</script>
```
![](https://i.imgur.com/lMbhtZ9.png)
![](https://i.imgur.com/tX56uwz.png)
### `computed` v.s `methods`
`computed` 會依據計算的資料進行緩存,只要資料沒有被更改,`computed` 就不會被重新計算執行。但無法傳入參數進行處理。
`methods` 不會進行緩存,每次都會重新執行,但是可以傳入參數進行處理。
使用選擇:依照 **緩存/效能** 與 **傳參數** 決定。
以上範例可以用 `methods` 改寫成這樣:
```htmlmixed=
<div id="app">
<a @click="handleListShow" class="title">課程列表</a>
<!-- 使用 methods 要加 () -->
<ul class="box" :style="{height: calculateListHeight()}">
<li v-for="(list, idx) in listArray" :key="list">{{list.name}}</li>
</ul>
</div>
```
```javascript=
<script>
const { ref, reactive, computed } = Vue
const App = {
setup () {
const isOpen = ref(true)
const listArray = reactive([
{ name: "2020 Vue3 專業職人 | 入門篇", money: 3200 },
{ name: "2020 Vue3 專業職人 | 加值篇", money: 100 },
{ name: "2020 Vue3 專業職人 | 進階篇", money: 500 },
{ name: "現代 JavaScript 職人之路|入門篇", money: 300 },
{ name: "現代 JavaScript 職人之路|中階實戰篇", money: 1600 },
{ name: "職人必修的RWD 網頁入門班", money: 900 },
{ name: "HTML5+Animate CC 網頁動畫與遊戲互動", money: 2000 },
{ name: "現代 JavaScript 職人之路|面試篇", money: 1800 },
])
const listHeight = computed(() => {
return isOpen.value ? `${listArray.length * 40}px` : '0px'
})
const calculateListHeight = () => {
return isOpen.value ? `${listArray.length * 40}px` : '0px'
}
const handleListShow = () => {
isOpen.value = !isOpen.value
}
return {
isOpen,
listArray,
listHeight,
handleListShow,
calculateListHeight
}
},
}
Vue.createApp(App).mount("#app")
</script>
```
### 組合資料
`computed` 組合資料應用:
```htmlmixed=
<div id="app">
<a @click="handleListShow" class="title">課程列表</a>
<ul class="box" :style="{height: listHeight}">
<li v-for="(list, idx) in newListArray" :key="list">{{ list }}</li>
</ul>
</div>
```
```javascript=
<script>
const { ref, reactive, computed } = Vue
const App = {
setup () {
const isOpen = ref(true)
const listArray = reactive([
{ name: "2020 Vue3 專業職人 | 入門篇", money: 3200 },
{ name: "2020 Vue3 專業職人 | 加值篇", money: 100 },
{ name: "2020 Vue3 專業職人 | 進階篇", money: 500 },
{ name: "現代 JavaScript 職人之路|入門篇", money: 300 },
{ name: "現代 JavaScript 職人之路|中階實戰篇", money: 1600 },
{ name: "職人必修的RWD 網頁入門班", money: 900 },
{ name: "HTML5+Animate CC 網頁動畫與遊戲互動", money: 2000 },
{ name: "現代 JavaScript 職人之路|面試篇", money: 1800 },
])
const newListArray = computed(() => {
const map = listArray.map((item, index) => {
return `${index + 1}. ${item.name} $:${item.money}`
})
return map
})
const listHeight = computed(() => {
return isOpen.value ? `${listArray.length * 40}px` : '0px'
})
const calculateListHeight = () => {
return isOpen.value ? `${listArray.length * 40}px` : '0px'
}
const handleListShow = () => {
isOpen.value = !isOpen.value
}
return {
isOpen,
newListArray,
listHeight,
handleListShow
}
},
}
Vue.createApp(App).mount("#app")
</script>
```
### 資料篩選
`v-if` 與 `v-for` 不推薦一起使用!`v-if` 的執行優先順序高於 `v-for`。
正確做法:先透過 `computed` 把資料篩選出來後再進行 `v-for`
:warning: 如果要取用 `computed` 的值,記得加上 `.value`
```htmlmixed=
<div id="app">
<a @click="handleListShow" class="title">課程列表</a>
<ul class="box" :style="{height: listHeight}">
<li v-for="(list, index) in newListArray" :key="list">{{ list }}</li>
</ul>
</div>
```
```javascript=
<script>
const { ref, reactive, computed } = Vue
const App = {
setup () {
const isOpen = ref(true)
const listArray = reactive([
{ name: "2020 Vue3 專業職人 | 入門篇", money: 3200 },
{ name: "2020 Vue3 專業職人 | 加值篇", money: 100 },
{ name: "2020 Vue3 專業職人 | 進階篇", money: 500 },
{ name: "現代 JavaScript 職人之路|入門篇", money: 300 },
{ name: "現代 JavaScript 職人之路|中階實戰篇", money: 1600 },
{ name: "職人必修的RWD 網頁入門班", money: 900 },
{ name: "HTML5+Animate CC 網頁動畫與遊戲互動", money: 2000 },
{ name: "現代 JavaScript 職人之路|面試篇", money: 1800 },
])
const filteredListArray = computed(() => {
const filter = listArray.filter(item => {
return item.money > 500
})
return filter
})
const newListArray = computed(() => {
const map = filteredListArray.value.map((item, index) => {
return `${index + 1}. ${item.name} $:${item.money}`
})
return map
})
const listHeight = computed(() => {
console.log(filteredListArray)
return isOpen.value ? `${newListArray.value.length * 40}px` : '0px'
})
const handleListShow = () => {
isOpen.value = !isOpen.value
}
return {
isOpen,
newListArray,
listHeight,
handleListShow
}
}
}
Vue.createApp(App).mount("#app")
</script>
```
![](https://i.imgur.com/cxY10tQ.png)
## `watch`
```javascript=
watch(..., (newValue, oldValue) => {
......
})
```
第一個參數:要被監控的值
第二個參數:callback function
### watching ref
#### 非物件資料型態 - number, string, boolean
```htmlmixed=
<div id="app">
<h1>refNumber: {{ refNumber }}</h1>
<h1>refString: {{ refString }}</h1>
<h1>refBoolean: {{ refBoolean }}</h1>
<button @click="changeValue">change value</button>
</div>
```
```javascript=
<script>
const { ref, watch } = Vue
const App = {
setup() {
const refNumber = ref(0)
const refString = ref('Hello')
const refBoolean = ref(true)
watch(refNumber, (newValue, oldValue) => {
console.log('refNumber newValue', newValue)
console.log('refNumber oldValue', oldValue)
})
watch(refString, (newValue, oldValue) => {
console.log('refString newValue', newValue)
console.log('refString oldValue', oldValue)
})
watch(refBoolean, (newValue, oldValue) => {
console.log('refBoolean newValue', newValue)
console.log('refBoolean oldValue', oldValue)
})
const changeValue = () => {
refNumber.value++
refString.value = refString.value + ' Vue'
refBoolean.value = !refBoolean.value
}
return {
refNumber,
refString,
refBoolean,
changeValue
}
}
}
Vue.createApp(App).mount("#app")
</script>
```
![](https://i.imgur.com/vIhuGri.png)
![](https://i.imgur.com/DqKioy8.png)
資料的值可以被改變,也能被監聽到。
#### 監聽多個值
使用 `array` 把要監聽的值包起來
```htmlmixed=
<div id="app">
<h1>refString1: {{ refString1 }}</h1>
<h1>refString2: {{ refString2 }}</h1>
<button @click="changeValue">change value</button>
</div>
```
```javascript=
<script>
const { ref, watch } = Vue
const App = {
setup() {
const refString = ref('Hello')
const refString1 = ref('Vue')
const refString2 = ref('World'
watch([refString1, refString2], (newValue, oldValue) => {
console.log('newValue', newValue)
console.log('oldValue', oldValue)
})
const changeValue = () => {
refString1.value = refString.value + refString1.value
refString2.value = refString.value + refString2.value
}
return {
refString1,
refString2,
changeValue
}
}
}
Vue.createApp(App).mount("#app")
</script>
```
![](https://i.imgur.com/wCzMGX2.png)
![](https://i.imgur.com/QWUj62K.png)
#### 物件資料型態 - object, array 監聽整個物件
```htmlmixed=
<div id="app">
<h1>refArray: {{ refArray }}</h1>
<h1>refObject: {{ refObject }}</h1>
<button @click="changeValue">change value</button>
</div>
```
```javascript=
<script>
const { ref, watch } = Vue
const App = {
setup() {
const refArray = ref([1, 2, 3, 4, 5])
const refObject = ref({ index: 0 })
watch(refArray, (newValue, oldValue) => {
console.log('refArray newValue', newValue)
console.log('refArray oldValue', oldValue)
})
watch(refObject, (newValue, oldValue) => {
console.log('refObject newValue', newValue)
console.log('refObject oldValue', oldValue)
})
const changeValue = () => {
refArray.value[0] = 0
refObject.value.index++
}
return {
refArray,
refObject,
changeValue
}
}
}
Vue.createApp(App).mount("#app")
</script>
```
![](https://i.imgur.com/PqbTstj.png)
![](https://i.imgur.com/fNfgAP9.png)
資料的值可以被改變,卻無法被監聽到。
#### 物件資料型態 - object, array 監聽物件中某個 index/key 的值
```javascript=
<script>
const { ref, watch } = Vue
const App = {
setup() {
const refArray = ref([1, 2, 3, 4, 5])
const refObject = ref({ index: 0 })
watch(refArray.value[0], (newValue, oldValue) => {
console.log('refArray newValue', newValue)
console.log('refArray oldValue', oldValue)
})
watch(refObject.value.index, (newValue, oldValue) => {
console.log('refObject newValue', newValue)
console.log('refObject oldValue', oldValue)
})
const changeValue = () => {
refArray.value[0] = 0
refObject.value.index++
}
return {
refArray,
refObject,
changeValue
}
}
}
Vue.createApp(App).mount("#app")
</script>
```
![](https://i.imgur.com/vcDgXSb.png)
會顯示 warning:
Invalid watch source: ~~
A watch source can only be a getter/effect function, a ref, a reactive object, or an array of these types.
**若要監聽物件中某個 key 的值,就必須用一個 function return 該值。**
```javascript=
<script>
const { ref, watch } = Vue
const App = {
setup() {
const refArray = ref([1, 2, 3, 4, 5])
const refObject = ref({ index: 0 })
watch(
() => refArray.value[0],
(newValue, oldValue) => {
console.log('refArray newValue', newValue)
console.log('refArray oldValue', oldValue)
}
)
watch(
() => refObject.value.index,
(newValue, oldValue) => {
console.log('refObject newValue', newValue)
console.log('refObject oldValue', oldValue)
}
)
const changeValue = () => {
refArray.value[0] = 0
refObject.value.index++
}
return {
refArray,
refObject,
changeValue
}
}
}
Vue.createApp(App).mount("#app")
</script>
```
![](https://i.imgur.com/sZucwkm.png)
### watching reactive
#### 物件資料型態 - object, array 監聽整個物件
```htmlmixed=
<div id="app">
<h1>reactiveArray: {{ reactiveArray }}</h1>
<h1>reactiveObject: {{ reactiveObject }}</h1>
<button @click="changeValue">change value</button>
</div>
```
```javascript=
<script>
const { reactive, watch } = Vue
const App = {
setup() {
const reactiveArray = reactive([1, 2, 3, 4, 5])
const reactiveObject = reactive({ index: 0 })
watch(reactiveArray, (newValue, oldValue) => {
console.log('reactiveArray newValue', newValue)
console.log('reactiveArray oldValue', oldValue)
})
watch(reactiveObject, (newValue, oldValue) => {
console.log('reactiveObject newValue', newValue)
console.log('reactiveObject oldValue', oldValue)
})
const changeValue = () => {
reactiveArray[0] = 0
reactiveObject.index++
}
return {
reactiveArray,
reactiveObject,
changeValue
}
}
}
Vue.createApp(App).mount("#app")
</script>
```
![](https://i.imgur.com/JrfIQBk.png)
![](https://i.imgur.com/zLwbceB.png)
雖然資料的值可以被改變,也能被監聽到,但新舊值卻是相同的。
若有需要監控舊值,則**必須在第一個參數 callback function return 一個 ==深拷貝== 的值**。
```javascript=
<script>
const { reactive, watch } = Vue
const App = {
setup() {
const reactiveArray = reactive([1, 2, 3, 4, 5])
const reactiveObject = reactive({ index: 0 })
watch(
() => JSON.parse(JSON.stringify(reactiveArray)),
(newValue, oldValue) => {
console.log('reactiveArray newValue', newValue)
console.log('reactiveArray oldValue', oldValue)
}
)
/* Array 的深拷貝也可以用解構賦值的方式 */
/*
watch(
() => [...reactiveArray],
(newValue, oldValue) => {
console.log('reactiveArray newValue', newValue)
console.log('reactiveArray oldValue', oldValue)
},
)
*/
watch(
() => JSON.parse(JSON.stringify(reactiveObject)),
(newValue, oldValue) => {
console.log('reactiveObject newValue', newValue)
console.log('reactiveObject oldValue', oldValue)
}
)
const changeValue = () => {
reactiveArray[0] = 0
reactiveObject.index++
}
return {
reactiveArray,
reactiveObject,
changeValue
}
}
}
Vue.createApp(App).mount("#app")
</script>
```
![](https://i.imgur.com/q6ro9mO.png)
#### 物件資料型態 - object, array 監聽物件中某個 index/key 的值
```javascript=
<script>
const { reactive, watch } = Vue
const App = {
setup() {
const reactiveArray = reactive([1, 2, 3, 4, 5])
const reactiveObject = reactive({ index: 0 })
watch(reactiveArray[0], (newValue, oldValue) => {
console.log('reactiveArray newValue', newValue)
console.log('reactiveArray oldValue', oldValue)
})
watch(reactiveObject.index, (newValue, oldValue) => {
console.log('reactiveObject newValue', newValue)
console.log('reactiveObject oldValue', oldValue)
})
const changeValue = () => {
reactiveArray[0] = 0
reactiveObject.index++
}
return {
reactiveArray,
reactiveObject,
changeValue
}
}
}
Vue.createApp(App).mount("#app")
</script>
```
![](https://i.imgur.com/SbiyPEj.png)
同 `ref` 會顯示 warning:
Invalid watch source: ~~
A watch source can only be a getter/effect function, a ref, a reactive object, or an array of these types.
**若要監聽物件中某個 key 的值,就必須用一個 function return 該值。**
:warning: 這邊 ==不需要深拷貝==!
```javascript=
<script>
const { reactive, watch } = Vue
const App = {
setup() {
const reactiveArray = reactive([1, 2, 3, 4, 5])
const reactiveObject = reactive({ index: 0 })
watch(
() => reactiveArray[0],
(newValue, oldValue) => {
console.log('reactiveArray newValue', newValue)
console.log('reactiveArray oldValue', oldValue)
}
)
watch(
() => reactiveObject.index,
(newValue, oldValue) => {
console.log('reactiveObject newValue', newValue)
console.log('reactiveObject oldValue', oldValue)
}
)
const changeValue = () => {
reactiveArray[0] = 0
reactiveObject.index++
}
return {
reactiveArray,
reactiveObject,
changeValue
}
}
}
Vue.createApp(App).mount("#app")
</script>
```
![](https://i.imgur.com/duhnNFN.png)
#### 物件資料型態 - object, array 監聽巢狀物件中某個 index/key 的值
加上第三個參數:**`{ deep: true }`**
:warning: 使用 `deep` 會很耗效能,因為他會針對物件中的每個 `key` 進行掃描。
```htmlmixed=
<div id="app">
<h1>name: {{ reactiveNestedObject.user.name }}</h1>
<h1>age: {{ reactiveNestedObject.user.age }}</h1>
<button @click="changeValue">change value</button>
</div>
```
```javascript=
<script>
const { reactive, watch } = Vue
const App = {
setup() {
const reactiveNestedObject = reactive({
user: {
name: 'John',
age: 18
}
})
watch(
() => reactiveNestedObject,
(newValue, oldValue) => {
console.log('no deep')
console.log('reactiveObject newValue', newValue)
console.log('reactiveObject oldValue', oldValue)
}
)
watch(
() => reactiveNestedObject,
(newValue, oldValue) => {
console.log('deep')
console.log('reactiveObject newValue', newValue)
console.log('reactiveObject oldValue', oldValue)
},
{ deep: true }
)
const changeValue = () => {
reactiveNestedObject.user.name = 'Mary'
reactiveNestedObject.user.age = 20
}
return {
reactiveNestedObject,
changeValue
}
}
}
Vue.createApp(App).mount("#app")
</script>
```
![](https://i.imgur.com/eafbyCm.png)
![](https://i.imgur.com/i3MLVvl.png)
只有加 `deep` 的才有被監控到。但注意整個物件的新舊值仍然相同。
若今天直接對 `reactive` 物件做深拷貝,就不需要加 `deep` 。
### 總結
* 非物件資料型態:使用 `ref` 直接監聽。
* 物件資料型態 - 整個物件:使用 `reactive` callback function return 物件的**深拷貝**。
* 物件資料型態 - 物件中某個 index / key 的值:使用 `ref` 或 `reactive` callback function return 物件中該 index / key 的值。
參考資料:
* [Learn Vue Composition API's watch() method - Watch API Tutorial](https://www.netlify.com/blog/2021/01/29/deep-dive-into-the-vue-composition-apis-watch-method/)
* [【Vue3.0】- 侦听器 - 简书](https://www.jianshu.com/p/e498b12b2c62)
## `watchEffect`
`watchEffect` 與 `watch` 功能相似,都可以監控資料,但不同的點是:
* 不需要傳入要被監聽的值
* 資料的值尚未被改變前,就會先執行一次 `watchEffect` function
* 只有在 `watchEffect` function 內被調用的值有被改變到,才會執行 `watchEffect` function
* 可以被停用
資料初始化時,`watchEffect` 就會先執行
```javascript=
<script>
const { ref, reactive, watchEffect } = Vue
const App = {
setup () {
const number = ref(0)
const numberObject = reactive({ index: 0 })
watchEffect(() => {
console.log('number', number.value)
})
setTimeout(() => {
number.value++
numberObject.index++
}, 1000)
return {}
},
};
Vue.createApp(App).mount("#app")
</script>
```
![](https://i.imgur.com/5nEJwnV.png)
若 `watchEffect` 內被調用的值未改變,則 `watchEffect` 就不會再執行,反之亦然。
```javascript=
<script>
const { ref, reactive, watchEffect } = Vue
const App = {
setup () {
const number = ref(0)
const numberObject = reactive({ index: 0 })
watchEffect(() => {
console.log('number', number.value)
})
setTimeout(() => {
// number.value++
numberObject.index++
}, 1000)
return {}
},
};
Vue.createApp(App).mount("#app")
</script>
```
![](https://i.imgur.com/mWjjdeQ.png)
```javascript=
<script>
const { ref, reactive, watchEffect } = Vue
const App = {
setup () {
const number = ref(0)
const numberObject = reactive({ index: 0 })
watchEffect(() => {
console.log('number', number.value)
console.log('numberObject', numberObject.index)
})
setTimeout(() => {
// number.value++
numberObject.index++
}, 1000)
return {}
},
};
Vue.createApp(App).mount("#app")
</script>
```
![](https://i.imgur.com/sXooGis.png)
`watchEffect` 可以被停用
:information_source: `watchStop` 為自行命名的變數。只要命名變數與被執行的 function 相同,就可以停用 `watchEffect`。
:warning: `setInterval` 還是在執行!若要停止 `setInterval`,就必須 `clearInterval`。
```javascript=
<script>
const { ref, reactive, watchEffect } = Vue
const App = {
setup () {
const number = ref(0)
let timer = null
const watchStop = watchEffect(() => {
console.log(number.value)
if (number.value >= 5) {
watchStop()
clearInterval(timer)
}
})
timer = setInterval(() => {
number.value++
}, 1000)
return {}
},
};
Vue.createApp(App).mount("#app")
</script>
```
![](https://i.imgur.com/dlKXiNu.png)
## Lifecycle hooks 生命週期
`setup()` 也是一種生命週期,
:warning: `onUpdated` 是因為資料改變,導致 virtual DOM 重新渲染後,才會調用。
```htmlmixed=
<div id="app">
<h1>{{ number }}</h1>
</div>
```
```javascript=
<script>
const { ref, onBeforeMount, onMounted, onUpdated } = Vue
const App = {
setup() {
console.log('當 Vue 掛載到 #app 後執行')
const number = ref(0)
setTimeout(() => {
number.value = 1
console.log(number.value)
}, 3000)
onBeforeMount(() => {
// DOM 渲染前
console.log('DOM 渲染前')
})
onMounted(() => {
// DOM 渲染完成後
console.log('DOM 渲染完成後')
})
onUpdated(() => {
// 在資料更改導致 virtual DOM 重新渲染後調用
console.log('資料更改後')
})
return {
number
}
}
}
Vue.createApp(App).mount("#app")
</script>
```
![](https://i.imgur.com/kJLiXzq.png)
詳細資料參閱:
[Lifecycle hooks | Vue.js](https://v3.vuejs.org/api/options-lifecycle-hooks.html)
![](https://i.imgur.com/rwjRNxa.png)