完整原文地址見簡書http://www.itdecent.cn/p/dc7652457d2a
更多完整Vue筆記目錄敬請見《前端 Web 筆記 匯總目錄(Updating)》
本文內(nèi)容提要
- Mixin基礎(chǔ)
Mixin 之 methods
Mixin 之 自定義屬性
Mixin 之 生命周期
全局Mixin
Vue3之后,推薦使用Composition API 或者 插件 替代Mixin
- 1. 首先是邏輯不直觀,出了問題難以定位,可維護性差
- 2. 全局Mixin的維護性也很差
Vue中的 ref 和 $refs
自定義指令 directive
以上是全局定義的自定義指令,下面是 局部自定義指令
再例:再驗生命周期
再例2:根據(jù)v-show和v-if的特性不同,會觸發(fā)的生命周期鉤子 也不一樣:
自定義指令 結(jié)合 style 【自定義指令傳參】
再結(jié)合data 和 updated鉤子,將上例 動態(tài)化
簡化上例 的 設(shè)計技巧
打印binding對象
用上binding.arg,自定義更加靈活
- Teleport傳送門
- CSS基礎(chǔ)案例回顧——居中布局
- 局部蒙版
- Teleport傳送門 助力 全局蒙版
- 或者傳送到某個body下覆蓋全局的DOM節(jié)點上,
- 加上字體
- Render函數(shù)
- Vue傳統(tǒng)寫法
- 使用Render函數(shù)優(yōu)化
- 使用Render函數(shù) 生成多層嵌套UI
插件
- install參數(shù)初解讀
- 插件 —— 使用
provide提供數(shù)據(jù)給 子組件 使用- 插件 —— 自定義指令 供 (子)組件使用
- 插件 —— 拓展生命周期
- 插件 —— 拓展底層變量
數(shù)據(jù)校驗案例
- Mixin方案 —— 對數(shù)據(jù)做校驗 案例
- 每層迭代 增加監(jiān)聽
- 將 校驗mixin 封裝進 plugin
Mixin基礎(chǔ)
Mixin其實就是定義一個
命名模塊,可以包含data字段,
定義后可以 賦值 給一個 引用;
然后可以在某個組件中,
使用mixins:[ Mixin字段 ...]的方式,引入這個Mixin字段;
引入Mixin模塊的組件,可以直接使用引入 Mixin 模塊的內(nèi)容(data字段等),而不需要自身有事先定義;
如果組件本身有 自身定義的data字段 且與 引入的Mixin 模塊的data字段有沖突,
則以組件本身的字段為準;
例程1:
組件本身定義number一個字段,
Mixin定義了number和 count兩個字段,
組件引入Mixin后,使用數(shù)據(jù)時,number以組件為準,count則可以直接使用Mixin的:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hello World! heheheheheheda</title>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="heheApp"></div>
</body>
<script>
const myMixin = {
data() {
return {
number: 666,
count: 666
}
}
}
const app = Vue.createApp({
data() {
return {
number: 1
}
},
mixins: [myMixin],
template: `
<div>
<div>{{number}}</div>
<div>{{count}}</div>
</div>`
});
const vm = app.mount('#heheApp');
</script>
</html>
運行效果:

再例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hello World! heheheheheheda</title>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="heheApp"></div>
</body>
<script>
const myMixin = {
data() {
return {
number: 666,
count: 666
}
}
}
const myMixin2 = {
data() {
return {
biubiu: 'biubiubiu'
}
}
}
const app = Vue.createApp({
data() {
return {
number: 1
}
},
mixins: [myMixin, myMixin2],
template: `
<div>
<div>{{number}}</div>
<div>{{count}}</div>
<div>{{biubiu}}</div>
</div>`
});
const vm = app.mount('#heheApp');
</script>
</html>
運行效果:

