自学前端开发 - VUE 框架 (二):模板引用、事件处理、表单输入绑定、生命周期、侦听器、组件基础

@[TOC](自学前端开发 - VUE 框架 (二) 事件处理、表单输入绑定、生命周期、侦听器、组件基础)

模板引用

模板引用就是直接访问模板对象实例。最经常使用的例子就是使用元素对象组件对象的方法。如果使用的是原生 DOM 对象,还可以使用 JS 的方式来获取元素对象(例如使用 getElementById 方法),对于 VUE 组件对象,就需要使用模板引用了。

设置模板引用

使用模板引用时,需要在元素或组件上设置一个特殊属性 ref,它能够在元素或组件挂载后,获取其直接引用。

<input ref="input">
<el-table ref="table">

访问模板引用

设置了特殊属性 ref 后,可以通过应用对象的 $refs 属性进行访问

<script>
export default {
  mounted() {
    this.$refs.input.focus()
  }
}
script>

<template>
  <input ref="input" />
template>

注意,只可以在组件挂载后才能访问模板引用。

使用数组

当在 v-for 中使用模板引用时,相应的引用中包含的值是一个数组

<script>
export default {
  data() {
    return {
      list: [
        /* ... */
      ]
    }
  },
  mounted() {
    console.log(this.$refs.items)
  }
}
script>

<template>
  <ul>
    <li v-for="item in list" ref="items">
      {{ item }}
    li>
  ul>
template>

需要注意的是,ref 数组并不保证与源数组相同的顺序。

函数模板引用

除了使用字符串值作名字,ref 属性还可以绑定为一个函数,会在每次组件更新时都被调用。该函数会收到元素引用作为其第一个参数:

<input :ref="el => { /* 将 el 赋值给一个数据属性或 ref 变量 */ }">

这样就能够在使用数组时保证顺序正确了

    <li v-for="(item, index) in list" :ref="el => items[index] = el">
      {{ item }}
    li>

需要注意的是使用动态的 :ref 绑定才能够传入一个函数。当绑定的元素被卸载时,函数也会被调用一次,此时的 el 参数会是 null。也可以绑定一个组件方法而不是内联函数。

另外,绑定 ref 属性的值可以没有经过定义就进行使用。实际上会将此值作为 this.$refs 这个对象的 key 来保存引用。而使用 :ref 则必须使用函数模板引用,而函数内部使用的变量必须是定义并暴露的,否则将无法正常访问引用。

使用对象

使用函数也可以将模板引用赋值给对象的成员,这样可以将若干组件来分类

<script>
export default {
	data() {
		return {
			inputRefs: ref({})
		}
	},
	mounted() {
		console.log(this.$refs.inputRefs)
	}
}
script>

<template>
    <input name='address' :ref="el => inputRefs['address'] = el" />
    <input name='age' :ref="el => inputRefs['age'] = el" />
template>

直接定义为对象或数组成员

直接将对象成员设置为 ref 的属性是不行的,例如

<input name='age' ref="input['age']" />

vue 会将 input['age'] 看作一个字符串整体而不是一个对象和其属性,调用时就成了 this.$refs."input['age']"。对于数组也是同理。

事件处理

可以使用 v-on 指令 (简写为 @) 来监听 DOM 事件,并在事件触发时执行对应的 JavaScript。用法:v-on:click="methodName"@click="handler"。事件处理器的值可以是:

  • 内联事件处理器:事件被触发时执行的内联 JavaScript 语句 (与 onclick 类似)。
  • 方法事件处理器:一个指向组件上定义的方法的属性名或是路径。

内联事件处理器

内联事件处理器通常用于简单场景,例如:

data() {
  return {
    count: 0
  }
}
<button @click="count++">Add 1button>
<p>Count is: {{ count }}p>

方法事件处理器

随着事件处理器的逻辑变得愈发复杂,内联代码方式变得不够灵活。因此 v-on 也可以接受一个方法名或对某个方法的调用。

data() {
  return {
    name: 'Vue.js'
  }
},
methods: {
  greet(event) {
    // 方法中的 `this` 指向当前活跃的组件实例
    alert(`Hello ${this.name}!`)
    // `event` 是 DOM 原生事件
    if (event) {
      alert(event.target.tagName)
    }
  }
}

<button @click="greet">Greetbutton>

方法事件处理器会自动接收原生 DOM 事件并触发执行。在上面的例子中,我们能够通过被触发事件的 event.target.tagName 访问到该 DOM 元素。

传递参数给方法事件处理器

除了直接绑定方法名,还可以在内联事件处理器中调用方法。这允许我们向方法传入自定义参数以代替原生事件:

