32
头图

Preface

Vue is simple and easy to use in many places. For example, it has built-in 32+ modifiers, which can easily prevent bubbling, prevent default events, mouse event processing, system keyboard events, etc., so that we can quickly get business done. It's so convenient! ! !

If you delay you for 15 minutes, you can gain:

  1. The meaning and use of 32+ modifiers (including event modifiers, mouse modifiers, form modifiers, system modifiers, etc.)
  2. How to use webpack to dynamically register vue routing, no more handwriting routing configuration!

The examples in the article are all on the github source code , or you can click to see the example

How to dynamically register routes?

Each modifier example in the article is carried by a page, so smart you don’t want to manually import dozens of .vue files and configure routing.

Is there any way to help us automatically complete the route registration?

1. File directory structure

The directory structure (other file directories have been removed) is roughly as follows
├── package.json
└── src
    ├── App.vue
    ├── main.js
    ├── router.js
    └── views
        ├── About.vue
        ├── Home.vue
        └── modifiers
            ├── capture.vue
            ├── once.vue
            ├── order.vue
            ├── passive.vue
            ├── prevent.vue
            ├── self.vue
            └── stop.vue
            └── ...

2. Desired routing configuration

The final vue-router looks like this, the most important parts of each configuration are path , name and component
[
  {
    "path": "/home",
    "name": "home",
    "component": {
      "name": "Home",
      "methods": {},
      "staticRenderFns": [],
      "_compiled": true,
      "_scopeId": "data-v-fae5bece",
      "beforeCreate": [
        null
      ],
      "beforeDestroy": [
        null
      ],
      "__file": "src/views/Home.vue"
    }
  },
  {
    "path": "/modifiers/capture",
    "name": "modifiersCapture",
    "component": {
      "name": "capture",
      "methods": {},
      "staticRenderFns": [],
      "_compiled": true,
      "_scopeId": "data-v-63b4eeee",
      "beforeCreate": [
        null
      ],
      "beforeDestroy": [
        null
      ],
      "__file": "src/views/modifiers/capture.vue"
    }
  },
  ... // 其他路由配置
]

3. require.context realizes dynamic registration routing

