日常开发记录-radioGroup组件
日常开发记录-radioGroup组件
1.前提
在上一章的,我们实现了radio组件。
新增个radioGroup组件呢。
<template>
<div
class="q-radio-group"
role="radiogroup"
>
<slot></slot>
</div>
</template>
<script>
export default {
name: 'MyRadioGroup',
componentName: 'MyRadioGroup',
props: {
value: {},
disabled: Boolean,
name: String
},
created() {
this.$on('change', value => {
this.$emit('change', value);
});
},
watch: {
value(value) {
// 当值变化时,通知子组件
this.$nextTick(() => {
// this.$children.forEach(child => {
// if (child.$options.name === 'MyRadio') {
// child.$refs.radio.checked = child.label === value;
// }
// });
});
}
}
};
</script>
<style>
.q-radio-group {
display: inline-block;
}
</style>
原来的radio组件改写成
<template>
<label
class="q-radio"
:class="{
'is-disabled': isDisabled,
'is-checked': model === label
}"
>
<span class="q-radio__input"
:class="{
'is-disabled': isDisabled,
'is-checked': model === label
}"
>
<span class="q-radio__inner"></span>
<input
ref="radio"
type="radio"
class="q-radio__original"
:value="label"
:name="name"
:disabled="isDisabled"
v-model="model"
@change="handleChange"
/>
</span>
<span class="q-radio__label">
<slot></slot>
<template v-if="!$slots.default">{{label}}</template>
</span>
</label>
</template>
<script>
export default {
name: 'MyRadio',
props: {
value: {},
name: String,
label: {},
disabled: Boolean,
},
data() {
return {
// 预先定义 _radioGroup,提高代码可读性
_radioGroup: null
};
},
computed: {
isGroup() {
let parent = this.$parent;
while (parent) {
if (parent.$options.name !== 'MyRadioGroup') {
parent = parent.$parent;
} else {
this._radioGroup = parent;
return true;
}
}
return false;
},
model: {
get() {
return this.isGroup ? this._radioGroup.value : this.value;
},
set(val) {
if (this.isGroup) {
// 使用 $emit 向上传递事件
this._radioGroup.$emit('input', val);
} else {
this.$emit('input', val);
}
}
},
isDisabled() {
// 考虑组的禁用状态
return this.isGroup
? this._radioGroup.disabled || this.disabled
: this.disabled;
}
},
methods: {
handleChange() {
// 使用 $nextTick 确保状态已更新
this.$nextTick(() => {
// 触发 change 事件
this.$emit('change', this.model);
// 如果在组中,也通知组
if (this.isGroup) {
this._radioGroup.$emit('change', this.model);
}
});
}
}
}
</script>
引用:
<template>
<div id="app">
<el-container>
<el-header>
<h1>Vue 2 + Element UI Template</h1>
</el-header>
<el-main>
<el-row>
<el-col :span="12" :offset="6">
<el-card>
<div slot="header">
<span>欢迎使用</span>
</div>
<el-button type="primary">点击我</el-button>
</el-card>
</el-col>
</el-row>
</el-main>
<input type="radio" id="contactChoice1" name="contact" value="email" />
<label for="contactChoice1">电子邮件</label>
<input type="radio" id="contactChoice2" name="contact" value="phone" />
<label for="contactChoice2">电话</label>
<input type="radio" id="contactChoice3" name="contact2" value="mail" />
<label for="contactChoice3">邮件</label>
<MyRadio v-model="currentValue.aaa" name="aaa" @input="handleInput" @change="handleChange" label="男"></MyRadio>
<MyRadio v-model="currentValue.aaa" name="aaa" @input="handleInput" @change="handleChange" label="女">女</MyRadio>
<MyRadio v-model="currentValue2" name="bbb" @input="handleInput" @change="handleChange" label="未知">未知</MyRadio>
<MyRadioGroup v-model="currentValue.bbb" name="bbb" @change="handleGroupChange">
<MyRadio label="男"></MyRadio>
<MyRadio label="女">女</MyRadio>
</MyRadioGroup>
<el-button @click="changeGroupVal">改变group的绑定值</el-button>
<el-radio v-model="radio" label="1" @input="change">备选项</el-radio>
<el-radio v-model="radio" label="2">备选项</el-radio>
</el-container>
</div>
</template>
<script>
import MyRadio from './components/myRadio.vue'
import MyRadioGroup from './components/myRadioGroup.vue'
export default {
name: 'App'
, data () {
return {
currentValue: {
aaa: '男',
bbb: ''
},
currentValue2: '',
radio: '1'
}
},
components: {
MyRadio,
MyRadioGroup
},
methods: {
handleInput (event) {
console.log('input event', event)
},
changeGroupVal () {
this.currentValue.bbb = '女'
},
handleChange (val) {
console.log('change event', val)
},
change (val) {
console.log(val)
},
handleGroupChange (val) {
console.log('group', val)
}
}
}
</script>
主要在于radio的isGroup和原绑定值的改变,radiogroup就是个插槽。
2.问题:无限循环调用
会出现无限调用radiogroup组件emit出来的change事件。为什么呢?感觉没问题的应该。
原有的逻辑,事件传递链分析:
当您点击 RadioGroup 中的一个选项时,事件流程如下:
MyRadio 的 model setter 被触发,它调用 this._radioGroup.
e m i t ( ′ i n p u t ′ , v a l ) 同时, M y R a d i o 的 h a n d l e C h a n g e 方法被触发,它调用 t h i s . emit(‘input’, val) 同时,MyRadio 的 handleChange 方法被触发,它调用 this.
e
mi
t
(
′
in
p
u
t
′
,
v
a
l
)
同时,
M
y
R
a
d
i
o
的
han
d
l
e
C
han
g
e
方法被触发,它调用
t
hi
s
. emit(‘change’, this.model) 和 this._radioGroup.
e m i t ( ′ c h a n g e ′ , t h i s . m o d e l ) M y R a d i o G r o u p 监听 到 ′ c h a n g e ′ 事件,它又调用 t h i s . emit(‘change’, this.model) MyRadioGroup 监听到 ‘change’ 事件,它又调用 this.
e
mi
t
(
′
c
han
g
e
′
,
t
hi
s
.
m
o
d
e
l
)
M
y
R
a
d
i
o
G
ro
u
p
监听
到
′
c
han
g
e
′
事件,它又调用
t
hi
s
. emit(‘change’, value)。
感觉这样就完了呀。怎么会一直重复的调用change事件呢。
??????
3.解释
Vue 事件传播机制分析与无限循环原因解释
分析为什么修改事件名可以解决无限循环问题,并解释 Vue 中 $emit 和 $on 的工作原理。
问题根源: 组件自我监听
查看自己的 MyRadioGroup 组件代码:
这里发现了关键问题 - 组件正在监听自己发出的事件,并且在监听器中又发出同名事件,创建了一个经典的无限循环:
步骤1.MyRadio 组件触发 change 事件,并将值发送给 MyRadioGroup
步骤2.MyRadioGroup 接收 change 事件,通过
o n 监听器处理它步骤 3. 处理器调用 t h i s . on 监听器处理它 步骤3.处理器调用 this.
o
n
监听器处理它步骤
处理器调用
t
hi
s
. emit(‘change’, value),再次向自己发送 change 事件
步骤4.这又激活了同一个监听器,返回步骤 3,形成无限循环
Vue 事件机制解析
基本工作流程:
$on 将事件处理器存储在组件实例的 _events 对象中
$emit 查找并调用存储在 _events[eventName] 中的处理器
关键点: 当组件对自己发出的事件设置监听器时,每次调用 $emit 都会触发这些监听器,包括那些由同一组件设置的监听器。
4.解决
所以我们只要抛出的方法名变一下就行
this._radioGroup.$emit('handleChange', this.model);
//接受
created() {
this.$on('handleChange', value => {
this.$emit('change', value);
});
}