# 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)