vue组件开发
组件的本质:产生虚拟DOM
配置VueComponent所需的属性;
实例化VueComponent;
render();
virtual DOM;
DOM;
定义:
组件是可复用的vue实例,准确的说是VueComponent的实例,继承自Vue。
我们编写的组件代码本质为组件配置,框架后续会生成其构造函数,基于VueComponent。
优点:
增加代码的复用性、可维护性、可测试性。
使用场景:
通用组件:如按钮组件、输入框组件
业务组件:如登录组件、轮播组件
页面组件:如列表页、详情页面
特点
高内聚,低耦合
组件间通信方式
- props
- $emit / $on
- vuex
- $parent / $children
- $attrs / $listeners
- provide / inject
1、组件注册
1.1全局注册
import DtContainer from './components/DtContainer'
Vue.component('dt-container', DtContainer)
使用
<template>
<dt-container></dt-container>
</template>
一次注册,多次使用,但不容易追溯
1.2 局部(推荐,依赖可追溯)
import DtContainer from './components/DtContainer'
new Vue({
el: '#app',
component: {
'dt-container': DtContainer
}
})
2、传值 Props
export default {
name: 'DtContainer',
props: ['title']
}
// 若希望传的值是必填的字符串
props: {
title: {
type: 'String',
required: true,
defaultValue: '' //默认值
}
}
3、组件内部通知父元素变化
// DtButton.js
<template>
<button @click="handleClick">发生变化</button>
</template>
<script>
exports default {
methods: {
handleClick() {
this.$emit('clickme', {msg: hello})
}
}
}
</script>
父页面
<template>
<div id="app">
<dt-button @clickme="handleClick">点击</dt-button>
</div>
</template>
<script>
exports default {
components: {
DtButton
}
methods: {
handleClick(obj) {
console.log(obj.msg) // hello
}
}
}
</script>
4、插槽
组件
<win>
<template slot="head"> // 对应具名插槽
<h3>window</h3>
</template>
content... // 对应匿名插槽
<template v-slot:foot> // 对应具名插槽,v-slot为vue 2.6后的写法
<button>click</button>
</template>
// 获取prop
<template v-slot:todo="slotProps">
<h1>{{slotProps.first + slotProps.second}}</h1>
</template>
/** 第二种写法,解构插槽Prop
<template v-slot:todo="{first, second}">
<h1>{{first + second}}</h1>
</template>
*/
</win>
使用组件
//win.vue
<tempalte>
<div>
<div>
<slot name="head"></slot> // 具名插槽
</div>
<slot></slot> // 匿名插槽
<div>
<slot name="foot"></slot> // 具名插槽
</div>
<div>
<slot name="todo" :first="one" :second="two"></slot>
</div>
</div>
</tempalte>
5、provide 和 inject
provide 和 inject 主要为高阶插件/组件库提供用例。并不推荐直接用于应用程序代码中。且只能够由父组件向子组件提供数据。
// 父组件中
<script>
export default {
name: 'parent',
provide(){
return {
someValue: '由父组件提供'
}
},
data() {
}
}
</script>
// 子组件中
<script>
export default {
name: 'child',
inject: ['someValue'],
data() {
},
created() {
console.log(this.someValue) // 由父组件提供
}
}
</script>
简单实现form表单
1、Input
v-model是一个特殊的语法糖,相当于绑定了 :value 和 @input 两个,下面两个写法是一样的
<dt-input v-model='content'></dt-input>
<dt-input :value='content' @input='content= $event'></dt-input>
// Input.vue
<template>
<div>
<input :type="type" :value="inputValue" @input="inputHandler">
</div>
</template>
<script>
export default {
props: {
value:{
type:String,
default:''
},
type:{ // input 类型传递
type:String,
default:'text'
}
},
data() {
return {// 单向数据流原则:组件内不能修改props属性
inputValue: this.value
}
},
methods: {
inputHandler(e) {
this.inputValue = e.target.value;
// 通知父组件值更新
this.$emit('input', this.inputValue)
// 通知FormItem做校验,$parent 指的是FormItem
this.$parent.$emit('validate', this.inputValue)
}
},
}
</script>
2、FormItem
// FormItem.vue
<template>
<div>
<label v-if="label">{{label}}</label>
<div>
<slot></slot>
<!-- 校验错误信息 -->
<p v-if="validateStatus == 'error'" class="error">{{errorMessage}}</p>
</div>
</div>
</template>
<script>
import schema from "async-validator";
export default {
inject: ["form"], // 注入form,获取model和rules
props: ["label", "prop"],
data() {
return {
validateStatus: "",
errorMessage: ""
};
},
created() {
this.$on("validate", this.validate);
},
mounted() {
// 挂载到form上时,派发一个添加事件
if (this.prop) {
this.$parent.$emit("formItemAdd", this);
}
},
methods: {
validate() {
return new Promise(resolve => {
// 校验当前项:依赖async-validate
const descriptor = {
//校验规则
[this.prop]: this.form.rules[this.prop]
};
const validator = new schema(descriptor);
// 使用es6计算属性动态设置key
validator.validate({ [this.prop]: this.form.model[this.prop] }, errors => {
if (errors) {
// 校验失败
this.validateStatus = "error";
this.errorMessage = errors[0].message;
resolve(false);//校验失败
} else {
this.validateStatus = "";
this.errorMessage = "";
resolve(true);
}
});
});
}
}
};
</script>
<style scoped>
.error {
color: #f00;
}
</style>
3、Form
// Form.vue
<template>
<form>
<slot></slot>
</form>
</template>
<script>
export default {
name: 'diy-form',
provide() {
return {
// 将表单实例传递给后代
form: this
};
},
props: {
model: {
type: Object,
required: true
},
rules: {
type: Object
}
},
created() {
// 缓存需要校验的表单项
this.fields = [];
this.$on("formItemAdd", item => this.fields.push(item));
},
methods: {
async validate(callback) {
// 将FormItem数组转换为validate()返回的promise数组
const tasks = this.fields.map(item => item.validate());
console.log(tasks);
// 获取所有结果统一处理
const results = await Promise.all(tasks);
let ret = true;
results.forEach(valid => {
if (!valid) {
ret = false; // 只要一个失败就失败
}
});
callback(ret);
}
}
};
</script>
<style scoped>
</style>
组件使用
<template>
<div>
<!-- 自实现的表单 -->
<k-form :model="ruleForm" :rules="rules" ref="loginForm2">
<k-form-item label="用户名" prop="name">
<k-input v-model="ruleForm.name"></k-input>
</k-form-item>
<k-form-item label="密码" prop="pwd">
<k-input v-model="ruleForm.pwd" type="password"></k-input>
</k-form-item>
<k-form-item>
<el-button type="primary" @click="submitForm2()">登录</el-button>
</k-form-item>
</k-form>
{{ruleForm}}
</div>
</template>
<script>
import KInput from "./Input.vue";
import KFormItem from "./FormItem.vue";
import KForm from "./Form.vue";
export default {
components: { KInput, KFormItem, KForm },
data() {
return {
someValue: "some value",
ruleForm: {
name: "",
pwd: ""
},
rules: {
name: [
{ required: true, message: "请输入名称" },
{ min: 6, max: 10, message: "请输入6~10位用户名" }
],
pwd: [{ required: true, message: "请输入密码" }]
}
};
},
methods: {
submitForm2() {
this.$refs.loginForm2.validate(valid => {
if (valid) {
alert("提交登录!");
} else {
console.log("校验失败");
return false;
}
});
}
}
};
</script>
<style scoped>
</style>
思考:如何跨层级的$emit派发事件
// 拓展一个公共方法(目标组件名,派发事件,参数)
$dispatch(componentName, eventName, params) {
// 向父元素广播事件
let parent = this.$parent || this.$root
let name = parent.$options.name // 获取父元素的export default {name: "app",...}中的name值
// 向上循环查找
while (parent && (!name && name !== componentName)) {
parent = parent.$parent
if (parent) {
name = parent.$options.name
}
}
if (parent) {
/** Function.apply(obj,args)方法能接收两个参数
obj:这个对象将代替Function类里this对象
args:这个是数组,它将作为参数传给Function **/
parent.$emit.apply(parent, [eventName].concat(params))
}
}
// formItem.vue 中的 this.$parent.$emit("formItemAdd", this) 可修改为
this.dispatch('diy-form', 'formItemAdd', this)