Vue的进阶属性

152 阅读4分钟

这篇文章主要记录一下Vue的一些进阶属性:

  • directives(指令)
  • mixins (混入【实际就是复制】)
  • extends (继承 【其实也是复制】)
  • provide (提供)
  • inject (注入)

Directives

除了内置指令:v-if, v-for, v-show, v-model外,Vue也允许我们自己注册自定义指令

例子:当页面加载一进来,输入框就属于聚焦状态

// 使用
<input v-focus />

// 注册一个全局自定义指令 `v-focus`
Vue.directive('focus', {
    // 当被绑定的元素插入到DOM中时...
    inserted: function(el) {
        // el就是当前指令绑定的元素
        el.focus()
    }
})

// 在组件中注册局部指令
directives: {
    focus: {
        inserted: function(el) {
            el.focus()
        }
    }
}

钩子函数

一个指令定义对象,提供了以下几个钩子函数:

  • bind: 只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置(相当于updated)
  • inserted: 被绑定的元素插入到DOM时调用(相当于mounted)
  • update: 所在组件的VNode更新时调用,但是可能发生在其子VNode更新前
  • componentUpdated: 指令所在组件的VNode及子VNode全部更新后调用
  • unbind: 只调用一次,指令与元素解绑时调用 (相当于destroyed)

常用的可能是:bind, inserted, unbind这三个

钩子函数的参数(el, binding, vnode, oldVnode)

指令钩子函数会被传入以下参数

  • el: 指令所绑定的元素,可以用来直接操作DOM
  • binding:一个对象,包含以下 property:
    • name:指令名,不包括 v- 前缀。
    • value:指令的绑定值,例如:v-my-directive="1 + 1" 中,绑定值为 2
    • oldValue:指令绑定的前一个值,仅在 update 和 componentUpdated 钩子中可用。无论值是否改变都可用。
    • expression:字符串形式的指令表达式。例如 v-my-directive="1 + 1" 中,表达式为 "1 + 1"
    • arg:传给指令的参数,可选。例如 v-my-directive:foo 中,参数为 "foo"
    • modifiers:一个包含修饰符的对象。例如:v-my-directive.foo.bar 中,修饰符对象为 { foo: true, bar: true }
  • vnode:Vue 编译生成的虚拟节点。移步 VNode API 来了解更多详情。
  • oldVnode:上一个虚拟节点,仅在 update 和 componentUpdated 钩子中可用。

自定义一个v-on的方法

<template>
    <div id="app">
        <button v-on2:click="handle">点击执行handle函数</button>
    </div>
    <script>
        export default {
            directives: {
                on2: {
                    inserted(el, binding) {
                        // 打印binding的参数
                        //name: "on2"
                        //rawName: "v-on2:click"
                        //value: ƒ bound handle() {}
                        //expression: "handle"
                        //arg: "click"
                        //modifiers: Object
                        //def: Object
                        // 由此可见, arg就是事件名,value就是要执行的函数
                        // 给元素绑定点击事件
                        el.addEventListener(binding.arg, binding.value);
                    },
                    // 页面卸载时解绑该事件
                    unbind(el, binding) {
                        el.removeEventListener(binding.arg, binding.value);
                    }
                }
            },
            methods: {
                handle() {
                    console.log('点击了按钮')
                }
            }
        }
    </script>
</template>

指令的作用

  • 主要用于DOM操作

    • Vue实例/组件用于数据绑定、事件监听、DOM更新
    • Vue指令主要目的就是原生DOM操作
  • 减少重复

    • 如果某个DOM操作你经常使用,就可以封装为指令
    • 如果某个DOM操作比较复杂,也可以封装为指令

实际上指令就是减少对DOM的重复操作

mixins

mixins: 混入,实际上就是复制的意思

  • 类比

    • directives的作用是减少DOM操作的重复
    • mixins的作用是减少data、methods、钩子等的重复
  • 场景描述

    • 假设我们需要在每个组件上添加name和time
    • 在created、beforeDestroy时,打出提示,并报出存活时间
    • 一共五个组件,请问怎么做
    • 一: 给每个组件添加data和钩子,共五次
    • 二: 使用mixins减少重复

App.vue

<template>
    <div id="app">
        <Child1 v-if="child1Visible" />
        <button @click="handle('child1Visible')">X</button>

        <Child2 v-if="child2Visible" />
        <button @click="handle('child2Visible')">X</button>

        <Child3 v-if="child3Visible" />
        <button @click="handle('child3Visible')">X</button>

        <Child4 v-if="child4Visible" />
        <button @click="handle('child4Visible')">X</button>

        <Child5 v-if="child5Visible" />
        <button @click="handle('child5Visible')">X</button>
    </div>
</template>
<script>
import Child1 from "@/components/Child1.vue";
import Child2 from "@/components/Child2.vue";
import Child3 from "@/components/Child3.vue";
import Child4 from "@/components/Child4.vue";
import Child5 from "@/components/Child5.vue";
export default {
    name: "App",
    components: {
        Child1,
        Child2,
        Child3,
        Child4,
        Child5,
    },
    data() {
        return {
            child1Visible: true,
            child2Visible: true,
            child3Visible: true,
            child4Visible: true,
            child5Visible: true,
        };
    },
    methods: {
        handle(params) {
            this[params] = !this[params];
        },
    },
};
</script>