Mixin 之 methods
Mixin 混入 methods 的規(guī)則同
data,
混入后,組件可以直接使用Mixin的methods,
如有沖突,則以組件自身的methods為準:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hello World! heheheheheheda</title>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="heheApp"></div>
</body>
<script>
const myMixin = {
created() {
console.log('mixin created');
},
methods: {
handleClick() {
console.log("mixin methods");
}
}
}
const myMixin2 = {
created() {
console.log('mixin2 created');
},
methods: {
handleClick() {
console.log("mixin2 methods");
}
}
}
const app = Vue.createApp({
data() {
return {
number: 1
}
},
created() {
console.log('rootApp created');
},
mixins: [myMixin, myMixin2],
methods: {
handleClick() {
console.log("rootApp methods");
}
},
template: `
<div>
<div>{{number}}</div>
<button @click="handleClick">testButton</button>
</div>`
});
const vm = app.mount('#heheApp');
</script>
</html>
運行效果:

Mixin 之 自定義屬性
Mixin 混入 自定義屬性 的 默認規(guī)則同
data/methods,
混入后,組件可以直接使用Mixin的自定義屬性,
如有沖突,則以組件自身的自定義屬性為準:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hello World! heheheheheheda</title>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="heheApp"></div>
</body>
<script>
const myMixin = {
myNumber: 1
}
const app = Vue.createApp({
mixins: [myMixin],
myNumber: 666,
template: `
<div>
<div>{{this.$options.myNumber}}</div>
</div>`
});
const vm = app.mount('#heheApp');
</script>
</html>
運行效果:
不過Mixin的自定義屬性這一塊比較特殊,開發(fā)者可以通過
app.config.optionMergeStrategies.[對應(yīng)沖突字段]自行定義優(yōu)先級策略;
app.config.optionMergeStrategies.[對應(yīng)沖突字段]對應(yīng)一個函數(shù),
其中一參為字段對應(yīng)的Mixin的值,二參為字段對應(yīng)的組件本身的值;
如下,自定義新的規(guī)則為——如果存在mixinValue,
默認優(yōu)先返回mixinValue,不存在再返回appValue;
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hello World! heheheheheheda</title>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="heheApp"></div>
</body>
<script>
const myMixin = {
myNumber: 1
}
const app = Vue.createApp({
mixins: [myMixin],
myNumber: 666,
template: `
<div>
<div>{{this.$options.myNumber}}</div>
</div>`
});
app.config.optionMergeStrategies.myNumber = (mixinValue, appValue) => {
return mixinValue || appValue;
}
const vm = app.mount('#heheApp');
</script>
</html>
運行效果:
Mixin 之 生命周期
Mixin混入生命周期時,規(guī)則 與
data、methods略不相同,
Mixin中的生命周期
與組件中的 沖突時,兩邊 都會執(zhí)行,
執(zhí)行順序,
先按mixins:[ Mixin字段 ...]引入的順序執(zhí)行完Mixin的鉤子,
最后執(zhí)行組件自己的鉤子;
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hello World! heheheheheheda</title>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="heheApp"></div>
</body>
<script>
const myMixin = {
created() {
console.log('mixin created');
}
}
const myMixin2 = {
created() {
console.log('mixin2 created');
}
}
const app = Vue.createApp({
data() {
return {
number: 1
}
},
created() {
console.log('rootApp created');
},
mixins: [myMixin, myMixin2],
template: `
<div>
<div>{{number}}</div>
</div>`
});
const vm = app.mount('#heheApp');
</script>
</html>

本例此前的Mixin都是局部Mixin??!在父組件中引入的Mixin,無法在子組件中使用
如下,父組件引入的Mixin【myMixin】,無法在子組件【child】中使用:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hello World! heheheheheheda</title>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="heheApp"></div>
</body>
<script>
const myMixin = {
data() {
return {
count: 666
}
},
created() {
console.log('mixin created');
},
methods: {
handleClick() {
console.log("mixin methods");
}
}
}
const app = Vue.createApp({
data() {
return {
number: 1
}
},
created() {
console.log('rootApp created');
},
mixins: [myMixin],
methods: {
handleClick() {
console.log("rootApp methods");
}
},
template: `
<div>
<div>{{number}}</div>
<child />
<button @click="handleClick">testButton</button>
</div>`
});
app.component('child', {
template:`<div>{{count}}</div>`
})
const vm = app.mount('#heheApp');
</script>
</html>
運行效果:

