The opening

I spent nearly half a month to look at radio components. I really found that MY foundation was very weak and I didn’t know a lot of things. I still need to learn more.

I’ve found that putting the code out doesn’t work very well because it has to be combined with the presentation. So from the beginning of this article, I’ll post my Github address, where the code I’m emulating will be placed, and if you’re interested, you can click on the link.

The main structure of radio

Take a look at the main structure of the Radio component

<template>
  <label class="el-radio">
    <span class="el-radio__input">
      <span class="el-radio__inner"></span>
        <input class="el-radio__original"> </span> <! -- keydown.stop stop events from bubbling --> <span class="el-radio__label"@keydown.stop> <slot></slot> <! <template v-if= <template v-if= <template v-if= <template v-if=! ""$slots.default">{{label}}</template>
      </span>
  </label>
</template>
Copy the code

A template code structure looks something like this, wrapped around the file with lable tags that extend the click range and ensure that the text and ICONS are clicked.

As you can see, the radio component does not use the native radio tag. This is because the native radio tag is not the same in different browsers. Therefore, we will hide the radio and write our own radio instead of the native one. Note that we cannot set the native radio to dispaly: None or visibility:hidden because we need to use the native radio to get focus and trigger the change event. How does Element do it?

opacity:0
radio
0
radio

The specific properties of the RADIO template

I’m going to put out the template, which is the body of the radio, and show you what the properties are

<template>
  <label
    class="el-radio"
    :class="[// radio size is valid only if border is true border && radioSize? 'el-radio--' + radioSize: ', // Whether to disable {'is-disabled': IsDisabled}, // Check whether the focus is here {'is-focus': focus}, // Display {'is-bordered': border}, // Check whether the current button {'is-checked' is selected: model === label} ]"
    role="radio"
    :aria-checked="model===label"
    :aria-disabled="isDisabled"
    :tabIndex="tabIndex"
    @keydown.space.stop.prevent="model = isDisabled ? model : label"
  >
    <span class="el-radio__input"
      :class="{ 'is-disabled': isDisabled, 'is-checked': model === label }"
    >
      <span class="el-radio__inner"></span>
        <input 
          ref="radio"
          class="el-radio__original"
          :value="label"
          type="radio"
          aria-hidden="true"
          v-model="model"
          @focus="focus = true"
          @blur="focus=false"
          @change="handleChange"
          :name="name"
          :disabled="isDisabled"
          tabindex="1"> </span> <! -- keydown.stop stop events from bubbling --> <span class="el-radio__label"@keydown.stop> <slot></slot> <! <template v-if= <template v-if= <template v-if= <template v-if=! ""$slots.default">{{label}}</template>
      </span>
  </label>
</template>
Copy the code
role = 'radio'
:aria-checked="model===label"
:aria-disabled="isDisabled"// These three lines are designed to provide functionality for people who are not comfortable using them. For example, when they're using a screen reader, the role tells the reader that this isa radio, Aria-Checked describes whether the radio is selected, and ARIa-disabled tells the reader that the button is unreadable. tabIndex="tabIndex"// Set whether to use the TAB key on the keyboard to select. -1 indicates that the option is not available, and 0 indicates that the option is availableCopy the code

Label The tabIndex on the label is calculated,

isGroup() {
      let parent = this.$parent
      while (parent) {
        if (parent.$options.componentName ! = ='ElTestRadioGroup') {
          parent = parent.$parent
        } else {
          // eslint-disable-next-line
          this._radioGroup = parent
          return true}}return false}, // Whether to disableisDisabled() {
      return this.isGroup
        ? this._radioGroup.disabled || this.disabled || (this.elTestForm || {}).disabled
        : this.disabled || (this.elTestForm || {}).disabled
    },
tabIndex() {
  return(this.isDisabled || (this.isGroup && this.model ! == this.label)) ? 1:0}Copy the code

This first iterates to see if the current radio is wrapped in a radio-group component and sets the isGroup value to true or false depending on whether it is wrapped in a radio-group component. Then judge isDisabled according to isGroup.

When isGroup is true, the value of isDisabled depends first on whether radioGroup isDisabled, then whether radio isDisabled, and finally whether radio is in a form. Depends on whether the form is disabled. When false, it depends on isGroup

The same tabIndex value depends on the disabled state, and when radio is in the radioGroup and the current radio is selected, the TAB key is not selected again, optimizing the experience.

In the calculation judgment of these pieces, it is worth noting that it does not use the if else judgment, but uses the and or short-circuit principle, which is worth learning.

&& judgment is the same true is true, the false is false, the operation if the expression values on the left is false, then it won’t execute the right of the expression, if left expression is true, will continue to implement the right expression | | judgment is a true is true, and false is false, operation if left expression evaluates to true, If the left expression is false, the right expression will continue;

Another thing worth learning about the template tag is vue’s event modifier. Event modifier. Vue adds the following modifiers to the event V-on

Question 1: How do you use passive?

Vue also supports key modifiers, mouse key modifiers, and key modifiers. A detailed explanation can be found in this article, which is written in great detail. Key modifier

With key modifiers introduced, this code is easy to understand

@keydown.space.stop.prevent="model = isDisabled ? model : label"
Copy the code

When the current radio is tab-selected and the space bar is hit on the keyboard, the native event is prevented (what native event happened? This is not known) and execute the code