With webpack require.context , you can easily realize the mapping from the above directory to the routing configuration. The source code is as follows
const registerRoutes = () => {
  const contextInfo = require.context('./views', true, /.vue$/)
  const routes = contextInfo.keys().map((filePath) => {
    // filePath 形如 ./Home.vue、./modifiers/capture.vue
    // path我们希望是/home、/modifiers/capture
    // 所以需要把开头的./和.vue都替换为空
    const path = filePath.toLowerCase().replace(/^\.|\.vue/g, '')
    // name的话将/home、/modifiers/capture转成小驼峰即可
    // 把开头的/先替换掉,再把第一个/后的单词变成大写就可以了
    const name = path.replace(/^\//, '').replace(/\/(\w)/, ($0, $1) => $1.toUpperCase())
    // 通过require去读取.vue文件内容
    const component = require(`./views${filePath.replace(/^\./, '')}`).default

    return {
      path,
      name,
      component
    }
  })

  return routes
}

effect

After the above simple processing, the dynamic registration of routing is complete! You can also click vue-demos view the effect

dynamic-router.gif

Event modifier

1. Two ways to stop bubbling


<template>
  <div class="parent" @click="onClickParent">
    我是爸爸
    <div class="child" @click="onClickChild">
      我是儿子
    </div>
  </div> 
</template>

export default {
  name: 'stop',
  methods: {
    onClickParent () {
      console.log('我是爸爸')
    },
    onClickChild () {
      console.log('我是儿子')
    }
  }
}

When you click hit the child node , it will not only print I am a son, but also I am a father, because of the bubbling event. Is there any way to prevent the event of the ?

stop2.gif

1 .stop

Just add the .stop modifier to prevent the event from bubbling in a simple way, which is very convenient, isn't it?

When the .stop modifier is added, only will appear. I am a son

<template>
  <div class="parent" @click="onClickParent">
    我是爸爸
    <div class="child" @click.stop="onClickChild">
      我是儿子
    </div>
  </div> 
</template>

stop.gif

2. event.stopPropagation

Of course, we can also prevent bubbling event.stopPropagation However, the practice of modifiers is more recommended, so that your function will focus more on logical processing, without worrying about the details of DOM events
export default {
  name: 'stop',
  methods: {
    onClickChild (event) {
      console.log('我是儿子')
      event.stopPropagation()
    }
  }
}

stop.gif

2. Two ways to prevent default events

There are two ways to prevent bubbling in vue. What about preventing default events?

1 .prevent

<template>
  <div class="prevent">
    <a href="https://juejin.cn/" @click="onNoPrevent">点击跳转掘金</a>
    <br />
    <br />
    <a href="https://juejin.cn/" @click.prevent="onPrevent">阻止默认事件,无法跳转掘金</a>
  </div>
</template>

export default {
  name: 'prevent',
  methods: {
    onNoPrevent () {
      console.log('未阻止默认事件')
    },
    onPrevent () {
      console.log('阻止默认事件')
    }
  }
}

Just add .prevent easily prevent default events

prevent.gif

2.event.preventDefault()

Like preventing bubbling, we can also prevent default events preventDefault
export default {
  name: 'prevent',
  methods: {
    onPrevent (event) {
      console.log('阻止默认事件')
      event.preventDefault()
    }
  }
}

3 .capture

By default, the event stream is in the form of 161849e41ee8a1 bubbling (from the inside to the outside). What if you want to capture (from the outside to the inside) in the form of
<template>
  <div class="capture parent" @click.capture="onClickParent">
    父节点
    <div class="child" @click.capture="onClickChild">自节点</div>
  </div>
</template>

export default {
  name: 'capture',
  methods: {
    onClickParent () {
      console.log('我是父节点')
    },
    onClickChild () {
      console.log('我是子节点')
    }
  }
}

Without the catpture modifier, click on the child node will successively print that I am the parent node and I am the child node. After adding it, it will be the other way around

capture.gif

4 .self

The event callback function will only be triggered event.target
<template>
  <div class="self" @click.self="onClickSelf">
    <div class="inner" @click="onClickInner"></div>
  </div>
</template>

export default {
  name: 'self',
  methods: {
    onClickSelf () {
      console.log('我是self节点')
    },
    onClickInner () {
      console.log('我是inner节点')
    }
  }
}

Without the self modifier, clicking on the inner node will also trigger the self event. After adding it, only the element that triggered the event itself is self , will print out I am a self node

self.gif

Pause: How to understand the order of modifiers?

I have reviewed 4 modifiers, they are easy to understand when used alone, but pay attention to the sentence on the official website

image.png

How to understand Let's take a look at two chestnuts

<template>
  <div class="order">
    <div class="order-0">
      <a href="https://juejin.cn/" class="order-parent" @click.self.prevent="onClickParent">
        我是父节点,会跳转掘金
        <br />
        <span class="order-child" @click="onClickChild">
          我是子节点
        </span>
      </a>
      <hr />
    </div>
    <div class="order-2">
      <a href="https://juejin.cn/" class="order-parent" @click.prevent.self="onClickParent">
        我是父节点,无法跳转掘金
        <br />
        <span class="order-child" @click="onClickChild">
          我是子节点
        </span>
      </a>
    </div>
  </div> 
</template>

export default {
  name: 'order',
  methods: {
    onClickParent () {
      console.log('我是父节点')
    },
    onClickChild () {
      console.log('我是子节点')
    }
  }
}

You can guess what will happen to the above code. Are the following three points clear?

  1. First of all, it is clear that clicking on the child nodes above and below will not trigger the click event of the parent node.
  2. Clicking on the parent node below will print out I am the parent node , but will not jump to Nuggets
  3. Click on the parent will print out I am a parent , not jump Nuggets

But click on the child node above, will the parent node jump to the Nuggets? The answer is will

Why?

a@click.self.prevent="onClickParent" means that when the clicked element is the a element itself, the default event will be blocked (can explain 3, no jump will occur), and the onClickParent callback will be executed.

When the span element is clicked, due to bubbling, the click event will be passed to a, but at this time a will determine that the event is not triggered by itself, so will not prevent the default event (the jump will also occur at this time) ), of course, it will not trigger the onClickParent callback