如在子組件也要用對應(yīng)
Mixin的字段,則子組件也要寫明mixins: [myMixin],自行引入:
...
app.component('child', {
mixins: [myMixin],
template:`<div>{{count}}</div>`
})
...
運行效果:

全局Mixin
使用
app.mixin()定義一個全局Mixin,
可以使得根組件及其所有子組件都自動注入這個全局Mixin,
無需再寫 似mixins: [myMixin]的引入語法:
例程:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hello World! heheheheheheda</title>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="heheApp"></div>
</body>
<script>
const app = Vue.createApp({
data() {
return {
number: 1
}
},
created() {
console.log('rootApp created');
},
methods: {
handleClick() {
console.log("rootApp methods");
}
},
template: `
<div>
<div>{{number}}</div>
<div>{{count}}</div>
<child />
<button @click="handleClick">testButton</button>
</div>`
});
app.mixin({
data() {
return {
count: 666
}
},
created() {
console.log('mixin created');
},
methods: {
handleClick() {
console.log("mixin methods");
}
}
})
app.component('child', {
template:`<div>{{count}}</div>`
})
const vm = app.mount('#heheApp');
</script>
</html>
運行效果:

Vue3之后,推薦使用Composition API 或者 插件 替代Mixin
--- Vue3之后,推薦使用Composition API 替代Mixin,
因為Mixin的可維護性其實不高;
1. 首先是邏輯不直觀,出了問題難以定位,可維護性差
像剛剛 混入自定義屬性的例子,運行的結(jié)果是返回1,
接盤俠要看為何返回1,需要去看到 mixin引入、找到對應(yīng)的 mixin模塊,
最后查閱 自定義的匹配策略,過程非常麻煩;
出了問題,要確認是 組件、mixin、沖突、匹配策略 等誰的鍋,也不容易;
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hello World! heheheheheheda</title>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="heheApp"></div>
</body>
<script>
const myMixin = {
myNumber: 1
}
const app = Vue.createApp({
mixins: [myMixin],
myNumber: 666,
template: `
<div>
<div>{{this.$options.myNumber}}</div>
</div>`
});
app.config.optionMergeStrategies.myNumber = (mixinValue, appValue) => {
return mixinValue || appValue;
}
const vm = app.mount('#heheApp');
</script>
</html>
2. 全局Mixin的維護性也很差
全局Mixin會對 注冊全局Mixin的 根組件及其所有子孫組件都自動混入內(nèi)容,
這個時候如果項目規(guī)模一大,管理起來就很麻煩,
首先
Mixin的內(nèi)容一大,往各種地方混入的時候就很難管理,
其次,
單子孫組件體量一大、數(shù)量一多,
容易 忽略了、忘了 定義的全局Mixin,也是個問題;
Vue中的 ref 和 $refs
科普文章《vue中的 ref 和 $refs》
console.log(this.$refs.input1) //<input type="text" id="input1">
console.log(document.getElementById('input1')) //<input type="text" id="input1">
這兩種方法獲得的都是Dom節(jié)點,
而$refs相對document.getElementById的方法,會減少獲取dom節(jié)點的消耗。
案例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hello World! heheheheheheda</title>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="heheApp"></div>
</body>
<script>
const app = Vue.createApp({
mounted() {
this.$refs.input1.focus();
},
template: `
<div>
<input ref="input">
<input ref="input1">
<input ref="input2">
</div>`
});
const vm = app.mount('#heheApp');
</script>
</html>
運行效果:
自定義指令 directive
使用自定義指令 可以封裝常用的邏輯,
使得常用的代碼模塊得到復(fù)用,提高效率;
使用自定義指令封裝focus邏輯,優(yōu)化上例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hello World! heheheheheheda</title>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="heheApp"></div>
</body>
<script>
const app = Vue.createApp({
template: `
<div>
<input v-focus>
</div>`
});
app.directive('focus', {
mounted(el) {
el.focus();
}
})
const vm = app.mount('#heheApp');
</script>
</html>
運行效果,自動聚焦:
以上是全局定義的自定義指令,下面是 局部自定義指令
同樣實現(xiàn)上例效果:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hello World! heheheheheheda</title>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="heheApp"></div>
</body>
<script>
const myDirective = {
focus: {
mounted(el) {
el.focus();
}
}
}
const app = Vue.createApp({
directives: myDirective,
template: `
<div>
<input v-focus>
</div>`
});
const vm = app.mount('#heheApp');
</script>
</html>
-再例:再驗生命周期
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hello World! heheheheheheda</title>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="heheApp"></div>
</body>
<script>
const app = Vue.createApp({
data() {
return {
hehe: true
}
},
template: `
<div>
<div v-show="hehe">
<input v-focus>
</div>
</div>`
});
app.directive('focus', {
beforeMount() {
console.log('beforeMount');
},
mounted(el) {
console.log('mounted');
el.focus();
},
beforeUpdate() {
console.log('beforeUpdate');
},
updated() {
console.log('updated');
}
})
const vm = app.mount('#heheApp');
</script>
</html>
運行效果:
再例2:
根據(jù)v-show和v-if的特性不同,會觸發(fā)的生命周期鉤子 也不一樣:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hello World! heheheheheheda</title>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="heheApp"></div>
</body>
<script>
const app = Vue.createApp({
data() {
return {
hehe: true
}
},
template: `
<div>
<div v-if="hehe">
<input v-focus>
</div>
</div>`
});
app.directive('focus', {
beforeMount() {
console.log('beforeMount');
},
mounted(el) {
console.log('mounted');
el.focus();
},
beforeUpdate() {
console.log('beforeUpdate');
},
updated() {
console.log('updated');
},
beforeUnmount() {
console.log('beforeUnmount');
},
unmounted() {
console.log('unmounted');
},
})
const vm = app.mount('#heheApp');
</script>
</html>
運行效果:
自定義指令 結(jié)合 style 【自定義指令傳參】
自定義指令 中的鉤子,
除了默認第一個參數(shù)【el】 為修飾的DOM節(jié)點外,
還可以有第二個參數(shù)【binding】,
這個參數(shù)可以把 使用 本自定義指令時,傳過來的參數(shù) 都 囊括其中;
如下,
定義css類【header】,指定為絕對布局樣式;
自定義指令pos,
鉤子接收兩個參數(shù)——el、binding;
使用指令時,傳入一個數(shù)值參數(shù)【80】,
這在指令中,會被接收,然后用于定義style布局樣式——
el.style.top = (binding.value + 'px');
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hello World! heheheheheheda</title>
<style>
.header {position: absolute}
</style>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="heheApp"></div>
</body>
<script>
const app = Vue.createApp({
data() {
return {
hehe: true
}
},
template: `
<div>
<div v-pos="80" class="header">
<input />
</div>
</div>`
});
app.directive('pos', {
mounted(el, binding) {
el.style.top = (binding.value + 'px');
}
})
const vm = app.mount('#heheApp');
</script>
</html>
運行效果:

