es6-string-html課程用的是CDN,官網有提供
Vue application instance 都是經由 createApp 函式建立createAPP 的 APP 這個物件其實是一個 root component 根元件root component根元件可以包含其他元件作為他的子元件,也就是他是最頂層的元件
全名Single-File Components,就是專案看到的.vue檔,每個vue檔就是完整的元件,裡面包含三個區塊:
(JavaScript/<script>)(html/<template>)(css/<style>)SFC的結構:
一個 .vue 檔中:
<template> 只能有一個<script> 只能有一個 (不包含<script setup>)<script setup> 只能有一個<style> 可以多個影片提到:
{{}}:moustache syntax
reactivity system 響應式系統
因為
{{}}無法使用在html的屬性,所以才有v-bind
:Day 8: 認識 Vue directive 和 v-if v.s v-show
vue doc - template syntax
vue doc - Built-in Directives
vue doc - Conditional Rendering
可接受的value類型有:數字、字串、陣列(包含Map/Set)、物件
從1開始,數字須為正整數
可以使用解構
可以拿到物件的 value 、 key 、 index
v-if和v-for不能放在同一個元素上,v-if的優先權高於v-for,會報錯
Day 9: v-for 與他的坑 feat. key & v-if
Vue doc- special attribute
@click:監聽的事件
handler:觸發的處理器,有 inline handler 、method handler和 Calling Methods in Inline Handlers
.stop.prevent.self.capture.once.passive範例:
渲染出來長這樣
如果style比較多,可以包裝成物件傳入:
當out of stock時,add button按鈕要套反灰設計&不能增加cart數量:
可以綁定陣列渲染多個class
Chris:vue 是如何做到抽換 class? 要知道這件事
computed(() => someValue) 傳入 getter ,計算新值computed({ get, set }) 要手動賦值就要傳入 getter 和 setter補充:getter function
encapsulate:封裝
元件有各自的獨立作用域,無法拿取外部的資料,如要使用到外部資料,用props來解決。
props : a custom attribute for passing data into a component (自訂屬性將資料傳遞給元件)
範例:
在父元件上引入子元件PropsComponent.vue,並用v-for渲染子元件的<p>內容
如果沒有使用<script setup>,寫法改成這樣:
emitting Events : tell "parent" when events happens,意思是子元件執行父元件的方法用 emit
子元件的按鈕點擊時,觸發父元件的事件處理:
payload : 具有意義、有效的資料
v-model : 雙向綁定,可添加modifier修飾符
lazy : 每次change事件後才更新數據,預設的話是每次input事件就更新數據number : 輸入自動轉換為數字如果 <input type="number">,v-model 會自動轉數字,不用 .number
trim : 自動去除輸入內容中兩端的空格cli : command line interface
Hot Module Replacement (HMR) 熱更新
exchanges, adds, or removes modules while an application is running, without a full reload.
當應用程式運行時,替換、新增或移除模組無須重整加載頁面。
scoped : applies styles to component only 僅將樣式套用至元件
定義一個vue元件,並指定元件的名稱:
SPA (Single-Page Application): 整個應用程式所有內容都在一個頁面中呈現(one index.html page)
path: url
name: name of route
component: which view component to render
Vue Router 提供的內建元件:router-link、router-view
<router-view> 就像是一個placeholder,會被route的元件程式碼取代如不要寫死程式碼,透過api call 來 fetch 外部資料
JSONmock database: My JSON Server
元件具有生命週期,並在生命週期中呼叫不同的鉤子(hook)或方法
axios 是基於 promise 且非同步
如果每個元件都有axios instance,API 程式碼會遍佈整個應用程式,這樣會很混亂且難debug
所以,在應用程式內有一個專門的位置,來處理 api 行為,在 src 建立一個 service folder ,並建立一個 new file 為 EventService.js,把 axios 包裝在這裡,要使用再各別 import 進去
將該 event 的 ID 新增至此URL的末尾
https://my-json-server.typicode.com/Code-Pop/Real-World_Vue-3/events/123
如何在 router 的 parameters 新增 event id ❓
to="/event/123" 原本route.path寫法會hard code
:to: 用v-bind綁定物件,並裡面改成放物件 :to="{ name:'EventDetails', params: { id: event.id } } "
path 屬性把動態字段以「冒號」開始,改成:id
:id :可以是 username 或是 user id 來進行更新
props: true : 將 route.params 自動作為 props 傳進元件(例如 EventDetails.vue)
props: ['id'] 是 option api 寫法,改成composition api寫法是用defineProps
部署前需要考慮的事情:
如何部署:
package.json中可以看到有一個script為build,指令下npm run build
完成後會出現一個 dist 資料夾,裡面有 html、css、js
Render 是自動部署靜態網站,不需要把 dist/ push 到 GitHub,須確定src/、package.json 等原始碼 push 上去
遇到 deploy 報錯,原因是 node 版本的 openSSL 跟 webpack 不相容,但改了 node 版本為v16還是一樣
解法:在package.json加入 engines 欄位,Render就會跑正確版本
重新部署成功了!
影片說到,當點EventList的時候,把網址複製再次貼上會出現not found (如圖)
解決方法:
先到 Render 的 redirects/rewrites 設定
再到 router 加上 not found的路由 和 component
VueX: storage center for data throughtout your app
http://localhost:8080/events?page=4$route.query.page,會得到 4 的結果用$route.params.page取得值,但這樣寫法會高度耦合(hightly coupled) 元件,也就是元件直接依賴 vue router 的 $route 物件。
Props Object ModeProps function Mode,函式可以做更複雜的轉換http://localhost:8080/?e=trueVue Router 會將 URL 查詢字串中的 e 參數,轉成一個叫 showExtra 的 prop 傳給 Home 元件。
這樣做讓元件不需要知道路由的細節,只關心它收到的 showExtra,這樣元件的職責就更單一、也更方便重複使用。
例如有一個網址:/events?_limit=2&_page=3
_limit:每頁多少項目
_page:正在哪一頁
1parseInt(route.query.page):因為 route.query.page 拿到是字串,須轉換成數字
1:表示預設為第一頁
用函式寫法是因為「要動態計算傳資料給props」。
為什麼props.page要 computed
defineProps(['page'])原本是寫成props 是 reactive proxy,而computed會把props.page變成 ref
也就是 props 有包含 page 屬性,就可以 props.page 取值,因為需要響應式變數來追蹤變化,透過 computed 包住props.page,才會自動更新。
:to="{ name: 'EventList', query: { page: page - 1 } } :導向 EventList ,並在網址加上 ?page=... 的參數,query參數會出現在?的後面網址長的樣子:
rel='prev':為了SEO但因為 onMounted 生命週期僅在初始載入時調用,而不是在元件重用時調用,所以要透過 watchEffect 來監聽page更新
watchEffect:
watch:
{ immediate: true }上面的程式碼改寫成 watch :
x-total-count: HTTP 回應標頭(header),常用在 分頁(pagination)API,也就是資料庫中符合條件的資料總筆數。
從資料庫拿到總筆數後,就能計算總共會有幾頁:
最後排版一下prev page和 next page
Lazy Loading Routes
建立一個 layout 元件給event底下所有的子元件,layout包含navigation和fetch events,這樣就不用每個子元件都寫。
嵌套路由:在父層寫 children,子層的path為空,表示載入父層的根路徑/event/:id
舊的 route 重新導向至新的 route
/about 到 /about-us?redirectredirect: 將路由導向另一個指定的路由
可以有三種寫法:
redirect: '/home'redirect: { path: '/home', query: { foo: 'bar' } }redirect: (to) => {...}alias 別名,但如果在意SEO搜尋就不建議可以把 alias 想成這個路由有小名叫做 /about
/event/:id 到 /events/:id?to參數: vue router 的 route 物件,包含使用者要前往的路由。透過to參數就能找到動態參數id。
瀏覽器的Vue可以查看 route 物件:
要改的路由本身底下如有嵌套路由的話:
.*: 正則表達式寫法,表示任何內容、任何長度(包括空字串)
也就是 :afterEvent(.*) 會匹配 /event/ 後面「所有的東西」
vue doc:路徑參數正則表達式
Vue doc: Redirect and Alias
This is the method called internally when you click a
<router-link>, so clicking<router-link :to="...">is the equivalent of callingrouter.push(...).
當點擊 router-link,內部會調用 router.push()
用官網的範例
和 router.push 一樣都是導向到另一個路由,差別在瀏覽器歷史紀錄history。
router.push:正常頁面導航home、/post/123router.replace:導入後不希望返回導航/dashboard(取代掉 /login)也可以在 router.push 增加屬性 replace:true
補充:
router.go(1):向前一條紀錄,等同router.forward()
router.go(-1):向後一條紀錄,等同router.back()
等同於瀏覽器的前進 / 返回按鈕!
vue doc: Programmatic Navigation
vue doc: Vue Router and the Composition API
MDN: History API
假如使用者輸入一個不存在的路由,設計頁面導向 NotFound.vue 元件。通常用來處理 404 頁面,匹配所有未被其他路由捕捉的網址
catchAll:是自訂參數,是習慣上的命名因為有語意,也可以取path: '/:notFound(.*)'之類
不論是頁面的 404 還是 event 的 404 ,都可以重用 NotFound.vue 元件!
在 NotFound.vue 加上 defineProps: resource屬性
打 api 時,如果沒有這個 event,則跑 catch 區塊,router.push 到 404, params 直接設定為 event
網址為 http://localhost:8080/404/event
範例: call api 的 EventService.js 假設 baseUrl 寫錯
建立一個 NetworkError 元件
建立 NetworkError 的 router
在 catch 寫 if 判斷,假入 event 不存在,載入 404 ;否則 network error
因為 EventList 也有call api,所以如有錯,則導入到network error
全域儲存機制
創建一個 reactive 全域物件GStore
provide: 提供給後代元件注入值,通常會跟inject搭配一起使用。
在register.vue 的register函式 注入(inject) GStore
當點擊 register 按鈕後 flash message 本來為空字串被改成You are successfully registered for ${props.event.title}
最後在 App.vue 的 template 中加入 GStore.flashMessage 的文字判斷和樣式。
onBeforeRouteUpdate:單一元件內,同一元件路由參數變動時執行。例如:/user/1 ➜ /user/2
onBeforeRouteLeave:單一元件內,要離開元件時(可攔下)
to:即將導航到的路由
from:當前導航正要離開的路由
next:vue 2需要用,但vue 3可用回傳值來取代next。沒有回傳值或是回傳true,導航就繼續,如果有回傳false,導航就停止
有以下寫法:
return true 繼續導航return false 取消導航return '/' 重新導向 / pathreturn {name:'event-list'} 重新導向 event-list 路徑範例:
TODO:需要把progress bar在每個頁面呈現
beforeEach 路由守衛:全域,每次導航前會先攔截
beforeResolve
afterEach:導航完成之後調用,所以不能取消導航
加上 return 原因是讓 vue router 知道載入頁面之前等待 API 呼叫。
為什麼 beforeRouteEnter 不用 return 但 beforeRouteUpdate 就要?
因為next會告訴vue router等到API呼叫回傳後,才能進行路由
meta 藉由子路由取得繼承,也就是父層路由有 meta field ,那子路由自動繼承 meta field 。
例如:父層是管理者頁面,設定 meta: {requireAuth:true},其子路由也都會一同繼承 meta: {requireAuth:true}
vue doc:route meta field 路由元信息
將路由對應的元件分割,當路由被訪問才加載。
當進入頁面,瀏覽器預設行為會帶使用者到頁面最頂端。
例如:google 搜尋引擎,當滑到底部有分頁,點選第二頁時候,會到第二頁的頂端
但影片一開始的範例點選 next page,仍停留在底部,需要改善使用者介面,所以在 router 加上 scrollBehavior :
按返回前一頁,要帶回到原本頁面的原本的位置:
盡可能讓元件保持彈性和複用設計
<pre></pre>可以直接在模板中看資料
因為 multi-root component (多個根元素) 無法自動繼承 attribute(例如 type="text"),需要手動把 $attrs 綁到指定的元素例如 <input>,這樣父層傳入的屬性才能正確應用。
如果 single-root component(一個根元素),Vue 會自動把這些 attribute 加到 root 上。
解法:手動注入 attribute ,綁定$attrs 物件
BaseInput元件就能吃到父層給的type="text"
其他範例:
父層傳來但沒有對應 prop 的所有屬性,例如type 和 maxlength沒有在 defineProps 中定義,就會被自動轉為 $attrs
用 v-bind="$attrs" 就能讓它們應用到 <input>。
$emit('update:modelValue', $event.target.value) 發送了一個事件:update:modelValue$event.target.value(即使用者輸入的字串)$event.target.value 將input 元素的輸入事件的 value,作為參數傳遞給父層。所以 @update:modelValue 的 $event 接收到子元件的 value,並賦值並更新 event.title
等於下面寫法:
v-model 綁定的時候,其實背後就是在監聽 update:modelValue 這個事件modelValue 是 Vue 3 的 v-model 預設接收 prop在vue 3,如果沒有使用@這個語法,事件將以關鍵字 on 作為前綴
例如:@change ➜ onChange、@input ➜ onInput
看不懂意思
GPT解釋這樣做的目的是:
- 保留並傳遞外部父元件可能傳進來的
$attrs(例如id、class、name等)- 但又要控制自己的
onChange行為- 如果你在
$attrs裡有傳入onChange,這邊就會覆蓋它
在排版上,如果 radio group 要水平排版,需要用 <span> 包 ; 如果要垂直排版,需要用 <div> 包。這時候可以使用 <component> 動態渲染其他組件 或 HTML 元素。
:is 屬性:決定渲染 哪個元件 或 標籤
範例:
意思是 href 為 true,則 <component> 會變成 <a></a> ,反之 <component> 會變成 <span></span>
v-for 和 :key 放在 <component> 上?doc: 動態組件
doc: <component>
doc: is
XMLHTTPRequests(XHR)
XMLHttpRequest(XHR) objects are used to interact with servers. You can retrieve data from a URL without having to do a full page refresh. This enables a Web page to update just part of a page without disrupting what the user is doing.JavaScript 提供的一個內建物件,主要用來向伺服器發送請求、接收資料,而不需要重新載入整個頁面。
submit.prevent 這個修飾符會呼叫e.preventDefault()type="submit" ,<form></form>標籤會發送sumbit事件安裝axios
發出特定的請求,例如:POST
到開發工具的network頁面可以觀察 fetch/XHR,Header可以看到為成功請求
Response則可以觀察伺服器回傳回來的資料
如果是在console.log頁面,則可以看到完整的回傳資料
<fieldset>:將一組相關的表單欄位包在一起的容器,螢幕閱讀器會解釋欄位間的關係。
<legend>:<legend> 必須放在 <fieldset> 裡面,它提供這一組欄位的說明標題。這段文字會被螢幕閱讀器唸出來。
placeholder不能取代說明的標籤(label)
通用唯一辨識碼(Universally Unique Identifier,縮寫:UUID)
aria-describedby:提供補充說明,不取代label
aria-live:某段內容變動時,自動唸出來通知使用者。例如:送出成功、3秒後自動跳轉等
aria-invalid:這個表單欄位目前是無效的(驗證失敗),但不會執行表單驗證!
submit button不要用disable,因為輔助科技不會取得到回饋而被忽略
MDN: accessibility( a11y 無障礙 )
安裝vee-validate
因為 useField() 是回傳一個物件,可以用解構的方式取得物件裡的屬性,能使用的屬性很多,詳細看 vee-validate:useField。
第一個屬性 value:是Vue ref,代表欄位的值
第二個屬性 errorMessage:是一個 ref,當驗證失敗時,它會包含錯誤訊息,否則為空字串
!regex.test(String(value).toLowerCase()為什麼轉成小寫?validation schema(驗證規則):一個統一的結構(通常是一個物件)來定義整個表單的驗證規則。因為用useField()只能一個一個欄位設定驗證,而 schema 則可以一次定義表單中每個欄位的驗證規則。範例:
以這個例子 validation schema 驗證規則為
需要注意的地方:
useField("email")和 validations schema 的 email 屬性名稱要一致!其他也是以此類推
return true 代表驗證通過
vee-validate:useForm
vee-validate:Typed Schemas
處理各種「空」的情況:
因為 .length 無法用在 null 或 undefined,所以先判斷 value === undefined || value === null 是為了避免錯誤發生
useForm({ validationSchema }) validationSchema 本身設定就是個物件,為什麼外層還要有{}?因為 useField 第一個參數是欄位
第二個參數 validate,undefined意思是 不需要驗證規則
第三個參數是可選擇性
如果省略第二個參數直接寫{ initialValue: 1 },會誤以為是第二個參數而報錯。
處理表單送出
把欄位名稱對應的的錯誤訊息包成一個物件
例如:
category 和 title 是必填,當沒填寫就送出表單,跳出錯誤訊息
原想改寫把 useField 的 value 改成從 useForm 解構取得 valuse 來綁定BaseSelect等元件的v-model,結果失敗
useForm().values 是 readonly的 reactive proxy,也就是只能讀,不能雙向綁定(v-model不行,會出現 readonly 的錯誤)vee-validate官網推薦使用第三方Yup函式庫,屬於 schema 驗證庫
基本安裝
範例:
yup.object():建立整體資料的schema。範例中裡面包含email 和 password 兩個欄位,代表有各自的驗證條件。yup.required():括號裡可以設置自訂訊息。例如:yup.required("the title field is required").validate() 是一個回傳 Promise 的非同步函式。例如:email 要連到後端確認 email 是否被註冊過tree shaking: 保留確定會用到的程式
原本 input 事件會每次輸入或刪除就會觸發,如果想要讓使用者體驗變好
handleChange,更新欄位值、驗證欄位那如果好幾個欄位都要使用handleChange,可以提取 useForm 的 setFieldValue 方法來設定