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 外部資料
JSON
mock 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 物件。
:star: pure 寫法
Props Object Mode
Props function Mode
,函式可以做更複雜的轉換http://localhost:8080/?e=true
Vue Router 會將 URL 查詢字串中的 e
參數,轉成一個叫 showExtra
的 prop 傳給 Home
元件。
這樣做讓元件不需要知道路由的細節,只關心它收到的 showExtra
,這樣元件的職責就更單一、也更方便重複使用。
例如有一個網址:/events?_limit=2&_page=3
_limit
:每頁多少項目
_page
:正在哪一頁
1
parseInt(route.query.page)
:因為 route.query.page 拿到是字串,須轉換成數字
1
:表示預設為第一頁
用函式寫法是因為「要動態計算傳資料給props」。
為什麼props.page
要 computed :question:
因為 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:star: 補充:params參數 和 query參數 使用時機
但因為 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
:question: 如果沒有發送 :id ,它將尋找並使用存在的 :id 參數
舊的 route 重新導向至新的 route
/about
到 /about-us
?redirect
redirect
: 將路由導向另一個指定的路由
可以有三種寫法:
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/123
router.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>
可以直接在模板中看資料
:face_holding_back_tears: 為什麼 input 沒有吃到樣式? :face_holding_back_tears:
因為 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
看不懂意思 :boom: :boom: :angry: :angry: 為什麼 onChange 是寫在 v-bind 裡面?
GPT解釋這樣做的目的是:
- 保留並傳遞外部父元件可能傳進來的
$attrs
(例如id
、class
、name
等)- 但又要控制自己的
onChange
行為- 如果你在
$attrs
裡有傳入onChange
,這邊就會覆蓋它
在排版上,如果 radio group 要水平排版,需要用 <span>
包 ; 如果要垂直排版,需要用 <div>
包。這時候可以使用 <component>
動態渲染其他組件 或 HTML 元素。
:is
屬性:決定渲染 哪個元件 或 標籤
範例:
意思是 href 為 true,則 <component>
會變成 <a></a>
,反之 <component>
會變成 <span></span>
:star: 為什麼把 v-for
和 :key
放在 <component>
上?
因為要把每個 radio 的外層包容器元素
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
,當驗證失敗時,它會包含錯誤訊息,否則為空字串
:star: !regex.test(String(value).toLowerCase()
為什麼轉成小寫?
Email 使用者名稱(@ 前):技術上是區分大小寫的。
Email 網域名稱(@ 後):一律 不區分大小寫。
開發上會統一轉小寫做驗證,如果要做更嚴格的驗證,就不須加上轉小寫。
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
是為了避免錯誤發生
:star: useForm({ validationSchema })
validationSchema 本身設定就是個物件,為什麼外層還要有{}
?
因為 useForm 本身會包含多個屬性選項,validationSchema 只是其中一個,如下方設計:
:star: undefined是什麼?
因為 useField 第一個參數是欄位
第二個參數 validate
,undefined
意思是 不需要驗證規則
第三個參數是可選擇性
如果省略第二個參數直接寫{ initialValue: 1 }
,會誤以為是第二個參數而報錯。
處理表單送出
把欄位名稱對應的的錯誤訊息包成一個物件
例如:
category 和 title 是必填,當沒填寫就送出表單,跳出錯誤訊息
原想改寫把 useField 的 value 改成從 useForm 解構取得 valuse 來綁定BaseSelect
等元件的v-model,結果失敗 :cry:
查詢發現 useForm().values
是 readonly的 reactive proxy,也就是只能讀,不能雙向綁定(v-model不行,會出現 readonly 的錯誤)
:100: 改寫成這樣就可以了:100:
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
方法來設定