換成180:
...
template: `
<div>
<div v-pos="180" class="header">
<input />
</div>
</div>`
...
運行效果:

再結(jié)合data 和 updated鉤子,將上例 動態(tài)化
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hello World! heheheheheheda</title>
<style>
.header {position: absolute}
</style>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="heheApp"></div>
</body>
<script>
const app = Vue.createApp({
data() {
return {
topMargin: 66
}
},
template: `
<div>
<div v-pos="topMargin" class="header">
<input />
</div>
</div>`
});
app.directive('pos', {
mounted(el, binding) {
el.style.top = (binding.value + 'px');
},
updated(el, binding) {
el.style.top = (binding.value + 'px');
}
})
const vm = app.mount('#heheApp');
</script>
</html>
運行效果:



簡化上例 的 設(shè)計技巧
例程:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hello World! heheheheheheda</title>
<style>
.header {position: absolute}
</style>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="heheApp"></div>
</body>
<script>
const app = Vue.createApp({
data() {
return {
topMargin: 66
}
},
template: `
<div>
<div v-pos="topMargin" class="header">
<input />
</div>
</div>`
});
app.directive('pos', (el, binding) => {
el.style.top = (binding.value + 'px');
})
const vm = app.mount('#heheApp');
</script>
</html>
即當自定義指令里邊,
只有mounted和updated兩個鉤子 且 這兩個鉤子的內(nèi)容和參數(shù)列表 是 完全一樣的話,
我們可以簡寫成下面的寫法,
即變對象為函數(shù),函數(shù)的內(nèi)容 為 鉤子中相同的內(nèi)容:
app.directive('pos', (el, binding) => {
el.style.top = (binding.value + 'px');
})
這種寫法 是 等價于上例的寫法的:
app.directive('pos', {
mounted(el, binding) {
el.style.top = (binding.value + 'px');
},
updated(el, binding) {
el.style.top = (binding.value + 'px');
}
})
打印binding對象
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hello World! heheheheheheda</title>
<style>
.header {position: absolute}
</style>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="heheApp"></div>
</body>
<script>
const app = Vue.createApp({
data() {
return {
topMargin: 66
}
},
template: `
<div>
<div v-pos:heheda="topMargin" class="header">
<input />
</div>
</div>`
});
app.directive('pos', (el, binding) => {
console.log(binding, 'binding');
el.style.top = (binding.value + 'px');
})
const vm = app.mount('#heheApp');
</script>
</html>
運行效果:
用上binding.arg,自定義更加靈活
直接v-pos:top="topMargin",
即[自定義指令]:[arg]="[value]"的結(jié)構(gòu);
如下例的寫法,用戶 既可以配置style的值,也可以 配置style的屬性:
如下,
配置為top的margin,數(shù)值是80:
<script>
const app = Vue.createApp({
data() {
return {
margin: 80
}
},
template: `
<div>
<div v-pos:top="margin" class="header">
<input />
</div>
</div>`
});
app.directive('pos', (el, binding) => {
el.style[binding.arg] = (binding.value + 'px');
})
const vm = app.mount('#heheApp');
</script>
運行效果:
配置為right的margin,數(shù)值是80:
<script>
const app = Vue.createApp({
data() {
return {
margin: 80
}
},
template: `
<div>
<div v-pos:right="margin" class="header">
<input />
</div>
</div>`
});
app.directive('pos', (el, binding) => {
el.style[binding.arg] = (binding.value + 'px');
})
const vm = app.mount('#heheApp');
</script>
運行效果:
配置為left的margin,數(shù)值是80:
<script>
const app = Vue.createApp({
data() {
return {
margin: 80
}
},
template: `
<div>
<div v-pos:left="margin" class="header">
<input />
</div>
</div>`
});
app.directive('pos', (el, binding) => {
el.style[binding.arg] = (binding.value + 'px');
})
const vm = app.mount('#heheApp');
</script>
運行效果:
CSS基礎(chǔ)案例回顧——居中布局
首先,
left: 50%;和top: 50%;
使得使用該CSS類的 DOM節(jié)點 的 左上角頂點,
在窗口的中點處:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hello World! heheheheheheda</title>
<style>
.area {
position: absolute;
left: 50%;
top: 50%;
width: 228px;
height: 336px;
background: paleturquoise;}
</style>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="heheApp"></div>
</body>
<script>
const app = Vue.createApp({
data() {
return {
}
},
template: `
<div class="area">
</div>`
});
const vm = app.mount('#heheApp');
</script>
</html>
運行效果:
再加上一筆,
transform: translate(-50%, -50%);使得組件在上面兩個margin之后,
讓本節(jié)點移動(-50%, -50%)的距離,
其實就是 左移和上移 分別為 節(jié)點寬高的一半 的距離:
<style>
.area {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
width: 228px;
height: 336px;
background: paleturquoise;}
</style>
運行效果:
局部蒙版
如下添加.mask這個蒙版樣式,
絕對布局,左上右下四方為0,即遍布父布局(<div class="area">):
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hello World! heheheheheheda</title>
<style>
.area {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
width: 228px;
height: 336px;
background: paleturquoise;}
.mask {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
background: #000;
opacity: 0.5;
}
</style>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="heheApp"></div>
</body>
<script>
const app = Vue.createApp({
data() {
return {
show: false
}
},
methods: {
handleBtnClick() {
this.show = !this.show;
}
},
template: `
<div class="area">
<button @click="handleBtnClick">蒙版</button>
<div class="mask" v-show="show"></div>
</div>`
});
const vm = app.mount('#heheApp');
</script>
</html>
運行,點擊按鈕,顯示蒙版:
Teleport傳送門 助力 全局蒙版
欲將上例中的【局部蒙版】升級成【全局蒙版】,
需要調(diào)用DOM將<div class="mask" v-show="show"></div>送到<body>的第一子組件位置,
這樣 蒙版節(jié)點css樣式的 遍布父布局的 特性,
就可以直接遍布 整個body 成為【全局蒙版】了,
這個時候就可以使用【Teleport】進行助力了:

