Luna Tech

Tutorials For Dummies.

Vue: Component Events

2022-03-25


0. Intro

Component Events | Vue.js (vuejs.org)


1. Emitting and Listening to Events

Child emitting

Child component 可以直接在 template 里面 emit custom events(用 built-in $emit function)。

<!-- MyComponent -->
<button @click="$emit('someEvent')">click me</button>

这个 $emit() function 也可以在 component instance 上使用(即this.$emit())。

Parent listening

Parent 可以用 v-on 来监听。

<MyComponent @some-event="callback" />

Component event listener 也支持之前讲过的 event handler modifier

<MyComponent @some-event.once="callback" />

Naming convention

正如 components 和 props,event name 也有自动的 case transformation,我们一般是用 camelCase 来定义 emit event,然后在 parent template 用 kebad-cased listener。

Event propagation

和 native DOM events 不同的是,component emitted events 不会冒泡,你只能监听 direct child component emitted events.

Unlike native DOM events, component emitted events do not bubble. You can only listen to the events emitted by a direct child component.


2. Event Arguments

Example - child

如果要在 emit event 的时候附加信息的话,可以加在 function argument 里面。

<button @click="$emit('increaseBy', 1)">Increase by 1</button>

Example - parent

在监听 event 的时候,可以用 inline arrow function 作为 listener。

<MyButton @increase-by="(n) => count += n" />

Example - parent 2

假如 event handler 是 method 的话,可以直接把 method name 作为 value,然后在 js 里面定义这个 method,从 child 传入的 argument 就会成为这个 method 的第一个 parameter。

<MyButton @increase-by="increaseCount" />

The value will be passed as the first parameter of that method.

methods: {
  increaseCount(n) {
    this.count += n
  }
}

All extra arguments passed to $emit() after the event name will be forwarded to the listener. For example, with $emit('foo', 1, 2, 3) the listener function will receive three arguments.


3. Declaring Emitted Events

我们可以用 emits option 来声明 component emitted events。

Example 1 - array syntax

export default {
  emits: ["inFocus", "submit"],
};

Example 2 - object syntax (可以加 runtime validation)

The emits option also supports an object syntax, which allows us to perform runtime validation of the payload of the emitted events.

export default {
  emits: {
    submit(payload) {
      // return `true` or `false` to indicate
      // validation pass / fail
    },
  },
};

Order

推荐按顺序来 define emitted events。

Although optional, it is recommended to define all emitted events in order to better document how a component should work. It also allows Vue to exclude known listeners from fallthrough attributes.

假如 emit event 跟 native event 冲突,只监听 emit event

If a native event (e.g., click) is defined in the emits option, the listener will now only listen to component-emitted click events and no longer respond to native click events.


4. Events Validation

如果用 object syntax 就可以加 event validation。

To add validation, the event is assigned a function that receives the arguments passed to the this.$emit call and returns a boolean to indicate whether the event is valid or not.

export default {
  emits: {
    // No validation
    click: null,

    // Validate submit event
    submit: ({ email, password }) => {
      if (email && password) {
        return true;
      } else {
        console.warn("Invalid submit event payload!");
        return false;
      }
    },
  },
  methods: {
    submitForm(email, password) {
      this.$emit("submit", { email, password });
    },
  },
};

5. Usage with v-model

v-model 没有用在 component 上时

v-model 例 1 和例 2 的效果一样。

Example 1

<input v-model="searchText" />

Example 2

<input :value="searchText" @input="searchText = $event.target.value" />

v-model 用在 component 上时

<CustomInput
  :modelValue="searchText"
  @update:modelValue="newValue => searchText = newValue"
/>

For this to actually work though, the <input> inside the component must:

Example - Component with v-model

<CustomInput v-model="searchText" />
<!-- CustomInput.vue -->
<script>
  export default {
    props: ["modelValue"],
    emits: ["update:modelValue"],
  };
</script>

<template>
  <input
    :value="modelValue"
    @input="$emit('update:modelValue', $event.target.value)"
  />
</template>

Example 2 - Component with v-model

通过 computed property 来完成。

Another way of implementing v-model within this component is to use a writable computed property with both a getter and a setter. The get method should return the modelValue property and the set method should emit the corresponding event.

<!-- CustomInput.vue -->
<script>
  export default {
    props: ["modelValue"],
    emits: ["update:modelValue"],
    computed: {
      value: {
        get() {
          return this.modelValue;
        },
        set(value) {
          this.$emit("update:modelValue", value);
        },
      },
    },
  };
</script>

<template>
  <input v-model="value" />
</template>

6. v-model arguments

默认情况下,component 的 v-modelmodelValue 作为 prop,用 update: modelValue 作为 event。我们可以通过 pass argument to v-model 来改变这些名字。

<MyComponent v-model:title="bookTitle" />

In this case, the child component should expect a title prop and emit an update:title event to update the parent value.

<!-- MyComponent.vue -->
<script>
  export default {
    props: ["title"],
    emits: ["update:title"],
  };
</script>

<template>
  <input
    type="text"
    :value="title"
    @input="$emit('update:title', $event.target.value)"
  />
</template>

Multiple v-model bindings

由于 v-model 可以 target 不同的 prop 和 event,所以我们可以绑定多个值。

<UserName v-model:first-name="firstName" v-model:last-name="lastName" />
<!-- UserName.vue -->
<script>
  export default {
    props: {
      firstName: String,
      lastName: String,
    },
    emits: ["update:firstName", "update:lastName"],
  };
</script>

<template>
  <input
    type="text"
    :value="firstName"
    @input="$emit('update:firstName', $event.target.value)"
  />
  <input
    type="text"
    :value="lastName"
    @input="$emit('update:lastName', $event.target.value)"
  />
</template>

7. Handling v-model modifiers

v-model 有一些 built-in modifier,比如 .trim .lazy等,不过有时候你希望用一个 custom modifier。

Example - create custom modifier .capitalize

<MyComponent v-model.capitalize="myText" />

Modifiers added to a component v-model will be provided to the component via the modelModifiers prop. 下面的例子里,我们的 component 包含 modelModifiers prop,默认是 empty object。由于我们在 parent 里面传入了 myText,所以这个 value 就是 true。

<script>
  export default {
    props: {
      modelValue: String,
      modelModifiers: {
        default: () => ({}),
      },
    },
    emits: ["update:modelValue"],
    created() {
      console.log(this.modelModifiers); // { capitalize: true }
    },
  };
</script>

<template>
  <input
    type="text"
    :value="modelValue"
    @input="$emit('update:modelValue', $event.target.value)"
  />
</template>

Now that we have our prop set up, we can check the modelModifiers object keys and write a handler to change the emitted value. In the code below we will capitalize the string whenever the <input /> element fires an input event.

//...
methods: {
    emitValue(e) {
      let value = e.target.value
      if (this.modelModifiers.capitalize) { // check keys
        value = value.charAt(0).toUpperCase() + value.slice(1) // handler
      }
      this.$emit('update:modelValue', value)
    }
  }
// ...

Example - v-modelbindings with both argument and modifiers

For v-model bindings with both argument and modifiers, the generated prop name will be arg + "Modifiers".

<MyComponent v-model:title.capitalize="myText"></MyComponent>

注意我们的 prop name 是 titleModifiers

export default {
  props: ["title", "titleModifiers"],
  emits: ["update:title"],
  created() {
    console.log(this.titleModifiers); // { capitalize: true }
  },
};