組件化
組件化是vue的核心思想,它能提高開發(fā)效率,方便重復使用,簡化調(diào)試步驟,提升整個項目的可維護 性,便于多人協(xié)同開發(fā)
組件通信
父組件 => 子組件:
- 屬性props
//child
props: {
msg: String
}
//parent
<HelloWorld msg="Welcome to Your Vue.js App" />
- 特性$attrs
//child
<p>attrs: {{$attrs.foo}}</p>
//parent
<HelloWorld foo="我是自組件attrs傳值" />
- 引用refs
//parent
<HelloWorld ref="comeon" />
mounted() {
this.$refs.hw.comeon = '我是parent'
},
//child
methods: {
sayHello() {
console.log(this.comeon)
}
},
- 子元素$children
//parent
this.$children[0].xx = 'xx'
子組件 => 父組件:自定義事件
// child
<div @click='testClick()'>
this is test
</div>
testClick() {
this.$emit('myclick','牛仔很忙');
}
// parent
<test @myclick="onyourclick($event)"/>
onyourclick($event){
console.log('onyourClick',$event)
},
兄弟組件:通過共同祖輩組件
通過共同的祖輩組件搭橋,parent 或 $root
// brother1
this.$parent.$on('foo', handle)
// brother2
this.$parent.$emit('foo')
祖先和后代之間
由于嵌套層數(shù)過多,傳遞props不切實際,vue提供了 provide/inject API完成該任務,實現(xiàn)祖先給后代傳值,缺點是非響應式
// ancestor
provide() {
return {foo: '肖邦的夜曲'}
}
// descendant
inject: ['foo']
任意兩個組件之間:事件總線 或 vuex
事件總線:創(chuàng)建一個Bus類負責事件派發(fā)、監(jiān)聽和回調(diào)管理,EventBus來作為一個橋梁的概念,就像是所有組件共用相同的事件中心,可以向該中心注冊發(fā)送事件,或接收事件,所以組件都可以上下平行地通知其他組件,但也就是太方便所以若使用不慎,就會造成難以維護的災難,因為才需要更完善的VUEX作為狀態(tài)管理中心,將通知的概念上升到共享狀態(tài)層次.
//increment.vue (child)
<button @click="increment()">+</button>
increment() {
this.$bus.$emit("incremented", {
num:this.num
})
}
//decrement.vue(child)
<button @click="decrease()">-</button>
decrease(){
this.$bus.$emit('decreased',{
num: this.num
})
}
//app.vue
<div class="increment">
<increment />
</div>
<div class="show-front"> {{fontCount}} </div>
<div class="decrement">
<decrement />
</div>
this.$bus.$on("incremented", ({num}) => {
this.fontCount += num
});
this.$bus.$on("decreased", ({num}) => {
this.fontCount -= num
})
slot 插槽
插槽可以分為具名插槽,作用域插槽,是在子組件中提供給父組件使用的一個占位符,用<slot></slot>表示,父組件可以在這個占位符中填充任何模版代碼,例如html ,組件等,填充的內(nèi)容會替換子組件的<slot></slot>標簽。
//子組件
<!-- 插槽 -->
<p><slot></slot></p>
<p><slot name="content" :foo="桃花諾"></slot></p>
//父組件 app.vue
<HelloWorld>
<template>abc</template>
<!-- 作用域插槽:顯示數(shù)據(jù)來自子組件 -->
<template v-slot:content="{foo}">content...{{foo}}</template>
</HelloWorld>
組件化實戰(zhàn)
模仿elementui 實現(xiàn)Form、FormItem、Input
首先實現(xiàn)input,功能實現(xiàn)數(shù)據(jù)收集,表單元素的輸入和校驗反饋,實現(xiàn)雙向綁定,監(jiān)聽@input,給:value賦值
// input.vue
<!-- input事件處理,值賦值 -->
<input :type="type" @input="onInput" :value="value" v-bind="$attrs">
export default {
inheritAttrs: false, //不繼承attrs
props: {
value: {
type: String,
default: ''
},
type: {
type: String,
default: 'text'
},
},
methods: {
onInput(e) {
// 轉(zhuǎn)發(fā)input事件即可
this.$emit('input', e.target.value)
// 通知校驗
this.$parent.$emit('validate')
}
},
}
formItem,表單項,用來校驗,顯示錯誤提示,顯示input,顯示label
<slot></slot> 用來顯示input 插槽
引入async-validator校驗包文件
<div>
<!-- label -->
<label v-if="label">{{label}}</label>
<slot></slot>
<!-- 錯誤信息 -->
<p class="error" v-if="error">{{error}}</p>
</div>
import Schema from 'async-validator';
export default {
inject: ['form'],
props: {
label: {
type: String,
default: ''
},
prop: {
type: String
}
},
data() {
return {
error: ''
}
},
mounted() {
this.$on('validate', () => {
this.validate();
})
},
methods: {
validate() {
// 0.獲取校驗規(guī)則和當前值
const rules = this.form.rules[this.prop];
const value = this.form.model[this.prop];
// 1.創(chuàng)建Schema實例
// this指向當前組件實例,this.prop是當前項的key
const schema = new Schema({
// [xxx] 計算屬性
[this.prop]: rules
})
// 2.使用該實例執(zhí)行校驗
return schema.validate({
[this.prop]: value
}, errors => {
if (errors) {
this.error = errors[0].message;
} else {
this.error = ''
}
})
}
},
}
form 表單 作為數(shù)據(jù)的容器傳遞給后代,實現(xiàn)父組件validate方法,收集所有item組件校驗成功與否,回調(diào)給子組件成功或者失敗參數(shù)。
<slot></slot>
export default {
provide() {
return {
// this指的是表單組件實例
form: this
};
},
props: {
model: {
type: Object,
required: true
},
rules: {
type: Object
}
},
methods: {
validate(cb) {
// 調(diào)用所有formitem的validate,只要一個失敗就失敗
// 結(jié)果是Promise數(shù)組
const tasks = this.$children
.filter(item => !!item.prop) // 留下帶prop屬性的formitem
.map(item => item.validate());
// 判斷所有結(jié)果
Promise.all(tasks)
.then(() => cb(true)) // 校驗通過
.catch(() => cb(false));// 校驗失敗
}
}
}
實現(xiàn)彈窗組件
彈窗之類的組件的特點是它們存在與vue實例之外的獨立存在,通常掛載與body,它們通過js獨立創(chuàng)建,不需要在任何組件中聲明,常用的姿勢:
this.$create(Notice, {
title: "社會你楊哥喊你來搬磚",
message: isValid?"提請求登錄!":"校驗失敗!",
duration: 2000
}).show();
接下來需要聲明一個Notice,還需要實現(xiàn)一個create方法,能夠創(chuàng)建我們的這個notice實例
先創(chuàng)建一個通知組件
一言不合就上代碼
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>
接下來需要實現(xiàn)一個能夠創(chuàng)建這個實例的方法 create.js
// 1.創(chuàng)建傳入組件實例
// 2.掛載它從而生成dom結(jié)構(gòu)
// 3.生成dom結(jié)構(gòu)追加到body
// 4.淘汰機制:不需要時組件實例應當被銷毀
import Vue from 'vue'
export default function create(Component, props) {
// 1.創(chuàng)建傳入組件實例
// Vue.extend({})
const vm = new Vue({
render(h) { // h即是createElement(tag, data, children)
// 返回虛擬dom
return h(Component, {props})
}
}).$mount(); // 只掛載,不設置宿主,意思是執(zhí)行初始化過程,但是沒有dom操作
document.body.appendChild(vm.$el)
// 獲取組件實例
const comp = vm.$children[0];
// 附加一個刪除方法
comp.remove = () => {
// 移除dom
document.body.removeChild(vm.$el)
// 銷毀組件
vm.$destroy();
}
return comp;
}
最后在全局掛載一下這個方法,就可以使用了
Vue.prototype.$create = create;
<
遺留問題
1, 在實現(xiàn)表單組件,查找父級元素的時候,使用this.$parent 查找,如果頁面嵌套結(jié)構(gòu)發(fā)生改變,這種查找方法就會失效,怎么解決?
2,彈窗組件中,創(chuàng)建組件實例,怎么用Vue.extend({}) 實現(xiàn)?