使用
<teleport>標簽將其包裹起來,指定to="body"傳送到 body:
...
template: `
<div class="area">
<button @click="handleBtnClick">蒙版</button>
<teleport to="body">
<div class="mask" v-show="show"></div>
</teleport>
</div>`
...
運行效果:


或者傳送到某個body下覆蓋全局的DOM節(jié)點上,
...
<body>
<div id="heheApp"></div>
<div id="heheda"></div>
</body>
<script>
const app = Vue.createApp({
...
template: `
<div class="area">
<button @click="handleBtnClick">蒙版</button>
<teleport to="#heheda">
<div class="mask" v-show="show"></div>
</teleport>
</div>`
});
...
</script>
</html>
運行效果同上例,
結(jié)構(gòu)圖:

加上字體
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hello World! heheheheheheda</title>
<style>
.area {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
width: 228px;
height: 336px;
background: paleturquoise;}
.mask {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
background: #000;
opacity: 0.5;
color: skyblue;
font-size: 88px;
}
</style>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="heheApp"></div>
<div id="heheda"></div>
</body>
<script>
const app = Vue.createApp({
data() {
return {
show: false,
message: 'heheda'
}
},
methods: {
handleBtnClick() {
this.show = !this.show;
}
},
template: `
<div class="area">
<button @click="handleBtnClick">蒙版</button>
<teleport to="#heheda">
<div class="mask" v-show="show">{{message}}</div>
</teleport>
</div>`
});
const vm = app.mount('#heheApp');
</script>
</html>
運行效果:

