【Vue3】自定义组件directive&app.use()

历史小剧场

崇祯很勤政,崇祯并非王国之君,弘光很昏庸,弘光活该倒霉,几百年来,我们都这样认为。

但我们之所以一直这样认为,只是因为有人这样告诉我们。

之所以有人这样告诉我们,是因为他们希望我们这样认为。 ---- 《明朝那些事儿》

app.directive

作用:注册自定义指令

自定义指令

指令钩子函数
  • created: 在绑定元素的attribute前或事件监听器前调用;
  • beforeMount: 在元素插入到DOM前调用;
  • mounted: 在绑定元素的父组件及它自己的所有子节点都挂载完成后调用(常用)
  • beforeUpdate: 绑定元素的父组件更新前调用
  • updated: 在绑定元素的父组件及它自己的所有子节点都更新后调用(常用)
  • beforeUnmount: 绑定元素的父组件卸载前调用
  • unmounted: 绑定元素的父组件卸载后调用
钩子参数

案例

ts 复制代码
export default {
    mounted(el: any, binding: any, vnode: any, prevNode: any) {
        console.log("tab mounted", el, binding, vnode, prevNode)
        const { activeClass, curIndex } = binding.value;
        const buttonList = el.querySelectorAll("button");
        buttonList[curIndex].classList.add(activeClass);
    },
    updated(el: any, binding: any, vnode: any, prevNode: any) {
        console.log("tab updated", el, binding, vnode, prevNode)
        const buttonList = el.querySelectorAll("button");
        let { activeClass, curIndex } = binding.oldValue;
        buttonList[curIndex].classList.remove(activeClass);
        activeClass = binding.value.activeClass;
        curIndex = binding.value.curIndex;
        buttonList[curIndex].classList.add(activeClass);
    }
}
  1. el: 指定绑定到的元素。
  2. binding: 对象,通常包含如下属性
    1. value: 传递给指令的值
    2. oldValue: 之前的值,仅在 beforeUpdate 和 updated 中可用
    3. arg: 传递给指令的参数
    4. modifers: 一个包含修饰符的对象
    5. instance: 使用该指令的组件实例
    6. dir: 指令的定义对象
  3. vnode: 代表绑定元素的底层VNode
  4. prevNode: 代表之前的渲染中指定所绑定元素的VNode。仅在 beforeUpdate 和 updated 钩子中可用
使用

这里,我们封装一个自定义v-tab指令来处理按钮点击切换样式变幻问题

定义一个组件 TestDirective.vue

js 复制代码
<!-- TestDirective.vue -->
<template>
    <div>
        <div class="tab" 
        @click="handleClick($event)"
        v-tab="{  
            activeClass: 'active',
            curIndex
        }">
            <button data-index="0">按钮一</button>
            <button data-index="1">按钮二</button>
            <button data-index="2">按钮三</button>
        </div>
    </div>
</template>

<script lang="ts">
import { ref } from 'vue'
import { tab } from '../directives'

export default {
    name: 'TestDirective',
    directives: {
        tab
    },
    setup() {
        const curIndex = ref<number>(0)
        const handleClick = (event: MouseEvent) => {
            const tar = event.target as HTMLElement
            const className = tar.className

            if (Object.is(className, '')) {
                const index = parseInt(tar.dataset?.index ?? "")
                curIndex.value = index
            }
        }

        return {
            handleClick,
            curIndex
        }
    }
}
</script>

<style lang="scss" scoped>
.tab {
    .active {
         background-color: skyblue;
         border: 2px solid slategrey;
         color: white;
    }
}
</style>

在src下面创建directives文件夹来存放自定义指令

index.ts

ts 复制代码
import tab from './tab'

export {
    tab
}

tab.ts

ts 复制代码
export default {
    mounted(el: any, binding: any, vnode: any, prevNode: any) {
        console.log("tab mounted", el, binding, vnode, prevNode)
        const { activeClass, curIndex } = binding.value;
        const buttonList = el.querySelectorAll("button");
        buttonList[curIndex].classList.add(activeClass);
    },
    updated(el: any, binding: any, vnode: any, prevNode: any) {
        console.log("tab updated", el, binding, vnode, prevNode)
        const buttonList = el.querySelectorAll("button");
        let { activeClass, curIndex } = binding.oldValue;
        buttonList[curIndex].classList.remove(activeClass);
        activeClass = binding.value.activeClass;
        curIndex = binding.value.curIndex;
        buttonList[curIndex].classList.add(activeClass);
    }
}

