Vue2官网教程查漏补缺学习笔记 - Part1基础 - 9事件处理10表单输入绑定11组件基础
9 事件处理
https://v2.cn.vuejs.org/v2/guide/events.html
9.1 监听事件
可以用 v-on
指令监听 DOM 事件,并在触发时运行一些 JavaScript 代码。
示例:
<div id="example-1">
<button v-on:click="counter += 1">Add 1</button>
<p>The button above has been clicked {{ counter }} times.</p>
</div>
var example1 = new Vue({
el: '#example-1',
data: {
counter: 0
}
})
9.2 事件处理方法
然而许多事件处理逻辑会更为复杂,所以直接把 JavaScript 代码写在 v-on
指令中是不可行的。因此 v-on
还可以接收一个需要调用的方法名称。
注意:这里是方法名称,不是调用方法
示例:
<div id="example-2">
<!-- `greet` 是在下面定义的方法名 -->
<button v-on:click="greet">Greet</button>
</div>
var example2 = new Vue({
el: '#example-2',
data: {
name: 'Vue.js'
},
// 在 `methods` 对象中定义方法
methods: {
greet: function (event) {
// `this` 在方法里指向当前 Vue 实例
alert('Hello ' + this.name + '!')
// `event` 是原生 DOM 事件
if (event) {
alert(event.target.tagName)
}
}
}
})
// 也可以用 JavaScript 直接调用方法
example2.greet() // => 'Hello Vue.js!'
9.3 内联处理器中的方法
除了直接绑定到一个方法,也可以在内联 JavaScript 语句中调用方法:
<div id="example-3">
<button v-on:click="say('hi')">Say hi</button>
<button v-on:click="say('what')">Say what</button>
</div>
new Vue({
el: '#example-3',
methods: {
say: function (message) {
alert(message)
}
}
})
有时也需要在内联语句处理器中访问原始的 DOM 事件。可以用特殊变量 $event
把它传入方法:
<button v-on:click="warn('Form cannot be submitted yet.', $event)">
Submit
</button>
// ...
methods: {
warn: function (message, event) {
// 现在我们可以访问原生事件对象
if (event) {
event.preventDefault()
}
alert(message)
}
}
9.4 事件修饰符
在事件处理程序中调用 event.preventDefault()
或 event.stopPropagation()
是非常常见的需求。尽管我们可以在方法中轻松实现这点,但更好的方式是:方法只有纯粹的数据逻辑,而不是去处理 DOM 事件细节。
为了解决这个问题,Vue.js 为 v-on
提供了事件修饰符。之前提过,修饰符是由点开头的指令后缀来表示的。
(基本没用过)
.stop
.prevent
.capture
.self
.once
.passive
<!-- 阻止单击事件继续传播 -->
<a v-on:click.stop="doThis"></a>
<!-- 提交事件不再重载页面 -->
<form v-on:submit.prevent="onSubmit"></form>
<!-- 修饰符可以串联 -->
<a v-on:click.stop.prevent="doThat"></a>
<!-- 只有修饰符 -->
<form v-on:submit.prevent></form>
<!-- 添加事件监听器时使用事件捕获模式 -->
<!-- 即内部元素触发的事件先在此处理,然后才交由内部元素进行处理 -->
<div v-on:click.capture="doThis">...</div>
<!-- 只当在 event.target 是当前元素自身时触发处理函数 -->
<!-- 即事件不是从内部元素触发的 -->
<div v-on:click.self="doThat">...</div>
使用修饰符时,顺序很重要;相应的代码会以同样的顺序产生。因此,用 `v-on:click.prevent.self` 会阻止**所有的点击**,而 `v-on:click.self.prevent` 只会阻止对元素自身的点击。
2.1.4 新增
<!-- 点击事件将只会触发一次 -->
<a v-on:click.once="doThis"></a>
不像其它只能对原生的 DOM 事件起作用的修饰符,.once
修饰符还能被用到自定义的组件事件上。如果你还没有阅读关于组件的文档,现在大可不必担心。
2.3.0 新增
Vue 还对应 addEventListener
中的 passive
选项提供了 .passive
修饰符。
<!-- 滚动事件的默认行为 (即滚动行为) 将会立即触发 -->
<!-- 而不会等待 `onScroll` 完成 -->
<!-- 这其中包含 `event.preventDefault()` 的情况 -->
<div v-on:scroll.passive="onScroll">...</div>
这个 .passive
修饰符尤其能够提升移动端的性能。
不要把 .passive
和 .prevent
一起使用,因为 .prevent
将会被忽略,同时浏览器可能会向你展示一个警告。请记住,.passive
会告诉浏览器你不想阻止事件的默认行为。
9.5 按键修饰符
(没用过,知道有这个东西到时候现查就行了)
在监听键盘事件时,我们经常需要检查详细的按键。Vue 允许为 v-on
在监听键盘事件时添加按键修饰符:
<!-- 只有在 `key` 是 `Enter` 时调用 `vm.submit()` -->
<input v-on:keyup.enter="submit">
你可以直接将 KeyboardEvent.key
暴露的任意有效按键名转换为 kebab-case 来作为修饰符。
<input v-on:keyup.page-down="onPageDown">
在上述示例中,处理函数只会在 $event.key
等于 PageDown
时被调用。
9.5.1 按键码
`keyCode` 的事件用法[已经被废弃了](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/keyCode)并可能不会被最新的浏览器支持。
使用 keyCode
attribute 也是允许的:
<input v-on:keyup.13="submit">
为了在必要的情况下支持旧浏览器,Vue 提供了绝大多数常用的按键码的别名:
.enter
.tab
.delete
(捕获“删除”和“退格”键).esc
.space
.up
.down
.left
.right
有一些按键 (`.esc` 以及所有的方向键) 在 IE9 中有不同的 `key` 值, 如果你想支持 IE9,这些内置的别名应该是首选。
你还可以通过全局 config.keyCodes
对象自定义按键修饰符别名:
// 可以使用 `v-on:keyup.f1`
Vue.config.keyCodes.f1 = 112
9.6 系统修饰键
(没用过,知道有这个东西到时候现查就行了)
2.1.0 新增
可以用如下修饰符来实现仅在按下相应按键时才触发鼠标或键盘事件的监听器。
.ctrl
.alt
.shift
.meta
注意:在 Mac 系统键盘上,meta 对应 command 键 (⌘)。在 Windows 系统键盘 meta 对应 Windows 徽标键 (⊞)。在 Sun 操作系统键盘上,meta 对应实心宝石键 (◆)。在其他特定键盘上,尤其在 MIT 和 Lisp 机器的键盘、以及其后继产品,比如 Knight 键盘、space-cadet 键盘,meta 被标记为“META”。在 Symbolics 键盘上,meta 被标记为“META”或者“Meta”。
例如:
<!-- Alt + C -->
<input v-on:keyup.alt.67="clear">
<!-- Ctrl + Click -->
<div v-on:click.ctrl="doSomething">Do something</div>
请注意修饰键与常规按键不同,在和 `keyup` 事件一起用时,事件触发时修饰键必须处于按下状态。换句话说,只有在按住 `ctrl` 的情况下释放其它按键,才能触发 `keyup.ctrl`。而单单释放 `ctrl` 也不会触发事件。如果你想要这样的行为,请为 `ctrl` 换用 `keyCode`:`keyup.17`。
9.6.1 .exact
修饰符
2.5.0 新增
.exact
修饰符允许你控制由精确的系统修饰符组合触发的事件。
<!-- 即使 Alt 或 Shift 被一同按下时也会触发 -->
<button v-on:click.ctrl="onClick">A</button>
<!-- 有且只有 Ctrl 被按下的时候才触发 -->
<button v-on:click.ctrl.exact="onCtrlClick">A</button>
<!-- 没有任何系统修饰符被按下的时候才触发 -->
<button v-on:click.exact="onClick">A</button>
9.6.2 鼠标按钮修饰符
2.2.0 新增
.left
.right
.middle
这些修饰符会限制处理函数仅响应特定的鼠标按钮。
9.7 为什么在 HTML 中监听事件?
你可能注意到这种事件监听的方式违背了关注点分离 (separation of concern) 这个长期以来的优良传统。但不必担心,因为所有的 Vue.js 事件处理方法和表达式都严格绑定在当前视图的 ViewModel 上,它不会导致任何维护上的困难。实际上,使用 v-on
有几个好处:
-
扫一眼 HTML 模板便能轻松定位在 JavaScript 代码里对应的方法。
-
因为你无须在 JavaScript 里手动绑定事件,你的 ViewModel 代码可以是非常纯粹的逻辑,和 DOM 完全解耦,更易于测试。
-
当一个 ViewModel 被销毁时,所有的事件处理器都会自动被删除。你无须担心如何清理它们。
本节新知识:1. 事件处理方法调用写法和内联处理器事件处理方法调用写法
10 表单输入绑定
https://v2.cn.vuejs.org/v2/guide/forms.html
10.1 基础用法
你可以用v-model 指令在表单 、 及 元素上创建双向数据绑定。它会根据控件类型自动选取正确的方法来更新元素。尽管有些神奇,但 v-model 本质上不过是语法糖。它负责监听用户的输入事件以更新数据,并对一些极端场景进行一些特殊处理。
v-model 在内部为不同的输入元素使用不同的 property 并抛出不同的事件:
text 和 textarea 元素使用 value property 和 input 事件;
checkbox 和 radio 使用 checked property 和 change 事件;
select 字段将 value 作为 prop 并将 change 作为事件。
10.1.1 文本
<input v-model="message" placeholder="edit me">
<p>Message is: {{ message }}</p>
此外有多行文本,复选框(单个复选框, v-model绑定变量,多个复选框,绑定数组),单选按钮,选择框(单选时绑定到一个变量,多选绑定到一个数组)
10.2 值绑定
对于单选按钮,复选框及选择框的选项,v-model 绑定的值通常是静态字符串 (对于复选框也可以是布尔值)。
但是有时我们可能想把值绑定到 Vue 实例的一个动态 property 上,这时可以用 v-bind 实现,并且这个 property 的值可以不是字符串。
复选框、单选按钮,选择框的选项。
10.3 修饰符
.lazy
在默认情况下,v-model 在每次 input 事件触发后将输入框的值与数据进行同步 (除了上述输入法组合文字时)。你可以添加 lazy 修饰符,从而转为在 change 事件之后进行同步。
.number
如果想自动将用户的输入值转为数值类型,可以给 v-model 添加 number 修饰符。
这通常很有用,因为即使在 type=“number” 时,HTML 输入元素的值也总会返回字符串。如果这个值无法被 parseFloat() 解析,则会返回原始的值。
纳尼,那type="number"有什么用。
.trim
如果要自动过滤用户输入的首尾空白字符,可以给 v-model 添加 trim 修饰符。
10.4 在组件上使用 v-model
HTML 原生的输入元素类型并不总能满足需求。幸好,Vue 的组件系统允许你创建具有完全自定义行为且可复用的输入组件。这些输入组件甚至可以和 v-model 一起使用!
要了解更多,请参阅组件指南中的自定义输入组件。
11 组件基础
https://v2.cn.vuejs.org/v2/guide/components.html
11.1 基本示例
组件是可复用的 Vue 实例,且带有一个名字:在这个例子中是 。我们可以在一个通过 new Vue 创建的 Vue 根实例中,把这个组件作为自定义元素来使用。
因为组件是可复用的 Vue 实例,所以它们与 new Vue 接收相同的选项,例如 data、computed、watch、methods 以及生命周期钩子等。仅有的例外是像 el 这样根实例特有的选项。
11.2 组件的复用
每个组件都会各自独立维护它的 count。因为每用一次组件,就会有一个它的新实例被创建。
11.2.1 data 必须是一个函数
当我们定义这个 组件时,你可能会发现它的 data 并不是像这样直接提供一个对象:
data: {
count: 0
}
取而代之的是,一个组件的 data 选项必须是一个函数,因此每个实例可以维护一份被返回对象的独立的拷贝:
data: function () {
return {
count: 0
}
}
如果 Vue 没有这条规则,点击一个按钮就可能会像如下代码一样影响到其它所有实例.
11.3 组件的组织
通常一个应用会以一棵嵌套的组件树的形式来组织:
为了能在模板中使用,这些组件必须先注册以便 Vue 能够识别。这里有两种组件的注册类型:全局注册和局部注册。至此,我们的组件都只是通过 Vue.component 全局注册的:
Vue.component('my-component-name', {
// ... options ...
})
全局注册的组件可以用在其被注册之后的任何 (通过 new Vue) 新创建的 Vue 根实例,也包括其组件树中的所有子组件的模板中。
哦,是吗?
11.4 通过 Prop 向子组件传递数据
Prop 是你可以在组件上注册的一些自定义 attribute。当一个值传递给一个 prop attribute 的时候,它就变成了那个组件实例的一个 property。为了给博文组件传递一个标题,我们可以用一个 props 选项将其包含在该组件可接受的 prop 列表中.
一个组件默认可以拥有任意数量的 prop,任何值都可以传递给任何 prop。在上述模板中,你会发现我们能够在组件实例中访问这个值,就像访问 data 中的值一样。
一个 prop 被注册之后,你就可以像这样把数据作为一个自定义 attribute 传递进来:
<blog-post title="My journey with Vue"></blog-post>
<blog-post title="Blogging with Vue"></blog-post>
<blog-post title="Why Vue is so fun"></blog-post>
如上所示,你会发现我们可以使用 v-bind 来动态传递 prop。这在你一开始不清楚要渲染的具体内容,比如从一个 API 获取博文列表的时候,是非常有用的。
11.5 单个根元素
every component must have a single root element (每个组件必须只有一个根元素)。你可以将模板的内容包裹在一个父元素内,来修复这个问题,例如:
<div class="blog-post">
<h3>{{ title }}</h3>
<div v-html="content"></div>
</div>
11.6 监听子组件事件
Vue 实例提供了一个自定义事件的系统来解决这个问题。父级组件可以像处理 native DOM 事件一样通过 v-on 监听子组件实例的任意事件:
<blog-post
...
v-on:enlarge-text="postFontSize += 0.1"
></blog-post>
同时子组件可以通过调用内建的 $emit 方法并传入事件名称来触发一个事件:
<button v-on:click="$emit('enlarge-text')">
Enlarge text
</button>
11.6.1 使用事件抛出一个值
有的时候用一个事件来抛出一个特定的值是非常有用的。例如我们可能想让 组件决定它的文本要放大多少。这时可以使用 $emit 的第二个参数来提供这个值:
<button v-on:click="$emit('enlarge-text', 0.1)">
Enlarge text
</button>
然后当在父级组件监听这个事件的时候,我们可以通过 $event 访问到被抛出的这个值:
<blog-post
...
v-on:enlarge-text="postFontSize += $event"
></blog-post>
或者,如果这个事件处理函数是一个方法:
<blog-post
...
v-on:enlarge-text="onEnlargeText"
></blog-post>
那么这个值将会作为第一个参数传入这个方法:
methods: {
onEnlargeText: function (enlargeAmount) {
this.postFontSize += enlargeAmount
}
}
11.6.2 在组件上使用 v-model
其实就是在自定义组件传入value属性,然后通过prop和emit实现value的双向绑定。
11.7 通过插槽分发内容
和 HTML 元素一样,我们经常需要向一个组件传递内容,像这样:
<alert-box>
Something bad happened.
</alert-box>
可能会渲染出这样的东西:
幸好,Vue 自定义的 元素让这变得非常简单:
Vue.component('alert-box', {
template: `
<div class="demo-alert-box">
<strong>Error!</strong>
<slot></slot>
</div>
})
如你所见,我们只要在需要的地方加入插槽就行了——就这么简单!
11.8动态组件
有的时候,在不同组件之间进行动态切换是非常有用的,比如在一个多标签的界面里:
上述内容可以通过 Vue 的 元素加一个特殊的 is attribute 来实现:
<!-- 组件会在 `currentTabComponent` 改变时改变 -->
<component v-bind:is="currentTabComponent"></component>
在上述示例中,currentTabComponent 可以包括: 1. 已注册组件的名字,或; 2. 一个组件的选项对象
请留意,这个 attribute 可以用于常规 HTML 元素,但这些元素将被视为组件,这意味着所有的 attribute 都会作为 DOM attribute 被绑定。对于像 value 这样的 property,若想让其如预期般工作,你需要使用 .prop 修饰器。
11.9 解析 DOM 模板时的注意事项
有些 HTML 元素,诸如 <ul>、<ol>、<table> 和 <select>
,对于哪些元素可以出现在其内部是有严格限制的。而有些元素,诸如<li>、<tr> 和 <option>
,只能出现在其它某些特定的元素内部。
这会导致我们使用这些有约束条件的元素时遇到一些问题。例如:
<table>
<blog-post-row></blog-post-row>
</table>
这个自定义组件 会被作为无效的内容提升到外部,并导致最终渲染结果出错。幸好这个特殊的 is attribute 给了我们一个变通的办法:
<table>
<tr is="blog-post-row"></tr>
</table>
需要注意的是如果我们从以下来源使用模板的话,这条限制是不存在的:
- 字符串 (例如:template: ‘…’)
- 单文件组件 (.vue)
<script type="text/x-template">
如果是单文件组件在table标签内,是可以生效的?这么理解吗
不是,是说table标签内使用的子组件写在单文件组件内是可以的。
问题解决:
好的!以下是扩展后的示例,分别展示 DOM 模板和字符串模板的完整用法,包含组件注册和组件使用部分:
1. DOM 模板(存在限制)
需要先注册组件,并在模板中通过 is
属性绕过浏览器限制。
<!DOCTYPE html>
<html>
<head>
<script src="https://cdn.jsdelivr.net/npm/vue@2"></script>
</head>
<body>
<div id="app">
<table>
<!-- 直接写 <blog-post-row> 会报错,必须用 `is` 属性 -->
<tr is="blog-post-row"></tr>
</table>
</div>
<script>
// 注册组件
Vue.component('blog-post-row', {
template: `
<tr>
<td>DOM 模板中必须用 is 属性</td>
</tr>
`
});
// 挂载 Vue 实例
new Vue({
el: '#app'
});
</script>
</body>
</html>
2. 字符串模板(无限制)
直接注册组件,并在父组件的字符串模板中自由使用。
<!DOCTYPE html>
<html>
<head>
<script src="https://cdn.jsdelivr.net/npm/vue@2"></script>
</head>
<body>
<div id="app">
<!-- 父组件直接渲染 -->
<parent-component></parent-component>
</div>
<script>
// 注册子组件
Vue.component('blog-post-row', {
template: `
<tr>
<td>字符串模板中无需伪装</td>
</tr>
`
});
// 注册父组件,使用字符串模板
Vue.component('parent-component', {
template: `
<table>
<!-- 直接使用组件名,无限制 -->
<blog-post-row></blog-post-row>
</table>
`
});
// 挂载 Vue 实例
new Vue({
el: '#app'
});
</script>
</body>
</html>
字符串模板是说父组件也是用字符串模板,然后template使用子组件
关键区别对比
场景 | 组件注册方式 | 组件使用方式 | 是否受 HTML 限制 |
---|---|---|---|
DOM 模板 | 注册后必须在 HTML 中用 is 属性 | <tr is="blog-post-row"></tr> | 是 |
字符串模板 | 注册后直接使用组件名 | <blog-post-row></blog-post-row> | 否 |
执行结果
- DOM 模板:渲染出合法的
<tr>
结构(通过is
属性伪装)。 - 字符串模板:直接渲染
<blog-post-row>
为<tr>
(Vue 内部处理,浏览器无感知)。
两者最终渲染的 HTML 结果一致,但字符串模板的代码更直观,无需处理浏览器限制!
以下是 单文件组件 和 <script type="text/x-template">
方法的完整示例,展示如何绕过 DOM 模板限制:
1. 单文件组件(.vue 文件)
特点:需构建工具(如 webpack + vue-loader),但代码组织最清晰。
<!-- BlogPostRow.vue -->
<template>
<tr>
<td>单文件组件中直接使用,无任何限制</td>
</tr>
</template>
<script>
export default {
// 组件逻辑
}
</script>
<style>
/* 组件作用域 CSS */
</style>
<!-- ParentComponent.vue -->
<template>
<table>
<!-- 直接使用组件名 -->
<blog-post-row></blog-post-row>
</table>
</template>
<script>
import BlogPostRow from './BlogPostRow.vue';
export default {
components: { BlogPostRow }
}
</script>
子组件使用单文件组件编写,原来不可以的写法就行得通了
2. <script type="text/x-template">
方法
特点:无需构建工具,直接在 HTML 中定义模板。
<!DOCTYPE html>
<html>
<head>
<script src="https://cdn.jsdelivr.net/npm/vue@2"></script>
</head>
<body>
<div id="app">
<table>
<blog-post-row></blog-post-row> <!-- 直接使用组件 -->
</table>
</div>
<!-- 定义模板 -->
<script type="text/x-template" id="blog-post-row-template">
<tr>
<td>通过 script 模板定义,无限制</td>
</tr>
</script>
<script>
// 注册组件并关联模板
Vue.component('blog-post-row', {
template: '#blog-post-row-template' // 指向 script 模板的 ID
});
// 挂载 Vue 实例
new Vue({
el: '#app'
});
</script>
</body>
</html>
使用script定义自定义组件,原来不可以的写法也可以了
对比总结
方法 | 是否需要构建工具 | 代码组织方式 | 适用场景 |
---|---|---|---|
单文件组件 (.vue) | 是 | 分离 template/script/style | 中大型项目,需工程化 |
script x-template | 否 | 模板写在 HTML 的 script 标签中 | 简单场景,快速原型开发 |
关键代码解析
-
单文件组件:
- 通过
.vue
文件将模板、逻辑、样式封装成独立模块。 - 父组件中直接使用
<blog-post-row>
,无需is
属性。
- 通过
-
script x-template:
- 用
<script type="text/x-template">
定义模板内容。 - 注册组件时通过
template: '#id'
引用模板。 - 浏览器会忽略此脚本内容,由 Vue 主动解析。
- 用
两种方法都完美绕过了浏览器对 DOM 模板的限制! 🚀
from DeepSeek-R1
太牛了,明白这个知识点的含义了。
本节新知识:1. 插槽 2. 动态组件component.