这篇文章主要给大家介绍了关于Vue中extends继承和组件复用性的相关资料,组件(Component)是Vue.js最强大的功能之一,组件可以扩展HTML元素,封装可重用的代码,需要的朋友可以参考下。
前言
提到extends继承,最先想到的可能是ES6中的class、TS中的interface、面向对象编程语言中中的类和接口概念等等,但是我们今天的关注点在于:如何在Vue中使用extends继承特性。
创建Vue组件实例的几种方式
再开始探讨Vue继承相关的内容之前,有必要回顾一下创建Vue组件实例的几种方式,个人总结如下:
构造函数方式:new Vue
这种方式是较为常见的,在Vue-cli脚手架构建的前端项目中,经常看到如下所示的代码段:
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
Vue.extend方式
# Vue.js开发库提供了Vue.extend()API,用于创建一个组件。
- 参数:{Object} options
- 用法:使用基础Vue构造器,创建一个"子类"。参数是一个包含组件选项的对象。
data 选项是特例,需要注意,在 Vue.extend() 中它必须是函数。
Vue.extend()方法的源码如下,内部主要是创建了一个Vue组件对象,并通过外部配置项,将其props、computed、mixin等选项设置为可用,最终将对象返回。
/**
* Class inheritance
*/
Vue.extend = function (extendOptions) {
extendOptions = extendOptions || {}; //外部配置项-即:Vue组件的选项配置
console.log(extendOptions);
var Super = this; //指向Vue自身实例的引用
var SuperId = Super.cid;
var cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {});
if (cachedCtors[SuperId]) {
return cachedCtors[SuperId];
}
var name = getComponentName(extendOptions) || getComponentName(Super.options);
if (name) {
validateComponentName(name);
}
var Sub = function VueComponent(options) {
this._init(options);
};
Sub.prototype = Object.create(Super.prototype);
Sub.prototype.constructor = Sub;
Sub.cid = cid++;
Sub.options = mergeOptions(Super.options, extendOptions);
Sub["super"] = Super;
// For props and computed properties, we define the proxy getters on
// the Vue instances at extension time, on the extended prototype. This
// avoids Object.defineProperty calls for each instance created.
if (Sub.options.props) {
initProps(Sub);
}
if (Sub.options.computed) {
initComputed(Sub);
}
// allow further extension/mixin/plugin usage
Sub.extend = Super.extend;
Sub.mixin = Super.mixin;
Sub.use = Super.use;
// create asset registers, so extended classes
// can have their private assets too.
ASSET_TYPES.forEach(function (type) {
Sub[type] = Super[type];
});
// enable recursive self-lookup
if (name) {
Sub.options.components[name] = Sub;
}
// keep a reference to the super options at extension time.
// later at instantiation we can check if Super's options have
// been updated.
Sub.superOptions = Super.options;
Sub.extendOptions = extendOptions;
Sub.sealedOptions = extend({}, Sub.options);
// cache constructor
cachedCtors[SuperId] = Sub;
return Sub;
};
通过查看Vue.extend()方法的源码,我们会发现,它内部是在调用Vue原型对象上面的_init()方法来完成组件初始化,通过如下图所示的一些核心配置,使其成为一个名副其实的Vue组件实例。
// expose real self
vm._self = vm;
initLifecycle(vm);
initEvents(vm);
initRender(vm);
callHook$1(vm, 'beforeCreate', undefined, false /* setContext */);
initInjections(vm); // resolve injections before data/props
initState(vm);
initProvide(vm); // resolve provide after data/props
callHook$1(vm.created');
/* istanbul ignore if */
if (config.performance && mark) {
vm._name = formatComponentName(vm, false);
mark(endTag);
measure("vue".concat(vm._name, "init"), startTag, endTag);
}
if (vm.$options.el) {
vm.$mount(vm.$options.el);
}
// expose real self
vm._self = vm;
initLifecycle(vm);
initEvents(vm);
initRender(vm);
callHook$1(vm, 'beforeCreate', undefined, false /* setContext */);
initInjections(vm); // resolve injections before data/props
initState(vm);
initProvide(vm); // resolve provide after data/props
callHook$1(vm.created');
/* istanbul ignore if */
if (config.performance && mark) {
vm._name = formatComponentName(vm, false);
mark(endTag);
measure("vue".concat(vm._name, "init"), startTag, endTag);
}
if (vm.$options.el) {
vm.$mount(vm.$options.el);
}
那么我们自己如何调用Vue.extend()方法创建组件呢?示例代码如下:
/**
* 方式1-Vue.extend-使用基础 Vue 构造器,创建一个组件
* PS:此种方式中,data必须为函数
* */
const IButton = Vue.extend({
name: "IButton",
template: `<button class="btn" @click="clickBtnHandler($event)">Click</button>`,
methods: {
clickBtnHandler(e) {
console.log(e.target.dataset);
},
},
});
Vue.component("i-button", IButton); //Vue.component用途之一:将组件注册到全局环境
Vue.component方式
# Vue.component(id, [definition] )
参数:
- (string) id
- {Function | Object) [definition]
用法:注册或获取全局组件。注册还会自动使用给定的 id 设置组件的名称
Vue.component()方法有两个作用,其①:将组件注册全局可用的组件;其②:以给定的id,创建一个全局范围内可用的组件。使用此接口创建一个Vue组件的示例代码如下:
/**
* 方式2-Vue.component-间接调用Vue.extend,创建一个组件
* PS:此种方式中,data必须为函数
* */
const IList = Vue.component("i-list", {
template: `<div>
<p>列表</p>
<ul>
<li v-for="n in number">{{n}}</li>
</ul></div>`,
data: function () {
return {
number: 5,
};
},
});
// Vue.component('i-list', IList); // Vue.component创建的组件无需再注册
render渲染函数方式
# render
- 类型:(createElement: () => VNode) => VNode
- 详细:
字符串横板的代替方案 允许你发挥 JawaScrint 最大的编程能力 该渲染函数接收一个
createElement 方法作为第一个参数用来创建 VNode。如果组件是一个函数组件,渲染函数还会接收一个额外的 context 参数,为没有实例的函数组件提供上下文信息。
■ Vue 选项中的 render 函数若存在,则 Vue 构造函数不会从 template 选项或通过 el
选项指定的挂载元素中提取出的 HTML 模板编译染函数。
也可以通过Vue.js提供的render()渲染函数创建一个Vue组件,如下示例代码,通过render函数的函数,根据props参数level来创建了一个级别为level的h标签,并提供插槽供开发者对其进行拓展。
//方式3:基于渲染函数构造函数式组件-[基于slot插槽方式提供组件内容1]
const ITitle = Vue.component(
"i-title",
{
render: function (createElement) {
return createElement(
'h' + this.level, // 标签名称
this.$slots.default // 子节点数组
)
},
props: {
level: {
type: Number,
required: true
}
}
});
对象方式
<template>
<div class="home"</div>
</template >
<script>
// @is an alias to /src
export default { // export default导出一个对象,创建了一个Home组件
name: 'Home',
components: { }
}
</script>
通过对象的形式定义组件-这也是我们在Vue前端应用开发中最常使用的方式,然后通过export default导出。示例代码如下:
//方式4-通过对象的形式定义组件-这也是我们在Vue前端应用开发中最常使用的方式,然后通过export default导出
const InfoBox = {
name: "InfoBox",
template: `<div class="box" :style="styleObject">{{content}}</div>`,
data() {
return {
content: "消息内容",
styleObject: {
boxSizing: "border-box",
padding: "25px",
width: "300px",
height: "200px",
backgroundColor: "rgba(0,0,0,0.3)",
},
};
},
};
Vue.component("info-box", InfoBox); //Vue.component用途之一:将组件注册到全局环境
Vue:extends继承特性
第一部分只介绍了如何创建一个组件,并没有介绍如何去提高一个组件的复用性。既然谈到复用性,可行的方法有很多,例如:slot插槽、mixix混入、Vue.directive自定义一个可复用的指令、通过Install方法开发一个可复用的插件、通过Vue.filter定义一个可复用的过滤器等。关于如上内容,Vue官网都有详细的介绍。
而接下来要讨论的就是Vue官网里面介绍比较含蓄的一种方法:借助extends实现组件的继承。
# extends
- 类型:Object Function
- 详细:允许声明扩展另一个组件(可以是一个简单的选项对象或构造函数),而无需使用 Vue.extend。这主要是为了便于扩展单文件组件。
这和 mixins 类似。
——更简洁的Vue.extend的替代方案- 示例:
var CompA = { ... } // 在没有调用 'Vue.extend' 时候继承 CompA var CompB = { extends: CompA, ... }
那么具体如何操作呢?我们先来定义一个基础列表组件IList,并以事件委托的方式为每一个列表元素注册点击事件,示例代码如下:
<!--
* @Description: IList列表组件,基于事件委托机制对列表事件回调做了优化处理
* @Author: Xwd
* @Date: 2023-02-16 00:21:49
* @LastEditors: Xwd
* @LastEditTime: 2023-02-19 17:03:25
* @Attention: 此列表组件的clickHandler()点击事件默认基于index下标来选择性的返回item的值,在一些场景下存在风险-->
<template>
<div class="i-list">
<p v-if="!!title" class="i-title">{{ title }}</p>
<!-- <div class="split-horizon"></div> -->
<div v-if="(list || []).length > 0" class="i-content" @click="clickHandler($event)">
<div class="i-item" v-for="(item, index) in list" :key="index">
<img class="i-item-icon" :src="item.image || noImage" />
<div class="i-item-body">
<div class="i-item-title">{{ item.title }}<span class="iconfont" title="地图定位" :data-id="item.id"
:data-index="index"></span></div>
<div class="i-item-desc" :title="item.desc">{{ item.desc }}</div>
</div>
</div>
</div>
</div>
</template>
<script>
import noImage from '@/assets/images/no.png';
export default {
name: "IList",
props: {
title: {
type: String,
required: false,
default: "",
},
list: {
type: Array,
required: false,
default: () => [],
}
},
mounted() { },
methods: {
/**
* 列表元素点击事件-回调函数
* @param {*} event
*/
clickHandler(event) {
const index = event.target.dataset.index;
if (typeof index !== "undefined" && index !== null) {
this.$emit("click", this.list[Number(index)], Number(index));
}
}
}
}
</script>
<style lang="less" scoped>
</style>
而由于我们存在一些不确定因素,例如:props中的list是否具有唯一id、点击回调函数中的具体逻辑是什么?所以我们可以将次组件作为一个基组件,在后续使用过程中,在子组件TownList.vue中通过extends的选项,来继承IList组件,实现复用。示例代码如下:
<!--
* @Description:
* @Author: Xwd
* @Date: 2023-02-19 16:50:16
* @LastEditors: Xwd
* @LastEditTime: 2023-02-19 16:56:57
-->
<script>
import IList from '@/components/layout/IList.vue';
export default {
name:"TownList",
extends:IList,
methods:{
/**
* 列表元素点击事件-回调函数,覆写父组件方法,基于元素id值重定义处理逻辑
* @param {*} event 事件对象
*/
clickHandler(event) {
const id = event.target.dataset.id;
console.log(`id=${id}`)
if (typeof id !== "undefined" && id !== null) {
const dataIndex = this.list.findIndex(item => item.id == id);
dataIndex !== -1 & this.$emit("click", this.list[dataIndex], dataIndex)
}
}
}
}
</script>
此处我们通过id来区分每一个元素,并覆写了父组件中的clickHandler——点击事件回调方法。最终效果如下:
此种方式的不足之处在于:无法在子组件中添加template节点,否则会直接覆盖掉原有的template模板。
番外小结
在Vue.js中,Vue.extend
是一个全局 API,它用于创建一个“子类”的构造函数,这个子类可以进一步被实例化以生成 Vue 组件的实例。这个方法主要用于在运行时动态地创建组件的构造函数,这在一些高级用法或插件开发中特别有用。
基本用法
// 创建一个 Vue 子类的构造函数
var Profile = Vue.extend({
template: '<p>{{firstName}} {{lastName}} aka {{alias}}</p>',
data: function () {
return {
firstName: 'Walter',
lastName: 'White',
alias: 'Heisenberg'
}
}
})
// 使用这个构造函数来创建 Vue 实例
new Profile().$mount('#mount-point')
在这个例子中,Vue.extend
被用来创建一个新的 Vue 组件构造函数 Profile
。然后,你可以使用这个构造函数来创建新的 Vue 实例,并通过 $mount
方法将其挂载到 DOM 上。
注意事项
- 使用
Vue.extend
创建的组件构造函数是“可复用的”,意味着你可以多次使用它来创建新的组件实例。 - 当你使用
Vue.extend
时,你实际上是在创建一个 Vue 组件的“类”,这个类可以包含模板、数据、计算属性、方法等。 - 通常情况下,我们会在
.vue
文件中定义组件,并通过 Vue 的单文件组件系统来注册和使用它们。然而,在某些情况下(如动态组件创建、高阶组件等),Vue.extend
可能会非常有用。 - 当你使用
Vue.extend
创建的组件时,你仍然需要遵循 Vue 的生命周期和响应式原理。
实际应用
Vue.extend
在一些特定的场景下非常有用,比如:
- 动态组件:当你需要根据某些条件动态地创建和销毁组件时。
- 高阶组件:当你需要创建一个可以接收其他组件作为参数并返回一个新组件的函数时。
- 插件开发:在开发 Vue 插件时,可能需要动态地创建和注册组件。
总之,Vue.extend
是 Vue.js 提供的一个强大的工具,它允许你在运行时动态地创建 Vue 组件的构造函数。然而,在大多数情况下,你可能不需要直接使用它,因为 Vue 的单文件组件系统和组件注册机制已经足够灵活和强大。
相关参考
Vue中extend基本用法_vue.extend-CSDN博客
Vue.js 高级技巧:深入理解 Vue.extend 方法-阿里云开发者社区