Vue3 | Mixin、自定義指令、Teleport傳送門、Render函數(shù)、插件 詳解 及 案例分析

完整原文地址見簡書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定義了numbercount兩個字段,
組件引入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,
混入后,組件可以直接使用Mixinmethods,
如有沖突,則以組件自身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>

運行效果:

初始:
動態(tài)賦值:

效果:


簡化上例 的 設(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>

即當自定義指令里邊,
只有mountedupdated兩個鉤子 且 這兩個鉤子的內(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】進行助力了:

將 蒙版節(jié)點 送到 body下第一層

使用<teleport>標簽將其包裹起來,指定to="body"傳送到 body:

... 
  template: `
            <div class="area">  
                <button @click="handleBtnClick">蒙版</button>
                <teleport to="body">
                    <div class="mask" v-show="show"></div>
                </teleport>    
            </div>`
...

運行效果:

可以看到結(jié)構(gòu)圖:

或者傳送到某個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>

運行效果同上例;

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容