methods: {
  say(message) {
    alert(message)
  }
}
<button @click="say('hello')">Say hellobutton>
<button @click="say('bye')">Say byebutton>

在内联事件处理器中访问事件参数

有时我们需要在内联事件处理器中访问原生 DOM 事件。你可以向该处理器方法传入一个特殊的 $event 变量,或者使用内联箭头函数:


<button @click="warn('Form cannot be submitted yet.', $event)">
  Submit
button>


<button @click="(event) => warn('Form cannot be submitted yet.', event)">
  Submit
button>
methods: {
  warn(message, event) {
    // 这里可以访问 DOM 原生事件
    if (event) {
      event.preventDefault()
    }
    alert(message)
  }
}

事件修饰符

在处理事件时调用 event.preventDefault()event.stopPropagation() 是很常见的。尽管我们可以直接在方法内调用,但如果方法能更专注于数据逻辑而不用去处理 DOM 事件的细节会更好。为解决这一问题,Vue 为 v-on 提供了事件修饰符。修饰符是用 . 表示的指令后缀,包含以下这些:

  • .stop ——调用 event.stopPropagation()
  • .prevent ——调用 event.preventDefault()
  • .self ——只有事件从元素本身发出才触发处理函数。
  • .capture ——在捕获模式添加事件监听器。
  • .once ——最多触发一次处理函数。
  • .passive ——通过 { passive: true } 附加一个 DOM 事件。

<a @click.stop="doThis">a>


<form @submit.prevent="onSubmit">form>


<a @click.stop.prevent="doThat">a>


<form @submit.prevent>form>



<div @click.self="doThat">...div>

按键修饰符

在监听键盘事件时,经常需要检查特定的按键。Vue 允许在 v-on@ 监听按键事件时添加按键修饰符。


<input @keyup.enter="submit" />

按键别名

Vue 为一些常用的按键提供了别名:

  • .enter
  • .tab
  • .delete (捕获“Delete”和“Backspace”两个按键)
  • .esc
  • .space
  • .up
  • .down
  • .left
  • .right

系统按键修饰符

系统按键修饰符用来触发鼠标或键盘事件监听器,只有当按键被按下时才会触发。

  • .ctrl
  • .alt
  • .shift
  • .meta

举例来说


<input @keyup.alt.enter="clear" />


<div @click.ctrl="doSomething">Do somethingdiv>

.exact 修饰符

.exact 修饰符允许控制触发一个事件所需的确定组合的系统按键修饰符。


<button @click.ctrl="onClick">Abutton>


<button @click.ctrl.exact="onCtrlClick">Abutton>


<button @click.exact="onClick">Abutton>

鼠标按键修饰符

这些修饰符将处理程序限定为由特定鼠标按键触发的事件:

  • .left
  • .right
  • .middle

表单输入绑定

在前端处理表单时,我们常常需要将表单输入框的内容同步给 JavaScript 中相应的变量。手动连接值绑定和更改事件监听器可能会很麻烦:

<input
  :value="text"
  @input="event => text = event.target.value">

v-model 指令帮我们简化了这一步骤:

<input v-model="text">

v-model 指令可以用于大多数表单元素,它会根据所使用的元素自动使用对应的 DOM 属性和事件组合。v-model 会忽略任何表单元素上初始的 value、checked 或 selected 属性。它将始终将当前绑定的 JavaScript 状态视为数据的正确来源。你应该在 JavaScript 中使用 data 选项来声明该初始值。

值绑定

对于单选按钮,复选框和选择器选项,v-model 绑定的值通常是静态的字符串 (或者对复选框是布尔值):


<input type="radio" v-model="picked" value="a" />


<input type="checkbox" v-model="toggle" />


<select v-model="selected">
  <option value="abc">ABCoption>
select>

但有时我们可能希望将该值绑定到当前组件实例上的动态数据。这可以通过使用 v-bind 来实现。此外,使用 v-bind 还使我们可以将选项值绑定为非字符串的数据类型。

复选框

<input
  type="checkbox"
  v-model="toggle"
  true-value="yes"
  false-value="no" />

true-valuefalse-value 是 Vue 特有的属性,仅支持和 v-model 配套使用。这里 toggle 属性的值会在选中时被设为 'yes',取消选择时设为 'no'。你同样可以通过 v-bind 将其绑定为其他动态值:

<input
  type="checkbox"
  v-model="toggle"
  :true-value="dynamicTrueValue"
  :false-value="dynamicFalseValue" />

true-valuefalse-value 属性不会影响 value 属性,因为浏览器在表单提交时,并不会包含未选择的复选框。为了保证这两个值 (例如:“yes”和“no”) 的其中之一被表单提交,请使用单选按钮作为替代。