In the same way, let’s analyze a@click.prevent.self="onClickParent"

Regardless of whether it is a child node or its own click, the default event is blocked first, and the onClickParent callback function will be executed only when the triggering click event is the a element itself.

order.gif

Looking back, do you understand the meaning of the sequence of events?

image.png

5. once

As the name suggests, the event will only be triggered once
<template>
  <div class="once" @click.once="onClickOnce">
    只触发一次
  </div>
</template>

export default {
  name: 'once',
  methods: {
    onClickOnce () {
      console.log('once,我只会触发一次点击事件回调')
    }
  }
}

After a click is triggered, no matter what I click, the callback will not be triggered.

once.gif

6 .native

We know that on custom components, we can only listen to custom events. Some native events (such as click) cannot be triggered directly, but using the .native modifier can help us do this.

native.vue

<template>
  <div class="native-custom">
    <input type="text" @keydown="onKeydown">
  </div>
</template>

export default {
  name: 'nativeCustom',
  methods: {
    onKeydown () {
      this.$emit('onKeydown')
    }
  }
}

custom.vue

<template>
  <div class="native">
    <!-- 加上.native之后原生事件才得以监听成功 -->
    <NativeCustom @onKeydown="onKeydown" @click.native="onClick" />
  </div>
</template>

import NativeCustom from '../../components/native.vue'

export default {
  name: 'native',
  components: {
    NativeCustom
  },
  methods: {
    onKeydown () {
      console.log('onKeydown')
    },
    onClick () {
      console.log('onClick')
    }
  }
}

native.gif

7 .passive

vue correspond addEventListener in passive option provides .passive modifiers
<!-- 滚动事件的默认行为 (即滚动行为) 将会立即触发 --> 
<!-- 而不会等待 `onScroll` 完成 --> 
<!-- 这其中包含 `event.preventDefault()` 的情况 --> 

<div v-on:scroll.passive="onScroll">...</div>

has not found a suitable example for the improvement of scrolling performance, so I beg your friends to have examples

has not found a suitable example for the improvement of scrolling performance, so I beg your friends to have examples

has not found a suitable example for the improvement of scrolling performance, so I beg your friends to have examples

v-bind modifier

8 .sync

When we want to bind a certain property value two-way between the parent component and the child component, what is a convenient way? Yes, as long as .sync modifier can be done

parent component

<template>
  <div class="sync-parent">
    我是父组件: {{ text }}
    <Child :text.sync="text" />
  </div>
</template>

import Child from './child.vue'

export default {
  name: 'SyncParent',
  data () {
    return {
      text: 'parent'
    }
  },
  components: {
    Child,
  }
}

subcomponent

<template>
  <div class="child">
    我是子组件: 
    <input type="text" v-model="value" @input="onInput">
  </div>
</template>

export default {
  name: 'child',
  props: {
    text: {
      type: String
    }
  },
  data () {
    return {
      value: this.text
    }
  },
  methods: {
    onInput () {
      // 注意这里,必须是update:xxx的形式xxx即属性prop
      this.$emit('update:text', this.value)
    }
  }
}

sync.gif

9 .camel

.camel v-bind property name to be camelized when using DOM templates, such as the viewBox property of SVG:
<svg :view-box.camel="viewBox"></svg>

10 .prop

