Vue.js
核心是一個(gè)允許采用簡潔的模板語法來聲明式地將數(shù)據(jù)渲染進(jìn)DOM的系統(tǒng)
組件本質(zhì)上是一個(gè)具有預(yù)定義選項(xiàng)的實(shí)例
const app = vue.createApp({ ... }) // 在應(yīng)用中創(chuàng)建“全局”組件
// 創(chuàng)建 Vue 應(yīng)用實(shí)例
const app = Vue.createApp(...)
// 定義名為 todo-item 的新組件
app.component('todo-item', {
template: `<li>This is a todo</li>`
})
// 掛載 Vue 應(yīng)用
app.mount(...)
Demo
const ComponentsApp = {
data() {
return {
groceryList: [
{ id: 0, text: 'Vegetables' },
{ id: 1, text: 'Cheese' },
{ id: 2, text: 'Whatever else humans are supposed to eat' }
]
}
}
}
// 允許鏈?zhǔn)綄懛?// Vue.createApp({})
// .component('SearchInput', SearchInputComponent)
// .directive('focus', FocusDirective)
// .use(LocalePlugin)
const app = Vue.createApp(ComponentsApp)
app.component('todo-item', {
props: ['todo'],
template: `<li>{{ todo.text }}</li>`
})
app.mount('#components-app')
<div id="todo-list-app">
<ol>
<!--
現(xiàn)在我們?yōu)槊總€(gè) todo-item 提供 todo 對(duì)象
todo 對(duì)象是變量,即其內(nèi)容可以是動(dòng)態(tài)的。
我們也需要為每個(gè)組件提供一個(gè)“key”,稍后再
作詳細(xì)解釋。
-->
<todo-item
v-for="item in groceryList"
v-bind:todo="item"
v-bind:key="item.id"
></todo-item>
</ol>
</div>
一、根組件
- 傳遞給
createApp的選項(xiàng)用于配置根組件。當(dāng)我們掛載應(yīng)用時(shí),該組件被用作渲染的起點(diǎn) *** 根組件與其他組件沒有什么不同,配置選項(xiàng)是一樣的,所對(duì)應(yīng)的組件實(shí)例 行為也是一樣的*** mount 返回的是根組件實(shí)例,不是應(yīng)用本身
Vue 應(yīng)用掛載到 <div id="app"></div>
const RootComponent = {
/* 選項(xiàng) */
}
const app = Vue.createApp(RootComponent)
const vm = app.mount('#app')
二、生命周期
- 生命周期函數(shù)、選項(xiàng)property(元素屬性)或回調(diào)上
不可使用箭頭函數(shù)。生命周期鉤子this上下文指向調(diào)用它的當(dāng)前活動(dòng)實(shí)例。 -
箭頭函數(shù)并沒有this,this作為變量一直向上級(jí)詞法作用域查找。直至找到為止,經(jīng)常導(dǎo)致Uncaught TypeError: Cannot read property of undefined 或 Uncaught TypeError: this.myMethod is not a function 之類的錯(cuò)誤。
vue.createApp({
data() {
return { count: 1 }
},
created() {
console.log('count is' + this.count) // => count is 1
}
})

