Vue3 - 入門篇

tags: Vue HiSKIO

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →
內容著重在 composition API 的使用

安裝

  1. CDN 引入
  2. 下載後引入
  3. npm install
  4. Vue CLI

setup 組件選項

<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 把全部的內容包起來

setupprops 進來的資料被解析後,就會作為 composition API 的入口點。
setup 被 return 出去的任何內容都可以被組件其他地方使用。

setup 在組件創建前就執行了,因此沒有 this。能使用的只有 props 進來的資料。

響應式變數 - ref

利用 ref 綁定變數,進行響應式改變。

<div id="app"> <h1>{{ text }}</h1> </div>

錯誤範例:

<script> const App = { setup () { let text = 'Hello Vue' setTimeout(() => { text = 'Hello world' console.log(text) }, 1000) return { text } } } Vue.createApp(App).mount('#app') </script>

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

ref 把接受的參數放進 value 這個 property 中,並以物件的形式返回,即可響應式地改變資料。

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →
DOM 中直接寫 {{ text }},不需要把 .value 寫出來。

正確範例:

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

也可以使用解構的方式使用:

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

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

其原理:
call by reference v.s. call by value

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

響應式變數 - reactive

reactive 也可以對變數進行綁定,並達到響應式改變的功能。

reactive 只接受物件陣列的型別變數。

<div id="app"> <h1>{{ message.text }}</h1> </div>
<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>

ref v.s reactive
Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

在大多數情況下兩者可以互相替換使用,主要取決於個人喜好或者團隊習慣,根據具體情況去決定要使用哪個就好。

  • ref( ) : 可以接受任何型態的資料,但是不會對物件或陣列內部的屬性變動做監聽。
  • reactive( ) : 只能接受物件或陣列,可以做深層的監聽,以及訪問資料不需要 .value
<div id="app"> <h1>{{ refMessage }}</h1> <h1>{{ reactiveMessage.text }}</h1> </div>
<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>

readonly

readonlyreactiveref 的資料變成只能讀取不能修改。適合在傳遞參數時避免修改到資料

<div id="app"> <h1>{{ number.index }}</h1> <button @click="addNumber">Add</button> <button @click="addNumberReadonly">Add ReadOnly</button> </div>
<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>

v-for key 的重要性

當改變 v-for 中的資料時,因為有綁定 key,所以只會針對那幾筆資料進行變動,而不會對整個陣列重新渲染。所以 key 要是唯一值。

不要拿 indexkey

v-if v.s v-show

v-if:一開始就決定要不要渲染出來。
v-show:先渲染出來,再用 display: none / block 來隱藏或顯示。

computed

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

computed v.s methods

computed 會依據計算的資料進行緩存,只要資料沒有被更改,computed 就不會被重新計算執行。但無法傳入參數進行處理。
methods 不會進行緩存,每次都會重新執行,但是可以傳入參數進行處理。

使用選擇:依照 緩存/效能傳參數 決定。

以上範例可以用 methods 改寫成這樣:

<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>
<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 組合資料應用:

<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>
<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-ifv-for 不推薦一起使用!v-if 的執行優先順序高於 v-for
正確做法:先透過 computed 把資料篩選出來後再進行 v-for

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →
如果要取用 computed 的值,記得加上 .value

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

watch

watch(..., (newValue, oldValue) => { ...... })

第一個參數:要被監控的值
第二個參數:callback function

watching ref

非物件資料型態 - number, string, boolean

<div id="app"> <h1>refNumber: {{ refNumber }}</h1> <h1>refString: {{ refString }}</h1> <h1>refBoolean: {{ refBoolean }}</h1> <button @click="changeValue">change value</button> </div>
<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>

資料的值可以被改變,也能被監聽到。

監聽多個值

使用 array 把要監聽的值包起來

<div id="app"> <h1>refString1: {{ refString1 }}</h1> <h1>refString2: {{ refString2 }}</h1> <button @click="changeValue">change value</button> </div>
<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>

物件資料型態 - object, array 監聽整個物件

<div id="app"> <h1>refArray: {{ refArray }}</h1> <h1>refObject: {{ refObject }}</h1> <button @click="changeValue">change value</button> </div>
<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>

資料的值可以被改變,卻無法被監聽到。

物件資料型態 - object, array 監聽物件中某個 index/key 的值

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

會顯示 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 該值。

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

watching reactive

物件資料型態 - object, array 監聽整個物件

<div id="app"> <h1>reactiveArray: {{ reactiveArray }}</h1> <h1>reactiveObject: {{ reactiveObject }}</h1> <button @click="changeValue">change value</button> </div>
<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>

雖然資料的值可以被改變,也能被監聽到,但新舊值卻是相同的。
若有需要監控舊值,則必須在第一個參數 callback function return 一個 深拷貝 的值

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

物件資料型態 - object, array 監聽物件中某個 index/key 的值

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

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 該值。

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →
這邊 不需要深拷貝

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

物件資料型態 - object, array 監聽巢狀物件中某個 index/key 的值

加上第三個參數:{ deep: true }

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →
使用 deep 會很耗效能,因為他會針對物件中的每個 key 進行掃描。

<div id="app"> <h1>name: {{ reactiveNestedObject.user.name }}</h1> <h1>age: {{ reactiveNestedObject.user.age }}</h1> <button @click="changeValue">change value</button> </div>
<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>

只有加 deep 的才有被監控到。但注意整個物件的新舊值仍然相同。
若今天直接對 reactive 物件做深拷貝,就不需要加 deep

總結

  • 非物件資料型態:使用 ref 直接監聽。
  • 物件資料型態 - 整個物件:使用 reactive callback function return 物件的深拷貝
  • 物件資料型態 - 物件中某個 index / key 的值:使用 refreactive callback function return 物件中該 index / key 的值。

參考資料:

watchEffect

watchEffectwatch 功能相似,都可以監控資料,但不同的點是:

  • 不需要傳入要被監聽的值
  • 資料的值尚未被改變前,就會先執行一次 watchEffect function
  • 只有在 watchEffect function 內被調用的值有被改變到,才會執行 watchEffect function
  • 可以被停用

資料初始化時,watchEffect 就會先執行

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

watchEffect 內被調用的值未改變,則 watchEffect 就不會再執行,反之亦然。

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

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

watchEffect 可以被停用

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →
watchStop 為自行命名的變數。只要命名變數與被執行的 function 相同,就可以停用 watchEffect
Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →
setInterval 還是在執行!若要停止 setInterval,就必須 clearInterval

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

Lifecycle hooks 生命週期

setup() 也是一種生命週期,

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →
onUpdated 是因為資料改變,導致 virtual DOM 重新渲染後,才會調用。

<div id="app"> <h1>{{ number }}</h1> </div>
<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>

詳細資料參閱:
Lifecycle hooks | Vue.js