Regarding the official website of the .prop modifier, there is only this sentence .prop as a DOM property binding instead of an attribute binding. `.

do?

  1. Store variables through custom attributes to avoid exposing data
  2. Prevent pollution of HTML structure

For example, the following code

<template>
  <div class="prop">
    <div class="prop-item" :my-name="prop"></div>
    // 最终变成了 <div my-name="hello prop" class="prop-item"></div>
    <div class="prop-item" :my-name.prop="prop2"></div>
    // 最终变成了<div class="prop-item"></div>
    <button @click="onGetResult">获取结果</button>
  </div>
</template>

export default {
  name: 'prop',
  data () {
    return {
      prop: 'hello prop',
      prop2: 'hello prop2'
    }
  },
  methods: {
    onGetResult () {
      const $refProp = this.$refs.prop
      const $refProp2 = this.$refs.prop2

      console.log($refProp.getAttribute('my-name')) // hello prop
      console.log($refProp2.getAttribute('my-name')) // null
    }
  }
}

It can be seen from the example that the my-name .prop modifier will be bound to the attribute of the dom node, which will be exposed.

prop.gif

Mouse modifier

I want to listen when the user clicks on the left, right or there are modifiers can quickly use, are the key when .left , .right , middle , look at an example to try

According to MDN MouseEvent.button , introduction.

image.png

In the outermost div.mouse listen mousedown event, look at the user clicks a mouse button which, three button were listening with three modifiers shortcut left, button, right and print out left , middle , right

<template>
  <div class="mouse" @mousedown="onMousedown">
    <button @click.left="onClickBtn('left')">left</button>
    <button @click.middle="onClickBtn('middle')">middle</button>
    <button @click.right="onClickBtn('right')">right</button>
  </div>
</template>

export default {
  name: 'mouse',
  mounted () {

  },
  methods: {
    onClickBtn (msg) {
      console.log(msg)
    },
    onMousedown (event) {
      const mosueMsgMap = {
        0: '鼠标左键',
        1: '鼠标中键',
        2: '鼠标右键'
      }
      console.log('点击了', mosueMsgMap[event.button])
    }
  }
}

did not bring the mouse back, the middle-click cannot be demonstrated temporarily, and

mouse.gif

11 .left

Same as the above example, monitor left mouse click

12 .right

Same as the above example, monitor the right-click of the mouse

13 .middle

Same as the above example, monitor the middle mouse button click

Form related modifiers

14 .trim

For the input content, I hope that can filter the leading and trailing spaces. What should I do?
<template>
  <div class="trim">
    <div class="trim-item">
      <input type="text" v-model="name">
      <p>用户名:<span>{{ name }}</span></p>
    </div>
    <div class="trim-item">
      <input type="text" v-model.trim="name2">
      <p>用户名2:<span>{{ name2 }}</span></p>
    </div>
  </div>
</template>

export default {
  name: 'trim',
  data () {
    return {
      name: '',
      name2: '',
    }
  },
  watch: {
    name (newVal) {
      console.log(`'----${newVal}----'`)
    },
    name2 (newVal) {
      console.log(`'----${newVal}----'`)
    },
  }
}

.trim modifier can be easily done

trim.gif

15 .lazy

v-model familiar to everyone. By default, every time the input event is triggered, the value of the input box will be synchronized with the data bound to it in real time. But what if you want to update the data when the cursor leaves?

Idea 1: binds the change event, and manually obtains the value of the target in the event callback

Idea 2: can be achieved by directly using the .lazy

<template>
  <div class="lazy">
    <div class="lazy-item">
      <input type="text" v-model="text">
      <p>无.lazy: {{ text }}</p>
    </div>

    <div class="lazy-item">
      <input type="text" v-model.lazy="text2">
      <p>.lazy: {{ text2 }}</p>
    </div>
  </div>
</template>

export default {
  name: 'lazy',
  data () {
    return {
      text: '',
      text2: ''
    }
  }
}

It can be seen that after adding the .lazy modifier, the value entered in the second input box will not be reflected below in real time, but the cursor will leave the real state, and text2 will be updated.

lazy.gif

16 .number

We know input input box type even number type of the value obtained is string , if we want to get directly number types of data, there do not want the trouble of manual conversion should be how to do it?
<template>
  <div class="number">
    <div class="number-item">
      <p>无.number </p>
      <input type="number" v-model="number">
    </div>
    <div class="number-item">
      <p>type:text .number </p>
      <input type="text" v-model.number="number1">
    </div>
    <div class="number-item">
      <p>type:number .number </p>
      <input type="number" v-model.number="number2">
    </div>
  </div>
</template>

export default {
  name: 'lazy',
  data () {
    return {
      number: 0,
      number1: '',
      number2: '',
    }
  },
  watch: {
    number (newVal) {
      console.log(typeof newVal, newVal)
    },
    number1 (newVal) {
      console.log(typeof newVal, newVal)
    },
    number2 (newVal) {
      console.log(typeof newVal, newVal)
    },
  }
}
  1. The type of the first input box is number, but the value obtained is string
  2. The type of the second input box is text, but with the number modifier added, the value obtained can be number (if this value cannot be parseFloat() , the original value will be returned.)
  3. The type of the third input box is number, and the final value is also number

number.gif

System modifier

When the click event or keyboard event needs to be triggered when the system keys are pressed at the same time, .ctrl , .alt , .shift , .meta can help a lot!

as follows code

  1. Monitor keydown events globally, try to see if .ctrl , .alt , .shift , .meta are pressed
  2. .ctrl , .alt , .shift , .meta modifiers to the four buttons respectively and cooperate with the click event to verify whether the specified keys are pressed at the same time, and then clicks will take effect

Note: Computer ctrl key + click estimate conflicts with the browser shortcut configuration, resulting in not triggering

<template>
  <div class="system">
    <p>{{ msg }}</p>
    <div class="buttons">
      <button @click.ctrl="onClickButon('ctrl')">ctrl</button>
      <button @click.alt="onClickButon('alt')">alt</button>
      <button @click.shift="onClickButon('shift')">shift</button>
      <button @click.meta="onClickButon('meta')">meta</button>
    </div>
  </div>  
</template>

export default {
  name: 'system',
  data () {
    return {
      msg: ''
    }
  },
  mounted () {
    this.onListenSystemKeyDown()
  },
  methods: {
    onListenSystemKeyDown () {
      document.addEventListener('keydown', (event) => {
        let msg = '按下了'

        if (event.ctrlKey) {
          msg += 'ctrl键'
        } else if (event.altKey) {
          msg += 'alt键'
        } else if (event.shiftKey) {
          msg += 'shift键'
        } else if (event.metaKey) {
          msg += 'meta键'
        } else {
          msg += '其他键'
        }

        this.msg = msg
      }, false)
    },
    onClickButon (key) {
      console.log(`只有同时按下${key}键,点击事件才会发生`)
    }
  }
}

system.gif

17 .ctrl

The listener for mouse or keyboard events is triggered only when the ctrl button is pressed. For a detailed example, please see above

18 .alt

The listener for mouse or keyboard events is triggered only when the alt button is pressed. For a detailed example, please see above

19 .shift

The listener for mouse or keyboard events is triggered only when the shift button is pressed. For a detailed example, please see above

20 .meta

The listener for mouse or keyboard events is triggered only when the meta button is pressed. For a detailed example, please see above

21 .exact

Strictly speaking, .exact not a system modifier, but there is a phenomenon in the above example. Pressing several system modifier keys (such as alt and shift) at the same time can trigger .alt or .shift .

Still use the above example, take a look at the gif below, at this time I press alt and shift at the same time, the corresponding two events can be triggered

system2.gif

  1. Only want to trigger a click when a certain system modifier key is pressed
  2. is triggered when no system modifier is pressed. Click

To achieve the above requirements .exact will come in handy, use the above example to make a slight modification

<template>
  <div class="extra">
    <p>{{ msg }}</p>
    <div class="buttons">
      <button @click.ctrl.exact="onClickButon('ctrl')">ctrl</button>
      <button @click.alt.exact="onClickButon('alt')">alt</button>
      <button @click.shift.exact="onClickButon('shift')">shift</button>
      <button @click.meta.exact="onClickButon('meta')">meta</button>
      <button @click.exact="onClickButon('非系统键')">非系统键</button>
    </div>
  </div>  
</template>

export default {
  name: 'extra',
  data () {
    return {
      msg: ''
    }
  },
  mounted () {
    this.onListenSystemKeyDown()
  },
  methods: {
    onListenSystemKeyDown () {
      document.addEventListener('keydown', (event) => {
        let msg = '按下了'

        if (event.ctrlKey) {
          msg += 'ctrl键'
        } else if (event.altKey) {
          msg += 'alt键'
        } else if (event.shiftKey) {
          msg += 'shift键'
        } else if (event.metaKey) {
          msg += 'meta键'
        } else {
          msg += '其他键'
        }

        this.msg = msg
      }, false)
    },
    onClickButon (key) {
      console.log(`只有同时按下${key}键,点击事件才会发生`)
    }
  }
}

extra.gif

Key modifier

When listening to keyboard events, we often need to check detailed keystrokes and then execute the corresponding logic. Vue also has built-in at least 11+ key modifiers for us.

In the following code, we assign the keydown enter , tab , delete etc. When the specified keyboard is pressed in the specified input box, it will print enter , tab , delete etc. Other keys cannot trigger the event in the input box. console

<template>
  <div class="key-modifiers">
    <div class="key-modifiers-item">
      enter:
      <input type="text" @keydown.enter="onKeydown('enter')">
    </div>
    <div class="key-modifiers-item">
      tab:
      <input type="text" @keydown.tab="onKeydown('tab')">
    </div>  
    <div class="key-modifiers-item">
      delete:
      <input type="text" @keydown.delete="onKeydown('delete')">
    </div>  
    <div class="key-modifiers-item">
      esc:
      <input type="text" @keydown.esc="onKeydown('esc')">
    </div>  
    <div class="key-modifiers-item">
      space:
      <input type="text" @keydown.space="onKeydown('space')">
    </div> 
    <div class="key-modifiers-item">
      up:
      <input type="text" @keydown.up="onKeydown('up')">
    </div>  
    <div class="key-modifiers-item">
      down:
      <input type="text" @keydown.down="onKeydown('down')">
    </div> 
    <div class="key-modifiers-item">
      left:
      <input type="text" @keydown.left="onKeydown('left')">
    </div>  
    <div class="key-modifiers-item">
      right:
      <input type="text" @keydown.right="onKeydown('right')">
    </div>  
    
    <div class="key-modifiers-item">
      page-down:
      <input type="text" @keydown.page-down="onKeydown('page-down')">
    </div>  
    <div class="key-modifiers-item">
      page-up:
      <input type="text" @keydown.page-up="onKeydown('page-up')">
    </div>  
  </div>
</template>

export default {
  name: 'keyModifiers',
  methods: {
    onKeydown (keyName) {
      console.log(keyName)
    }
  }
}

key-modifiers.gif

22 .enter

The listener for mouse or keyboard events is triggered when the enter button is pressed. For a detailed example, please see the above

23 .tab

The mouse or keyboard event listener is triggered when the tab button is pressed. For a detailed example, please see the above

24 .delete

The mouse or keyboard event listener is triggered when the delete button is pressed. For a detailed example, please see the above

25 .esc

The mouse or keyboard event listener is triggered when the esc button is pressed, please see the above for a detailed example

26 .space

The mouse or keyboard event listener is triggered when the space button is pressed. For a detailed example, please see the above

27 .up

The mouse or keyboard event listener is triggered when the up button is pressed. For a detailed example, please see the above

28 .down

The mouse or keyboard event listener is triggered when the down button is pressed. For a detailed example, please see the above

29 .left

The mouse or keyboard event listener is triggered when the left button is pressed. For a detailed example, please see the above

30 .right

The mouse or keyboard event listener is triggered when the right button is pressed. For a detailed example, please see the above

31 .page-down

The listener for mouse or keyboard events is triggered when the (fn + down) button is pressed. For a detailed example, please see above

32 .page-up

The listener for mouse or keyboard events is triggered when the (fn + up) button is pressed. For a detailed example, please see above

How to customize key modifiers

Vue itself has built-in many practical key modifiers for us, which can meet our daily needs in most cases, so is there a way to customize key modifiers?

Through the following configuration, we can define a key modifier of our own. For example, we define q as a shortcut key to press q.


Vue.config.keyCodes = {
  q: 81
}

<div class="custom">
  <input type="text" @keydown.q="f1Keydown">
</div>

export default {
  name: 'custom',
  methods: {
    f1Keydown () {
      console.log('按下了q')
    }
  }
}

custom.gif

do not say goodbye

The above is the content that Fathead Fish has learned and understood about vue modifiers! Everyone is welcome to add and comment. O(∩_∩)O haha~

The examples in the article are all placed on the github source code , or you can click to see the example


前端胖头鱼
3.7k 声望6.2k 粉丝