单选按钮

<input type="radio" v-model="pick" :value="first" />
<input type="radio" v-model="pick" :value="second" />

pick 会在第一个按钮选中时被设为 first,在第二个按钮选中时被设为 second。

选择器选项

<select v-model="selected">
  
  <option :value="{ number: 123 }">123option>
select>

v-model 同样也支持非字符串类型的值绑定!在上面这个例子中,当某个选项被选中,selected 会被设为该对象字面量值 { number: 123 }

修饰符

v-model 可以使用的修饰符有:

  • .lazy
    默认情况下,v-model 会在每次 input 事件后更新数据 (IME 拼字阶段的状态例外)。可以添加 .lazy 修饰符来改为在每次 change 事件后更新数据:

<input v-model.lazy="msg" />
  • .number
    如果你想让用户输入自动转换为数字,你可以在 v-model 后添加 .number 修饰符来管理输入。如果该值无法被 parseFloat() 处理,那么将返回原始值。.number 修饰符会在输入框有 type="number" 时自动启用。
<input v-model.number="age" />
  • .trim
    如果你想要默认自动去除用户输入内容中两端的空格,你可以在 v-model 后添加 .trim 修饰符:
<input v-model.trim="msg" />

组件上的 v-model

生命周期

每个 Vue 组件实例在创建时都需要经历一系列的初始化步骤,比如设置好数据侦听,编译模板,挂载实例到 DOM,以及在数据改变时更新 DOM。在此过程中,它也会运行被称为生命周期钩子的函数,让开发者有机会在特定阶段运行自己的代码。

vue 应用组件的生命周期

vue 应用的生命周期为:

  1. 设置(创建代码,还未进入生命周期)
  2. 初始化事件(生命周期的开始)
  3. 创建对象并注入响应式状态(Init Options API)
  4. 模板预处理(pre-compile template)
  5. 初始化并渲染模板,替换数据(initial render)
  6. 组件挂载完成(Mounted),根据需要重渲染(re-render)
  7. 组件卸载(Unmounted)
    自学前端开发 - VUE 框架 (二):模板引用、事件处理、表单输入绑定、生命周期、侦听器、组件基础_第1张图片
    图示中的红色框即生命周期中的钩子。

注册周期钩子

举例来说,mounted 钩子可以用来在组件完成初始渲染并创建 DOM 节点后运行代码:

export default {
  mounted() {
    console.log(`the component is now mounted.`)
  }
}

还有其他一些钩子,会在实例生命周期的不同阶段被调用,最常用的是 created、mounted、updated 和 unmounted。

所有生命周期钩子函数的 this 上下文都会自动指向当前调用它的组件实例。注意:避免用箭头函数来定义生命周期钩子,因为如果这样的话将无法在函数中通过 this 获取组件实例。

created

created 钩子在初始化完成状态后调用,此时已经能够获取初始化的 data 等状态信息了。通常会在此阶段进行 ajax 请求(methods 中的方法),并加载需要的数据到 data 中。

mounted

mounted 钩子在组件挂载完成后被调用,此时组件已经渲染完成(在此之前组件未渲染,没有创建 $el),能够获取渲染后的元素和节点信息。所以一般在此阶段编写初始化页面的操作代码。

updated

updated 钩子在响应式状态被改动后调用。此时状态已经被改变,并且也将结果重渲染至页面中。一般在此执行数据改动后同步保存至数据库的 ajax 代码。

如果需要在渲染前完成某些操作,例如判断数据是否有效,可以在 beforeUpdate 钩子中进行。

unmounted

unmounted 钩子在组件被卸载时被调用,主要用来回收资源。

侦听器

计算属性允许我们声明性地计算衍生值。然而在有些情况下,我们需要在状态变化时执行一些“副作用”:例如更改 DOM,或是根据异步操作的结果去修改另一处的状态。侦听器会监听指定的数据的变化情况,并触发一个回调函数。

基本侦听

可以使用 watch 选项在每次响应式属性发生变化时触发一个函数。此函数函数名为监听的数据变量名,参数接收该变量改动前和改动后的值。

export default {
  data() {
    return {
      question: '',
      answer: 'Questions usually contain a question mark. ;-)'
    }
  },
  watch: {
    // 每当 question 改变时,这个函数就会执行
    question(newQuestion, oldQuestion) {
      if (newQuestion.includes('?')) {
        this.getAnswer()
      }
    }
  },
  methods: {
    async getAnswer() {
      this.answer = 'Thinking...'
      try {
        const res = await fetch('https://yesno.wtf/api')
        this.answer = (await res.json()).answer
      } catch (error) {
        this.answer = 'Error! Could not reach the API. ' + error
      }
    }
  }
}
<p>
  Ask a yes/no question:
  <input v-model="question" />