运行之后,我们可以鼠标点击按钮切换样式

app.use

安装插件。 必须带一个install()方法

封装插件

这里,我们自己封装一个MyUI插件,且在插件里有两个自定义组件:MyButton 和 MyInput

MyButton.vue

js 复制代码
<!-- MyButton.vue -->
<template>
    <div>
        <button class="my-button"><slot></slot></button>
    </div>
</template>

<script lang="ts">
export default {
    name: 'MyButton',
}
</script>

<style lang="scss" scoped>
    .my-button {
        background-color: #41b883;
        color: #fff;
        border: none;
        border-radius: 10px;
        width: 80px;
    }
</style>

MyInput.vue

js 复制代码
<!--  -->
<template>
    <div>
        <input class="my-input" type="text" placeholder="placeholderText" />
    </div>
</template>

<script lang="ts">
export default {
    name: 'MyInput',
    props: {
        placeholderText: {
            type: String,
            default: 'This is my input'
        }
    }
}
</script>

<style lang="scss" scoped>
    .my-input {
        width: 180px;
        border: 1px solid #ccc;
        border-radius: 4px;
        padding: 4px;
    }
</style>

index.ts

ts 复制代码
import MyButton from './MyButton.vue'
import MyInput from './MyInput.vue'

const componentsPool: Map<String, any> = new Map()
componentsPool.set(MyButton?.name ?? "", MyButton)
componentsPool.set(MyInput?.name ?? "", MyInput)

export default {

    install(app: any, options: any) {
        console.log("options => ", options)
        console.log("componentsPool => ", componentsPool)
        if (options?.components.length > 0) {
            // 按需加载
            options.components.map((com: String) => {
                componentsPool.has(com) && app.component(com, componentsPool.get(com))
            })
        } else {
            // 全部加载
            for (let [name, com] of componentsPool.entries()) {
                app.component(name, com)
            }
        }
    }
}

注意:在插件加载逻辑中,我们实现了按需加载。就是,如果我们在app.use()方法的第二个参数中传入components数组且长度大于0就进行按需加载,优化性能,否则的话,全部加载。

注册

我们在程序入口文件main.ts中进行注册

ts 复制代码
import MyUI from './libs/MyUI'
app.use(MyUI, {
    components: ['MyButton', 'MyInput']
})
使用

然后,我们创建一个TestUse.vue

js 复制代码
<!--  -->
<template>
    <div>
        <my-button>测试按钮</my-button>
        <my-input></my-input>
    </div>
</template>

<script lang="ts">
export default {
    name: "TestUse",
}
</script>

<style lang="scss" scoped>

</style>

按照上面我们运行结果则是两个都可以展示

如果,我们在注册的时候只加载按钮

ts 复制代码
app.use(MyUI, {
    components: ['MyButton']
})

则只展示按钮

相关推荐
编程百晓君1 小时前
一文解释清楚OpenHarmony面向全场景的分布式操作系统
vue.js
暴富的Tdy2 小时前
【CryptoJS库AES加密】
前端·javascript·vue.js
neeef_se2 小时前
Vue中使用a标签下载静态资源文件(比如excel、pdf等),纯前端操作
前端·vue.js·excel
℘团子এ2 小时前
js和html中,将Excel文件渲染在页面上
javascript·html·excel
z千鑫2 小时前
【前端】入门指南:Vue中使用Node.js进行数据库CRUD操作的详细步骤
前端·vue.js·node.js
生产队队长4 小时前
项目练习:element-ui的valid表单验证功能用法
前端·vue.js·ui
胡西风_foxww4 小时前
【es6复习笔记】Promise对象详解(12)
javascript·笔记·es6·promise·异步·回调·地狱
web137656076434 小时前
WebStorm 创建一个Vue项目
ide·vue.js·webstorm
秃头女孩y4 小时前
【React中最优雅的异步请求】
javascript·vue.js·react.js