【筆記】Vue Element+Node.js開發企業通用管理後臺系統——Vue進階(下)
【筆記】Vue Element+Node.js開發企業通用管理後臺系統——Vue進階(下)
一、元件通訊 provide 和 inject
<body>
<div id="root">
<Test></Test>
</div>
<script>
function registerPlugin() {
Vue.component('Test', {
template: '<div>{{message}}<Test2 /></div>',
provide() {
return {
elTest: this
}
}, // function 的用途是為了獲取執行時環境,否則 this 將指向 window
data() {
return {
message: 'message from Test'
}
},
methods: {
change(component) {
this.message = 'message from ' + component
}
}
})
Vue.component('Test2', {
template: '<Test3 />'
})
Vue.component('Test3', {
template: '<button @click="changeMessage">change</button>',
inject: ['elTest'],
methods: {
changeMessage() {
this.elTest.change(this.$options._componentTag)
}
}
})
}
Vue.use(registerPlugin)
new Vue({
el: '#root'
})
</script>
</body>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
先來看一下官方文件中的說明:
型別:
- provide:Object | () => Object
- inject:Array< string > | { [key: string]: string | Symbol | Object }
詳細:
- 這對選項需要一起使用,以允許一個祖先元件向其所有子孫後代注入一個依賴,不論元件層次有多深,並在起上下游關係成立的時間裡始終生效。
- provide 選項應該是一個物件或返回一個物件的函式。該物件包含可注入其子孫的屬性。
- inject 選項應該是:
- 一個字串陣列,或一個物件,物件的 key 是本地的繫結名,value 是:
- 在可用的注入內容中搜索用的 key (字串或 Symbol),或
- 一個物件,該物件的:
- from 屬性是在可用的注入內容中搜索用的 key (字串或 Symbol)
- default 屬性是降級情況下使用的 value
PS:provide 和 inject 主要在開發高階外掛/元件庫時使用。並不推薦用於普通應用程式程式碼中。
示例程式碼解析:
- 可以說一個元件它的內部資源是私有的,子孫也不可及,通過
provide
provide() {
return {
elTest: this
}
},
- 1
- 2
- 3
- 4
- 5
this
:VueComponent(祖先元件:Test)
inject: ['elTest'],
- 1
this.elTest.change(this.$options._componentTag)
- 1
this
:VueComponent(孫輩元件:Test3)this.elTest
:VueComponent(祖先元件:Test)this.$options._componentTag
:這裡的this
指的是呼叫change
方法的this.elTest
,也就是祖先元件:Test
二、過濾器 filter
官方文件:https://cn.vuejs.org/v2/api/#Vue-filter
官方文件:https://cn.vuejs.org/v2/guide/filters.html
例項:過濾器 filter
<body>
<div id="root">
{{message | lower}}
</div>
<script>
new Vue({
el: '#root',
filters: {
lower(value) {
return value.toLowerCase()
}
},
data() {
return {
message: 'Hello Vue'
}
}
})
</script>
</body>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
先來看一下官方文件中的說明:
Vue.js 允許自定義過濾器,可被用於一些常見的文字格式化。
過濾器可以用在兩個地方:雙花括號插值和 v-bind 表示式 (後者從 2.1.0+ 開始支援)。
過濾器應該被新增在 JavaScript 表示式的尾部,由“管道”符號指示:
<!-- 在雙花括號中 -->
{{ message | capitalize }}
<!-- 在 `v-bind` 中 -->
<div v-bind:id="rawId | formatId"></div>
- 1
- 2
- 3
- 4
- 5
可以在一個元件的選項中定義本地的過濾器
或者在建立 Vue 例項之前全域性定義過濾器
當全域性過濾器和區域性過濾器重名時,會採用區域性過濾器。
過濾器可以串聯:
{{ message | filterA | filterB }}
- 1
過濾器是 JavaScript 函式,因此可以接收引數:
{{ message | filterA('arg1', arg2) }}
- 1
這裡,filterA 被定義為接收三個引數的過濾器函式。其中 message 的值作為第一個引數,普通字串 ‘arg1’ 作為第二個引數,表示式 arg2 的值作為第三個引數。
filters執行前後過程分析(by call stack):
- Vue,_init:實現vue初始化
- 在Vue.$mount中通過mountComponent例項化一個Watcher物件
- 接下來呼叫updateComponent方法
- 方法內執行Vue._render函式
- 函式通過對template解析
- 然後解析過程中對傳進來的引數進行指定的過濾處理
- 最後將結果返回,渲染
具體解析:傳送門:filter原始碼詳解
三、監聽器 watch
官方文件:https://cn.vuejs.org/v2/api/#watch
官方文件:https://cn.vuejs.org/v2/api/#vm-watch
例項:監聽器 watch
Watch 用法1:常見用法
<body>
<div id="root">
<h3>Watch 用法1:常見用法</h3>
<input v-model="message">
<span>{{copyMessage}}</span>
</div>
<script>
new Vue({
el: '#root',
watch: {
message(value) {
this.copyMessage = value
}
},
data() {
return {
message: 'Hello Vue',
copyMessage: ''
}
}
})
</script>
</body>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
鍵值一體,鍵為message
,值為message()
方法
message(value) {
this.copyMessage = value
}
- 1
- 2
- 3
監聽器的一些預設值:
Watch 用法2:繫結方法
<body>
<div id="root2">
<h3>Watch 用法2:繫結方法</h3>
<input v-model="message">
<span>{{copyMessage}}</span>
</div>
<script>
new Vue({
el: '#root2',
watch: {
message: 'handleMessage'
},
data() {
return {
message: 'Hello Vue',
copyMessage: ''
}
},
methods: {
handleMessage(value) {
this.copyMessage = value
}
}
})
</script>
</body>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
鍵為message
,值為’handleMessage()
方法,每次監聽到message
變化,'handleMessage()
方法就會執行一次
PS:雙向繫結的值(
v-model="message"
和data() {return {message}}
)和watch
監聽的鍵要保持一致,同為message
Watch 用法3:deep + handler
<body>
<div id="root3">
<h3>Watch 用法3:deep + handler</h3>
<input v-model="deepMessage.a.b">
<span>{{copyMessage}}</span>
</div>
<script>
new Vue({
el: '#root3',
watch: {
deepMessage: {
handler: 'handleDeepMessage',
deep: true
}
},
data() {
return {
deepMessage: {
a: {
b: 'Deep Message'
}
},
copyMessage: ''
}
},
methods: {
handleDeepMessage(value) {
this.copyMessage = value.a.b
}
}
})
</script>
</body>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
預設情況下 watch方法只監聽data中的物件,而無法監聽到物件內部屬性的改變,此時就需要deep屬性對物件進行深度監聽。(預設:deep:false)
這個案例可以看出:雙向繫結的值(v-model="deepMessage.a.b"
和deepMessage:{a: {b:'Deep Message'}},
)保持一致;watch
不能直接監聽deepMessage.a.b
,而是需要通過handler
和deep
屬性來完成監聽
Watch 用法4:immediate
<body>
<div id="root">
<div id="root4">
<h3>Watch 用法4:immediate</h3>
<input v-model="message">
<span>{{copyMessage}}</span>
</div>
<script>
new Vue({
el: '#root4',
watch: {
message: {
handler: 'handleMessage',
immediate: true,
}
},
data() {
return {
message: 'Hello Vue',
copyMessage: ''
}
},
methods: {
handleMessage(value) {
this.copyMessage = value
}
}
})
</script>
</body>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
watch
預設情況下在頁面首次渲染時,即使監聽的值有初始值,也不會直接執行,這種情況下想要第一次渲染後直接監聽就需要新增屬性:immediate: true
initWatch
createWatch
Watch 用法5:繫結多個 handler
<body>
<div id="root5">
<h3>Watch 用法5:繫結多個 handler</h3>
<input v-model="message">
<span>{{copyMessage}}</span>
</div>
<script>
new Vue({
el: '#root5',
watch: {
message: [{
handler: 'handleMessage',
},
'handleMessage2',
function(value) {
this.copyMessage = this.copyMessage + '...'
}]
},
data() {
return {
message: 'Hello Vue',
copyMessage: ''
}
},
methods: {
handleMessage(value) {
this.copyMessage = value
},
handleMessage2(value) {
this.copyMessage = this.copyMessage + '*'
}
}
})
</script>
</body>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
監聽值為多個,需要用陣列形式:
- 值為物件:執行物件的handler屬性值對應方法handleMessage
- 值為字串:執行字串對應方法handleMessage2
- 值為方法:直接執行方法
先監聽的先執行,各自獨立,每個都是獨立的監聽器
本示例中value
都是一致的,只有handleMessage
獲取了這個value
,其他監聽器處理的都是上一步處理過的copyMessage
若多個監聽器監聽同一個物件,那麼只會渲染最後一次處理結果
Watch 用法6:監聽物件屬性
<body>
<div id="root6">
<h3>Watch 用法6:監聽物件屬性</h3>
<input v-model="deepMessage.a.b">
<span>{{copyMessage}}</span>
</div>
<script>
new Vue({
el: '#root6',
watch: {
'deepMessage.a.b': 'handleMessage'
},
data() {
return {
deepMessage: { a: { b: 'Hello Vue' } },
copyMessage: ''
}
},
methods: {
handleMessage(value) {
this.copyMessage = value
}
}
})
</script>
</body>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
只監聽物件的某少數個屬性值時,可以用物件.屬性
字串形式進行監聽
Vue.js 原始碼分析(七) 基礎篇 偵聽器 watch屬性詳解
四、class 和 style 繫結的高階用法
<body>
<div id="root">
<div :class="['active', 'normal']">陣列繫結多個class</div>
<div :class="[{active: isActive}, 'normal']">陣列包含物件繫結class</div>
<div :class="[showWarning(), 'normal']">陣列包含方法繫結class</div>
<div :style="[warning, bold]">陣列繫結多個style</div>
<div :style="[warning, mix()]">陣列包含方法繫結style</div>
<div :style="{ display: ['-webkit-box', '-ms-flexbox', 'flex'] }">style多重值</div>
</div>
<script>
new Vue({
el: '#root',
data() {
return {
isActive: true,
warning: {
color: 'orange'
},
bold: {
fontWeight: 'bold'
}
}
},
methods: {
showWarning() {
return 'warning'
},
mix() {
return {
...this.bold,
fontSize: 20
}
}
}
})
</script>
</body>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
陣列繫結多個class
:class="'active normal'"
:class="'active'+'normal'"
:class="['active', 'normal']" // 更靈活
- 1
- 2
- 3
陣列包含物件繫結class
:class="[{active: isActive}, 'normal']" // 老寫法:class="(isActive?'active':'') + 'normal'"
- 1
陣列包含方法繫結class
:class="[showWarning(), 'normal']"
- 1
陣列繫結多個style
:style="[warning, bold]" // 這裡的每個陣列元素都是物件而不是字串
- 1
陣列包含方法繫結style
:style="[warning, mix()]"
- 1
style多重值
:style="{display:['-webkit-box','-ms-flexbox','flex']}"//從後往前,相容哪個匹配哪個
- 1
五、Vue2.6 新特性
1.Vue.observable
<body>
<div id="root">
{{message}}
<button @click="change">Change</button>
</div>
<script>
const state = Vue.observable({ message: 'Vue 2.6' })
const mutation = {
setMessage(value) {
state.message = value
}
}
new Vue({
el: '#root',
computed: {
message() {
return state.message
}
},
methods: {
change() {
mutation.setMessage('Vue 3.0')
}
}
})
</script>
</body>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
官方解釋:
Vue.observable(object) 是vue2.6版本新增的全域性API,它可以讓一個物件可響應。Vue 內部會用它來處理 data 函式返回的物件。
返回的物件可以直接用於渲染函式和計算屬性內,並且會在發生改變時觸發相應的更新。也可以作為最小化的跨元件狀態儲存器,用於簡單的場景
在 Vue 2.x 中,被傳入的物件會直接被 Vue.observable 改變;在 Vue 3.x 中,則會返回一個可響應的代理,而對源物件直接進行修改仍然是不可響應的。因此,為了向前相容,官方推薦始終操作使用 Vue.observable 返回的物件,而不是傳入源物件。
通俗來說,Vue.observable在簡單場景下可以代替vuex
本示例中Vue.observable執行流程:
- 首先initGlobalAPI (vue.js:5406)
- 執行Vue.observable內容
// 2.6 explicit observable API
Vue.observable = function (obj) {
observe(obj);
return obj
};
- 1
- 2
- 3
- 4
- 5
- vue初始化
- 使用計算屬性將state.message渲染到頁面
- 點選按鈕
- 執行change()
- 執行setMessage(),修改state.message的值
- 使用計算屬性將新的state.message值渲染到頁面
可以看出Vue.observable實際就是封裝了observe:
- 首先判斷是否包含__ob__這個屬性
- 例項化一個Observer物件:
- 由於本例項中傳入的不是陣列,進入walk()
- 在walk中遍歷key,並使用defineReactive$$1建立響應式物件
Walk through all properties and convert them into getter/setters. This method should only be called when value type is Object.
遍歷所有屬性並將它們轉換為getter/setter。僅當值型別為Object時才應呼叫此方法。
- 通過property.get進行取值,通過property.set進行賦值
- 接下來呼叫Object.defineProperty()給物件定義響應式屬性(Object.defineProperty是vue.js實現「響應式系統」的關鍵之一)
- enumerable,屬性是否可列舉,預設 false。
- configurable,屬性是否可以被修改或者刪除,預設 false。
- get,獲取屬性的方法。(進行依賴收集)(資料劫持)
- set,設定屬性的方法。(進行響應式更新)
- dep.notify():通過dep.notify()對觀察者watchers進行通知
- 然後state就成全域性響應式物件了,
拓展:
- 深入響應式原理 — Vue.js
- vue原理探索–響應式系統
- Vue setter/getter 是何原理?
- 深入解析Vue依賴收集原理
- Vue 核心之資料劫持
- Vue原始碼解讀之Dep,Observer和Watcher
思想來源:觀察者模式 vs 釋出訂閱模式
2.插槽 slot
官方文件:https://cn.vuejs.org/v2/guide/components.html#通過插槽分發內容
官方文件:https://cn.vuejs.org/v2/guide/components-slots.html
官方文件:https://cn.vuejs.org/v2/api/#v-slot
例項:插槽 slot
<body>
<div id="root">
<div>案例1:slot的基本用法</div>
<Test>
<template v-slot:header="{user}">
<div>自定義header({{user.a}})</div>
</template>
<template v-slot="{user}">
<div>自定義body({{user.b}})</div>
</template>
</Test>
</div>
<div id="root2">
<div>案例2:Vue2.6新特性 - 動態slot</div>
<Test>
<template v-slot:[section]="{section}">
<div>this is {{section}}</div>
</template>
</Test>
<button @click="change">switch header and body</button>
</div>
<script>
Vue.component('Test', {
template:
'<div>' +
'<slot name="header" :user="obj" :section="\'header\'">' +
'<div>預設header</div>' +
'</slot>' +
'<slot :user="obj" :section="\'body\'">預設body</slot>' +
'</div>',
data() {
return {
obj: { a: 1, b: 2 }
}
}
})
new Vue({ el: '#root' })
new Vue({
el: '#root2',
data() {
return {
section: 'header'
}
},
methods: {
change() {
this.section === 'header' ?
this.section = 'default' :
this.section = 'header'
}
}
})
</script>
</body>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
渲染結果:
<div id="root">
<div>案例1:slot的基本用法</div>
<div>
<div>自定義header(1)</div>
<div>自定義body(2)</div>
</div>
</div>
<div id="root2">
<div>案例2:Vue2.6新特性 - 動態slot</div>
<div>
<div>this is header</div>
預設body
</div>
<button>switch header and body</button>
</div>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
案例1中:
- 帶有
name="header"
的插槽為具名插槽,在案例中綁定了兩個變數,變數user
是元件的data
中定義的物件obj
,變數section
是字串'header'
- 不帶name屬性的插槽為匿名插槽(也叫預設插槽default),在案例中綁定了兩個變數,變數
user
是元件的data
中定義的物件obj
,變數section
是字串'body'
- 使用
元件Test
和template標籤
引入插槽(v-slot只能用在component和template上)
PS:
- 插槽
slot
不能直接獲取當前vue例項
的資料,只能獲取定義插槽時繫結的資料- 匿名插槽,之前為
slot-scope="{user}"
,新寫法寫全了是v-slot:default="{user}"
,省略寫法為v-slot:"{user}"
或v-slot="{user}"
v-slot
可簡寫為#
案例2中:
v-slot:[section]="{section}"
是動態插槽,section
預設為#root2
的data
中定義的'header'
- 點選按鈕便可呼叫
change()
方法,切換section
的值,然後兩個插糟的位置就會相互交換
其他用法可參考如下:
- 傳送門 => vue 2.6 插槽更新 v-slot 用法總結
- 傳送門 => Vue.js 你需要知道的 v-slot
- https://github.com/vuejs/rfcs/blob/master/active-rfcs/0001-new-slot-syntax.md
- 可利用動態插槽作為開關事件,切換 隱藏狀態 或是 元素位置 等
PS:developer tool 斷點除錯小技巧
- Call Stack:顯示當前斷點的環境呼叫棧
- Breakpoints:當前js斷點列表,新增的每個斷點都會出現在此處,點選列表中斷點就會定位到內容區的斷點上
- DOM Breakpoints:當前DOM斷點列表列表
- XHR Breakpoints:當前xhr斷點列表,可點選右側+新增斷點
- Event Listener Breakpoints:事件監聽器斷點設定處
- Event Listeners:當前事件監聽斷點列表