p>
<p>{{ answer }}p>

watch 选项也支持把键设置成用 . 分隔的路径:

export default {
  data:{ return {
    a: {
      b: {
        c: 'hello'
      }
    }
  }},
  watch: {
    // 注意:只能是简单的路径,不支持表达式。
    a.b.c(newValue, oldValue) {
      // ...
    }
  }
}

深层侦听器

watch 默认是浅层的:被侦听的属性,仅在被赋新值时,才会触发回调函数——而嵌套属性的变化不会触发。如果想侦听所有嵌套的变更,则需要使用深层侦听器:

export default {
  watch: {
    someObject: {
      handler(newValue, oldValue) {
        // 注意:在嵌套的变更中,
        // 只要没有替换对象本身,
        // 那么这里的 `newValue` 和 `oldValue` 相同
      },
      deep: true
    }
  }
}

需注意,深度侦听需要遍历被侦听对象中的所有嵌套的属性,当用于大型数据结构时,开销很大。因此只在必要时才使用它,并且要留意性能。

即时回调的侦听器

watch 默认是懒执行的:仅当数据源变化时,才会执行回调。但在某些场景中,希望在创建侦听器时,立即执行一遍回调。举例来说,想请求一些初始数据,然后在相关状态更改时重新请求数据。

我们可以用一个对象来声明侦听器,这个对象有 handler 方法和 immediate: true 选项,这样便能强制回调函数立即执行:

export default {
  // ...
  watch: {
    question: {
      handler(newQuestion, oldQuestion) {
        // 在组件实例创建时会立即调用
        // 立即调用时,oldQuestion为 null
      },
      // 强制立即执行回调
      immediate: true
    }
  }
  // ...
}

回调函数的初次执行就发生在 created 钩子之前。Vue 此时已经处理了 data、computed 和 methods 选项,所以这些属性在第一次调用时就是可用的。

回调的触发时机

当更改了响应式状态,它可能会同时触发 Vue 组件更新和侦听器回调。默认情况下,用户创建的侦听器回调,都会在 Vue 组件更新之前被调用。这意味着在侦听器回调中访问的 DOM 将是被 Vue 更新之前的状态。

如果想在侦听器回调中能访问被 Vue 更新之后的DOM,你需要指明 flush: 'post' 选项:

export default {
  // ...
  watch: {
    key: {
      handler() {},
      flush: 'post'
    }
  }
}

this.$watch()

也可以使用组件实例的 $watch() 方法来命令式地创建一个侦听器:

export default {
  created() {
    this.$watch('question', (newQuestion) => {
      // ...
    })
  }
}

如果要在特定条件下设置一个侦听器,或者只侦听响应用户交互的内容,这方法很有用。它还允许提前停止该侦听器。

停止侦听器

用 watch 选项或者 $watch() 实例方法声明的侦听器,会在宿主组件卸载时自动停止。因此,在大多数场景下,无需关心怎么停止它。在少数情况下,的确需要在组件卸载之前就停止一个侦听器,这时可以调用 $watch() API 返回的函数:

const unwatch = this.$watch('foo', callback)

// ...当该侦听器不再需要时
unwatch()

组件基础

组件(component)允许我们将 UI 划分为独立的、可重用的部分,并且可以对每个部分进行单独的思考。和我们嵌套 HTML 元素的方式类似,Vue 实现了自己的组件模型,使我们可以在每个组件内封装自定义内容与逻辑。即使重复使用同一个组件,组件与组件之间也是相互独立的,这使得各组件能够独立维护自身的状态。

定义一个组件

当使用构建步骤时,我们一般会将 Vue 组件定义在一个单独的 .vue 文件中,这被叫做单文件组件 (简称 SFC):

<script>
export default {
  data() {
    return {
      count: 0
    }
  }
}
script>

<template>
  <button @click="count++">You clicked me {{ count }} times.button>
template>

当不使用构建步骤时,一个 Vue 组件以一个包含 Vue 特定选项的 JavaScript 对象来定义:

// 声明组件
// 注意,Vue 对象不是 vue 框架的类对象,而应该是 mount 后的 vm 应用实例对象。
Vue.component('组件名', {
  data() {		// 组件内部的数据
    ...
  },
  methods: {	// 组件内部的方法
  	...
  },
  template: ``,		// 组件的 html 代码
})

这里的模板是一个内联的 JavaScript 字符串,Vue 将会在运行时编译它。也可以使用 ID 选择器来指向一个元素 (通常是原生的