Render函數(shù)
【template】標簽實際上在編譯的時候會生成一個render函數(shù);
我們可以直接使用render函數(shù)去生成UI;
render函數(shù) 提高了Vue的性能,且使其獲得跨平臺的能力;
首先假設(shè)有這么一個需求,
定義一個子組件,
接受調(diào)用它的父組件的一個參數(shù)level,
子組件 根據(jù)這個level顯示不同的DOM節(jié)點,
最基本的也許我們會寫成這樣:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hello World! heheheheheheda</title>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="heheApp"></div>
</body>
<script>
const app = Vue.createApp({
template: `
<my-title :level="2">
heheda
</my-title>
`
});
app.component('my-title', {
props: ['level'],
template:
`
<h1 v-if="level === 1"><slot /></h1>
<h2 v-if="level === 2"><slot /></h2>
<h3 v-if="level === 3"><slot /></h3>
<h4 v-if="level === 4"><slot /></h4>`
})
const vm = app.mount('#heheApp');
</script>
</html>
運行效果:
使用Render函數(shù)優(yōu)化
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hello World! heheheheheheda</title>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="heheApp"></div>
</body>
<script>
const app = Vue.createApp({
template: `
<my-title :level="6">
heheda
</my-title>
`
});
app.component('my-title', {
props: ['level'],
render() {
const { h } = Vue;
return h('h' + this.level, {}, this.$slots.default());
}
})
const vm = app.mount('#heheApp');
</script>
</html>
關(guān)鍵代碼:
render() {
const { h } = Vue;
return h('h' + this.level, {}, this.$slots.default());
}
h() 的 三個參數(shù):
標題等級【tagName】,
其他屬性的 鍵值對形式, 【attributes】
標題內(nèi)容;【text】
這里h()返回的是一個虛擬DOM【JS對象】,
虛擬DOM 簡要說就是 用JS映射【表示】一個真實DOM節(jié)點;
結(jié)構(gòu)類似于:{ tagName: 'h3', text: 'heheda', attributes: {} }Vue接收到
render函數(shù)返回的虛擬DOM之后,
會將其映射成真正的DOM節(jié)點并展示出來;
運行效果:

