1. 概述
1.1 說明
在項目過程中,會有很多重復功能在多個頁面中處理,此時則需要把這些重復的功能進行單獨拎出,編寫公用組件(控件)進行引用。在VUE中,組件是可復用的VUE實例,此時組件中的data必須是一個函數,因為data是一個函數時,每次引用此組件時相當於重新對返回對象進行獨立的拷貝(實例化/new),如果data不是一個函數而是一個對象,那么多個引用同一組件時其中一個引用更改數據,其他引用中的數據都會更改。由於業務的不同,組件中的數據交互也會不同。
1.2 父組件向組件傳遞數據(Prop)
Prop可以在組件上注冊一些自定義特性,當一個值傳遞給一個prop特性的時候,它就變成了那個組件(子組件)實例的一個屬性,可以像訪問data中的值一樣去訪問prop中的特性(屬性列表)。任何類型的值都可以傳給一個prop,prop使得其父子prop之間形成一個單向下行綁定,即數據是在父組件中更改后才綁定到子組件中,父組件每次發生更新,子組件中所有的prop都將會刷新為最新的值。
prop的常見書寫方式如:
-
- props: ['name', 'address', 'desc']
- props: { name: String, address: String, desc: String, userInfo: Object }
1.2.1 子組件示例代碼
子組件props中需要包含父組件流向的數據信息,以便顯示在子組件中(可以直接頁面綁定{{}},也可數據接收處理)。
<template>
<div class="child-wrap">
<p>姓名:{{name}}</p>
<p>性別:{{sex}}</p>
<p>年齡:{{age}}</p>
</div>
</template>
<script>
export default {
props:{
name:{
type:String,
default:''
},
sex:{
type:String,
default:''
},
age:{
type:Number,
default:0
}
}
}
</script>
<style scoped>
</style>
1.2.2 父組件示例代碼
父組件中對子組件傳遞的數據像使用屬性一樣的去傳遞。
<template>
<div class="parent-wrap">
<child-component :sex="pSex" :name="pName" :age="pAge"></child-component>
</div>
</template>
<script>
import child from './child/child'
export default {
data() {
return {
pSex:'男',
pName:'張三',
pAge:28
}
},
components: {
"child-component": child
},
}
</script>
<style scoped>
</style>
1.3 父組件調用子組件事件
父組件直接訪問子組件時,需要使用ref為子組件指定一個引用ID,渲染完成后即為指定子組件的實例,即可操作其內方法。
1.3.1 子組件示例代碼
子組件methods中需要包含父組件所調用的子組件方法,可以傳遞參數值從父組件至子組件,以便來更改或操作子組件中的某些數據。
<template>
<div class="child-wrap">
<button @click="confirmClick">子組件按鈕</button>
</div>
</template>
<script>
export default {
methods: {
confirmClick(val) {
console.log(val)
}
}
}
</script>
<style scoped>
</style>
1.3.2 父組件示例代碼
在父組件中,子組件使用ref來注冊引用信息,引用信息將會注冊在父組件的$refs對象上。
注意:ref被用來給元素或子組件注冊引用信息,如果是dom元素,引用只想的就是dom元素,如果是組件,引用就指向組件實例。
ref注冊時間:ref本身是作為渲染結果被創建的,在初始渲染的時候不能夠進行訪問它。
<template>
<div class="parent-wrap">
<child-component ref="child"></child-component>
<button @click="parentClick" class="btnClass">父組件按鈕</button>
</div>
</template>
<script>
import child from './child/child'
export default {
data() {
return {}
},
components: {
"child-component": child
},
methods: {
parentClick() {
this.$refs.child.confirmClick("父組件調用");
}
}
}
</script>
<style scoped>
</style>
1.4 子組件觸發父組件事件
在子組件里用$emit向父組件觸發一個事件,父組件監聽這個事件。
1.4.1 子組件示例代碼
子組件methods中去注冊能夠調用父組件方法的入口。
<template>
<div class="child-wrap">
<button @click="confirmClick">子組件按鈕</button>
</div>
</template>
<script>
export default {
methods: {
confirmClick() {
this.$emit('child-click','點擊了子組件按鈕')
}
}
}
</script>
<style scoped>
</style>
1.4.2 父組件示例代碼
父組件的子組件中使用事件進行監聽所需調用的方法,其中@后跟隨的是子組件中注冊的自定義事件名,后邊所調用的方法為父組件中所定義的方法名。
<template>
<div class="parent-wrap">
<child-component @child-click="childClick"></child-component>
<p>{{strChild}}</p>
</div>
</template>
<script>
import child from './child/child'
export default {
data() {
return {
strChild:''
}
},
components: {
"child-component": child
},
methods: {
childClick(val) {
this.$data.strChild=val;
}
}
}
</script>
<style scoped>
</style>
注意:也可直接props傳遞方法進行子組件調用父組件方法。
2. 數據交互
在實際項目應用中,很多時候需要實時的獲取到子組件中的數據信息,即子組件數據更改父組件數據。此時就需要監控子組件數據是否更改(watch),而子組件則需要進行計算其數據集(computed)
2.1 代碼示例
2.1.1 父組件示例代碼
子組件每次調用時都需要是重新實例化的(每次調用子組件才會是單獨的組件),故可以使用v-if來進行處理
<template>
<div>
<Button
type='primary'
@click='addPerson()'>新增</Button>
<Button
type='primary'
@click='editPerson()'>編輯</Button>
<Modal
v-model='childModalVisible'
title="新增人員"
width='800'
@on-cancel='cancleModal'
closable>
<child-update v-if="childModalVisible" ref="childUpdate" :data='objChild' />
<div slot='footer'>
<Button @click='cancleModal'>取消</Button>
<Button @click='submitModal' type='primary'>確定</Button>
</div>
</Modal>
<Row style="margin-top: 20px; background: #f5f5f5;font-size: 40px;font-weight: 600;">
<Col span="6">姓名:{{objChild.name}}</Col>
<Col span="6">性別:{{objChild.sex}}</Col>
<Col span="6">電話:{{objChild.phoneNo}}</Col>
<Col span="6">地址:{{objChild.address}}</Col>
</Row>
</div>
</template>
<script>
import ChildUpdate from '../components/updateChild'
export default {
name: "updateParent.vue",
components: {
ChildUpdate,
},
data(){
return {
childModalVisible:false,
objChild:{}
}
},
methods:{
addPerson(){
this.objChild={
name: '',
sex: 0,
phoneNo: '',
address:''
};
this.childModalVisible=true
},
editPerson(){
this.objChild={
name: '張三',
sex: 0,
phoneNo: '15136466666',
address:'浙江省杭州市'
}
this.childModalVisible=true
},
cancleModal(){
this.childModalVisible=false
},
submitModal(){
if(this.$refs.childUpdate.validateForm('childPersonForm')){
let bSubmit=true
if (this.objChild.phoneNo) {
const reg = /^[1][3,4,5,7,8][0-9]{9}$/
if (!reg.test(this.objChild.phoneNo)) {
bSubmit=false
}
}
if(bSubmit){
this.childModalVisible=false
} else {
this.$Message.error('請輸入正確的電話號碼!')
}
} else {
this.$Message.error('請輸入正確的人員信息!')
}
}
}
}
</script>
<style scoped>
</style>
2.1.2 子組件示例代碼
通過監控子組件數據更新去更新父組件數據信息,使用watch;
通過計算屬性去判斷父組件數據更新賦值於子組件數據,使用 computed
<template>
<div>
<Form
ref='childPersonForm'
:model='childPersonForm'
:rules='formRules'
:label-width='90'>
<Row>
<Col span='12'>
<FormItem label='姓名' prop='name'>
<Input
v-model='childPersonForm.name'
:maxlength='20'
type='text'
placeholder='請輸入'></Input>
</FormItem>
</Col>
<Col span='12'>
<FormItem label='性別' prop='sex'>
<Select v-model='childPersonForm.sex' clearable>
<Option
v-for='item in personSexOption'
:value='item.value'
:label='item.name'
:key='item.value'>{{item.name}}</Option>
</Select>
</FormItem>
</Col>
</Row>
<Row>
<Col span='12'>
<FormItem label='電話' prop='phoneNo'>
<Input
v-model='childPersonForm.phoneNo'
:maxlength='11'
type='text'
@on-blur='telNoValidate'
placeholder='請輸入'></Input>
</FormItem>
</Col>
<Col span='12'>
<FormItem label='住址' prop='address'>
<Input
v-model='childPersonForm.address'
:maxlength='20'
type='text'
placeholder='請輸入'></Input>
</FormItem>
</Col>
</Row>
</Form>
</div>
</template>
<script>
export default {
props: {
data: {
type: Object,
default() {
return {
name: '',
sex: 0,
phoneNo: '',
address:''
}
},
},
},
data() {
// eslint-disable-next-line
const nameRules = (rule, value, callback) => {
const reg = /[0-9]/
const regEn = /[`~!@#$%^&*()_+<>?:"{},\\.\/;'[\]]/im
const regCn = /[·!#¥(——):;“”‘、,|《。》?、【】[\]]/im
if (value === '') {
callback(new Error('姓名不能為空'))
} else if (regEn.test(value) || regCn.test(value)) {
callback(new Error('姓名不能包含特殊字符'))
} else if (reg.test(value)) {
callback(new Error('姓名不能包含數字'))
} else {
callback()
}
}
return {
formRules: {
name: [{ required: true, validator: nameRules, trigger: 'blur' }],
},
}
},
computed: {
personSexOption() {
return [{value:0,name:'男'},{value:1,name:'女'}]
},
childPersonForm() {
console.log(this.data)
return this.data
},
},
watch: {
childPersonForm: {
handler(oldVal, newVal) {
this.data.name = newVal.name
this.data.sex = newVal.sex
this.data.phoneNo = newVal.phoneNo
this.data.address = newVal.address
},
deep: true,
},
},
methods: {
telNoValidate() {
if (this.childPersonForm.phoneNo) {
const reg = /^[1][3,4,5,7,8][0-9]{9}$/
if (!reg.test(this.childPersonForm.phoneNo)) {
this.$Message.error('請輸入正確的電話號碼!')
}
}
},
/**
* 表單驗證
*/
validateForm(name) {
let bReturn = false
this.$refs[name].validate(valid => {
if (valid) {
bReturn = true
} else {
bReturn = false
}
})
return bReturn
},
},
}
</script>
<style scoped>
</style>
3. 父子組件生命周期鈎子執行順序
- 加載渲染過程
- 父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount->子mounted->父mounted
- 子組件更新過程
- 父beforeUpdate->子beforeUpdate->子updated->父updated
- 父組件更新過程
- 父beforeUpdate->父updated
- 銷毀過程
- 父beforeDestroy->子beforeDestroy->子destroyed->父destroyed
