查漏补缺方为上策!!两万六字总结vue的基本使用和高级特性,周边插件vuex和vue-router任你挑选
vue的基本使用和高級(jí)特性,周邊插件vuex和vue-router
- 一、vue的使用
- 1、vue-cli
- 2、基本使用
- (1)模板(插值,指令)
- (2)computed和watch
- (3)class和style
- (4)條件
- (5)循環(huán)(列表)渲染
- (6)事件
- (7)表單
- 3、組件
- (1)props和$emit(適合父子組件間的通信)
- (2)組件間通信 - 自定義事件
- (3)組件生命周期
- 二、vue的高級(jí)特性
- 1、自定義 v-model
- 2、$nextTick
- (1)知識(shí)點(diǎn)
- (2)例子展示🌰
- 3、slot插槽
- (1)插槽的作用
- (2)三種插槽類型
- 1)普通slot插槽
- 2)作用域插槽slot-scope
- 3)具名插槽
- 4、動(dòng)態(tài)、異步組件
- (1)動(dòng)態(tài)組件
- 1)舉個(gè)例子🌰
- 2)動(dòng)態(tài)組件的用法和應(yīng)用場(chǎng)景
- 3)演示
- (2)異步組件
- 5、keep-alive
- (1)定義和應(yīng)用場(chǎng)景
- (2)舉例🌰
- 6、mixin
- (1)mixin是什么
- (2)mixin的問(wèn)題
- (3)舉個(gè)栗子🌰
- 三、vue的周邊插件:vuex和vue-router
- 1、vuex
- (1)vuex基本概念
- (2)用于vue組件中的API
- (3)圖例
- 2、vue-router
- (1)路由模式
- (2)路由配置
- 四、結(jié)束語(yǔ)
以下文章將講解對(duì) vue 的基本使用以及各種高級(jí)特性還有周邊插件 vuex 和 vue-router ,融合大量案例🌰和動(dòng)圖🕹?進(jìn)行展示??梢园阉?dāng)成是 vue 的入門寶庫(kù),有不懂的語(yǔ)法知識(shí)點(diǎn)時(shí)或許在這里可以尋找到你的答案并且通過(guò)例子運(yùn)用起來(lái)。
廢話不多說(shuō),下面來(lái)開(kāi)始探索 vue 的奧秘吧🙆
一、vue的使用
1、vue-cli
vue 項(xiàng)目是基于 vue-cli 腳手架搭建的項(xiàng)目。當(dāng)我們要?jiǎng)?chuàng)建一個(gè)項(xiàng)目時(shí),首先要先全局安裝 vue-cli 腳手架,命令行為:
npm i -g @vue/cli在搭建完成項(xiàng)目以后,我們需要來(lái)了解 src 目錄下各個(gè)文件夾和文件的用法。
├── assets 放置靜態(tài)資源 ├── components 放組件 ├── router 定義路由的相關(guān)配置 ├── views 視圖 ├── views 視圖 ├── app.vue 應(yīng)用主組件 ├── main.js 入口文件2、基本使用
(1)模板(插值,指令)
1)插值、表達(dá)式
<template><div><p>文本插值 {{message}}</p><p>JS 表達(dá)式 {{ flag ? 'yes' : 'no' }} (只能是表達(dá)式,不能是 js 語(yǔ)句)</p></div> </template><script> export default {data() {return {message: 'I am Monday.',flag: true}} } </script> //瀏覽器顯示結(jié)果 // 文本插值 I am Monday. // JS 表達(dá)式 yes2)動(dòng)態(tài)屬性
<template><div><p :id="dynamicId">動(dòng)態(tài)屬性:{{dynamicId}}</p></div> </template><script> export default {data() {return {dynamicId: `id-${Date.now()}`}} } </script>//瀏覽器顯示結(jié)果 //動(dòng)態(tài)屬性:id-16223395768753)v-html指令
<template><div><p v-html="rawHtml"><span>有 xss 風(fēng)險(xiǎn)</span><span>【注意】使用 v-html 之后,將會(huì)覆蓋子元素</span></p></div> </template><script> export default {data() {return {rawHtml: '指令 - 原始 html <b>加粗</b> <i>斜體</i>',}} } </script>//瀏覽器顯示結(jié)果 //指令 - 原始 html 加粗 斜體值得注意的是, v-html 指令會(huì)有 xss 風(fēng)險(xiǎn),且會(huì)覆蓋子組件,要謹(jǐn)慎使用!!
(2)computed和watch
1)computed
computed 有緩存, data 不變則不會(huì)重新計(jì)算。如下代碼所示:
<template><div><p>num {{num}}</p><p>double1 {{double1}}</p>//用v-model一定要有g(shù)et和set,否則會(huì)報(bào)錯(cuò)<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},// 設(shè)置值set(val) {this.num = val/2}}} } </script>此時(shí)瀏覽器的打印結(jié)果如圖所示。
大家可以看到,當(dāng) num 的值如果一直是 20 的值時(shí),那么 double1 和 double2 的 get() 方法是不會(huì)重新計(jì)算的,它會(huì)被緩存下來(lái),達(dá)到提高運(yùn)算性能的效果。
2)watch
- watch 監(jiān)聽(tīng)基本數(shù)據(jù)類型時(shí),可正常拿到 oldVal 和 val 的值。
- watch 如果監(jiān)聽(tīng)引用數(shù)據(jù)類型時(shí),需要進(jìn)行深度監(jiān)聽(tīng),且拿不到 oldVal 。因?yàn)橹羔樝嗤?#xff0c;監(jiān)聽(tīng)時(shí),指針已經(jīng)指向了新的 val 。
如下代碼所示:
<template><div><input v-model="name"/><input v-model="info.city"/></div> </template><script> export default {data() {return {name: 'Monday',info: {city: 'FuZhou'}}},watch: {name(oldVal, val) {// eslint-disable-next-lineconsole.log('watch name', oldVal, val) // 值類型,即基本數(shù)據(jù)類型,可正常拿到 oldVal 和 val},info: {handler(oldVal, val) {// eslint-disable-next-lineconsole.log('watch info', oldVal, val) // 引用數(shù)據(jù)類型,拿不到 oldVal 。因?yàn)橹羔樝嗤?#xff0c;此時(shí)已經(jīng)指向了新的 val},deep: true // 深度監(jiān)聽(tīng)}} } </script>(3)class和style
-
使用動(dòng)態(tài)屬性,即 v-bind 綁定;
-
使用駝峰式寫法。
如下代碼所示:
<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>此時(shí)瀏覽器的顯示效果如下。
(4)條件
- v-if 、 v-else 的用法:可使用變量,也可以使用 === 表達(dá)式。
- v-if 和 v-show 的區(qū)別?
- v-if 會(huì)根據(jù)條件對(duì)元素進(jìn)行渲染的控制,此處的控制渲染指的是將元素添加到 dom 中或移除 dom,所以會(huì)存在dom的增刪。假設(shè)頁(yè)面初始化時(shí),條件判斷結(jié)果為 false ,則不會(huì)將該元素添加到 dom 當(dāng)中。
- v-show 與 v-if 不一樣的是,不管條件是 true 還是 false ,都會(huì)將對(duì)應(yīng)的元素添加到 dom 中,在條件為 false 時(shí)將元素的 css 屬性display設(shè)置為none,所以我們只是肉眼看到元素從頁(yè)面中消失了,但是它還存在于 dom 當(dāng)中。
- v-if 和 v-show 的使用場(chǎng)景?
- v-if 適用于條件變化頻率不高的時(shí)候,這樣不會(huì)頻繁的去對(duì) DOM 進(jìn)行增刪操作;
- v-show 適用于需要做頻繁切換的時(shí)候,比如說(shuō)A和B兩個(gè)元素,需要一會(huì)顯示 A ,一會(huì)顯示 B ,這樣就算比較頻繁。如果去操作 v-if ,讓 DOM 瘋狂的增添和銷毀是會(huì)非常耗費(fèi)性能的,所以這個(gè)時(shí)候就應(yīng)該用 v-show , display 屬性原本就已經(jīng)存放在 DOM 當(dāng)中,只是對(duì) display 屬性進(jìn)行修改修做即可。
具體使用方式如下代碼所示:
<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>(5)循環(huán)(列表)渲染
-
vue 中如何遍歷對(duì)象? —— 使用 v-for 。
-
v-for 和 v-if 不能一起使用。
-
key 的重要性。在對(duì)數(shù)據(jù)進(jìn)行 v-for 遍歷時(shí),需要加上 key 值來(lái)保證數(shù)據(jù)的唯一性。同時(shí), key 不能亂寫,一般不要使用 random 或者 index ,而是綁定一個(gè)與該綁定數(shù)據(jù)相關(guān)的唯一值來(lái)確定。如果用 random 或者 index 時(shí),在對(duì)數(shù)據(jù)進(jìn)行刪除或增添操作時(shí),有可能會(huì)出現(xiàn)數(shù)據(jù)錯(cuò)亂等問(wèn)題出現(xiàn)。
-
key 的作用。在對(duì)節(jié)點(diǎn)進(jìn)行 diff 的過(guò)程中,判斷兩個(gè)節(jié)點(diǎn)是否為相同節(jié)點(diǎn)的一個(gè)很重要的條件就是 key 值是否相等,如果 key 值相等,則說(shuō)明兩個(gè)節(jié)點(diǎn)是相同節(jié)點(diǎn),所以會(huì)盡可能的復(fù)用原有的 DOM 節(jié)點(diǎn),減少不必要的 DOM 操作,提升系統(tǒng)性能。
下面用一段代碼演示 v-for 遍歷數(shù)組和遍歷對(duì)象時(shí)的效果。
<template><div><p>遍歷數(shù)組</p><ul><li v-for="(item, index) in listArr" :key="item.id">{{index}} - {{item.id}} - {{item.title}}</li></ul><p>遍歷對(duì)象</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 {listArr: [{ id: 'a', title: '今天周一' }, // 在數(shù)據(jù)結(jié)構(gòu)中,最好有 id ,目的是為了方便使用 key{ id: 'b', title: '今天周二' },{ id: 'c', title: '今天周三' }],listObj: {a: { title: '今天周一' },b: { title: '今天周二' },c: { title: '今天周三' },}}} } </script>此時(shí)瀏覽器的顯示效果如下所示:
(6)事件
1)event參數(shù),自定義參數(shù)
下面通過(guò)綁定一個(gè)兩個(gè)按鈕事件,來(lái)觀察 vue 中的事件,參數(shù)時(shí)怎么進(jìn)行傳遞的,事件又會(huì)被綁定到哪里去?
<template><div><!-- <p>{{num}}</p> --><!-- event參數(shù) --><button @click="increment1">+1</button><!-- 自定義參數(shù) --> <button @click="increment2(2, $event)">+2</button><div>遞增值:{{num}}</div></div> </template><script> export default {data() {return {num: 0}},methods: {increment1(event) {console.log('event', event, event.__proto__.constructor) // 是原生的 event 對(duì)象console.log(event.target) //vue中的event,放在什么元素下,就會(huì)被掛載在什么元素下console.log(event.currentTarget) // 注意,事件是被注冊(cè)到當(dāng)前元素的,和 React 不一樣this.num++// 1. event 是原生的// 2. 事件被掛載到當(dāng)前元素// 和 DOM 事件一樣},increment2(val, event) {console.log(event.target)this.num = this.num + val}} } </script>大家可以看到,上面的 increment1 方法,沒(méi)有傳遞參數(shù),則它可以直接使用 event 來(lái)觸發(fā)當(dāng)前對(duì)象;而 increment2 方法中,通過(guò)傳遞了一個(gè) 2 的參數(shù),我們可以通過(guò) $event 的方式,把 2 傳遞進(jìn)來(lái),之后同樣用 event 觸發(fā)原生對(duì)象。
同時(shí),當(dāng)觸發(fā)到當(dāng)前元素時(shí),事件會(huì)被掛載到當(dāng)前元素,和 DOM 事件一樣。
此時(shí)瀏覽器的打印效果如下:
2)自定義事件
上面的演示中, vue 事件是直接掛載到 vue 上面進(jìn)行監(jiān)聽(tīng)的。而下面的代碼中,我們可以看到,如果我們需要自定義一個(gè)事件,而不進(jìn)行直接掛載,則可以通過(guò) addEventListener 來(lái)進(jìn)行注冊(cè)。
但值得注意的是,用 vue 綁定的事件,組件銷毀時(shí)會(huì)自動(dòng)進(jìn)行解綁。但是呢,如果是自己定義的事件,需要自己再進(jìn)行手動(dòng)銷毀!!這一點(diǎn)需要特別注意,不然寫代碼過(guò)程很容易因?yàn)闆](méi)有銷毀熱導(dǎo)致后面引發(fā)的一系列 bug 出現(xiàn)。
<template><div></div> </template><script> export default {data() {return {num: 0}},methods: {loadHandler() {// do some thing}},mounted() {window.addEventListener('load', this.loadHandler)},beforeDestroy() {//【注意】用 vue 綁定的事件,組建銷毀時(shí)會(huì)自動(dòng)被解綁// 但如果是,自己綁定的事件,需要自己銷毀!!!window.removeEventListener('load', this.loadHandler)} } </script>3)事件修飾符
對(duì)于 vue 來(lái)說(shuō),經(jīng)常用到的修飾符有下面6種事件修飾符和3種按鍵修飾符。
<!-- 事件修飾符 --><!-- 阻止單擊事件繼續(xù)傳播 --> <a v-on:stop="doThis"></a><!-- 提交事件不再重載頁(yè)面 --> <form v-on:submit.prevent="onSubmit"></form><!-- 修飾符可以串聯(lián) --> <a v-on:click.stop.prevent="doThat"></a><!-- 只有修飾符 --> <form v-on:submit.prevent></form><!-- 添加事件監(jiān)聽(tīng)器時(shí)使用事件捕獲模式 --> <!-- 即內(nèi)部元素觸發(fā)的事件先在此處理,然后才交由內(nèi)部元素進(jìn)行處理 --> <div v-on:click.capture="doThis">...</div><!-- 只當(dāng)在event.target 是當(dāng)前元素自身時(shí)觸發(fā)處理函數(shù) --> <!-- 即事件不是從內(nèi)部元素觸發(fā)的 --> <div v-on:click.self="doThat">...</div> <!-- 按鍵修飾符 --><!-- 即使Alt 或 Shift 被一同按下時(shí)也會(huì)觸發(fā) --> <button @click.ctrl="onClick">A</button><!-- 有且只有ctrl被按下時(shí)候才會(huì)觸發(fā) --> <button @click.ctrl.exact="onCtrlClick">A</button><!-- 沒(méi)有任何系統(tǒng)修飾符被按下的時(shí)候才觸發(fā) --> <button @click,exact="onClick">A</button>(7)表單
在vue項(xiàng)目中,主要使用 v-model 指令在表單 input 、 textarea 、 checkbox 、 radio 、 select 等元素上創(chuàng)建雙向數(shù)據(jù)綁定。
對(duì)于 v-model 來(lái)說(shuō),常見(jiàn)的修飾符有 trim 、 lazy 和 number 。 trim 表示去除輸入內(nèi)容前后的空格, lazy 表示需要按回車鍵數(shù)據(jù)才會(huì)進(jìn)行創(chuàng)建, number 表示把用戶輸入的值轉(zhuǎn)換為 number 類型,如果先輸入字符,則整串?dāng)?shù)據(jù)都會(huì)被當(dāng)成字符串 string 類型看待,如果先輸入數(shù)字,后輸入字母,則后面的字母無(wú)法輸入到輸入框當(dāng)中。
接下來(lái)我們用代碼演示一遍常見(jiàn)的表單。
<template><div><p>輸入框: {{name}} {{age}}</p><!-- .trim去除前后空格 --><input type="text" v-model.trim="name"/><!-- .lazy需要按回車鍵數(shù)據(jù)才會(huì)起作用 --><input type="text" v-model.lazy="name"/><!-- .number表示把用戶輸入的值轉(zhuǎn)為number類型 --><input type="text" v-model.number="age"/><hr><p>多行文本: {{intro}}</p><textarea v-model="intro"></textarea><!-- 注意,<textarea>{{intro}}</textarea> 是不允許的!!! --><hr><p>復(fù)選框 {{checked}}</p><input type="checkbox" v-model="checked"/><hr><p>多個(gè)復(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><hr><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><hr><p>下拉列表選擇 {{selected}}</p><select v-model="selected"><option disabled value="">請(qǐng)選擇</option><option>A</option><option>B</option><option>C</option></select><hr><p>下拉列表選擇(多選) {{selectedList}}</p><select v-model="selectedList" multiple><option disabled value="">請(qǐng)選擇</option><option>A</option><option>B</option><option>C</option></select></div> </template><script> export default {data() {return {name: 'Monday',age: 18,intro: '自我介紹',checked: true,checkedNames: [],gender: 'female',selected: '',selectedList: []}} } </script>此時(shí)瀏覽器的顯示效果如下:
3、組件
(1)props和$emit(適合父子組件間的通信)
| $emit | 子組件向父組件傳遞數(shù)據(jù) |
| props | 父組件的數(shù)據(jù)需要通過(guò)props把數(shù)據(jù)傳給子組件,props的取值可以是數(shù)組也可以是對(duì)象 |
(2)組件間通信 - 自定義事件
除了父子組件通信外,一般還有兄弟間的組件通信。對(duì)于兄弟間的組件通信來(lái)說(shuō), 兄弟1 可以通過(guò) event.$on 來(lái)綁定自定義事件,綁定完成之后,需要再 beforeDestroy 的生命周期里面用 event.$off及時(shí)銷毀自定義事件, 防止內(nèi)存泄漏發(fā)生。之后 兄弟2 通過(guò) event.$emit 來(lái)調(diào)用 兄弟1 的自定義事件。
下面我們結(jié)合父子組件通信和兄弟組件通信的知識(shí)點(diǎn),來(lái)實(shí)現(xiàn)一個(gè)添加和刪除元素的功能。具體代碼如下:
父組件index.vue:
<template><div><!-- 當(dāng)前使用是父組件,背后的組件是子組件,即input.vue和List.vue是子組件 --><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) {// 過(guò)濾出不等于當(dāng)前刪除內(nèi)容的元素this.list = this.list.filter(item => item.id !== id)}} } </script>子組件List.vue:
<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() {},mounted() {// 綁定自定義事件//event是通過(guò)實(shí)例化一個(gè)vue對(duì)象,來(lái)調(diào)用vue中本身具有的自定義事件能力event.$on('onAddTitle', this.addTitleHandler)}beforeDestroy() {// 及時(shí)銷毀,否則可能造成內(nèi)存泄露event.$off('onAddTitle', this.addTitleHandler)} } </script>子組件Input.vue:
<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)用自定義事件,調(diào)用List.vue中自定義的事件event.$emit('onAddTitle', this.title)this.title = ''}} } </script>event.vue:
import Vue from 'vue'export default new Vue()此時(shí)瀏覽器的顯示效果如下:
(3)組件生命周期
1)組件生命周期(單個(gè)組件)
一般來(lái)說(shuō),組件生命周期的執(zhí)行順序?yàn)?#xff1a;掛載階段 → 更新階段 → 銷毀階段。下面給出常用組件生命周期的解析。
| beforeCreate | 在實(shí)例初始化之后,數(shù)據(jù)觀測(cè)(data observer) 和 event/watcher 事件配置之前被調(diào)用。 |
| created | 頁(yè)面還沒(méi)有渲染,但是vue的實(shí)例已經(jīng)初始化結(jié)束。 |
| beforeMount | 在掛載開(kāi)始之前被調(diào)用:相關(guān)的 render 函數(shù)首次被調(diào)用。 |
| mounted | 頁(yè)面已經(jīng)渲染完畢。 |
| beforeUpdate | 數(shù)據(jù)更新時(shí)調(diào)用,發(fā)生在虛擬 DOM 重新渲染和打補(bǔ)丁之前。你可以在這個(gè)鉤子中進(jìn)一步地更改狀態(tài),這不會(huì)觸發(fā)附加的重渲染過(guò)程。 |
| updated | 由于數(shù)據(jù)更改導(dǎo)致的虛擬 DOM 重新渲染和打補(bǔ)丁,在這之后會(huì)調(diào)用該鉤子。當(dāng)這個(gè)鉤子被調(diào)用時(shí),組件 DOM 已經(jīng)更新,所以你現(xiàn)在可以執(zhí)行依賴于 DOM 的操作。 |
| activated | keep-alive 組件激活時(shí)調(diào)用。 |
| deactivated | keep-alive 組件停用時(shí)調(diào)用。 |
| beforeDestroy | 實(shí)例銷毀之前調(diào)用。在這一步,實(shí)例仍然完全可用。常用場(chǎng)景有: 自定義事件的綁定要解除、setTimeout等定時(shí)任務(wù)需要銷毀、自己綁定的window或者document事件需要銷毀。 |
| destroyed | Vue 實(shí)例銷毀后調(diào)用。調(diào)用后,Vue 實(shí)例指示的所有東西都會(huì)解綁定,所有的事件監(jiān)聽(tīng)器會(huì)被移除,所有的子實(shí)例也會(huì)被銷毀。 |
2)組件生命周期(父子組件)
加載渲染過(guò)程
父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount->子mounted->父mounted子組件更新過(guò)程
父beforeUpdate->子beforeUpdate->子updated->父updated父組件更新過(guò)程
父beforeUpdate->父updated銷毀過(guò)程
父beforeDestroy->子beforeDestroy->子destroyed->父destroyed二、vue的高級(jí)特性
vue的高級(jí)特性主要有以下6種:
- 自定義 v-model
- $nextTick
- slot
- 動(dòng)態(tài)、異步組件
- keep-alive
- mixin
下面對(duì)這6種高級(jí)特性進(jìn)行一一講解。
1、自定義 v-model
在文章的前半部分我們有講到了 v-model 在表單中的應(yīng)用,那么接下來(lái)我們將動(dòng)手來(lái)實(shí)現(xiàn)一個(gè) v-model 。
第一步,我們先定義一個(gè)子組件,名字叫 CustomVModel.vue ,具體代碼如下:
<template><!-- $emit是子組件往父組件傳遞數(shù)據(jù) --><input type="text":value="text1"@input="$emit('change1', $event.target.value)"><!--1. 上面的 input 使用了 :value 來(lái)綁定數(shù)據(jù),而不是使用 v-model2. 上面的 change1 和 model.event 要對(duì)應(yīng)起來(lái)3. 上面的 text1 與下面props的 text1 屬性對(duì)應(yīng)起來(lái)--> </template><script> export default {model: {prop: 'text1', // 對(duì)應(yīng)下面 props 的 text1event: 'change1'},props: {text1: String,default() {return ''}} } </script>第二步,我們?cè)诟附M件中使用上面的這個(gè)子組件:
<template><div><p>vue 高級(jí)特性</p><hr><!-- 自定義 v-model --><p>{{name}}</p><CustomVModel v-model="name"/></div> </template><script> import CustomVModel from './CustomVModel'export default {components: {CustomVModel},data() {return {name: 'Monday'}} } </script>通過(guò)上面的代碼我們可以發(fā)現(xiàn),通過(guò)綁定 value 屬性和 input 事件這兩個(gè)語(yǔ)法糖,最終實(shí)現(xiàn)數(shù)據(jù)的雙向綁定。
此時(shí)我們看下瀏覽器的顯示效果。
通過(guò)上圖我們自己發(fā)現(xiàn),結(jié)果跟實(shí)際的 v-model 結(jié)果是一樣的。至此,我們就實(shí)現(xiàn)了自定義的 v-model ,以此來(lái)操作數(shù)據(jù)的雙向綁定。
2、$nextTick
(1)知識(shí)點(diǎn)
先來(lái)了解三個(gè)知識(shí)點(diǎn):
- Vue 是異步渲染;
- 當(dāng) data 發(fā)生改變之后, DOM 不會(huì)立刻進(jìn)行渲染;
- $nextTick 會(huì)在 DOM 渲染之后被觸發(fā),以獲取最新 DOM 節(jié)點(diǎn)。
(2)例子展示🌰
假設(shè)我們現(xiàn)在要實(shí)現(xiàn)一個(gè)功能,當(dāng)我們點(diǎn)擊按鈕時(shí),打印出列表的項(xiàng)數(shù)。這個(gè)時(shí)候我們大多人可能會(huì)這么操作。
<template><div id="app"><!-- ref的設(shè)置時(shí)為了方便后續(xù)可以用來(lái):取節(jié)點(diǎn)的DOM元素 --><ul ref="ul1"><li v-for="(item, index) in list" :key="index">{{item}}</li></ul><button @click="addItem">添加一項(xiàng)</button></div> </template><script> export default {name: 'app',data() {return {list: ['a', 'b', 'c']}},methods: {addItem() {this.list.push(`${Date.now()}`)this.list.push(`${Date.now()}`)this.list.push(`${Date.now()}`)// 獲取 DOM 元素const ulElem = this.$refs.ul1console.log( ulElem.childNodes.length )}} } </script>此時(shí)瀏覽器的顯示效果如下。
細(xì)心的小伙伴已經(jīng)發(fā)現(xiàn),瀏覽器并沒(méi)有按照我們所想的打印。當(dāng)頁(yè)面上的列表顯示 6項(xiàng) 內(nèi)容時(shí),此時(shí)控制臺(tái)只打印 3項(xiàng) ;當(dāng)顯示 9項(xiàng) 時(shí),此時(shí)控制臺(tái)直接只打印 6項(xiàng) 。
那這究竟時(shí)為什么呢?
其實(shí),當(dāng)我們點(diǎn)擊的那一刻, data 發(fā)生變化,但是 DOM 并不會(huì)立刻進(jìn)行渲染。所以等到我們點(diǎn)擊完成的時(shí)候,獲取的元素還是原來(lái)觸發(fā)的內(nèi)容,而不會(huì)增添上新的內(nèi)容。
那我們所期望的是,當(dāng)點(diǎn)擊之后立刻觸發(fā) DOM 渲染并拿到最新的值。這個(gè)時(shí)候就需要用到 nextTick 。具體代碼如下:
<script> export default {name: 'app',data() {return {list: ['a', 'b', 'c']}},methods: {addItem() {this.list.push(`${Date.now()}`)this.list.push(`${Date.now()}`)this.list.push(`${Date.now()}`)// 1. 異步渲染,$nextTick 待 DOM 渲染完再回調(diào),// 即NextTick函數(shù)會(huì)在多次data修改完并且全部DOM渲染完再觸發(fā),僅在最后觸發(fā)一次// 2. 頁(yè)面渲染時(shí)會(huì)將 data 的修改做整合this.$nextTick(() => {// 獲取 DOM 元素const ulElem = this.$refs.ul1console.log( ulElem.childNodes.length )})}} } </script>我們通過(guò)給獲取DOM元素的代碼外面再嵌套一層 $nextTick 函數(shù),來(lái)達(dá)到我們想要的效果。在此過(guò)程中,當(dāng)我們點(diǎn)擊結(jié)束后,data的值發(fā)生變化,此時(shí) $nextTick 會(huì)等待DOM全部渲染完成之后再進(jìn)行回調(diào)。
最終瀏覽器的打印效果如下:
3、slot插槽
(1)插槽的作用
讓用戶可以拓展組件,去更好地復(fù)用組件和對(duì)其做定制化處理。
(2)三種插槽類型
1)普通slot插槽
slot的基本使用方法是,父組件往子組件中插入一段內(nèi)容。
我們來(lái)演示一下。
假設(shè)我們現(xiàn)在有一個(gè)子組件,名字叫SlotDemo.vue,它的代碼如下。
<template><a :href="url"><slot></slot></a> </template><script> export default {props: ['url'], data() {return {}} } </script>我們可能希望這個(gè) <a> 標(biāo)簽內(nèi)絕大多數(shù)情況下都渲染文本 Sunday ,但是有時(shí)候卻希望渲染文本不是 Sunday ,而是其他內(nèi)容,那該怎么實(shí)現(xiàn)呢?
我們就可以將 Sunday 作為后備內(nèi)容,這個(gè)時(shí)候把它放在 <slot> 標(biāo)簽內(nèi):
<a :href="url"><slot>Sunday</slot></a>現(xiàn)在,我定義一個(gè)父級(jí)組件,名字叫 index.vue ,并且在父級(jí)組件中引用上面的 SlotDemo 組件,具體代碼如下:
<template><div><p>vue 高級(jí)特性</p><hr><SlotDemo :url="website.url"></SlotDemo></div> </template><script> import SlotDemo from './SlotDemo'export default {components: {SlotDemo},data() {return {name: 'Monday',website: {url: 'https://blog.csdn.net/weixin_44803753',title: 'Monday',subTitle: '穿梭于前端開(kāi)發(fā)的學(xué)習(xí)永動(dòng)機(jī)'}}} } </script>這個(gè)時(shí)候后備內(nèi)容 Sunday 就會(huì)被渲染出來(lái)。此時(shí)瀏覽器顯示效果如下:
那么如果想要把這個(gè)后備內(nèi)容渲染成我們想要的內(nèi)容,而不是 Sunday 呢,這個(gè)時(shí)候我們應(yīng)該這么做:
<SlotDemo :url="website.url">{{website.title}} </SlotDemo>我們可以通過(guò)在父組件 index.vue 中引用的子組件 SlotDemo.vue 里面,插入我們想要的內(nèi)容。這個(gè)時(shí)候子組件 SlotDemo.vue 的后備內(nèi)容 Sunday 就會(huì)渲染成我們提供的新內(nèi)容,以此來(lái)達(dá)到我們想要的目的。
此時(shí)瀏覽器的打印效果如下:
2)作用域插槽slot-scope
vue官方的說(shuō)法給到的解釋是:
- 父組件模板的所有東西都會(huì)在父級(jí)作用域內(nèi)編譯;
- 子組件模板的所有東西都會(huì)在子級(jí)作用域內(nèi)編譯。
這樣看起來(lái)好像有點(diǎn)難懂,接下來(lái)我們用一個(gè)例子來(lái)了解作用域插槽想要解決的問(wèn)題究竟是什么!
首先,我們先定義一個(gè)子組件,名字叫 ScopedSlotDemo.vue ,具體代碼如下所示:
<template><a><slot>{{website.subTitle}} <!-- 默認(rèn)值顯示 subTitle ,即父組件不傳內(nèi)容時(shí) --></slot></a> </template><script> export default {props: ['url'],data() {return {website: {url: 'http://tinymce.ax-z.cn/',title: 'tinymce',subTitle: '一款易用、且功能強(qiáng)大的富文本編輯器'}}} } </script>通過(guò)以上代碼可以分析,這個(gè)時(shí)候我們想要把 slot 里面想要呈現(xiàn)的內(nèi)容綁定下方的data數(shù)據(jù),于是通過(guò) {{website.subTitle}} 引用。
接下來(lái)我們定義一個(gè)父組件,名字叫 index.vue ,父組件代碼如下:
<template><div><p>vue 高級(jí)特性</p><hr><ScopedSlotDemo>{{website.title}}</ScopedSlotDemo></div> </template> <script> import ScopedSlotDemo from './ScopedSlotDemo'export default {components: {ScopedSlotDemo},data() {return {name: 'Monday'}} } </script>我們希望父組件不顯示子組件的后備內(nèi)容 subTitle ,而是顯示子組件的另外一個(gè) data 屬性 ? title ,于是我們按照默認(rèn)綁定的操作,直接在父組件進(jìn)行 {{website.title}} 的綁定。此時(shí)我們運(yùn)行一下:
阿歐!這是什么東西呢?為什么會(huì)報(bào)錯(cuò)呢?
這就談到了我們開(kāi)頭說(shuō)的定義了。在vue當(dāng)中,父組件模板的所有東西只會(huì)在父級(jí)作用域內(nèi)編譯;子組件模板的所有東西只會(huì)在子級(jí)作用域內(nèi)編譯。
所以,我們?cè)诟附M件當(dāng)中引用子組件的內(nèi)容,當(dāng)然是要報(bào)錯(cuò)的。這就相當(dāng)于,一個(gè)父親稱呼他的兒子為爸爸一樣,怎么可能嘛對(duì)吧!
在vue中,父級(jí)組件只能訪問(wèn)父級(jí)的數(shù)據(jù),而子級(jí)組件也只能訪問(wèn)子級(jí)的數(shù)據(jù),不能躍級(jí)訪問(wèn)。
因此,有了這個(gè)問(wèn)題的出現(xiàn),我們引用了作用域插槽來(lái)解決這個(gè)問(wèn)題。怎么做呢?來(lái)看下面內(nèi)容。
我們需要在父級(jí)作用域 index.vue 中為我們要綁定的元素設(shè)置一個(gè) v-slot ,這個(gè) v-slot 用來(lái)定義我們提供的插槽 prop 的名字。
index.vue:
<ScopedSlotDemo> <template v-slot="slotProps"> {{slotProps.slotData.title}} </template> </ScopedSlotDemo>那么父級(jí)定義完只是第一步,接下來(lái)我們要讓子級(jí) ScopedSlotDemo.vue 的插槽內(nèi)容在父級(jí)當(dāng)中可用,于是我們需要在子級(jí)的插槽中綁定一個(gè)自定義事件,并且指向我們需要的數(shù)據(jù),如下代碼所示:
ScopedSlotDemo.vue:
<slot v-bind:slotData="website"> {{website.subTitle}} </slot>從上面我們可以看到,我們通過(guò)綁定一個(gè)自定義事件 v-bind:slotData ,并把它指向我們想要的數(shù)據(jù) website 。指向之后,我們?cè)诟讣?jí)的插槽當(dāng)中,通過(guò) slopProps.slotData ,就可以訪問(wèn)到自己子級(jí) website 的數(shù)據(jù),這樣,就達(dá)到最終我們想要的效果。
最終瀏覽器訪問(wèn)結(jié)果:
最后貼上完整的父子級(jí)代碼,大家可以根據(jù)以上講解進(jìn)行演示。
子級(jí) ScopedSlotDemo.vue:
<template><a><slot>{{website.subTitle}} <!-- 默認(rèn)值顯示 subTitle ,即父組件不傳內(nèi)容時(shí) --></slot></a> </template><script> export default {props: ['url'],data() {return {website: {url: 'http://tinymce.ax-z.cn/',title: 'tinymce',subTitle: '一款易用、且功能強(qiáng)大的富文本編輯器'}}} } </script>父級(jí) index.vue:
<template><div><p>vue 高級(jí)特性</p><hr><ScopedSlotDemo><template v-slot="slotProps">{{slotProps.slotData.title}}</template></ScopedSlotDemo></div> </template><script> import ScopedSlotDemo from './ScopedSlotDemo'export default {components: {ScopedSlotDemo},data() {return {name: 'Monday',website: {url: 'https://blog.csdn.net/weixin_44803753',title: 'Monday',subTitle: '穿梭于前端開(kāi)發(fā)的學(xué)習(xí)永動(dòng)機(jī)'}}} } </script>3)具名插槽
具名插槽的定義: 將父組件中的內(nèi)容插入指定的子組件位置中。
接下來(lái)來(lái)看一下具名插槽是怎么使用的?
有時(shí)候我們一個(gè)組件里需要多個(gè)插槽。比如,現(xiàn)在我們有一個(gè)子組件,名字叫 named.vue ,具體代碼如下:
<template><div><header><!-- 我們希望把標(biāo)題放這里 --></header><main><!-- 我們希望把主體內(nèi)容放這里 --></main><footer><!-- 我們希望把頁(yè)腳放這里 --></footer></div> </template>大家可以看到,上面的代碼中,我們希望在 header , main 和 footer 當(dāng)中放入對(duì)應(yīng)模塊的內(nèi)容進(jìn)去,這個(gè)時(shí)候就需要使用多個(gè)插槽來(lái)解決。
那怎么處理呢? 對(duì)于這樣的情況, <slot> 元素有一個(gè)特殊的屬性: name ,這個(gè)屬性可以用來(lái)定義額外的插槽。我們通過(guò)使用 name 屬性來(lái)對(duì)子組件 named.vue 的插槽加上屬性名。具體做法如下:
<template><div><header><slot name="header"></slot></header><main><!-- 如果一個(gè)<slot>不帶name屬性的話,那么它的name默認(rèn)為default --><slot></slot></main><footer><slot name="footer"></slot></footer></div> </template>值得注意的是,如果一個(gè) <slot> 不帶 name 屬性的話,那么它的 name 默認(rèn)為 default 。
子級(jí)組件插入完插槽之后,接下來(lái)我們來(lái)定義一個(gè)父組件 index.vue 。在向子級(jí)組件的具名插槽提供內(nèi)容的時(shí)候,我們可以在父級(jí)組件的 <template> 元素上使用 v-slot 指令,并以參數(shù)的形式傳遞具名插槽的名稱。
index.vue:
<template><div><template v-slot:header><h1>這里是頁(yè)面標(biāo)題</h1></template><p>頁(yè)面主題內(nèi)容第一頁(yè)</p><p>頁(yè)面主題內(nèi)容第二頁(yè)</p><template v-slot:footer><p>這里是頁(yè)腳</p></template></div> </template>現(xiàn)在 <template> 元素中的所有內(nèi)容都將會(huì)被傳入到子組件相應(yīng)的插槽當(dāng)中,且任何沒(méi)有被包裹在帶有 v-slot 的 <template> 中的內(nèi)容都會(huì)被視為默認(rèn)插槽的內(nèi)容。
如果你希望更加明確一點(diǎn)的話,那就把主體內(nèi)容包裹上一個(gè)插槽,并設(shè)置 name="default" 。
<template v-slot:default><p>頁(yè)面主題內(nèi)容第一頁(yè)</p><p>頁(yè)面主題內(nèi)容第二頁(yè)</p> </template>這樣看起來(lái)或許會(huì)更優(yōu)雅一點(diǎn)。
最終打印結(jié)果如下:
這里是頁(yè)面標(biāo)題頁(yè)面主題內(nèi)容第一頁(yè) 頁(yè)面主題內(nèi)容第二頁(yè)這里是頁(yè)腳講完具名插槽,我們來(lái)對(duì)它的使用方法進(jìn)行一個(gè)總結(jié)歸納。
具名插槽的使用語(yǔ)法:
- 子組件定義 slot 時(shí),在標(biāo)簽上加上 name='xxx' 屬性,不寫 name 則默認(rèn) name=default 。
- 父組件將想插入的部分最外部的 div 上加上 v-slot="xxx" 屬性, xxx 對(duì)應(yīng)name的值。
- v-slot: 可以簡(jiǎn)寫為 # ,如 v-slot:header 相當(dāng)于 #header 。
都說(shuō)一個(gè)蘿卜一個(gè)坑,具名插槽通過(guò)將父組件中的內(nèi)容一個(gè)一個(gè)對(duì)應(yīng)地插入到指定的子組件位置當(dāng)中,把坑填上了,蘿卜也就長(zhǎng)出來(lái)了。
4、動(dòng)態(tài)、異步組件
(1)動(dòng)態(tài)組件
1)舉個(gè)例子🌰
比如我們?cè)谧瞿硞€(gè)新聞網(wǎng)站的文章詳情頁(yè),那這個(gè)時(shí)候文章詳情頁(yè)顯示的內(nèi)容是非常多樣化的。有可能是文本、視頻和圖片類型,也有可能是文本和圖片類型,這個(gè)時(shí)候我么就沒(méi)有辦法確定這個(gè)組件想要的是什么類型的內(nèi)容,于是我們據(jù)需要用到動(dòng)態(tài)組件。
2)動(dòng)態(tài)組件的用法和應(yīng)用場(chǎng)景
- 用法: :is = "component-name" 用法;
- 應(yīng)用場(chǎng)景: 需要根據(jù) 數(shù)據(jù) 進(jìn)行動(dòng)態(tài)渲染的場(chǎng)景,即組件類型不確定的場(chǎng)景。
3)演示
演示1:
我們定義一個(gè)子組件,名字叫 Dynamic.vue ,具體代碼如下:
<template><div><p>動(dòng)態(tài)組件子組件</p></div> </template><script>export default {data() {reuturn{}} } </script>接下來(lái)定義一個(gè)父組件,名字叫 index.vue 。我們希望地引入子組件 Dynamic.vue 。因此,我們可以這樣做:
<template><div><p>vue 高級(jí)特性</p><hr><!-- 動(dòng)態(tài)組件 --><component :is="DynamicName"/></div> </template><script> import Dynamic from './Dynamic' export default {components: {Dynamic},data() {return {DynamicName: "Dynamic"}} } </script>這個(gè)時(shí)候我們可以發(fā)現(xiàn),通過(guò)在 data 中綁定組件的名字,之后在視圖上用 is="ComponentName" 的形式進(jìn)行引用,達(dá)到對(duì)組件進(jìn)行動(dòng)態(tài)綁定的效果。
演示2:
假設(shè)我們要給某一個(gè)頁(yè)面上的一組數(shù)據(jù)進(jìn)行動(dòng)態(tài)綁定,這個(gè)時(shí)候我們可以這么做:
<template><div><!-- 動(dòng)態(tài)組件 --><div v-for="(val, key) in newsData" :key = "key"><component :is = "val.type"/></div></div> </template><script>export default {data() {return {newsData:{1:{type:'text'},2:{type:'text'},3:{type:'image'}}}} } </script>(2)異步組件
平常我們?cè)陂_(kāi)發(fā)中,通常用的是 import()函數(shù) 來(lái)引入一個(gè)組件,但是import雖然好用,可是大家有沒(méi)有想過(guò),如果當(dāng)我們要引入一個(gè)比較大的組件時(shí),也用 import 去引入的話,它會(huì)進(jìn)行同步加載操作,這樣子對(duì)性能優(yōu)化和用戶體驗(yàn)來(lái)說(shuō)是非常不友好的。
因此,我們要引入異步組件來(lái)解決 import 同步加載的問(wèn)題。
我們來(lái)舉個(gè)例子:
<template><div><!-- 異步組件 --><FormDemo v-if="showFormDemo"/><button @click="showFormDemo = true">show form demo</button></div> </template><script> export default {components: {FormDemo: () => import('./FormDemo')},data() {return {showFormDemo: false}} } </script>通過(guò)上面的代碼,我們可以了解到,通過(guò)控制 showFormDemo 的布爾值,來(lái)決定 FormDemo 組件在什么時(shí)候引入。因此,在button點(diǎn)擊之前, FormDemo 的判斷條件 showFormDemo 為 false ,這個(gè)時(shí)候組件不會(huì)被加載。只有當(dāng) button 點(diǎn)擊之后, FormDemo 的判斷條件 showFormDemo 變?yōu)閠rue,這個(gè)時(shí)候才按需將組件的內(nèi)容加載出來(lái)。
通過(guò)以上分析,大家應(yīng)該可以了解到, import 是同步加載,不管頁(yè)面需不需要這個(gè)組件,都會(huì)在頁(yè)面加載時(shí)就加載出來(lái)。
而如果將組件直接注冊(cè)在 components 中,通過(guò)布爾值數(shù)據(jù)來(lái)控制組件是否加載,而不會(huì)從一開(kāi)始就加載,這樣的方式來(lái)加載組件,從性能上會(huì)好非常多。
因此,我們對(duì)異步組件做出以下總結(jié):
- 不適合用 import() 函數(shù);
- 按需加載,異步方式加載大組件;
- 異步加載,即什么時(shí)候用什么時(shí)候加載,什么時(shí)候不用,就永遠(yuǎn)不會(huì)加載。
5、keep-alive
(1)定義和應(yīng)用場(chǎng)景
keep-alive ,從字面意思來(lái)講就是,保持它存活著,保持它不死亡。
我們先拋出 keep-alive 的定義和應(yīng)用場(chǎng)景:
- keep-alive 是一個(gè)緩存組件;
- 應(yīng)用于頻繁切換的場(chǎng)景,用 keep-alive 就不需要重復(fù)渲染;
- keep-alive 是 vue 中常見(jiàn)的性能優(yōu)化方法。
(2)舉例🌰
了解完定義和應(yīng)用場(chǎng)景,我們來(lái)開(kāi)始舉例子看看這個(gè)緩存組件,到底是何方神圣,讓人們?cè)陂_(kāi)發(fā)時(shí)都對(duì)之有所熱愛(ài)。
首先,我們先來(lái)定義一個(gè)父組件,名字叫 keepAlive.vue ,具體代碼如下:
keepAlive.vue:
<template><div><button @click="changeState('A')">button A</button><button @click="changeState('B')">button B</button><button @click="changeState('C')">button C</button><KeepAliveStageA v-if="state === 'A'"/> <KeepAliveStageB v-if="state === 'B'"/><KeepAliveStageC v-if="state === 'C'"/></div> </template><script> import KeepAliveStageA from './KeepAliveStateA' import KeepAliveStageB from './KeepAliveStateB' import KeepAliveStageC from './KeepAliveStateC'export default {components: {KeepAliveStageA,KeepAliveStageB,KeepAliveStageC},data() {return {state: 'A'}},methods: {changeState(state) {this.state = state}} } </script>我們?cè)賮?lái)定義三個(gè)子組件,名字分別為 keepAliveStateA.vue 、 keepAliveStateB.vue 、 keepAliveStateC.vue 。 具體代碼如下:
keepAliveStateA.vue:
<template><p>state A</p> </template><script> export default {mounted() {console.log('A mounted')},destroyed() {console.log('A destroyed')} } </script>keepAliveStateB.vue:
<template><p>state B</p> </template><script> export default {mounted() {console.log('B mounted')},destroyed() {console.log('B destroyed')} } </script>keepAliveStateC.vue:
<template><p>state C</p> </template><script> export default {mounted() {console.log('C mounted')},destroyed() {console.log('C destroyed')} } </script>此時(shí)我們來(lái)看下瀏覽器的運(yùn)行效果:
通過(guò)以上動(dòng)圖可以發(fā)現(xiàn),我們初始化時(shí)是狀態(tài) A ,之后當(dāng)我們每次點(diǎn)擊按鈕時(shí),編譯器都會(huì)對(duì)這個(gè)組件進(jìn)行銷毀,再創(chuàng)建我們點(diǎn)擊的新組件。這樣反復(fù)的創(chuàng)建銷毀,創(chuàng)建銷毀,對(duì)用戶的體驗(yàn)似乎是不太好的。因?yàn)橛脩裘奎c(diǎn)擊一個(gè)東西就得重新下載,如果遇到代碼量很多的頁(yè)面呢?那用戶可能會(huì)從此把這個(gè)網(wǎng)站拉入黑名單了。
因此,我們可以使用vue中的 keep-alive ,來(lái)解決這個(gè)問(wèn)題。我們想要達(dá)到的目的很簡(jiǎn)單,就是把用戶每次點(diǎn)擊后的內(nèi)容給緩存下來(lái),之后用戶每次點(diǎn)擊時(shí)就可以直接從緩存中拉取資源直接顯示,而無(wú)需重新加載。
那么,怎么做呢?
很簡(jiǎn)單,我們只需要在父組件 keepAlive.vue 中引入的三個(gè)子組件外面,再包上一個(gè)緩存組件 keep-alive , 這樣,就能達(dá)到我們想要的目的了。
<keep-alive> <KeepAliveStageA v-if="state === 'A'"/><KeepAliveStageB v-if="state === 'B'"/><KeepAliveStageC v-if="state === 'C'"/> </keep-alive>我們來(lái)看下此時(shí)瀏覽器的打印效果:
我們可以發(fā)現(xiàn),有keep-alive時(shí),瀏覽器直接把子組件的內(nèi)容緩存下來(lái)了,當(dāng)我們?cè)俅吸c(diǎn)擊它時(shí),就不會(huì)再進(jìn)行重復(fù)的創(chuàng)建和銷毀,直接從緩存中拉取資源,并加載到我們的頁(yè)面中來(lái)。
6、mixin
(1)mixin是什么
我們先來(lái)了解下 mixin 的定義:
- 多個(gè)組件有相同的邏輯,抽離出來(lái),當(dāng)作 mixin 來(lái)使用。
- mixin 并不是完美的解決方案,會(huì)存在一些小問(wèn)題的出現(xiàn)。
- Vue 3 提出的 Composition API 旨在解決這些問(wèn)題。這里不對(duì) Vue 3 的 Composition API 進(jìn)行細(xì)講,將在后面的文章中做介紹。
(2)mixin的問(wèn)題
mixin雖說(shuō)可以用來(lái)抽離相同邏輯的組件,但是總會(huì)存在各種各樣的問(wèn)題。比如:
- 變量來(lái)源不明確,不利于閱讀。
- 多個(gè) mixin 可能會(huì)造成命名沖突。
- mixin 和組件可能出現(xiàn)多對(duì)多的關(guān)系,復(fù)雜度較高。
(3)舉個(gè)栗子🌰
我們先來(lái)定義一個(gè)組件,名字叫 MixinDemo.vue ,具體代碼如下:
<template><div><p>{{name}}: {{intro}}: {{city}}</p><button @click="showName">顯示姓名</button></div> </template><script> import myMixin from './mixin'export default {mixins: [myMixin], // 可以添加多個(gè),會(huì)自動(dòng)合并起來(lái)data() {return {name: 'Monday',intro: '熱愛(ài)前端的學(xué)習(xí)永動(dòng)機(jī)'}},methods: {},mounted() {console.log('component mounted', this.name)} } </script>再定義一個(gè) js 文件,名字叫 mixin.js ,具體代碼如下:
export default {data() {return {city: '廣東'}},methods: {showName() {console.log(this.name)}},mounted() {console.log('mixin mounted', this.name)} }此時(shí)瀏覽器的演示效果如下:
我們來(lái)解析下上面這波操作。假設(shè)此時(shí) js 文件的代碼是多個(gè)組件中抽離出來(lái)的比較相似的代碼部分,那我們我們對(duì)它進(jìn)行抽離后并注冊(cè)到 MixinDemo.vue 當(dāng)中,用 mixins: [myMixin] 的方式進(jìn)行引入。此時(shí),我們?cè)?MixinDemo.vue 組件中就可以訪問(wèn)到 mixin.js 里面的內(nèi)容,這樣就完成了一波邏輯復(fù)用。
mixin看著是挺好用,但是它會(huì)存在各種各樣的問(wèn)題發(fā)生。這就談到我們第二點(diǎn)中所說(shuō)的。
我們來(lái)對(duì)問(wèn)題進(jìn)行一一剖析。
第一個(gè),變量來(lái)源不明確,不利于閱讀。 比如我們的第一塊代碼,里面有一個(gè)按鈕綁定了showName的方法,此時(shí)showName的方法放在了mixin里面,而沒(méi)有放在該頁(yè)面中。假如我們定義了多個(gè)mixin,那我們得對(duì)每個(gè)mixin一個(gè)一個(gè)地去找showName這個(gè)方法,因?yàn)槲覀円膊恢纒howName來(lái)自于哪里,只能這樣干。那這樣子的話得有多麻煩,非常的影響開(kāi)發(fā)效率。
第二個(gè),多個(gè) mixin 可能會(huì)造成命名沖突。 比如說(shuō)當(dāng)出現(xiàn)多個(gè) mixin 時(shí),里面的 data 綁定的屬性一模一樣,那這個(gè)時(shí)候就有可能出現(xiàn)覆蓋的情況,也很不利于我們找錯(cuò)。
第三個(gè), mixin 和組件可能出現(xiàn)多對(duì)多的關(guān)系,復(fù)雜度較高。 我們?cè)陂_(kāi)發(fā)過(guò)程中,有可能會(huì)遇到一個(gè)組件引入多個(gè) mixin ,也有可能多個(gè)組件引入一個(gè) mixin ,這樣就是一個(gè)多對(duì)多的關(guān)系。這有一天如果某個(gè)組件想要改個(gè)屬性值,但是它只能動(dòng) mixin 。這一動(dòng),其他組件就都被影響到了,很容易導(dǎo)致了剪不斷理還亂的尷尬局面。
所以, mixin 有它的好處所在,也有它的問(wèn)題所在。而 vue3 中的 composition API 就旨在解決這個(gè)問(wèn)題。這里先給大家拋個(gè)概念, composition API 將在后面的文章中進(jìn)行講解。
三、vue的周邊插件:vuex和vue-router
1、vuex
(1)vuex基本概念
- state:單一狀態(tài)樹(shù),用一個(gè)對(duì)象來(lái)包含全部的應(yīng)用層級(jí)狀態(tài);
- getters: vuex 允許我們?cè)?store 中定義 getter ,就像計(jì)算屬性一樣, getter 的返回值會(huì)根據(jù)它的依賴被緩存起來(lái),且只有當(dāng)它的依賴值發(fā)生改變時(shí),才會(huì)被重新計(jì)算。
- action:異步邏輯封裝;
- mutation:更改狀態(tài)的唯一方法,并且這個(gè)過(guò)程是同步的。
(2)用于vue組件中的API
- dispatch
- commit
- mapState
- mapGetters
- mapActions
- mapMutations
(3)圖例
對(duì)于 vuex 來(lái)說(shuō),先理解上面這幾個(gè)概念,后面還會(huì)有文章講解關(guān)于 vuex 的實(shí)際應(yīng)用。
2、vue-router
(1)路由模式
1)hash 模式(默認(rèn))
hash模式,如 http://abc.com/#/user/10 。早期的前端路由就是基于 location.hash 來(lái)實(shí)現(xiàn)的。其實(shí)原理很簡(jiǎn)單, location.hash 的值就是 url 中 # 后面的內(nèi)容。比如下面這個(gè)網(wǎng)站,它的 location.hash 就是 #abc 。
https://www.baidu.com#abc2)H5 history模式
history 模式,如 http://abc.com/user/20 ;與 hash 模式不同的是,這種模式需要 server 端支持,因此無(wú)特殊需求可選擇前者。
HTML5 提供了 History API 來(lái)實(shí)現(xiàn) URL 的變化。其中最主要的 API 有以下兩個(gè)。 history.pushState() 和 history.replaceState() 。這兩個(gè) API 可以在不刷新的情況下,操作瀏覽器的歷史記錄。唯一不用的是,前者是新增一個(gè)記錄,后者是直接替換當(dāng)前的歷史記錄。如下所示:
window.history.pushState(null, null, path); window.history.replaceState(null, null, path);同時(shí),對(duì)于 history 來(lái)說(shuō),如果刷新時(shí),服務(wù)器沒(méi)有響應(yīng)的資源,會(huì)報(bào)錯(cuò) 404 。
(2)路由配置
1)動(dòng)態(tài)路由
動(dòng)態(tài)路徑參數(shù),以 冒號(hào) : 開(kāi)頭進(jìn)行路由配置。如下所示:
const User = {//獲取參數(shù)如 3 5template: '<div>User {{$route.params.id}}</div>' }const router = new VueRouter({routes:[//動(dòng)態(tài)路徑參數(shù) 以冒號(hào)開(kāi)頭。能命中`/user/3` `/user/5` 等格式的路由{path: 'user/:id', component: User}] })2)懶加載
懶加載即對(duì)應(yīng)一個(gè) import() 函數(shù),然后去導(dǎo)入一個(gè)組件。跟我們前面主題二的第4小點(diǎn)講過(guò)的異步組件類似,當(dāng)我們?nèi)ピL問(wèn)一級(jí)二級(jí)等各種級(jí)別的的路由時(shí),就會(huì)異步的去把對(duì)應(yīng)的組件加載出來(lái)。不會(huì)出現(xiàn)一下子全部同時(shí)加載的問(wèn)題,而是該加載時(shí)再加載,可以理解為我需要你的時(shí)候你再出現(xiàn)就好了。示例代碼如下所示:
export default new VueRouter({routes:[{path:'/',component: () => import('./../compoents/Navigator')},{path:'/detail',component: () => import('./../components/Detail')}] })四、結(jié)束語(yǔ)
以上文章總結(jié)了 vue 的基本使用以及 vue 的高級(jí)特性,還有概括了 vue 的周邊插件 vuex 和 vue-router 的一些常見(jiàn)知識(shí),融合大量案例進(jìn)行講解。
前端在做 vue 的項(xiàng)目中,總是脫離不開(kāi)以上文章所涉及到的內(nèi)容,唯一的區(qū)別在于用的多和用的少的問(wèn)題。
希望通過(guò)以上文章的講解,小伙伴們能有所收獲🥂
- 關(guān)注公眾號(hào) 星期一研究室 ,不定期分享學(xué)習(xí)干貨,學(xué)習(xí)路上不迷路~
- 如果這篇文章對(duì)你有用,記得點(diǎn)個(gè)贊加個(gè)關(guān)注再走哦~
總結(jié)
以上是生活随笔為你收集整理的查漏补缺方为上策!!两万六字总结vue的基本使用和高级特性,周边插件vuex和vue-router任你挑选的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 博主:国产供应链取代海外供应后 手机厂商
- 下一篇: 手把手教你剖析vue响应式原理,监听数据