组件化
- 提高开发效率
- 方便重复使用
- 提高维护性
- 便于多人协同开发
组件常用通信方式
- props
- eventbus
- vuex
- 自定义事件
- 边界情况
$parent
$children
$root
$refs
provide/inject - 非prop特性
$attrs
$listeners
- 边界情况
props 父传子
// child
props: { msg:String }
// 父
<hellow msg="hello">
自定义事件
// 子
this.$emit('add',goods)
// 父
<cart @add="Add($event)">
事件总线
任意两个组件之间传值
class Bus {
constructor(){
this.callbacks = {}
}
$on(name, fn){
this.callbacks[name] = this.callbacks[name] || []
this.callbacks[name].push(fn)
}
$emiet(name, args){
if(this.callbacks[name]){
this.callbacks[name].forEach(cb => cb(args))
}
}
}
// main.js
Vue.prototype.$bus = new Bus()
// 子1
this.$bus.$on('foo', '12345')
// 子2
this.$bus.$emit('foo')
vuex 全局数据管理
$parent/$root
兄弟组件间通信可通过共同的同祖辈搭桥
// brother1
this.$parent.$on('foo', '123')
// brother2
this.$parent.$emit('foo')
$children
父组件可通过$children访问子组件
// 父
this.$children[0].xxx = 'xxx'
注意:$children不能保证子元素顺序
$attrs/$listeners
包含了父作用域中不作为 prop 被识别 (且获取) 的特性绑定 ( class 和 style 除外)。当一个组件没有
声明任何 prop 时,这里会包含所有父作用域的绑定 ( class 和 style 除外),并且可以通过 vbind=“$attrs” 传入内部组件——在创建高级别的组
// 子 并未在props中声明
<p>{{$attrs.foo}}</p>
// 父
<hello foo="foo"/>
refs
获取子节点
<hello ref="hw" />
mounted(){
this.$refs.hw.xx = 'xxxx'
}
provide/inject
实现祖先和后代之间通信
// 祖先
provide() {
return { foo: 'foo'}
}
// 后代
inject: ['foo']
插槽 slot
匿名插槽
// 组件 comp
<div><slot><slot></div>
// 父
<comp>hello</comp>
具名插槽
将内容分发到子组件指定位置
// comp
<div>
<slot></slot>
<slot name="con"></slot>
</div>
// 父
<comp>
<template v-slot:default>111</template>
<template v-slot:con>222</template>
</comp>
作用域插槽
分发内容要用到子组件中的数据
// comp
<div><slot :foo="foo"></slot></div>
// 父
<comp>
<template v-slot:default = "slotProps">{{slotProps.foo}}</template>
</comp>
表单组件
kInput.vue
<template>
<div>
<!-- 自定义组件双向绑定::value @input -->
<!-- v-bind="$attrs"展开$attrs -->
<input :type="type" :value="value" @input="onInput" v-bind="$attrs">
</div>
</template>
<script>
export default {
inheritAttrs: false, // 设置为false避免设置到根元素上
props: {
value: {
type: String,
default: ''
},
type: {
type: String,
default: 'text'
}
},
methods: {
onInput(e) {
// 派发一个input事件即可
this.$emit('input', e.target.value)
// 通知父级执行校验
this.$parent.$emit('validate')
}
},
}
</script>
<style scoped>
</style>
index.vue
<template>
<div>
<!-- KForm -->
<KForm :model="userInfo" :rules="rules" ref="loginForm">
<!-- 用户名 -->
<KFormItem label="用户名" prop="username">
<KInput v-model="userInfo.username" placeholder="请输入用户名"></KInput>
</KFormItem>
<!-- 密码 -->
<KFormItem label="密码" prop="password">
<KInput type="password" v-model="userInfo.password" placeholder="请输入用户名"></KInput>
</KFormItem>
<!-- 提交按钮 -->
<KFormItem>
<button @click="login">登录</button>
</KFormItem>
</KForm>
</div>
</template>
<script>
import ElementTest from "@/components/form/ElementTest.vue";
import KInput from "@/components/form/KInput.vue";
import KFormItem from "@/components/form/KFormItem.vue";
import KForm from "@/components/form/KForm.vue";
export default {
data() {
return {
userInfo: {
username: "tom",
password: ""
},
rules: {
username: [{ required: true, message: "请输入用户名称" }],
password: [{ required: true, message: "请输入密码" }]
}
};
},
components: {
ElementTest,
KInput,
KFormItem,
KForm
},
methods: {
login() {
this.$refs["loginForm"].validate(valid => {
if (valid) {
alert("submit");
} else {
console.log("error submit!");
return false;
}
});
}
}
};
</script>
<style scoped>
</style>
KFormItem.vue
<template>
<div>
<!-- label -->
<label v-if="label">{{label}}</label>
<slot></slot>
<!-- 校验信息显示 -->
<p v-if="error">{{error}}</p>
</div>
</template>
<script>
// Asyc-validator
import Schema from "async-validator";
export default {
inject: ["form"],
data() {
return {
error: "" // error是空说明校验通过
};
},
props: {
label: {
type: String,
default: ""
},
prop: {
type: String
}
},
mounted() {
this.$on("validate", () => {
this.validate();
});
},
methods: {
validate() {
// 规则
const rules = this.form.rules[this.prop];
// 当前值
const value = this.form.model[this.prop];
// 校验描述对象
const desc = { [this.prop]: rules };
// 创建Schema实例
const schema = new Schema(desc);
return schema.validate({ [this.prop]: value }, errors => {
if (errors) {
this.error = errors[0].message;
} else {
// 校验通过
this.error = "";
}
});
}
}
};
</script>
<style scoped>
</style>
kForm.vue
<template>
<div>
<slot></slot>
</div>
</template>
<script>
export default {
provide() {
return {
form: this
};
},
props: {
model: {
type: Object,
required: true
},
rules: {
type: Object
}
},
methods: {
validate(cb) {
// 获取所有孩子KFormItem
// [resultPromise]
const tasks = this.$children
.filter(item => item.prop) // 过滤掉没有prop属性的Item
.map(item => item.validate());
// 统一处理所有Promise结果
Promise.all(tasks)
.then(() => cb(true))
.catch(() => cb(false));
}
}
};
</script>
<style scoped>
</style>
弹窗组件
特点:它们在当前vue实例之外独立存在,通常挂载于body;它们是通过JS动态创建的,不需要在任何组件中声明。
样式:
this.$create(Notice, {
title: '名称',
message: '提示信息',
duration: 1000
}).show();
create函数
import Vue from "vue";
// 创建函数接收要创建组件定义
function create(Component, props) {
// 创建一个Vue新实例
const vm = new Vue({
render(h) {
// render函数将传入组件配置对象转换为虚拟dom
console.log(h(Component, { props }));
return h(Component, { props });
}
}).$mount(); //执行挂载函数,但未指定挂载目标,表示只执行初始化工作
// 将生成dom元素追加至body
document.body.appendChild(vm.$el);
// 给组件实例添加销毁方法
const comp = vm.$children[0];
comp.remove = () => {
document.body.removeChild(vm.$el);
vm.$destroy();
};
return comp;
}
// 暴露调用接口
export default create;
Notice.vue
<template>
<div class="box" v-if="isShow">
<h3>{{ title }}</h3>
<p class="box-content">{{ message }}</p>
</div>
</template>
<script>
export default {
props: {
title: {
type: String,
default: "",
},
message: {
type: String,
default: "",
},
duration: {
type: Number,
default: 1000,
},
},
data() {
return {
isShow: false,
};
},
methods: {
show() {
this.isShow = true;
setTimeout(this.hide, this.duration);
},
hide() {
this.isShow = false;
this.remove();
},
},
};
</script>
<style>
.box {
position: fixed;
width: 100%;
top: 16px;
left: 0;
text-align: center;
pointer-events: none;
background-color: #fff;
border: grey 3px solid;
box-sizing: border-box;
}
.box-content {
width: 200px;
margin: 10px auto;
font-size: 14px;
padding: 8px 16px;
background: #fff;
border-radius: 3px;
margin-bottom: 8px;
}
</style>
使用
import create from "@/utils/create";
import Notice from "@/components/Notice";
const notice = create(Notice, {
title: "名称",
message: valid ? "请求登录!" : "校验失败!",
duration: 1000
});
notice.show()
使用extend创建
import Vue from 'vue'
function create(comp,props) {
const Ctor = Vue.extend(comp)
const com = new Ctor({propsData: props})
com.$mount()
document.body.appendChild(com.$el)
com.remove = () => {
document.body.removeChild(com.$el)
com.$destroy();
}
return com
}
变成插件形式
import Vue from 'vue'
import Notice from "@/components/Notice";
function create(component,props) {
const Ctor = Vue.extend(component)
const comp = new Ctor({propsData: props })
comp.$mount()
document.body.appendChild(comp.$el)
comp.remove = () => {
document.removeChild(comp.$el)
comp.$destroy()
}
return comp
}
// 注册插件
export default {
install(Vue) {
Vue.prototype.$Notice = function(options) {
return create(Notice, options)
}
}
}
// main.js
import create from "@/utils/create";
Vue.use(create)
// 使用
this.$Notice({
title:"标题",
message: "失败!",
duration:1000
}).show()