目录

日常开发记录-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.问题:无限循环调用

https://i-blog.csdnimg.cn/direct/a0c46ffd756d40e783b83b8b7b6db558.gif#pic_center

会出现无限调用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);
      });
    }