Ch.22 基礎 Vue.js
認識 Vue 與基礎 Vue.js 語法
介紹影片
工具
架構
Vue 的開發概念是把網頁轉換成一個一個的元件,拼湊出網頁
與 jQuery 最大的不同是使用 MVVM 的概念開發網頁,在開發 Vue 網頁時不需要寫 ViewModel
View
視圖ViewModel
資料繫結器,用於連結視圖和資料Model
資料
在網頁上引用 Vue.js 使用時架構如下
<div id="app">
<!-- HTML 部分 -->
</div>
Vue.createApp({
setup () {
},
data () {
return {}
},
methods: {}
}).mount('#app')
HTML 語法
- 模板語法和標籤的
v-text
可以將資料以文字方式輸出html<!-- {{ }} 裡面放 data 的名稱可以顯示文字 --> {{ name }} 的座號是 {{ number }} <span v-text="name"></span>
v-html
屬性可以將資料以 HTML 輸出html<!-- v-html 放 html 文字的 data 可以顯示 HTML --> <span v-html="html"></span>
- 標籤內判斷式,符合條件才出現html
<!-- 符合條件才出現在 DOM --> <p v-if="number == 1">number 為 1</p> <p v-else-if="number == 2">number 為 2</p> <p v-else>number 為 其他數字</p> <!-- 符合條件才顯示,不符合時是 display:none --> <p v-show="number == 1">number 為 1</p>
- 標籤內迴圈html
<ul> <!-- v-for="(值, 索引) in 陣列" --> <li v-for="(fruit, index) in fruits"> {{ fruit }} </li> </ul>
- 綁定屬性,使用
v-bind:屬性
或是:屬性
html<!-- <img v-bind:src="image" height="100px"> --> <img :src="image" height="100px"> <!-- 綁定 style,裡面要放 json,key 是樣式名,value 是 data --> <img :src="image" :style="{border: '10px solid red'}" height="100px"> <img :src="image" :style="{border: redBorder}" height="100px"> <img :src="image" :style="myborder" height="100px"> <!-- 綁定 class,裡面要放 json,key 是 class 名,value 是 true 或 false --> <p :class="{big: true, red: false}">123</p> <!-- v-model 綁定輸入欄位的 value --> <input type="text" v-model="modeltext"> <!-- checkbox 和 radio 以 v-model 分組, v-model 是使用者選的值,value 是欄位的值--> <input type="checkbox" v-model="checkValue" :value="1"> <input type="checkbox" v-model="checkValue" :value="2"> <input type="radio" v-model="radioValue" :value="1"> <input type="radio" v-model="radioValue" :value="2">
- 事件,使用
v-on:事件
或是@事件
事件有.prevent
.stop
修飾符可以用
鍵盤事件則有按鍵修飾符,如@keydown.enter
和@keydown.13
html<!-- 如果沒有要傳資料,可以只寫 function 名 如果 v-on 呼叫 function 時只寫名字,仍會收到一個 event 物件 --> <input type="button" value="新增" @click="add"> <input type="button" v-model="modeltext" @keydown.enter="add"> <!-- 如果可以帶資料進去時需要 event 事件的話 需要使用 $event 變數 --> <input type="button" keydown.enter="print('hi', $event)">
JS 語法
JS 語法分為 Options API
與 Composition API
Composition API 是 Vue 3 的新語法,將所有元件的邏輯全部放到 setup()
裡面
若要在 Vue 2 使用需要安裝 @vue/composition-api
生命週期
比較需要注意的生命週期有:
created
Vue 元件被建立,無法存取 DOMmounted
Vue 元件被建立,可以存取 DOMunmounted
Vue 元件被銷毀時,如果有 setTimeout、setInterval 時需要在這時 clearTimeout、clearInterval,否則元件被銷毀仍然會執行
Options API
Vue.createApp({
// 元件的資料,JS 內呼叫要加 this
data () {
return {
firstName: '小明',
lastName: '王',
obj: {
name:1,
age:2,
job: {
j1: {
salary: 3
}
}
}
}
},
// computed 是 function 處理後產生的 data,值會在相依變數修改時動態更新
computed: {
fullName () {
return this.lastName + this.firstName
}
},
// 元件會使用到的 function,JS 內呼叫要加 this
methods: {
sayHello () {
console.log('你好,我是' + fullName.value)
},
emitEvent () {
this.$emit('customEvent', 'someValue')
},
print (text, event) {
console.log(text, event)
}
},
// 偵測
watch: {
// 文字、數字、布林的資料型態
firstName (newValue, oldValue) {
console.log(newValue, oldValue)
},
// 物件型態
obj (newValue, oldValue) {
console.log('obj 變更', newValue, oldValue)
},
// 深層監聽物件
obj: {
handler (newValue, oldValue) {
console.log('obj 變更', newValue, oldValue)
},
deep: true
},
// 監聽指定物件屬性
'obj.name' (newValue, oldValue) {
console.log('obj name 變更', newValue, oldValue)
}
},
// 生命週期
mounted () {
console.log('mounted')
}
}).mount('#app')
Composition API
// 用解構方式提取需要的東西
const { ref, reactive, computed, onMounted, watch } = Vue
Vue.createApp({
// setup 可以代入兩個東西
// props 代表傳入元件的 props
// context 就是元件本身
setup(props, context) {
// 只有 HTML 有使用到的變數才需要 ref 與 reactive 綁定
// ref() 可接受所有種類的資料型態,但是不會偵測陣列和物件內部的變化
// 需要取值時寫成 變數名.value
const firstName = ref('小明')
const lastName = ref('王')
// reactive() 只能接受陣列和物件,會偵測陣列和物件內部變化
// 使用時不需要 .value
const obj = reactive({
name:1,
age:2,
job: {
j1: {
salary: 3
}
}
})
// computed
const fullName = computed(() => {
return lastName.value + firstName.value
})
// methods 直接寫一般的 function 就好
const sayHello = () => {
console.log('你好,我是' + fullName.value)
}
// 觸發事件的 $emit 寫法不一樣
const emitEvent = () => {
context.emit('customEvent', 'someValue')
}
// 偵測 ref 變更
watch(firstName, (newValue, oldValue) => {
console.log(newValue, oldValue)
})
// 偵測 reactive 全部屬性變更,無法取得 oldValue
watch(obj, (newValue, oldValue) => {
console.log('obj 變更', newValue, oldValue)
})
// 偵測 reactive 內一個屬性的變更,可以取得 oldValue
watch(() => obj.name, (newValue, oldValue) => {
console.log('obj.name 變更', newValue, oldValue)
})
// 偵測 reactive 內多個屬性的變更,可以取得 oldValue
watch([() => obj.name, () => obj.age], (newValue, oldValue) => {
console.log('obj.name 和 obj.age 變更', newValue, oldValue)
})
// 偵測 reactive 內屬性的變更,若屬性是 Object 的話需要 deep: true
watch(() => obj.job, (newValue, oldValue) => {
console.log('obj.job 變更', newValue, oldValue)
}, { deep: true })
// 生命週期相關的直接寫,不用回傳
onMounted(() => {
console.log('mounted')
})
// 只有 HTML 有使用到的 function 與變數才需要傳出去
return {
firstName,
lastName,
fullName,
sayHello,
emitEvent
}
}
}).mount('#app')
存取 HTML 元素
注意
若要存取的元素上有使用 v-if 判斷,且要在出現時存取
需等下次 Vue 渲染完 DOM,否則可能會抓取不到
const { nextTick } = Vue
await nextTick()
$ref
可以綁定元素,類似 document.getElementById()
需要在 mounted
後使用
<h1 ref="mytext">文字文字</h1>
// Options API
this.$refs.mytext
// Composition API
setup () {
const mytext = ref(null)
console.log(mytext.innerText)
return {
mytext
}
}
練習
製作一個購物清單
- 兩個字以上才能新增
- 新增欄位邊框小於兩個字時是紅色,成功是藍色,空白是黑色
- 已完成打勾
- 可以保存資料到
localStorage
- 個別刪除、全部刪除
- 全部標記已完成、全部未完成
- 請使用 Composition API
作業
製作進階待辦清單,必須要有下列功能:
- 新增功能,兩個字以上才能新增
- 小於兩個字時輸入欄位邊框是紅色,成功時是藍色,空白時是黑色
- 每個項目有 checkbox 可以打勾標記已完成或未完成,完成的項目文字必須要有刪除線
- 可以個別刪除清單項目
- 可以將清單資料保存到
localStorage
- 可以點兩下清單項目開啟編輯欄位
- 在編輯欄位按
ENTER
可以儲存編輯 - 在編輯欄位可以按
ESC
鍵可以取消編輯 - 可以點按鈕過濾顯示全部項目、已完成項目、未完成項目
- 可以顯示目前過濾的方式及過濾後的項目總數
- 可以點按鈕一次刪除全部項目、已完成項目、未完成項目
- 可以點按鈕將所有項目標記為已完成
- 可以點按鈕將所有項目標記為未完成
- 請使用 Composition API
提示:
- 點兩下的事件為
@dblclick
- 按
ESC
鍵的,事件為@keydown.esc
- 過濾可以使用
computed
搭配.filter()
元件
Vue 的開發概念是把網頁轉換成一個一個的元件,拼湊出網頁
建立與使用
<!-- 宣告元件的 HTML 部分 -->
<script type="text/x-template" id="counter">
<button @click="count++">你點擊了 {{ count }} 次</button>
</script>
Vue 2
// Vue 2
Vue.component('counter', {
// 元件模板
template: '#counter',
// 元件資料
data() {
return {
count: 0
}
}
// ...其他
})
new Vue({
//...略
})
Vue 3
const app = Vue.createApp({
// ...略
})
app.component('counter', {
// 元件模板
template: '#counter',
// 元件資料
data() {
return {
count: 0
}
}
// ...其他
})
app.mount('#app')
傳入資料
可以使用 props
將資料傳入子元件
<component v-for="post in posts" :posts="post"></blog-post>
// Options API
props: {
// 傳入的資料名及類型
text: {
type: String,
validator(value) {
return ['success', 'warning', 'danger'].includes(value)
},
default () {
return ''
}
}
}
// Composition API
props: {
text: {
type: String,
required: true,
validator(value) {
return ['success', 'warning', 'danger'].includes(value)
},
default () {
return ''
}
}
},
setup (props) {
// 整個 props 轉成 refs 並解構
const { text } = toRefs(props)
// 或是單獨轉成 ref 使用
const text = toRef(props, 'text')
console.log(text.value)
}
傳出資料
子元件傳出則需要使用 $emit
<!-- 外層 -->
<component @btnClick="handleBtnClick"></component>
<!-- 子元件內 -->
<input type="button" @click="onBtnClick" value="點我">
元件觸發
// Options API
const app = Vue.createApp({
methods: {
// 外部處理,會收到傳出的值
handleBtnClick (value) {
console.log(value) // 'abcd'
}
}
}).component('component', {
methods: {
// 子元件觸發自訂義事件,名稱為 btnClick,將 'abcd' 帶出去
onBtnClick () {
this.$emit('btnClick', 'abcd')
}
}
}).mount('#app')
// Composition API
const app = Vue.createApp({
setup() {
// 外部處理,會收到傳出的值
const handleBtnClick = (value) => {
console.log(value) // 'abcd'
}
return {
handleBtnClick
}
}
}).component('component', {
setup(props, { emit }) {
// 子元件觸發自訂義事件,名稱為 btnClick,將 'abcd' 帶出去
const onBtnClick = () => {
emit('btnClick', 'abcd')
}
return {
onBtnClick
}
}
}).mount('#app')
內外同步
在 prop 前使用 v-model
<my-component v-model="data"></my-component>
// Options API
props: {
data: String
},
computed:{
syncData:{
get(){
return this.data
},
set(value){
this.$emit('update:data',value)
}
}
}
// Composition API
setup (props, { emit }) {
const syncData = computed({
get () {
return props.data
},
set (value) {
emit('update:data', value)
}
})
}
子元件互傳資料
子元件互傳可以建立一個 eventBus
幫忙,節省傳到外面再傳進去的程式碼
官方建議使用 mitt 或 tiny-emitter 等套件
以 mitt 為例
const emitter = mitt()
// Options API
app.component('component1', {
// ...
methods: {
count () {
// 觸發事件
emitter.emit('sayhi', 'hi')
}
}
})
app.component('component2', {
// ...
mounted () {
emitter.on('sayhi', e => {
console.log(e)
})
}
})
// Composition API
app.component('component1', {
setup () {
const count = () => {
emitter.emit('sayhi', 'hi')
}
return {
count
}
}
})
app.component('component2', {
setup () {
onMounted(() => {
emitter.on('sayhi', e => {
console.log(e)
})
})
}
})
provide/inject
上層使用 provide
// Options API
{
data () {
string: 'abcd'
},
provide () {
return {
// 變數名: 值
string: this.string
}
}
}
// Composition API
const { provide, ref } = Vue
setup () {
const string = ref('abcd')
provide('string', string.value)
}
子元件就能使用 inject 取得資料
// Options API
{
// 陣列放變數名
inject: ['string'],
// 若需要預設值,或重新命名變數
inject: {
stringInjected: {
from: 'string',
default: 'abcd'
}
}
created () {
console.log(`data `+ this.stringInjected)
}
}
// Composition API
const { inject, ref, onCreated } = Vue
setup () {
const message = inject('message')
onCreated(() => {
console.log(message)
})
}
插槽
插槽可以在元件內預留一部分的 HTML 給呼叫元件的地方使用
在元件內用 slot
標籤,讓該部分的 HTML 由呼叫元件的地方自訂slot
標籤內的東西會在沒有使用插槽時顯示 ,也可以不放東西
<button>
<slot>送出</slot>
</button>
使用元件時將內容放進元件標籤內即可
<mytemplate>按鈕</mytemplate>
使用多個 slot
時必須要給插槽名字
<button>
<h1>
<slot name="title">標題</slot>
</h1>
<p>
<slot name="description">內文</slot>
</p>
</button>
使用元件時將內容放進 template
標籤內即可
<mytemplate>
<template v-slot:title>
ABCDEFG
</template>
<!-- v-slot: 可以縮寫為 # -->
<template #description>
1234567
</template>
</mytemplate>
若要在插槽內使用元件內的資料,必須將資料綁定到 slot
標籤
<button>
<h1>
<slot name="title" :data="someData" :data2="someData2"></slot>
</h1>
</button>
使用時將資料解構出來
<component>>
<template #title="{data, data2}">
ABCDEFG
</template>
</component>
練習
製作一個卡片收集頁
- 卡片需用子元件製作,包含圖片、文字及按讚、收回讚按鈕
- 需要有 5 張以上排列
- 外層可以統計總共按了幾個讚
- 請使用 Composition API