model = isDisabled ? Model: label // Select radio on the keyboardCopy the code

Did you ever wonder if the Radio component did not have a click event, but when clicked it triggered a change in the model value? Because of the v-model rewrite, I printed the set and handleChange of the model

  model: {
      get() {},set(val) {
        console.warn(val)
       }
    },
    
    handleChange() {
      console.log('change')}Copy the code

After clicking the event trigger, the execution sequence is set->handleChange.

At this point we cannot but mention the implementation principle of the V-Model

The essence of v-model is a grammatical sugar (almost a bad word). It is essentially a combination of v-bind v-on, and the following two cases are equal

<input v-model="test"></input>

<input v-bind:value="test" v-on:input = "test = $event.target.value"

So let’s see why these two cases are equal.

From a source perspective, we actually trigger this function when we use the V-Model

functiongenRadioModel ( el: ASTElement, value: string, modifiers: ? ASTModifiers ) { const number = modifiers && modifiers.numberlet valueBinding = getBindingAttr(el, 'value') | |'null'
  valueBinding = number ? `_n(${valueBinding})` : valueBinding
  addProp(el, 'checked', `_q(${value}.${valueBinding})`)
  addHandler(el, 'change', genAssignmentCode(value, valueBinding), null, true)}Copy the code

You might notice why I added the checked property and the change event, right

The V-Model actually monitors different attributes and throws different events on different HTML tags

  • texT andtextareaElements usingvalueProperties andinputThe event
  • checkboxradioElements usingcheckedProperties andchangeThe event
  • selectwillvalueAs apropAnd will bechangeAs the event
  • On a custom componentv-modelusevalueProperties andinputEvent we can see the function on the source codegenRadioModelReally is to giveinputComponent addedcheckedProperties andchangeEvents. Source code forinputDifferent types of tags also do different processing (I put out part of the code here), the detailed process interested students can go to seeVUESource code or go to seeVue.js technology revealedA good book is worth reading!! Strong push
  if (el.component) {
    genComponentModel(el, value, modifiers)
    // component v-model doesn't need extra runtime return false } else if (tag === 'select') { genSelect(el, value, modifiers) } else if (tag === 'input' && type === 'checkbox') { genCheckboxModel(el, value, modifiers) } else if (tag === 'input' && type === 'radio') { genRadioModel(el, value, modifiers) } else if (tag === 'input' || tag === 'textarea') { genDefaultModel(el, value, modifiers) } else if (! config.isReservedTag(tag)) { genComponentModel(el, value, modifiers) // component v-model doesn't need extra runtime
    return false
  } else if(process.env.NODE_ENV ! = ='production') {
    warn(
      `<${el.tag} v-model="${value}">: ` +
      `v-model is not supported on this element type. ` +
      'If you are working with contenteditable, it\'s recommended to '+'wrap a library dedicated for that purpose inside a custom component.', el.rawAttrsMap['v-model'])}Copy the code

So when we introduce the RADIO component and use it, our code looks like this

<el-radio v-model="radio"></el-radio> is equivalent to <el-radio v-bind:value="radio" v-on:input="radio = #event.tagret.value"></el-radio>
Copy the code

Native input tags in the code

<input 
...
          v-model="model"
          type="radio". >Copy the code

Is converted to

<input  v-bind: checked="model" v-on: change="model=$event.target.checked">
Copy the code

When native Radio is clicked, the value of the Model changes, triggering the set

model: {
      get() {// Take the value of the group if the package is el-radio-groupreturn this.isGroup ? this._radioGroup.value : this.value
      },

      set(val) {
        if (this.isGroup) {
          this.dispatch('ElTestRadioGroup'.'input', [val])
        } else {
          this.$emit('input', val)
        }
        this.$refs.radio && (this.$refs.radio.checked = this.model === this.label)
      }
    },
Copy the code

Then call this.$emit(‘input’) to pass the value to our custom radio component, thus changing the value of our bound radio. Use on/emit listening to pass events.

This triggers the Dispatch event if it is radio-group, and the input event if it is not group.

 <input 
   ...
          @focus="focus = true"
          @blur="focus=false"
          @change="handleChange". >Copy the code

Finally, focus and blur events are triggered when the mouse focuses on radio and moves away, and handleChange events are triggered when the value of the model changes

    handleChange() {
      this.$nextTick(() => {
        this.$emit('change', this.model) // dispatch the event based on whether it is a group call this.isgroup && this.dispatch('ElTestRadioGroup'.'handleChange', this.model)
      })
    }
Copy the code

$emit(‘change’, ‘this.model’) emits a custom change event if the component has a custom change event.

Question 2

How to interrupt the vUE source point? At runtime, vue enforce a node_modules/vue/dist/vue. Runtime. Ems. Js, but after dis ` is packaged files, how can I to SRC /… Inside a separate single file interrupt point and view?

For example, a file like model.js that handles v-Model directives, how do I break the point and look at it at run time? consult

subsequent

Radio actually refers to the mixins property, since no single radio is emitted to the functions in the emitter file emitters. So I’ll talk about mixins in terms of the order in which the events are emitted in the next radio-group article.

conclusion

Feeling can start to read vUE source code, a lot of things still need to be combined with source code to understand ah, a radio to see nearly half a month, most spent on the source code. Hope you come on together. Have a problem hope everybody points out!