封装Vue组件的一些技巧
Posted 水香木鱼
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了封装Vue组件的一些技巧相关的知识,希望对你有一定的参考价值。
本文整理了开发Vue组件的一些技巧,包含大量代码示例。
一、开发环境
vue-cli3提供了非常方便的功能,可以快速编写一些测试demo,是开发组件必备的环境。
下面是安装使用步骤
全局安装vue-cli3
npm install -g @vue/cli
查看版本是否为3.x
vue -V
安装扩展,此后可以快速启动单个vue文件
npm install -g @vue/cli-service-global
快速启动demo文件
vue serve demo.vue
如果需要scss,则还需要在目录下安装sass-loader等。
自定义入口文件
如果需要(比如需要开发移动端的组件),可以在使用vue serve时自定义html入口文件,在根目录下编写index.html,并确保页面包含#app的dom即可。
引入公共混合文件
通过style-resources-loader在每个文件引入公共样式混合等,参考自动化导入
需要访问Vue全局对象
在某些时候需要放问全局Vue对象,如开发全局指令、插件时
import Vue from "vue"
import somePlugin from "../src/somePlugin"
Vue.use(somePlugin)
上面这种写法并不会生效,这是因为vue serve xxx.vue仅仅只能作为快速原型开发的方案,使用的Vue与 import引入的Vue不是同一个对象。一种解决办法是手动指定vue serve的入口文件
// index.js
import Vue from "../node_modules/vue/dist/vue.min"
import placeholder from "../src/placeholder/placeholder"
Vue.use(placeholder)
new Vue({
el: "#app",
template: ``,
created(){},
})
二、Vue的组件系统
Vue组件的API主要包含三部分:prop、event、slot
-
props 表示组件接收的参数,最好用对象的写法,这样可以针对每个属性设置类型、默认值或自定义校验属性的值,此外还可以通过type、validator等方式对输入进行验证
-
slot可以给组件动态插入一些内容或组件,是实现高阶组件的重要途径;当需要多个插槽时,可以使用具名slot
-
event是子组件向父组件传递消息的重要途径
单向数据流
参考:单向数据流-官方文档。
父级 prop 的更新会向下流动到子组件中,但是反过来则不行
单向数据流是Vue组件一个非常明显的特征,不应该在子组件中直接修改props的值
如果传递的prop仅仅用作展示,不涉及修改,则在模板中直接使用即可
如果需要对prop的值进行转化然后展示,则应该使用computed计算属性
如果prop的值用作初始化,应该定义一个子组件的data属性并将prop作为其初始值
从源码/src/core/vdom/create-component.js和/src/core/vdom/helpers/extract-props.js里可以看见,在处理props的取值时,首先从
function extractPropsFromVNodeData(){
const res = {}
const { attrs, props } = data
// 执行浅拷贝
checkProp(res, props, key, altKey, true) || checkProp(res, attrs, key, altKey, false)
return res
}
在子组件修改props,却不会修改父组件,这是因为extractPropsFromVNodeData中是通过浅复制将attrs传递给props的。
浅复制意味着在子组件中对对象和数组的props进行修改还是会影响父组件,这就违背了单向数据流的设计。因此需要避免这种情况出现。
组件之间的通信
这里可以参考:vue组件通信全揭秘,写的比较全面
父子组件的关系可以总结为 prop 向下传递,事件event向上传递
祖先组件和后代组件(跨多代)的数据传递,可以使用provide和inject来实现
此外,如果需要跨组件或者兄弟组件之间的通信,可以通过eventBus或者vuex等方式来实现。
“绕开”单向数据流
考虑下面场景:父组件将数据通过prop形式传递给子组件,子组件进行相关操作并修改数据,需要修改父组件的prop值(一个典型的例子是:购物车的商品数量counter组件)。
根据组件单向数据流和和事件通信机制,需要由子组件通过事件通知父组件,并在父组件中修改原始的prop数据,完成状态的更新。在子组件中修改父组件的数据的场景在业务中也是比较常见的,那么有什么办法可以“绕开”单向数据流的限制呢?
状态提升
可以参考React的状态提升,直接通过props将父元素的数据处理逻辑传入子组件,子组件只做数据展示和事件挂载即可
<template>
<div class="counter">
<div class="counter_btn" @click="onMinus">-</div>
<div class="counter_val">{{value}}</div>
<div class="counter_btn" @click="onPlus">+</div>
</div>
</template>
<script>
export default {
props: {
value: {
type: Number,
default: 0
},
onMinus: Function,
onPlus: Function
},
};
</script>
然后在调用时传入事件处理函数
<template>
<div>
<counter :value="counter2Val" :on-minus="minusVal" :on-plus="plusVal"></counter>
</div>
</template>
<script>
export default {
data() {
return {
counter2Val: 0,
}
},
methods: {
minusVal(){
this.counter2Val--
},
plusVal(){
this.counter2Val++
}
}
}
</script>
很明显,由于在每个父组件中都需要实现on-minus和on-plus,因此状态提升并没有从根本上解决问题。
v-model语法糖
Vue内置了v-model指令,v-model 是一个语法糖,可以拆解为 props: value 和 events: input。就是说组件只要提供一个名为 value 的 prop,以及名为 input 的自定义事件,满足这两个条件,使用者就能在自定义组件上使用 v-model
<template>
<div>
<button @click="changeValue(-1)">-1</button>
<span>{{currentVal}}</span>
<button @click="changeValue(1)">+1</button>
</div>
</template>
<script>
export default {
props: {
value: {
type: Number // 定义value属性
}
},
data() {
return {
currentVal: this.value
};
},
methods: {
changeVal(val) {
this.currentVal += parseInt(val);
this.$emit("input", this.currentVal); // 定义input事件
}
}
};
</script>
然后调用的时候只需要传入v-model指令即可
<counter v-model="counerVal"/>
使用v-model,可以很方便地在子组件中同步父组件的数据。在2.2之后的版本中,可以定制v-model指令的prop和event名称,参考model配置项
export default {
model: {
prop: 'value',
event: 'input'
},
// ...
}
获得组件实例的引用
在开发组件中,获取组件实例是一个非常有用的方法。组件可以通过 r e f s 、 refs、 refs、parents、$children等方式获得vm实例引用
-
$refs在组件(或者dom上)增加ref属性即可
-
$parents获取子组件挂载的父组件节点
-
$children,获取组件的所有子节点
这些接口返回的都是vnode,可以通过vnode.componentInstance获得对应的组件实例,然后直接调用组件的方法或访问数据。虽然这种方式多多少少有些违背组件的设计理念,增加了组件之间的耦合成本,但代码实现会更加简洁。
表单验证组件
通常情况下,表单验证是表单提交前一个十分常见的应用场景。那么,如何把表单验证的功能封装在组件内部呢?
下面是一个表单组件的示例,展示了通过获得组件的引用来实现表单验证功能。
首先定义组件的使用方式,
xm-form接收model和rule两个prop
model表示表单绑定的数据对象,最后表单提交的就是这个对象
rule表示验证规则策略,表单验证可以使用async-validator插件
xm-form-item接收的prop属性,对应form组件的model和rule的某个key值,根据该key从model上取表单数据,从rule上取验证规则
下面是使用示例代码
<template>
<div class="page">
<xm-form :model="form" :rule="rule" ref="baseForm">
<xm-form-item label="姓名" prop="name">
<input v-model="form.name"/>
</xm-form-item>
<xm-form-item label="邮箱" prop="email">
<input v-model="form.email"/>
</xm-form-item>
<xm-form-item>
<button @click="submit">提交</button>
</xm-form-item>
</xm-form>
</div>
</template>
<script>
import xmForm from "../src/form/form"
import xmFormItem from "../src/form/form-item"
export default {
components: {
xmForm,
xmFormItem,
},
data() {
return {
form: {
name: "",
email: ""
},
rule: {
name: [
{required: true, message: '用户名不能为空', trigger: 'blur'}
],
email: [
{required: true, message: '邮箱不能为空', trigger: 'blur'},
{type: 'email', message: '邮箱格式不正确', trigger: 'blur'}
],
}
}
},
methods: {
submit() {
// 调用form组件的validate方法
this.$refs.baseForm.validate().then(res => {
console.log(res)
}).catch(e => {
console.log(e)
})
}
}
}
</script>
接下来让我们实现form-item组件,其主要作用是放置表单元素,及展示错误信息
<template>
<label class="form-item">
<div class="form-item_label">{{label}}</div>
<div class="form-item_mn">
<slot></slot>
</div>
<div class="form-item_error" v-if="errorMsg">{{errorMsg}}</div>
</label>
</template>
<script>
export default {
name: "form-item",
props: {
label: String,
prop: String
},
data() {
return {
errorMsg: ""
}
},
methods: {
showError(msg) {
this.errorMsg = msg
}
}
}
</script>
form组件
通过calcFormItems获取每个xm-form-item的引用,保存在formItems中
暴露validate接口,内部调用AsyncValidator,并根据结果遍历formItems中每个表单元素的prop属性,处理对应的error信息
<template>
<div class="form">
<slot></slot>
</div>
</template>
<script>
import AsyncValidator from 'async-validator';
export default {
name: "xm-form",
props: {
model: {
type: Object
},
rule: {
type: Object,
default: {}
}
},
data() {
return {
formItems: []
}
},
mounted() {
this.calcFormItems()
},
updated() {
this.calcFormItems()
},
methods: {
calcFormItems() {
// 获取form-item的引用
if (this.$slots.default) {
let children = this.$slots.default.filter(vnode => {
return vnode.tag &&
vnode.componentOptions && vnode.componentOptions.Ctor.options.name === 'form-item'
}).map(({componentInstance}) => componentInstance)
if (!(children.length === this.formItems.length && children.every((pane, index) => pane === this.formItems[index]))) {
this.formItems = children
}
}
},
validate() {
let validator = new AsyncValidator(this.rule);
let isSuccess = true
let findErrorByProp = (errors, prop) => {
return errors.find((error) => {
return error.field === prop
}) || ""
}
validator.validate(this.model, (errors, fields) => {
this.formItems.forEach(formItem => {
let prop = formItem.prop
let error = findErrorByProp(errors || [], prop)
if (error) {
isSuccess = false
}
formItem.showError(error && error.message || "")
})
});
return Promise.resolve(isSuccess)
}
}
}
</script>
这样我们就完成了一个通用的表单验证组件。从这个例子中可以看出获取组件引用,在组件开发中是一个非常有用的方法。
封装API组件
一些组件如提示框、弹出框等,更适合单独的API调用方式,如
import MessageBox from '@/components/MessageBox.vue'
MessageBox.toast('hello)
如何实现制这种不需要手动嵌入模板里面的组件呢?原来,除了在通过在模板中嵌入组件到children挂载组件,Vue还为组件提供了手动挂载的方法$mount
let component = new MessageBox().$mount()
document.getElementById('app').appendChild(component.$el)
通过这种方式,我们就是可以封装API形式调用组件,下面是一个alert消息提示的接口封装
消息弹窗组件
一个消息组件就是在页面指定绘制展示提示消息的组件,下面是简单实现
<template>
<div class="alert">
<div class="alert-main" v-for="item in notices" :key="item.name">
<div class="alert-content">{{ item.content }}</div>
</div>
</div>
</template>
<script>
let seed = 0;
function getUuid() {
return 'alert_' + (seed++);
}
export default {
data() {
return {
notices: []
}
},
methods: {
add(notice) {
const name = getUuid();
let _notice = Object.assign({
name: name
}, notice);
this.notices.push(_notice);
// 定时移除,单位:秒
const duration = notice.duration;
setTimeout(() => {
this.remove(name);
}, duration * 1000);
},
remove(name) {
const notices = this.notices;
for (let i = 0; i <以上是关于封装Vue组件的一些技巧的主要内容,如果未能解决你的问题,请参考以下文章