三、模板語法
在底層的實(shí)現(xiàn)上,Vue將模板編譯成虛擬DOM渲染函數(shù)。結(jié)合響應(yīng)性系統(tǒng),Vue能夠智能地計(jì)算出最少需要重新渲染多少組件,并把DOM操作次數(shù)減到最少。
*** v-html不能復(fù)合局部模板,因?yàn)閂ue不是基于字符串的模板引擎,反之,對(duì)于用戶界面(UI),組件更適合作為可重用和可組合的基本單位
*** 動(dòng)態(tài)渲染任意的HTML是非常危險(xiǎn)的,因?yàn)楹苋菀讓?dǎo)致XSS攻擊
1. 文本插值使用雙大括號(hào){{ 變量 }}
2. 動(dòng)態(tài)渲染HTML使用v-html
const RenderHtmlApp = {
data() {
return {
rawHtml: '<span style="color: red">這幾個(gè)文字是紅色</span>'
}
}
}
Vue.createApp(RenderHtmlApp).mount('#example')
<div id="example">
<p> v-html解析<span v-html="rawHtml"></span> </p>
</div>
3. 插值雙大括號(hào){{ 單個(gè)表達(dá)式 }}可以使用js表達(dá)式
{{ n + 1 }}
{{ ok ? 'Yes' : 'No' }}
{{ arrayData.split(',') }}
...
{{ var a = 1 }} // 不會(huì)生效, 這是語句,不是表達(dá)式
{{ if( ok ) { return message } }} // 流控制也不會(huì)生效
4. v- 指令職責(zé)是:當(dāng)表達(dá)式的值改變時(shí),將其產(chǎn)生的連帶影響,響應(yīng)式作用于DOM
5. 參數(shù)
<a v-on:click="doSomthing"> ... </a>
6. 動(dòng)態(tài)參數(shù)
*** 動(dòng)態(tài)參數(shù)預(yù)期是求出一個(gè)字符串,異常情況下為null。這個(gè)null會(huì)被顯性地用于移除綁定
// 若attrname的值為click
<a v-on:[attrname]="doSomthing">...</a>
等價(jià)于
<a v-on:click="doSomthing">...</a>
*** 在 DOM 中使用模板時(shí) (直接在一個(gè) HTML 文件里撰寫模板),還需要避免使用大寫字符來命名鍵名,因?yàn)闉g覽器會(huì)把 attribute 名全部強(qiáng)制轉(zhuǎn)為小寫:
<!--
在 DOM 中使用模板時(shí)這段代碼會(huì)被轉(zhuǎn)換為 `v-bind:[someattr]`。
除非在實(shí)例中有一個(gè)名為“someattr”的 property,否則代碼不會(huì)工作。
-->
<a v-bind:[someAttr]="value"> ... </a>
7. 縮寫
v-on
// 完整語法
<a v-on:click="doSomthing">...</a>
// 縮寫
<a @click="doSomthing">...</a>
// 動(dòng)態(tài)參數(shù)縮寫
<a @[attrname]="doSomthing">...</a>
v-bind
// 完整語法
<a v-bind:href="url">...</a>
// 縮寫
<a :href="url">...</a>
// 動(dòng)態(tài)參數(shù)縮寫
<a :[key]="url">...</a>
四、Data Property
vue自動(dòng)為methods綁定this,以便它始終指向組件實(shí)例。這將確保方法再用作事件監(jiān)聽或回調(diào)時(shí)保持正確的this。定義methods時(shí)應(yīng)避免使用箭頭函數(shù)
1. vue沒有內(nèi)置的防抖、節(jié)流,可以引用lodash
為了使組件實(shí)例彼此獨(dú)立,可以在生命周期鉤子created里添加防抖函數(shù)
app.component( 'save-button', {
created() {
// 使用lodash 防抖函數(shù)
this.debouncedClick = _.debounce(this.click, 500)
},
unmounted() {
// 移除組件時(shí),取消定時(shí)器
this.debouncedClick.cancle()
},
methods: {
click() {
// ... 響應(yīng)事件...
}
},
template: `
<button @click='debouncedClick'> Save </button>
`
})
五、計(jì)算屬性、偵聽器
1.computed 計(jì)算屬性
computed:{ ... }計(jì)算屬性是基于它們的響應(yīng)依賴關(guān)系緩存的。
以下:如果data的值不發(fā)生改變,即使多次訪問computFun計(jì)算屬性會(huì)立即返回之前的計(jì)算結(jié)果,而不必再次執(zhí)行函數(shù)。如果不希望緩存,可用methods來代替
computed: {
computFun() {
return this.data == 1 ? '是' :'否'
}
}
以下計(jì)算屬性將不再更新,因?yàn)镈ate.now()不是響應(yīng)式依賴
computed: {
now() {
return Date.now()
}
}
2.watch 偵聽器
當(dāng)需要在數(shù)據(jù)變化時(shí)執(zhí)行異步 或 開銷較大的操作時(shí),watch方式最有用
六、條件渲染
v-if、v-else-if、v-else、v-show
1.v-if、v-else-if、v-else
v-if和v-for不推薦同時(shí)使用,如果一起使用了v-if的優(yōu)先級(jí)更高
<template>元素當(dāng)做不可見的包裹元素,在上面使用v-if。最終的渲染結(jié)果不包括<template>元素
<template v-if="ok">
<p>渲染后template標(biāo)簽不存在</p>
</template>
// ok為true, 渲染結(jié)果為
<p>渲染后template標(biāo)簽不存在</p>
2.v-show
v-show的元素始終會(huì)被渲染并保留在DOM中,v-show只是簡單地切換元素的CSS property display
**** v-show不支持<template>元素,也不支持v-else
3.v-if VS v-show
-
v-if是真正的條件渲染,因?yàn)樗鼤?huì)確保再切換過程中,條件塊內(nèi)的事件監(jiān)聽器和子組件適當(dāng)?shù)乇?code>銷毀和重建。 -
v-if也是惰性的:如果在初始渲染時(shí)條件為假,則什么也不做——直到條件第一次變?yōu)檎鏁r(shí),才會(huì)開始渲染條件塊。 -
v-show不管初始條件是什么,元素總是會(huì)被渲染,并且只是簡單地基于CSS進(jìn)行切換(樣式display控制的顯示與隱藏) **** 一般而言,v-if具有更高的切換開銷,而v-show有更高的初始渲染開銷。因此,如果需要非常頻繁地切換,則使用v-show較好;如果在運(yùn)行時(shí)條件很少改變,則使用v-if較好
七、列表渲染
用v-for把一個(gè)數(shù)組對(duì)應(yīng)為一組元素
1.數(shù)組
item為迭代數(shù)組元素別名。index為索引。key作為Vue識(shí)別節(jié)點(diǎn)的一個(gè)通用機(jī)制,key需使用字符串或數(shù)值類型的值- v-for可以在template元素中使用
當(dāng)Vue正在更新使用v-for渲染的元素列表時(shí),它默認(rèn)使用“就地更新”的策略。如果數(shù)據(jù)項(xiàng)的順序被改變,Vue將不會(huì)移動(dòng)DOM元素來匹配數(shù)據(jù)項(xiàng)的順序,而是就地更新每個(gè)元素,并且確保它們?cè)诿總€(gè)索引位置正確渲染
v-for與v-if同時(shí)使用時(shí)建議使用以下寫法:
<template v-for="(item, index) in items" :key="item.id">
<li v-if="!item.isShow">{{item.name}}</li>
<li>{{item.name}}</li>
</template>
2.對(duì)象
value為value。name為key。index為索引- 會(huì)按照
object.keys()結(jié)果遍歷對(duì)象,但是不能保證它在不同JavaScript引擎下的結(jié)果一致
<li v-for="(value, name, index) in myObject">
{{ index }}. {{ name }}: {{ value }}
</li>
3.數(shù)組更新
變更方法:變更調(diào)用了這些方法的原始數(shù)組
替換數(shù)組:即非變更方法,不會(huì)變更原始數(shù)組,而總是返回一個(gè)新數(shù)組
由于 JavaScript 的限制,Vue 不能檢測數(shù)組和對(duì)象的變化。當(dāng)你直接修改了對(duì)象屬性的值,會(huì)發(fā)現(xiàn)只有數(shù)據(jù)改了,頁面內(nèi)容沒有更新。Vue將被偵聽數(shù)組的變更方法進(jìn)行了包裹,所以它們也將會(huì)觸發(fā)視圖更新。
- 變更方法:
push()、pop()、shift()、unshift()、splice()、sort()、reverse() - 替換數(shù)組方法:
filter()、concat() 、slice()
八、事件處理
1. @click監(jiān)聽點(diǎn)擊事件
<button @click="btnFun('click me', $event)">點(diǎn)擊事件</button>
...
methods: {
btnFun(msg, event) {
// doSomthing
}
}
2. @click多事件處理器
<button @click="one($event), two($event)">處理多事件按鈕</button>
...
methods: {
one(event) {
// 第一個(gè)事件處理邏輯
},
two(event) {
// 第二個(gè)事件處理邏輯
}
}
3. 事件修飾符
.stop、.prevent、.capture、.self、.once、.passive
使用修飾符時(shí),順序很重要;相應(yīng)代碼會(huì)以同樣的順序產(chǎn)生
<!-- 阻止單擊事件繼續(xù)傳播 -->
<a @click.stop="doThis"></a>
<!-- 提交事件不再重載頁面 -->
<form @submit.prevent="onSubmit"></form>
<!-- 修飾符可以串聯(lián) -->
<a @click.stop.prevent="doThat"></a>
<!-- 只有修飾符 -->
<form @submit.prevent></form>
<!-- 添加事件監(jiān)聽器時(shí)使用事件捕獲模式 -->
<!-- 即內(nèi)部元素觸發(fā)的事件先在此處理,然后才交由內(nèi)部元素進(jìn)行處理 -->
<div @click.capture="doThis">...</div>
<!-- 只當(dāng)在 event.target 是當(dāng)前元素自身時(shí)觸發(fā)處理函數(shù) -->
<!-- 即事件不是從內(nèi)部元素觸發(fā)的 -->
<div @click.self="doThat">...</div>
<!-- 點(diǎn)擊事件將只會(huì)觸發(fā)一次 -->
<a @click.once="doThis"></a>
<!-- 滾動(dòng)事件的默認(rèn)行為 (即滾動(dòng)行為) 將會(huì)立即觸發(fā) -->
<!-- 而不會(huì)等待 `onScroll` 完成 -->
<!-- 這其中包含 `event.preventDefault()` 的情況 -->
<div @scroll.passive="onScroll">...</div>
4. 其它修飾符
- 按鍵修飾符:
.enter、.tab、.delete、.esc、.space、.up、.down、.left、.right
直接將 KeyboardEvent.key 暴露的任意有效按鍵名轉(zhuǎn)換為 kebab-case 來作為修飾符
<!-- 只有在 `key` 是 `Enter` 時(shí)調(diào)用 `vm.submit()` -->
<input @keyup.enter="submit" />
<input @keyup.page-down="onPageDown" />
- 系統(tǒng)修飾符:
.ctrl、.alt、.shift、.meta(在 Mac 系統(tǒng)鍵盤上,meta 對(duì)應(yīng) command 鍵 (?)。在 Windows 系統(tǒng)鍵盤 meta 對(duì)應(yīng) Windows 徽標(biāo)鍵 (?))
<!-- Alt + Enter -->
<input @keyup.alt.enter="clear" />
<!-- Ctrl + Click -->
<div @click.ctrl="doSomething">Do something</div>
- .exact 修飾符:
.exact 修飾符允許你控制由精確的系統(tǒng)修飾符組合觸發(fā)的事件
<!-- 即使 Alt 或 Shift 被一同按下時(shí)也會(huì)觸發(fā) -->
<button @click.ctrl="onClick">A</button>
<!-- 有且只有 Ctrl 被按下的時(shí)候才觸發(fā) -->
<button @click.ctrl.exact="onCtrlClick">A</button>
<!-- 沒有任何系統(tǒng)修飾符被按下的時(shí)候才觸發(fā) -->
<button @click.exact="onClick">A</button>
- 鼠標(biāo)按鈕修飾符:
.left、.right、.middle
九、表單輸入綁定
-
v-model語法糖:可以在表單<input>、<textarea>、<select>等元素上創(chuàng)建雙向數(shù)據(jù)綁定。它負(fù)責(zé)監(jiān)聽用戶的輸入事件來更新數(shù)據(jù),并在某種極端場景下進(jìn)行一些特殊處理 -
v-model會(huì)忽略所有表單元素的value、checked、selectedattribute的初始值而總是將當(dāng)前活動(dòng)實(shí)例的數(shù)據(jù)作為數(shù)據(jù)來源,應(yīng)該通過JavaScript在組件的data選項(xiàng)中聲明初始值
1. 值綁定
<!-- 當(dāng)選中時(shí),`picked` 為字符串 "a" -->
<input type="radio" v-model="picked" value="a" />
<!-- `toggle` 為 true 或 false -->
<input type="checkbox" v-model="toggle" />
<!-- 當(dāng)選中第一個(gè)選項(xiàng)時(shí),`selected` 為字符串 "abc" -->
<select v-model="selected">
<option value="abc">ABC</option>
</select>
<!-- 當(dāng)選中時(shí)vm.pick === vm.a-->
<input type="radio" v-model="pick" v-bind:value="a" />
2. 修飾符
-
.lazy:在默認(rèn)情況下,v-model在每次input事件觸發(fā)后將輸入框的值與數(shù)據(jù)進(jìn)行同步。添加.lazy修飾符,可以轉(zhuǎn)為change事件之后進(jìn)行同步
<!-- 在“change”時(shí)而非“input”時(shí)更新 -->
<input v-model.lazy="msg" />
-
.number:自動(dòng)將用戶的輸入值轉(zhuǎn)為數(shù)值類型
<input v-model.number="age" type="number" />
-
.trim: 自動(dòng)過濾用戶輸入的首位空白字符
<input v-model.trim="msg" />
十、組件基礎(chǔ)
- 父組件通過
props向子組件傳遞數(shù)據(jù) - 子組件通過調(diào)用內(nèi)建的
$emit()并傳入事件名稱來觸發(fā)一個(gè)事件
<button @click="$emit('enlargeText', 0.1)">
Enlarge text
</button>
...
methods: {
onEnlargeText(enlargeAmount) {
this.postFontSize += enlargeAmount
}
}
1. 動(dòng)態(tài)組件
- is:不同組件之間進(jìn)行動(dòng)態(tài)切換
以下currentTabComponent可以包括:已注冊(cè)的名字 或 一個(gè)組件的選項(xiàng)對(duì)象
<!-- 組件會(huì)在 `currentTabComponent` 改變時(shí)改變 -->
<component :is="currentTabComponent"></component>
- 有些HTML元素,諸如<table>內(nèi)只能出現(xiàn)<tr>元素,可以使用is變通
// blog-post-row為 自定義組件
<table>
<tr is="vue:blog-post-row"></tr>
</table>
2. 屬性名稱 大小寫
HTML attribute 名不區(qū)分大小寫,因此瀏覽器將所有大寫字符解釋為小寫。這意味著當(dāng)你在 DOM 模板中使用時(shí),駝峰 prop 名稱和 event 處理器參數(shù)需要使用它們的 kebab-cased (橫線字符分隔) 等效
app.component('blog-post', {
props: ['postTitle'],
template: `
<h3>{{ postTitle }}</h3>
`
})
...
<blog-post post-title="hello!"></blog-post>
十一、組件注冊(cè)
1.全局注冊(cè): 在注冊(cè)以后可以用在任何新創(chuàng)建的組件實(shí)例模板中
app.component的第一個(gè)參數(shù)是組件名
const app = Vue.createApp({ ... })
app.component('my-component-name', {
...
})
...
<my-component-name></my-component-name>
2.局部注冊(cè): 在components選項(xiàng)中定義組件以后才可使用
局部注冊(cè)的組件在其子組件中不可使用
在 ComponentB 中可用ComponentA。舉例:
const ComponentA = {
/* ... */
}
const ComponentB = {
components: {
'component-a': ComponentA
}
// ...
}
或
import ComponentA from './ComponentA'
import ComponentC from './ComponentC'
export default {
components: {
ComponentA,
ComponentC
}
// ...
}
十二、props:父組件向子組件傳值
props:單向下綁定的數(shù)據(jù)流向。數(shù)據(jù)只能從父組件流向子組件,在子組件中不可修改prop
app.component('my-component', {
props: {
// 基礎(chǔ)的類型檢查 (`null` 和 `undefined` 會(huì)通過任何類型驗(yàn)證)
propA: Number,
// 多個(gè)可能的類型
propB: [String, Number],
// 必填的字符串
propC: {
type: String,
required: true
},
// 帶有默認(rèn)值的數(shù)字
propD: {
type: Number,
default: 100
},
// 帶有默認(rèn)值的對(duì)象
propE: {
type: Object,
// 對(duì)象或數(shù)組默認(rèn)值必須從一個(gè)工廠函數(shù)獲取
default() {
return { message: 'hello' }
}
},
// 自定義驗(yàn)證函數(shù)
propF: {
validator(value) {
// 這個(gè)值必須匹配下列字符串中的一個(gè)
return ['success', 'warning', 'danger'].includes(value)
}
},
// 具有默認(rèn)值的函數(shù)
propG: {
type: Function,
// 與對(duì)象或數(shù)組默認(rèn)值不同,這不是一個(gè)工廠函數(shù) —— 這是一個(gè)用作默認(rèn)值的函數(shù)
default() {
return 'Default function'
}
}
}
})
- type 還可以是一個(gè)自定義的構(gòu)造函數(shù)
用于驗(yàn)證author prop的值是否通過new Person創(chuàng)建的。舉例:
function Person(firstName, lastName) {
this.firstName = firstName
this.lastName = lastName
}
...可以使用
app.component('blog-post', {
props: {
author: Person
}
})
- prop的大小寫命名
HTML中的attribute名是大小寫不敏感的,所以瀏覽器會(huì)把所有的大寫字符解釋為小寫字符。這意味著當(dāng)你使用DOM中的模板時(shí),駝峰命名法的prop名需要使其等價(jià)的短橫線分割命名:
const app = Vue.createApp({})
app.component('blog-post', {
// camelCase in JavaScript
props: ['postTitle'],
template: '<h3>{{ postTitle }}</h3>'
})
...
<!-- kebab-case in HTML -->
<blog-post post-title="hello!"></blog-post>