Vue 基本使用
指令,插值
- 插值馁龟,表達(dá)式
- 指令崩侠,動態(tài)屬性
- v-html:會有 XSS 風(fēng)險,會覆蓋子組件
<template>
<div>
<p>文本插值 {{message}}</p>
<p>JS 表達(dá)式 {{ flag ? 'yes' : 'no' }} (只能是表達(dá)式坷檩,不能是 js 語句)</p>
<p :id="dynamicId">動態(tài)屬性 id</p>
<hr/>
<p v-html="rawHtml">
<span>有 xss 風(fēng)險</span>
<span>【注意】使用 v-html 之后却音,將會覆蓋子元素</span>
</p>
</div>
</template>
<script>
export default {
data() {
return {
message: 'hello vue',
flag: true,
rawHtml: '指令 - 原始 html <b>加粗</b> <i>斜體</i>',
dynamicId: `id-${Date.now()}`
}
}
}
</script>
computed 和 watch
- computed 有緩存改抡,data 不變則不會重新計(jì)算
- computed 可以設(shè)置 set 和 get 方法
- watch 深度監(jiān)聽
- watch 默認(rèn)不是深度監(jiān)聽
- 啟動深度監(jiān)聽的話需要設(shè)置 deep 為 true
- watch 監(jiān)聽引用類型,拿不到 old value
computed demo
<template>
<div>
<p>num {{num}}</p>
<p>double1 {{double1}}</p>
<input v-model="double2"/>
</div>
</template>
<script>
export default {
data() {
return {
num: 20
}
},
computed: {
double1() {
return this.num * 2
},
double2: {
get() {
return this.num * 2
},
set(val) {
this.num = val/2
}
}
}
}
</script>
watch demo
<template>
<div>
<input v-model="name"/>
<input v-model="info.city"/>
</div>
</template>
<script>
export default {
data() {
return {
name: '吉良吉影',
info: {
city: '上海'
}
}
},
watch: {
name(oldVal, val) {
console.log('watch name', oldVal, val) // 值類型系瓢,可正常拿到 oldVal 和 val
},
info: {
handler(oldVal, val) {
console.log('watch info', oldVal, val) // 引用類型阿纤,拿不到 oldVal 。因?yàn)橹羔樝嗤穆藭r已經(jīng)指向了新的 val
},
deep: true // 深度監(jiān)聽
}
}
}
</script>
class 和 style
注意事項(xiàng)
- 使用動態(tài)屬性
- 使用駝峰式寫法
代碼演示
<template>
<div>
<p :class="{ black: isBlack, yellow: isYellow }">使用 class</p>
<p :class="[black, yellow]">使用 class (數(shù)組)</p>
<p :style="styleData">使用 style</p>
</div>
</template>
<script>
export default {
data() {
return {
isBlack: true,
isYellow: true,
black: 'black',
yellow: 'yellow',
styleData: {
fontSize: '40px', // 轉(zhuǎn)換為駝峰式
color: 'red',
backgroundColor: '#ccc' // 轉(zhuǎn)換為駝峰式
}
}
}
}
</script>
<style scoped>
.black {
background-color: #999;
}
.yellow {
color: yellow;
}
</style>
條件渲染
- v-if , v-else 的用法欠拾,可使用變量,也可使用 === 表達(dá)式
- v-if 和 v-show 的區(qū)別骗绕?
- v-if 會直接銷毀和加載 DOM
- v-show 修改 display 屬性為顯示或隱藏
- v-if 和 v-show 的使用場景藐窄?
- 頻繁切換顯示隱藏場景使用 v-show
- 非頻繁切換顯示隱藏場景(如切換注冊/登錄)可使用 v-if
代碼演示
<template>
<div>
<p v-if="type === 'a'">A</p>
<p v-else-if="type === 'b'">B</p>
<p v-else>other</p>
<p v-show="type === 'a'">A by v-show</p>
<p v-show="type === 'b'">B by v-show</p>
</div>
</template>
<script>
export default {
data() {
return {
type: 'a'
}
}
}
</script>
循環(huán)(列表)渲染
注意事項(xiàng)
- v-for 可遍歷對象
- key 不可亂寫(如 random 或者 index)
- v-for 和 v-if 不建議一起使用!
代碼演示
<template>
<div>
<p>遍歷數(shù)組</p>
<ul>
<li v-for="(item, index) in listArr" :key="item.id">
{{index}} - {{item.id}} - {{item.title}}
</li>
</ul>
<p>遍歷對象</p>
<ul >
<li v-for="(val, key, index) in listObj" :key="key">
{{index}} - {{key}} - {{val.title}}
</li>
</ul>
</div>
</template>
<script>
export default {
data() {
return {
flag: false,
listArr: [
{ id: 'a', title: '標(biāo)題1' }, // 數(shù)據(jù)結(jié)構(gòu)中酬土,最好有 id 枷邪,方便使用 key
{ id: 'b', title: '標(biāo)題2' },
{ id: 'c', title: '標(biāo)題3' }
],
listObj: {
a: { title: '標(biāo)題1' },
b: { title: '標(biāo)題2' },
c: { title: '標(biāo)題3' },
}
}
}
}
</script>
事件
- event參數(shù),自定義參數(shù)
- 在不傳自定義參數(shù)的情況下诺凡,event 對象可以直接在函數(shù)形參獲取
- 傳入自定義參數(shù)情況下,需要將 $event 傳入
- 此 event 是原生的 event 對象
- 事件修飾符践惑,按鍵修飾符
-
stop
阻值點(diǎn)擊事件繼續(xù)傳播 -
prevent
提交事件不再重載頁面 -
v-on:click.stop.prevent
修飾符可以串聯(lián)使用
-
- 【觀察】事件被綁定到哪里腹泌?
- Vue 的事件是被掛載到當(dāng)前元素的
代碼演示
<template>
<div>
<p>{{num}}</p>
<button @click="increment1">+1</button>
<button @click="increment2(2, $event)">+2</button>
</div>
</template>
<script>
export default {
data() {
return {
num: 0
}
},
methods: {
increment1(event) {
console.log('event', event, event.__proto__.constructor) // 是原生的 event 對象
console.log(event.target)
console.log(event.currentTarget) // 注意,事件是被注冊到當(dāng)前元素的尔觉,和 React 不一樣
this.num++
// 1. event 是原生的
// 2. 事件被掛載到當(dāng)前元素
// 和 DOM 事件一樣
},
increment2(val, event) {
console.log(event.target)
this.num = this.num + val
},
loadHandler() {
// do some thing
}
},
mounted() {
window.addEventListener('load', this.loadHandler)
},
beforeDestroy() {
//【注意】用 vue 綁定的事件凉袱,組建銷毀時會自動被解綁
// 自己綁定的事件,需要自己銷毀U焱WㄋΑ!
window.removeEventListener('load', this.loadHandler)
}
}
</script>
表單
-
v-model
雙向數(shù)據(jù)綁定 - 常見表單項(xiàng)
textarea
checkbox
radio
select
- 修飾符
lazy
(類似防抖效果)number
(只能輸入數(shù)字)trim
(去除兩邊空格)
代碼演示
<template>
<div>
<p>輸入框: {{name}}</p>
<input type="text" v-model.trim="name"/>
<input type="text" v-model.lazy="name"/>
<input type="text" v-model.number="age"/>
<p>多行文本: {{desc}}</p>
<textarea v-model="desc"></textarea>
<!-- 注意钉稍,<textarea>{{desc}}</textarea> 是不允許的5佣恪!贡未! -->
<p>復(fù)選框 {{checked}}</p>
<input type="checkbox" v-model="checked"/>
<p>多個復(fù)選框 {{checkedNames}}</p>
<input type="checkbox" id="jack" value="Jack" v-model="checkedNames">
<label for="jack">Jack</label>
<input type="checkbox" id="john" value="John" v-model="checkedNames">
<label for="john">John</label>
<input type="checkbox" id="mike" value="Mike" v-model="checkedNames">
<label for="mike">Mike</label>
<p>單選 {{gender}}</p>
<input type="radio" id="male" value="male" v-model="gender"/>
<label for="male">男</label>
<input type="radio" id="female" value="female" v-model="gender"/>
<label for="female">女</label>
<p>下拉列表選擇 {{selected}}</p>
<select v-model="selected">
<option disabled value="">請選擇</option>
<option>A</option>
<option>B</option>
<option>C</option>
</select>
<p>下拉列表選擇(多選) {{selectedList}}</p>
<select v-model="selectedList" multiple>
<option disabled value="">請選擇</option>
<option>A</option>
<option>B</option>
<option>C</option>
</select>
</div>
</template>
<script>
export default {
data() {
return {
name: '吉良吉影',
age: 33,
desc: '自我介紹',
checked: true,
checkedNames: [],
gender: 'male',
selected: '',
selectedList: []
}
}
}
</script>
其他
-
v-once
讓某元素標(biāo)簽只渲染一次 -
ref
寫在標(biāo)簽上可以通過this.$refs
獲取操作 DOM 節(jié)點(diǎn)-
ref
還可以寫在子組件上种樱,從而獲取子組件的引用
-
組件
props 和 $emit
- props 父組件向子組件傳遞信息
- $emit 子組件向父組件觸發(fā)一個事件
- 實(shí)現(xiàn)父子組件之間通訊
父組件
<template>
<div>
<Input @add="addHandler"/>
<List :list="list" @delete="deleteHandler"/>
</div>
</template>
<script>
import Input from './Input'
import List from './List'
export default {
components: {
Input,
List
},
data() {
return {
list: [
{
id: 'id-1',
title: '標(biāo)題1'
},
{
id: 'id-2',
title: '標(biāo)題2'
}
]
}
},
methods: {
addHandler(title) {
this.list.push({
id: `id-${Date.now()}`,
title
})
},
deleteHandler(id) {
this.list = this.list.filter(item => item.id !== id)
}
},
created() {
console.log('index created')
},
mounted() {
console.log('index mounted')
},
beforeUpdate() {
console.log('index before update')
},
updated() {
console.log('index updated')
},
}
</script>
Input 組件
<template>
<div>
<input type="text" v-model="title"/>
<button @click="addTitle">add</button>
</div>
</template>
<script>
import event from './event'
export default {
data() {
return {
title: ''
}
},
methods: {
addTitle() {
// 調(diào)用父組件的事件
this.$emit('add', this.title)
// 調(diào)用自定義事件
event.$emit('onAddTitle', this.title)
this.title = ''
}
}
}
</script>
List 組件
<template>
<div>
<ul>
<li v-for="item in list" :key="item.id">
{{item.title}}
<button @click="deleteItem(item.id)">刪除</button>
</li>
</ul>
</div>
</template>
<script>
import event from './event'
export default {
// props: ['list']
props: {
// prop 類型和默認(rèn)值
list: {
type: Array,
default() {
return []
}
}
},
data() {
return {
}
},
methods: {
deleteItem(id) {
this.$emit('delete', id)
},
addTitleHandler(title) {
console.log('on add title', title)
}
},
created() {
console.log('list created')
},
mounted() {
console.log('list mounted')
// 綁定自定義事件
event.$on('onAddTitle', this.addTitleHandler)
},
beforeUpdate() {
console.log('list before update')
},
updated() {
console.log('list updated')
},
beforeDestroy() {
// 及時銷毀,否則可能造成內(nèi)存泄露
event.$off('onAddTitle', this.addTitleHandler)
}
}
</script>
自定義事件
- 創(chuàng)建一個 event.js (如下圖)
- 通過在 event 上綁定自定義事件俊卤,調(diào)用自定義事件實(shí)現(xiàn)兄弟組件之間通訊(上圖代碼所示)
// event.js
import Vue from 'vue'
export default new Vue()
生命周期
單個組件
帶有父子組件的生命周期
- 初始化父子組件執(zhí)行順序
先觸發(fā)父元素嫩挤,然后子元素先進(jìn)行,父元素收尾消恍。
父beforeCreate => 父created => 父beforeMount => 子beforeCreate => 子created => 子beforeMount
=> 子mounted => 父mounted
先父組件創(chuàng)建虛擬dom岂昭,再子組件創(chuàng)建虛擬dom 。初始化先保證父組件初始化完再初始化子組件狠怨。(外 => 內(nèi))
然后子組件渲染约啊,再父組件渲染 邑遏。渲染先保證子組件渲染完再渲染父組件 (內(nèi) => 外)
最先開始創(chuàng)建的是最外層組件,但是最先創(chuàng)建完的是最內(nèi)層組件棍苹。 => 類似先進(jìn)后出
(棧)无宿。
- 刪除子組件觸發(fā)生命周期順序
先觸發(fā)父元素,然后子元素先進(jìn)行枢里,父元素收尾孽鸡。
父beforeUpdate => 子beforeDestroy => 子destroyed => 父updated
- 更新子組件觸發(fā)生命周期順序
先觸發(fā)父元素,然后子元素先進(jìn)行栏豺,父元素收尾彬碱。
父beforeUpdate => 子beforeUpdate => 子updated => 父updated
首先是修改父組件的data,所以觸發(fā)父組件的beforeUpdate奥洼,它要把更新后數(shù)據(jù)再傳遞給給子組件巷疼,子組件更新props,因此再觸發(fā)子組件的beforeUpdate灵奖,緊接著渲染頁面觸發(fā)子組件的updated嚼沿,只有子組件渲染完頁面后,這個時候父組件也就渲染完瓷患,觸發(fā)父組件的updated骡尽。
Non-Props
- 父組件向子組件傳值,子組件沒有通過 props 接收擅编,那么子組件會把該屬性變成子組件最外層的 DOM 上的一個屬性
- 如果不希望發(fā)生此特性那么可以在子組件上加 一條屬性:
inheritAttrs: false
- 子組件可以通過
this.$attrs
手動獲取 non-props 屬性
插槽
-
slot 中使用的作用域的問題
- 父模板里調(diào)用的數(shù)據(jù)屬性攀细,使用的都是父模板里的數(shù)據(jù)
- 子模板里調(diào)用的數(shù)據(jù)屬性,使用的都是子模板里的數(shù)據(jù)
-
具名插槽
- 父組件傳遞時加上屬性
v-slot="..."
可簡寫為#...
- 子組件
<slot name="..."></slot>
- 父組件傳遞時加上屬性
-
作用域插槽
代碼演示:
const app = Vue.createApp({ template: ` <List v-slot="slotProps"> // 可簡寫為 ES6 解構(gòu)形式 v-slot="{ item }", 則下面直接用 item 即可 <span>{{ slotProps.item }}</span> // 2. slotProps.item 就是子組件傳入的數(shù)據(jù) </List> ` }) app.component('List', { data() { return { list: [1, 2, 3] } }, template: ` <div> <slot v-for="item in list" :item=item /> // 1. 將item傳給父組件 </div> ` })
分析上面代碼:
父組件調(diào)子組件的時候傳入 slot 進(jìn)來
子組件通過 :item=item 把數(shù)據(jù)傳給父組件
-
作用域插槽解決了什么問題爱态?
- 當(dāng)子組件渲染的內(nèi)容要由父組件決定的情況
- 通過作用域插槽谭贪,可以讓使父組件調(diào)用子組件里的數(shù)據(jù)