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