这是app.vue里的内容,引入五个子组件,点击对应的按钮显示隐藏对应的子组件,这样就能出发created和beforeDistroy的钩子函数

child1.vue 内容

<template>
    <div>Child1</div>
</template>
<script>
export default {
    data() {
        return {
            name: "Child1",
            beginTime: null,
        };
    },
    created() {
        this.beginTime = Date.now();
        console.log(`我是${this.name}诞生了`);
    },
    beforeDestroy() {
        let diffTime = Date.now() - this.beginTime;
        console.log(`我是${this.name}结束了, 存活了${diffTime} ms`);
    },
};
</script>

那么:如果要实现上面的场景,五个子组件里都得加上Child1.vue一样的内容。

太多重复的了吧....

用mixins实现

父组件不变,创建一个mixins文件夹

ab8fd76f2d2f0f4d4c82b2e56cf06f3.png

myMixin.js

export default {
    data() {
        return {
            name: undefined,
            beginTime: null,
        };
    },
    created() {
        if (!this.name) {
           throw new Error("name is nodefined");
        }
        this.beginTime = Date.now();
        console.log(`我是${this.name}诞生了`);
    },
    beforeDestroy() {
        let diffTime = Date.now() - this.beginTime;
        console.log(`我是${this.name}结束了, 存活了${diffTime} ms`);
    },
};

Child1.vue

<template>
    <div>Child1</div>
</template>
<script>
import myMixin from '@/mixins/myMixin.js'
export default {
    mixins: [myMixin],
    data() {
        return {
            name: "Child1",
        };
    },
};
</script>
  • 创建一个mixin的js文件,把重复的data, methods, 钩子等传入进去,然后在需要调用的文件里,调用mixins:[]就行了
  • mixins就是把这部分公用的智能导入到组件里

所以mixins实际上就是复制

extends

  • extends: 继承
  • 它是比mixins更抽象一点的封装
  • 场景跟mixins一样,接着上面的代码,在src目录上创建一个js文件

1686930391571.png

MyVue.js

import Vue from "vue";
import myMixin from '@/mixins/myMixin.js'
const MyVue = Vue.extend({
    mixins: [myMixin]
});
export default MyVue

Child1.vue

<template>
    <div>Child1</div>
</template>
<script>
import MyVue from '@/MyVue.js'
export default {
    extends: MyVue,
    data() {
        return {
            name: "Child1",
        };
    },
};
</script>

总结: 个人感觉跟mixins差不多,都是复制,除非有多个mixins,才用extends,否则感觉用mixins就可以了

provide和inject

提供和注入

  • 功能: 父组件里的属性和方法,在子孙组件里面调用

  • 场景: 换肤场景

  • 三个文件: app.vue, Child1.vue, Button.vue

  • app.vue文件

<template>
    <div id="app" :class="`app theme-${themeName}`">
        <Child1 />
    </div>
</template>
<script>
import Child1 from "@/components/Child1.vue";
export default {
    name: "App",
    components: {
        Child1,
    },
    // 提供data和method可以让子孙组件们可以调用
    provide() {
        return {
            themeName: this.themeName,
            changeTheme: this.changeTheme,
        };
    },
    data() {
        return {
            themeName: "blue",
        };
    },
    methods: {
        changeTheme() {
            this.themeName = this.themeName == "blue" ? "red" : "blue";
        },
    },
};
</script>
<style>
.app {
    width: 100%;
    height: 100vh;
}
.app.theme-blue {
    background: blue;
}
.app.theme-red {
    background: red;
}
</style>

这是app.vue,引入Child1.vue子组件,换肤主要是:class="`app theme-${themeName}`"这个代码,改变变量themeName,依靠css来换背景

  • Child1.vue文件
<template>
    <Button />
</template>
<script>
import Button from "@/components/Button.vue";
export default {
    components: {
        Button,
    },
};
</script>

Child1.vue这个文件只是调用了Button.vue文件,没有别的操作

  • Button.vue文件
<template>
<div>
    <button @click="changeTheme">换肤,当前{{ themeName }}色</button>
</div>
</template>
<script>
export default {
    inject: ["themeName", "changeTheme"],
};
</script>
  • 通过inject注入可以获取到app.vue中provide的变量和方法
  • 这里的themeName这个变量是一个字符串,它和app.vue里面的themeName 没有任何关系,即使在Button.vue里面改变这个变量,app.vue里面的这个变量也不会变。。。在app.vue里面改变themeName变量,Button.vue里的这个themeName变量不会跟着改变
  • 那么在Button.vue里面只能调用themeName这个变量,怎么修改app.vue里的themeName这个变量呢?
  • 在app.vue里定义个changeTheme函数修改themeName变量,然后在Button.vue里面调用changeTheme函数

总结

  • directives 指令

    • 全局用Vue.directive('X', {...})
    • 局部用options.directives
    • 作用是减少DOM操作的相关重复代码
  • mixins 混入

    • 全局用Vue.mixin({...})
    • 局部用options.mixins: [mixin1, mixin2]
    • 作用是减少options里的重复
  • extends 继承/扩展

    • 全局用Vue.extend({...})
    • 局部用options.extends: {...}
    • 作用跟mixins差不多,只是形式不同
  • provide / inject 提供和注入

    • 祖先提供东西,后代注入东西
    • 作用是大范围,隔N代共享信息