[TOC] # C_Vue官方文件筆記 :::info 怕忘記隨便記 ::: ## Vue.js ### Creating a Vue Application ```javascript import { createApp } from 'vue' // 這裡import的是component instance import App from './App.vue' // 這裡的變數是application instance // 負責把最終的root component instance // 打包成application instance const app = createApp(App) app.mount('#app') // .mount回傳的是root component instance ``` ### Reactivity Fundamentals #### ref() template中使用不用.value,但在js中取值的話要 ```javascript const count = ref(0) console.log(count) // { value: 0 } console.log(count.value) // 0 ``` ```html <button @click="count++"> {{ count }} </button> ``` 為什麼要.value呢? 跟getter和setter有關 如果ref的不是基本型別,就會幫你轉成`reactive()` #### `<script setup>` 用`<script setup>`取代`setup()` ```javascript export default { setup() {} } ``` #### reactive() 傳入的是object 回傳的是proxy ```javascript import { reactive } from 'vue' const state = reactive({ count: 0 }) ``` 在template中使用 ```html <button @click="state.count++"> {{ state.count }} </button> ``` #### reactive()的限制 1. 不能用在基本型別 2. 不能換掉整個object 3. 解構賦值的時候會出問題(? ### ?Computed Properties #### Getting the Previous Value :question:previous是幹麻用的 好像是可以使用到上一次儲存的值(上一次的快照) ### Class and Style Bindings 影片教學是更改style ```html <div v-for="variant in variants" :style="{ backgroundColor: variant.color }" > </div> ``` :heavy_check_mark:能不能用動態class? 實作上偏好哪一種? ```html <div v-for="variant in variants" :class="changeColor(variant)" > </div> ``` ```javascript methods: { changeColor(item) { return { [`${item.color}`]: true, // class名稱和資料一樣 }; }, ``` > 大部分用class 可以用三元運算子單行處理 ```javascript :class="[ data.span ? 'table__cell--span' : 'table__cell']" ``` > style會給function或ref,不會寫一長串 #### 要怎麼在devtool看到data? 都存在mountedApp裡面了 ![image](https://hackmd.io/_uploads/HyHtCsMSkx.png) mountedApp.變數名稱 ![image](https://hackmd.io/_uploads/rkCh0jfSJx.png) ### Conditional Rendering [REF](https://vuejs.org/guide/essentials/conditional.html) #### `v-else` 一定要接在`v-if`或`v-else-if`後面,否則不會被識別到 #### `v-if` on `<template>` 可以掛在template上讓template內的子元素連動顯示/不顯示 實測可以直接掛父層 ```html <div v-if="onSale"> <h2>On sale!!!</h2> <p>要買要快</p> </div> ``` #### `v-show`和`v-if`的差別 `v-show`只是更改display屬性的值,所以這個元素會一直掛在DOM上面;`v-if`會把元素刪掉 `v-show`適合用在會一直改變顯示狀態的元素(按鈕變化) `v-if`適合用在網頁運行期間不太會改變狀態的元素(資料呈現) ### List Rendering [REF](https://vuejs.org/guide/essentials/list.html) #### v-for 類似於forEach 可以用解構賦值 ```html <ul> <li v-for="({ size }, index) in sizes">{{ index }} {{ size }}</li> </ul> ``` ```javascript const app = Vue.createApp({ data() { return { sizes: [{ size: "S" }, { size: "M" }, { size: "L" }, { size: "XL" }], }; }, }); ``` 如果不是陣列,是物件 ```html <li v-for="(value, key, index) in myObject"> {{ index }}. {{ key }}: {{ value }} </li> ``` 直接渲染出數字,但會從==1==開始,不是0 ```html <span v-for="n in 10">{{ n }}</span> ``` #### v-for 和 v-if `v-if`的順位比`v-for`還要高,所以如果在`v-if`用了`v-for`中的變數,就會出錯 解決方法:把`v-for`移到父層 #### key :heavy_check_mark:"in-place patch" strategy This default mode is efficient, but only suitable when your list render output does not rely on child component state or temporary DOM state (e.g. form input values). 什麼時候會用到key > vue本身會用到,而不是自己去操作(但我們要提供) > 雖然可以用index作為key值,但如果刪除元素的話可能會有問題 #### v-for with a Component ==跳過,之後再回來看QQ== ### Event Handling [REF](https://vuejs.org/guide/essentials/event-handling.html#event-handling) #### Method vs. Inline Detection - 被視為method: foo, foo.bar, foo['bar'] 理解上應該是作為callback function傳進去 例如: `v-on:click="addToCart"` 會自動接收DOM Event object,所以在function內可以直接使用event ```javascript addToCart(event) { this.cart++; console.log(event.type); }, ``` ![image](https://hackmd.io/_uploads/SJAMptfrkx.png) - 被視為inline: foo(), count++ 直接執行 例如: `v-on:mouseover="updateImage(variant.image)"` #### Accessing Event Argument in Inline Handlers 要怎麼使用event object? 例如`v-on:click="addToCart"`,我要怎麼在addToCart這個function使用`click`這個event? 1. 傳入`$event`作為argument ```html <button @click="addToCart($event)"> Add to Cart </button> ``` 2. 用箭頭函式 ```html <button @click="(event)=> addToCart(event)"> Add to Cart </button> ``` #### Event Modifiers 要把跟event相關的事情(例如`event.preventDefault()`)跟method中的邏輯分開,所以有了修飾子 ```html <!-- 修飾子可以串連 --> <a @click.stop.prevent="doThat"></a> <!-- only trigger handler if event.target is the element itself --> <!-- i.e. not from a child element --> <div @click.self="doThat">...</div> ``` #### 修飾子串連的順序會有影響 [++的鐵人文章](https://ithelp.ithome.com.tw/articles/10363728) `@click.prevent.self` vs `@click.self.prevent` 1. `.self`設置在父元素 => 點擊子元素時 => 印出子元素 => 跳轉 ```html <div @click.self="parentSay" class="parent"> 父元素 <a href="https://www.example.com" @click="childSay" class="child" >子元素</a > </div> ``` 2. `.self.prevent`設置在父元素 => 點擊子元素時 => 印出子元素 => 跳轉 ```html <div @click.self.prevent="parentSay" class="parent"> 父元素 <a href="https://www.example.com" @click="childSay" class="child" >子元素</a > </div> ``` 3. `.prevent`設置在父元素 => 點擊子元素時 => 印出子元素 => 印出父元素 ```html <div @click.prevent="parentSay" class="parent"> 父元素 <a href="https://www.example.com" @click="childSay" class="child" >子元素</a > </div> ``` 4. `.prevent.self`設置在父元素 => 點擊子元素時 => 印出子元素 ```html <div @click.prevent="parentSay" class="parent"> 父元素 <a href="https://www.example.com" @click="childSay" class="child" >子元素</a > </div> ``` 也就是說如果`.prevent`設在父元素,會影響到父元素和其子元素的預設行為,但如果先`.self`再`.prevent`就不會影響到子元素 `.passive`通常用在觸控裝置 `.passive`和`.prevent`不要同時用,因為`.passive`已經有不使用預設功能的含意在了,同時用的話瀏覽器會跳警告(? #### Key Modifiers 可以把`KeyboardEvent.key`當作修飾子直接用在鍵盤相關的event,只是要轉換成烤肉串寫法 ```html <input @keyup.page-down="onPageDown" /> ``` 也可以串連,當作這個event發生時哪些按鍵處於被按住的狀態 ```html <!-- Alt + Enter --> <input @keyup.alt.enter="clear" /> <!-- Ctrl + Click --> <div @click.ctrl="doSomething">Do something</div> ``` 注意,使用`@keyup`的話,後面如果接的是system key,會有不同的效果 1. 接的不是system key,可觸發saySomething ```html <input @keyup.enter="saySomething" placeholder="到底按了誰" /> ``` 2. 接的是system key,單獨放開alt不可觸發saySomething,但壓住alt且放開其他任何按鍵都可觸發saySomething ```html <input @keyup.alt="saySomething" placeholder="到底按了誰" /> ``` 3. 接的是system key加enter,壓住alt且放開enter可觸發saySomething ```html <input @keyup.alt.enter="saySomething" placeholder="到底按了誰" /> ``` 4. 接的是enter加system key,壓住alt且放開enter可觸發saySomething,但壓住enter且放開alt不可 ```html <input @keyup.enter.alt="saySomething" placeholder="到底按了誰" /> ``` `.exact`限縮按鍵範圍 ```html <!-- 沒有加exact,按住ctrl同時按別的也會觸發 --> <button @click.ctrl="onClick">A</button> <!-- 只有ctrl被按住的時候才會觸發 --> <button @click.ctrl.exact="onCtrlClick">A</button> <!-- 沒有任何system key被按住才會觸發 --> <button @click.exact="onClick">A</button> ``` ### Form Input Bindings `v-model`不會在IME(例如注音還在拼的時候)更新。 `true-value`和`false-value`是vue的特殊屬性,只能和`v-model`一起使用。 ```html <input type="checkbox" v-model="toggle" true-value="yes" false-value="no" /> ``` 此時`toggle`的值就會被設成`true-value`或`false-value`的值 #### 修飾子 `.number`在`<input type="number" />`的時候會被自動使用 ### Watchers #### watch 直接指定要監測的東西是什麼,這個東西只能是ref (including computed refs), a reactive object, a getter function, or an array of multiple sources 所以要監測props的話,可以用getter function ```javascript watch( () => props.page, // getter function (newPage) => { events.value = null EventService.getEvents(2, newPage) .then((response) => { console.log('Get response successfully.') events.value = response.data }) .catch((error) => console.log(error)) }, { immediate: true }, // 解決第一次載入無法執行的問題 ) ``` 好吧這個有個問題,就是page不變的話(第一次載入)就不會執行了@@ => 可以用`{ immediate: true }` #### watchEffect implicit監測內容變更 ```javascript watchEffect(() => { events.value = null EventService.getEvents(2, props.page) .then((response) => { console.log('Get response successfully.') events.value = response.data }) .catch((error) => console.log(error)) }) ``` ### ?Components Basics :question:大問號,影片教的是global registration,但文件寫的大部分都是local,不知道怎麼改XDD ### Lifecycle Hooks ![image](https://hackmd.io/_uploads/H14C7ooIkl.png) ![image](https://hackmd.io/_uploads/BynjQjsL1x.png) ### ?Slots #### Scoped Slots 把子元件的東西藉由v-slot傳上來父元件給父元件使用 ```html <!-- 子元件 --> <fieldset> // 冒號後面的onAddOption傳入父元件 <slot :onAddOption="onAddOption(props.options)"></slot> </fieldset> <!-- 父元件 --> <CheckboxGroup :value="props.form.hobbies" :options="hobbyOptions" @change=" emits('update:hobbies', { ...props.form, hobbies: $event, }) " v-slot="{ onAddOption }" // 父元件接收 > <div class="input-field"> <input type="text" v-model="inputHobby" /> <button class="btn-add" @click="emits('update:hobbyOptions', onAddOption(inputHobby))"> // 使用接收來的東西 新增 </button> </div> </CheckboxGroup> ``` :question:[文件](https://vuejs.org/guide/components/slots.html#fancy-list-example)的`v-bind="item"`為啥可以這樣用 以下兩個寫法都等同於文件的範例 ```html // 第一種 // App.vue -1 <template> <FancyList api-url="url" :per-page="10"> <template #item="slotProps"> <div class="item"> <p>{{ slotProps.item.body }}</p> <p class="meta">by {{ slotProps.item.username }} | {{ slotProps.item.likes }} likes</p> </div> </template> </FancyList> </template> // App.vue -2 <template> <FancyList api-url="url" :per-page="10"> <template #item="{ item }"> <div class="item"> <p>{{ item.body }}</p> <p class="meta">by {{ item.username }} | {{ item.likes }} likes</p> </div> </template> </FancyList> </template> // FancyList.vue <template> <ul> <li v-if="!items.length"> Loading... </li> <li v-for="item in items"> <slot name="item" :item="item"/> </li> </ul> </template> ``` ```html // 第二種 // App.vue <template> <FancyList api-url="url" :per-page="10"> <template #item="{ body, username, likes }"> <div class="item"> <p>{{ body }}</p> <p class="meta">by {{ username }} | {{ likes }} likes</p> </div> </template> </FancyList> </template> // FancyList.vue <template> <ul> <li v-if="!items.length"> Loading... </li> <li v-for="item in items"> <slot name="item" :body="item.body" :username="item.username" :likes="item.likes"/> </li> </ul> </template> ``` :question:renderless components是什麼? 沒有DOM要插入slot就算? ## Vue Router ### Passing Props to Route Components :heavy_check_mark:props從router傳出去之後是全域變數嗎? 使用上接收props的component會自己找到這個props? > 不是全域變數,從router傳出去有指定傳到哪一個component,而那個component才會可以接收這個props ```javascript { path: '/events/:id', name: 'EventLayout', component: EventLayout, // 指定傳到EventLayout props: true, } ``` :heavy_check_mark:管理上要怎麼知道這個props是從哪裡來? > 可以簡單分成頁面檔還是元件檔:如果是頁面檔,大部份是從router來或是RouterView;如果是元件檔,就去找這個元件的父層 ### Programmatic Navigation #### router.push vs router.replace push 會有歷史紀錄,replace 不會,意思是用 replace的話按上一頁不會回到剛才的頁面 #### router.go `router.go(1)`等於瀏覽器的下一頁 `router.go(-1)`等於瀏覽器的上一頁 ### Redirect and Alias > Make the alias start with a / to make the path absolute in nested routes. 如果alias開頭是`/`就表示絕對路徑 ```javascript const routes = [ { path: '/users/:id', component: UsersByIdLayout, children: [ // this will render the UserDetails for these 3 URLs // - /users/24 // - /users/24/profile // - /24 { path: 'profile', component: UserDetails, alias: ['/:id', ''] }, ], }, ] ``` - `path: 'profile'` => 相對路徑 => `'/users/:id'` 加上 `'profile'` => `'/users/:id/profile'` - `alias: '/:id'` => 絕對路徑 => `'/:id'` - `alias: ''` => 相對路徑 => `'/users/:id'` :heavy_check_mark:為什麼這邊的to不是from? 感覺是使用原本的那個url的資訊? 因為是從當下的url(from)要進入到那個url(to),再被redirect到新的url(redirect),要取到的資訊是to ```javascript { path: '/event/:afterEvent(.*)', redirect: (to) => { return { path: `/events/${to.params.afterEvent}`, } }, }, ``` ### RouterOptions 實現更改當下route的連結樣式 ![image](https://hackmd.io/_uploads/ByybHGYIye.png) 直接使用這個class就好 ```css .nav a.router-link-exact-active { color: hsla(160, 100%, 37%, 1); } ``` ### Navigation Guards #### In-Component Guards * beforeRouteEnter - 沒有this - :heavy_check_mark:沒有特別說在Composition API可以用什麼替代方案,為什麼? > 應該是Composition API的關係沒辦法用,可以以寫在router物件的方式取代 * beforeRouteUpdate - 有this - 在Composition API可以用`onBeforeRouteUpdate` * beforeRouteLeave - 有this - 在Composition API可以用`onBeforeRouteLeave` ```javascript { path: '/', name: 'EventList', component: EventListView, beforeEnter: async (to) => { const page = to.query.page || 1 await EventService.getEvents(2, page) .then((response) => { // NProgress.start() console.log(page) console.log('Get response successfully.') GStore.events = response.data GStore.totalEvents = response.headers['x-total-count'] }) .catch((error) => { console.log(error) return { name: 'NetworkError' } }) }, props: (route) => ({ page: parseInt(route.query.page) || 1 }), meta: { transitionName: 'leftToRight' }, }, ``` :heavy_check_mark:影片沒有寫async await, 而是直接return 分成幾種情況 1. 沒有async、沒有return:function回傳undefined,因為不是false,所以進入route 進入route後,fetch才完成,才有資料畫面 3. 沒有async、有return:function回傳Promise,等Promise resolve後,either是fulfilled的undefined或是reject後進到回傳的route `{ name: 'NetworkError' }` 4. 有async、沒有await:function回傳Promise,馬上fulfilled然後回傳undefined,因為不是false,所以進入route 進入route後,fetch才完成,才有資料畫面 4. 有async、有await:等await結束,取得資料畫面,function才回傳undefined :heavy_check_mark:beforeEnter寫一大包是正常的嗎? 通常會直接寫在router內,除非有重複的code才會拉出來寫function,function也會寫在同一支檔案 ### Route Meta Fields 把from參數印出來看看 ![image](https://hackmd.io/_uploads/SJn2a-AU1x.png) ### ?Composition API #### useLink :question:看不太懂這個的功能和用法 ### Lazy Loading Routes > In general, it's a good idea to always use dynamic imports for all your routes. :heavy_check_mark:什麼時候使用? 實作上真的都會用dynamic import嗎 直接在開頭寫import如果檔案很大就會卡住,所以會使用dynamic import。實作上會使用! ### Scroll Behavior 瀏覽器預設點擊連結後畫面會跳到最頂端,但Vue預設不會 :heavy_check_mark:文件內寫的hash是什麼? `href="#top"` 可以藉由id跳轉的連結 ## 其他問題 :heavy_check_mark:點進活動卡片後,沒有出現活動資訊 ## Form :::info Vue 3 Forms 影片 ::: ### Base Input :heavy_check_mark:`v-bind="$attr"` 指定來自父層的那些attribute要套在誰身上(通常用在元件沒有root element的時候,或是沒有要套用在root element的時候) `input="$emit('update:modelValue', $event.target.value)"` :heavy_check_mark:update和modelValue之間的冒號是什麼 命名慣用法 :warning:未完成 動態創建uuid給label和input ### ?submit :question:axios.post不用包成一個js嗎 ### accessibility :heavy_check_mark:`aria-live="assertive"`是啥 有更新時優先通知閱讀器 ### 其他紀錄 阿傑的包法 form進form出 ```html <!-- Parent --> <ProfileForm :form="form" @update:form="form = $event" ></ProfileForm> <!-- Child --> <InputPassword :value="props.form.password" @input=" emits('update:form', { ...props.form, password: $event}) " ></InputPassword> ``` ### validation - 因為email想要lazy validation,`<InputString>`改成`@change`,如果只想針對email改動怎麼辦? - 可以用v-if的方式決定要顯示`<input @input>`或是`<input @change>` - `v-if="lazy"` - submit emits了handleSubmit這個function出去是對的嗎? - handleSubmit主要功用是驗證後把values傳出去,但我直接emits這個function,語意上就跳過了驗證這個步驟,所以改成: ```html <!-- ProfileForm.vue --> <form @submit.prevent="submit" @reset.prevent="emits('reset')"> <!-- App.vue --> <ProfileForm @submit="onSubmit" ></ProfileForm> ``` ```javascript // ProfileForm.vue const submit = handleSubmit((values) => { console.log(values) // 驗證後再把values傳出去 return emits('submit', values) }) // App.vue function onSubmit(event) { console.log(event) // 處理API } ``` - validationSchema的key用中文可以嗎? - 通常不會直接用拿來「顯示」的變數作為「辨識」項,所以可以再用一個name="gender"的attribute來存取