level改成3:
使用Render函數(shù) 生成多層嵌套UI
<script>
const app = Vue.createApp({
template: `
<my-title :level="1">
heheda
</my-title>
`
});
app.component('my-title', {
props: ['level'],
render() {
const { h } = Vue;
return h('h' + this.level, {}, [
this.$slots.default(),
h('h' + String(Number(this.level) + 1), {}, [
this.$slots.default(),
h('h' + String(Number(this.level) + 3), {},
this.$slots.default()
)
])
]);
}
})
const vm = app.mount('#heheApp');
</script>
運行效果:

插件
插件,即
plugin,可以用于封裝通用性的功能;
install鉤子,在插件運行時會回調(diào);
install參數(shù)初解讀
如圖,打印插件接收的app、plugin參數(shù):
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hello World! heheheheheheda</title>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="heheApp"></div>
</body>
<script>
const myPlugin = {
install(app, options) {
console.log(app, options);
}
}
const app = Vue.createApp({
template: `
<my-title />
`
});
app.component('my-title', {
template: `<div>heheda</div>`
})
app.use(myPlugin, {myTestKey: 'lululu'})
const vm = app.mount('#heheApp');
</script>
</html>
運行效果:
插件 —— 使用provide提供數(shù)據(jù)給 子組件 使用
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hello World! heheheheheheda</title>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="heheApp"></div>
</body>
<script>
const myPlugin = {
install(app, options) {
app.provide('myTestKey', "lululu");
}
}
const app = Vue.createApp({
template: `
<my-title />
`
});
app.component('my-title', {
inject: ['myTestKey'],
template: `<div>{{myTestKey}}</div>`
})
app.use(myPlugin, {})
const vm = app.mount('#heheApp');
</script>
</html>
運行效果:
插件 —— 自定義指令 供 (子)組件使用
...
<script>
const myPlugin = {
install(app, options) {
app.directive('focus', {
mounted(el) {
el.focus();
}
})
}
}
const app = Vue.createApp({
template: `
<my-title />
`
});
app.component('my-title', {
template: `
<div><input /></div>
<div><input v-focus /></div>
<div><input /></div>`
})
app.use(myPlugin, {})
const vm = app.mount('#heheApp');
</script>
...
運行效果:
插件 —— 拓展生命周期
<script>
const myPlugin = {
install(app, options) {
app.mixin({
mounted() {
console.log('mixin');
}
})
}
}
const app = Vue.createApp({
template: `
<my-title />
`
});
app.component('my-title', {
template: `
<div><input /></div>`
})
app.use(myPlugin, {})
const vm = app.mount('#heheApp');
</script>
運行效果:
可以看到打印了兩次,上次說過,
因為這是全局mixin,所以掛載的組件,根組件子組件都會打印!
插件 —— 拓展底層變量
app.config.globalProperties.$[變量名]可以 在底層拓展的拓展app私有字段;
這樣在使用時,只要看到$[變量名],
就可以知道是 自己底層定義的拓展,
而不是組件里定義的普通變量,也不是父組件傳遞過來的參數(shù),
語義上和可維護性上是比較有特性的;
<script>
const myPlugin = {
install(app, options) {
app.config.globalProperties.$heheDa = "heheda!";
}
}
const app = Vue.createApp({
template: `
<my-title />
`
});
app.component('my-title', {
mounted() {
console.log(this.$heheDa);
},
template: `
<div><input /></div>`
})
app.use(myPlugin, {})
const vm = app.mount('#heheApp');
</script>
運行效果:
Mixin方案 —— 對數(shù)據(jù)做校驗 案例
首先打印觀察rules對象:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hello World! heheheheheheda</title>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="heheApp"></div>
</body>
<script>
const app = Vue.createApp({
data() {
return { myName: 'zhao', age: 66}
},
rules: {
age: {
// validate: age => {return age > 23},
validate: age => age > 23,
message: 'too young, to simple'
},
myName: {
validate: myName => myName !== 'zhao',
message: 'heheda'
}
},
template: `
<div>name:{{myName}}, age:{{age}} </div>
`
});
app.mixin({
created() {
console.log(this.$options.rules);
for(let key in this.$options.rules) {
const item = this.$options.rules[key];
console.log(key, item);
}
}
})
const vm = app.mount('#heheApp');
</script>
</html>
運行效果:
可以看到,
this.$options.rules的內(nèi)容就是一個JSON Object Array,
而這array的每一個元素,都是一個rule對象,
它都是由[定義了校驗的變量名]: {校驗相關(guān)的信息}這么一個鍵值對組成;
這里這個key的引用其實就完全是data中對應(yīng)的字段,完全同步的!!
其中,
{校驗相關(guān)的信息}這個值,也是一個JSON Object,
其中主要信息主要是message、validate兩個鍵值對,
validate便是存儲檢驗規(guī)則,
message提供不通過檢驗時的信息;
每層迭代 增加監(jiān)聽
例程:
遍歷rules的key,
每一層迭代里——
對每一個key,都用這個key去獲取對應(yīng)的rule對象,賦給item,
然后對item的key【被校驗字段】設(shè)置監(jiān)聽,
當key/被校驗字段發(fā)生改變時,觸發(fā)回調(diào),
這時,可以用[rule對象].validate()去校驗值,然后返回結(jié)果,
如果校驗不通過,
可以用[rule對象].message獲取到我們準備好的話術(shù)??!
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hello World! heheheheheheda</title>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="heheApp"></div>
</body>
<script>
const app = Vue.createApp({
data() {
return { myName: 'zhao', age: 66}
},
rules: {
age: {
// validate: age => {return age > 23},
validate: age => age > 23,
message: 'too young, to simple'
},
myName: {
validate: myName => myName.length > 3,
message: 'heheda'
}
},
template: `
<div>name:{{myName}}, age:{{age}} </div>
`
});
app.mixin({
created() {
console.log(this.$options.rules);
for(let key in this.$options.rules) {
const item = this.$options.rules[key];
this.$watch(key, (value) => {
const result = item.validate(value);
if(!result) console.log(item.message);
})
console.log(key, item);
}
}
})
const vm = app.mount('#heheApp');
</script>
</html>
運行效果:
將 校驗mixin 封裝進 plugin
將mixin 封裝進plugin,
把這個plugin存進一個字段 并基于功能進行命名,
可讀性會比無名無姓的mixin高很多,也更加規(guī)范;
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hello World! heheheheheheda</title>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="heheApp"></div>
</body>
<script>
const app = Vue.createApp({
data() {
return { myName: 'zhao', age: 66}
},
rules: {
age: {
// validate: age => {return age > 23},
validate: age => age > 23,
message: 'too young, to simple'
},
myName: {
validate: myName => myName.length > 3,
message: 'heheda'
}
},
template: `
<div>name:{{myName}}, age:{{age}} </div>
`
});
const validatorPlugin = (app, options) => {
app.mixin({
created() {
console.log(this.$options.rules);
for(let key in this.$options.rules) {
const item = this.$options.rules[key];
this.$watch(key, (value) => {
const result = item.validate(value);
if(!result) console.log(item.message);
})
console.log(key, item);
}
}
})
};
app.use(validatorPlugin);
const vm = app.mount('#heheApp');
</script>
</html>
運行效果同上例;