Vue.js(读音 /vjuː/, 类似于 view) 是一套构建用户界面的渐进式框架。
Vue 只关注视图层, 采用自底向上增量开发的设计。
Vue 的目标是通过尽可能简单的 API 实现响应的数据绑定和组合的视图组件。
Vue 生命周期
beforeCreate: function () { // 第一个生命周期函数,表示实例完全被创建出来之前,会执行它 // 注意: 在 beforeCreate 生命周期函数执行的时候,data 和 methods 中的 属性与方法定义都还没有没初始化 }, created: function () { // 在 created 中,data 和 methods 都已经被初始化好了! // 如果要调用 methods 中的方法,或者操作 data 中的数据,最早,只能在 created 中操作,但是还不能对"dom"节点进行操作 }, beforeMount: function () { // 表示 模板已经在内存中编辑完成了,但是尚未把 模板渲染到 页面中。 // 在 beforeMount 执行的时候,页面中的元素,还没有被真正替换过来,只是之前写的一些模板字符串 }, mounted: function () { // 内存中的模板,已经真实的挂载到了页面中,用户已经可以看到渲染好的页面了 // 挂载完毕,这时dom节点被渲染到文档内,一些需要dom的操作在此时才能正常进行 // 注意: mounted 是 实例创建期间的最后一个生命周期函数,当执行完 mounted 就表示,实例已经被完全创建好了 // 此时,表示vue实例已经初始化完毕了,组建已脱离创建阶段,进入运行阶段了。接下来的是运行中的两个事件 }, beforeUpdate: function () { // 你可以在这个钩子中进一步地修改vm.data,这不会触发附加的重渲染过程 }, updated: function () { // 当这个钩子被调用时,组件DOM的data已经更新,所以你现在可以执行依赖于DOM的操作 // 但是不要在此时修改data,否则会继续触发beforeUpdate、updated这两个生命周期,进入死循环 }, beforeDestroy: function () { // 销毁之前执行,当beforeDestroy函数执行时,表示vue实例已从运行阶段进入销毁阶段 // vue实例身上所有的方法与数据都处于可用状态 }, destroyed: function () { // 当destroy函数执行时,组件中所有的方法与数据已经被完全销毁,不可用 // 这时候能做的事情已经不多了,只能加点儿提示toast之类的东西吧 }, activated: function () { // 在vue对象存活的情况下,进入当前存在activated()函数的页面时,一进入页面就触发;可用于初始化页面数据等 // 在keep-alive 组件激活时调用,也就是没有被<keep-alive>包裹的话,activated是不起作用的 // 简单的说activated()函数就是一个页面激活后的钩子函数,一进入页面就触发 // 该钩子在服务器端渲染期间不被调用 // 所以当我们运用了组件缓存时,如果想每次切换都发送一次请求的话,需要把请求函数写在activated中,而写在created或mounted中其只会在首次加载该组件的时候起作用 deactivated: function () { // 页面消失的时候执行,deactivated生命周期函数。是配合 keep-alive 进行使用> }
创建一个 Vue 实例
每个 Vue 应用都是通过用 Vue 函数创建一个新的 Vue 实例开始的:
<div id="app"> {{ message }} </div> <script> //初始化一个vue实例 var app = new Vue({ el: '#app', //element,选择器 data: { //定义数据模型 message: 'Hello Vue!' }, methods:{ //定义方法 print(){ console.log(this.message); } } }) </script>
当一个 Vue 实例被创建时, 它会尝试获取在data中定义的所有属性,用于视图的渲染,并且监视data中的属性变化,当data发生改变,所有相关的视图都将重新渲染,这就是"响应式"系统。
可以看到在 Vue 构造器中有一个el 参数,它是 DOM 元素中的 id。在上面实例中 id 为 app,这意味着我们接下来的改动全部在以上指定的 div 内,div 外部不受影响。
模板语法
Vue.js 使用了基于 HTML 的模版语法,允许开发者声明式地将 DOM 绑定至底层 Vue 实例的数据。
Vue.js 的核心是一个允许你采用简洁的模板语法来声明式的将数据渲染进 DOM 的系统。
结合响应系统,在应用状态改变时, Vue 能够智能地计算出重新渲染组件的最小代价并应用到 DOM 操作上。
插值
数据绑定最常见的形式就是使用“Mustache”语法 (双大括号) 的文本插值:
<span>Message: {{ msg }}</span> <span v-once>这个将不会改变: {{ msg }}</span>
双大括号标签将会被替代为对应 data 数据对象上 msg 属性的值。无论何时,绑定的数据对象上 msg 属性发生了改变,插值处的内容都会更新。
通过使用 v-once 指令,也能执行一次性地插值,当数据改变时,插值处的内容不会更新。但请留心这会影响到该节点上的其它数据绑定。
v-text 和 v-html
使用 v-text 和 v-html 指令来替代{{ }}。
说明:
- v-text:将数据输出到元素内部,如果输出的数据有HTML代码,会作为普通文本输出
- v-html:将数据输出到元素内部,如果输出的数据有HTML代码,会被渲染
<div id="app"> <span v-text="hello"></span> <span v-html="hello"></span> </div> <script> var vm = new Vue({ el:"#app", data:{ hello:"hello vue" } }) </script>
使用JavaScript表达式
{{ number + 1 }} {{ ok ? 'YES' : 'NO' }} {{ message.split('').reverse().join('') }} <div v-bind:id="'list-' + id"></div>
这些表达式会在所属 Vue 实例的数据作用域下作为 JavaScript 被解析。有个限制就是,每个绑定都只能包含单个表达式,所以下面的例子都不会生效。
<!-- 这是语句,不是表达式 --> {{ var a = 1 }} <!-- 流程控制也不会生效,请使用三元表达式 --> {{ if (ok) { return message } }}
注意:模板表达式都被放在沙盒中,只能访问全局变量的一个白名单,如 Math 和 Date 。你不应该在模板表达式中试图访问用户定义的全局变量。
指令
指令 (Directives) 是带有 v- 前缀的特殊特性。指令特性的值预期是单个 JavaScript 表达式。指令的职责是,当表达式的值改变时,将其产生的连带影响,响应式地作用于 DOM。
<p v-if="seen">现在你看到我了</p>
这里,v-if 指令将根据表达式 seen 的值的真假来插入/移除 <p> 元素。
参数
一些指令能够接收一个“参数”,在指令名称之后以冒号表示。例如,v-bind 指令可以用于响应式地更新 HTML 特性:
<a v-bind:href="url">...</a>
在这里 href 是参数,告知 v-bind 指令将该元素的 href 特性与表达式 url 的值绑定。
动态参数
从 2.6.0 开始,可以用方括号括起来的 JavaScript 表达式作为一个指令的参数:
<a v-bind:[attributeName]="url"> ... </a>
这里的 attributeName 会被作为一个 JavaScript 表达式进行动态求值,求得的值将会作为最终的参数来使用。例如,如果你的 Vue 实例有一个 data 属性 attributeName,其值为 " href ",那么这个绑定将等价于 v-bind:href。
对动态参数的值的约束
动态参数预期会求出一个字符串,异常情况下值为 null。这个特殊的 null 值可以被显性地用于移除绑定。任何其它非字符串类型的值都将会触发一个警告。
对动态参数表达式的约束
动态参数表达式有一些语法约束,因为某些字符,如空格和引号,放在 HTML attribute 名里是无效的。例如:
<!-- 这会触发一个编译警告 --> <a v-bind:['foo' + bar]="value"> ... </a>
变通的办法是使用没有空格或引号的表达式,或用计算属性替代这种复杂表达式。
在 DOM 中使用模板时 (直接在一个 HTML 文件里撰写模板),还需要避免使用大写字符来命名键名,因为浏览器会把 attribute 名全部强制转为小写:
<!-- 在 DOM 中使用模板时这段代码会被转换为 `v-bind:[someattr]`。 除非在实例中有一个名为“someattr”的 property,否则代码不会工作。 --> <a v-bind:[someAttr]="value"> ... </a>
修饰符
修饰符 (modifier) 是以半角句号 . 指明的特殊后缀,用于指出一个指令应该以特殊方式绑定。例如,.prevent 修饰符告诉 v-on 指令对于触发的事件调用 event.preventDefault():
<form v-on:submit.prevent="onSubmit">...</form>
缩写
Vue 为 v-bind 和 v-on 这两个最常用的指令,提供了特定简写:
<!-- v-bind 缩写 --> <!-- 完整语法 --> <a v-bind:href="url">...</a> <!-- 缩写 --> <a :href="url">...</a> <!-- v-on 缩写 --> <!-- 完整语法 --> <a v-on:click="doSomething">...</a> <!-- 缩写 --> <a @click="doSomething">...</a>
双向数据绑定
上面讲过的 v-text 和 v-html 可以看做是单向绑定,数据影响了视图渲染,但是反过来就不行,接下来学习的 v-model 是双向绑定,视图和模型之间会相互影响。
既然是双向绑定,一定是在视图中可以修改数据,这样就限定了视图的元素类型。目前 v-model 的可使用元素有:
input、select、textarea、checkbox、radio、components(Vue中的自定义组件)。基本上除了最后一项,其他都是表单的输入项。
v-model 会忽略所有表单元素的 value、checked、selected 特性的初始值而总是将 Vue 实例的数据作为数据来源。你应该通过 JavaScript 在组件的 data 选项中声明初始值。
文本
<!-- 在页面有一个input元素,通过v-model与message绑定,并且通过{{message}}在页面输出 --> <!-- 这样通过v-model的绑定,实现了在input框修改message时可以同步p标签内容的展示 --> <input v-model="message" placeholder="edit me"> <p>Message is: {{ message }}</p>
多行文本
<p style="white-space: pre-line;">{{ message }}</p> <textarea v-model="message" placeholder="add multiple lines"></textarea>
复选框
<input type="checkbox" id="checkbox" v-model="checked"> <label for="checkbox">{{ checked }}</label>
多个复选框,绑定到同一个数组:
<div id='example-3'> <input type="checkbox" id="jack" value="Jack" v-model="checkedNames"> <label for="jack">Jack</label> <input type="checkbox" id="john" value="John" v-model="checkedNames"> <label for="john">John</label> <input type="checkbox" id="mike" value="Mike" v-model="checkedNames"> <label for="mike">Mike</label> <span>Checked names: {{ checkedNames }}</span> </div> <script> new Vue({ el: '#example-3', data: { checkedNames: [] } }) </script>
单选框
<div id="app"> <input type="radio" id="runoob" value="Runoob" v-model="picked"> <label for="runoob">Runoob</label> <br> <input type="radio" id="google" value="Google" v-model="picked"> <label for="google">Google</label> <br> <span>选中值为: {{ picked }}</span> </div> <script> new Vue({ el: '#app', data: { picked : 'Runoob' } }) </script>
选择框
<div id="example-5"> <select v-model="selected"> <option disabled value="">请选择</option> <option>A</option> <option>B</option> <option>C</option> </select> <span>Selected: {{ selected }}</span> </div> new Vue({ el: '...', data: { selected: '' } })
用 v-for 渲染的动态选项:
<select v-model="selected"> <option v-for="option in options" v-bind:value="option.value"> {{ option.text }} </option> </select> <span>Selected: {{ selected }}</span> new Vue({ el: '...', data: { selected: 'A', options: [ { text: 'One', value: 'A' }, { text: 'Two', value: 'B' }, { text: 'Three', value: 'C' } ] } })
值绑定
对于单选按钮,复选框及选择框的选项,v-model 绑定的值通常是静态字符串 (对于复选框也可以是布尔值):
<!-- 当选中时,`picked` 为字符串 "a" --> <input type="radio" v-model="picked" value="a"> <!-- `toggle` 为 true 或 false --> <input type="checkbox" v-model="toggle"> <!-- 当选中第一个选项时,`selected` 为字符串 "abc" --> <select v-model="selected"> <option value="abc">ABC</option> </select>
有时我们可能想把值绑定到 Vue 实例的一个动态属性上,这时可以用 v-bind 实现,并且这个属性的值可以不是字符串。
复选框
<input type="checkbox" v-model="toggle" true-value="yes" false-value="no"> // 当选中时 vm.toggle === 'yes' // 当没有选中时 vm.toggle === 'no'
单选按钮
<input type="radio" v-model="pick" v-bind:value="a"> // 当选中时 vm.pick === vm.a
选择框的选项
<select v-model="selected"> <!-- 内联对象字面量 --> <option v-bind:value="{ number: 123 }">123</option> </select> // 当选中时 typeof vm.selected // => 'object' vm.selected.number // => 123
修饰符
.lazy
在默认情况下,v-model 在每次 input 事件触发后将输入框的值与数据进行同步 (除了上述输入法组合文字时)。你可以添加 lazy 修饰符,从而转变为使用 change 事件进行同步:
<!-- 在“change”时而非“input”时更新 --> <input v-model.lazy="msg" >
.number
如果想自动将用户的输入值转为数值类型,可以给 v-model 添加 number 修饰符:
<input v-model.number="age" type="number">
.trim
如果要自动过滤用户输入的首尾空白字符,可以给 v-model 添加 trim 修饰符:
<input v-model.trim="msg">
事件处理
监听事件
可以用 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>
然而许多事件处理逻辑会更为复杂,所以直接把 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 语句中调用方法:
<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) } } })
事件修饰符
在事件处理程序中调用 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>
按键修饰符
在监听键盘事件时,我们经常需要检查详细的按键。Vue 允许为 v-on 在监听键盘事件时添加按键修饰符:
<!-- 只有在 `key` 是 `Enter` 时调用 `vm.submit()`,即按回车键提交表单 --> <input v-on:keyup.13="submit"> <!-- 记住所有的 keyCode 比较困难,所以Vue为最常用的按键提供了别名 --> <input v-on:keyup.enter="submit">
全部的按键别名。
- .enter
- .tab
- .delete(捕获删除和退格键)
- .esc
- .space
- .up
- .down
- .left
- .right
组合按键
可以用如下修饰符来实现仅在按下相应按键时才触发鼠标或键盘事件的监听器。
- .ctrl
- .alt
- .shift
<!-- Alt + C --> <input type="text" @keyup.alt.67="submit"/> <!-- Ctrl + Click --> <div @click.ctrl="doSomething">Do something</div>
条件渲染
v-if 指令
v-if 指令用于条件性地渲染一块内容。这块内容只会在指令的表达式返回 truthy 值的时候被渲染。
<div v-if="type === 'A'"> A </div> <div v-else-if="type === 'B'"> B </div> <div v-else-if="type === 'C'"> C </div> <div v-else> Not A/B/C </div>
用 key 管理可复用的元素
Vue 会尽可能高效地渲染元素,通常会复用已有元素而不是从头开始渲染。
<template v-if="loginType === 'username'"> <label>Username</label> <input placeholder="Enter your username"> </template> <template v-else> <label>Email</label> <input placeholder="Enter your email address"> </template>
在上面的代码中切换 loginType 将不会清除用户已经输入的内容。因为两个模板使用了相同的元素,<input> 不会被替换掉。
这样也不总是符合实际需求,所以 Vue 为你提供了一种方式来表达“这两个元素是完全独立的,不要复用它们”。只需添加一个具有唯一值的 key 属性即可:
<template v-if="loginType === 'username'"> <label>Username</label> <input placeholder="Enter your username" key="username-input"> </template> <template v-else> <label>Email</label> <input placeholder="Enter your email address" key="email-input"> </template>
现在,每次切换时,输入框都将被重新渲染。
v-show
另一个用于根据条件展示元素的选项是 v-show 指令。用法大致一样:
<h1 v-show="ok">Hello!</h1>
不同的是带有 v-if 的元素始终会被渲染并保留在 DOM 中。v-show 只是简单地切换元素的 CSS 属性 display。
注意,v-show 不支持 <template> 元素,也不支持 v-else。
一般来说,v-if 有更高的切换开销,而 v-show 有更高的初始渲染开销。如果需要非常频繁地切换,则使用 v-show 较好;如果在运行时条件很少改变,则使用 v-if 较好。
列表渲染
用 v-for 把一个数组对应为一组元素。
我们可以用 v-for 指令基于一个数组来渲染一个列表。v-for 指令需要使用 item in items 形式的特殊语法,其中 items 是源数据数组,而 item 则是被迭代的数组元素的别名。
<ul id="example-1"> <li v-for="item in items"> {{ item.message }} </li> </ul>
在 v-for 块中,我们可以访问所有父作用域的属性。v-for 还支持一个可选的第二个参数,即当前项的索引。
<ul id="example-2"> <!-- 也可以用 of 替代 in 作为分隔符,因为它更接近 JavaScript 迭代器的语法 --> <li v-for="(item, index) of items"> {{ parentMessage }} - {{ index }} - {{ item.message }} </li> </ul>
也可以用 v-for 来遍历一个对象的属性。
<ul id="v-for-object" class="demo"> <li v-for="value in object"> {{ value }} </li> </ul>
也可以提供第二个参数作为 property 名称 (也就是键名)和第三个参数作为索引:
<div v-for="(value, name, index) in object"> {{ index }}. {{ name }}: {{ value }} </div>
维护状态
当 Vue 正在更新使用 v-for 渲染的元素列表时,它默认使用“就地更新”的策略。如果数据项的顺序被改变,Vue 将不会移动 DOM 元素来匹配数据项的顺序,而是就地更新每个元素,并且确保它们在每个索引位置正确渲染。
<div v-for="item in items" v-bind:key="item.id"> <!-- 内容 --> </div>
建议尽可能在使用 v-for 时提供 key attribute,除非遍历输出的 DOM 内容非常简单,或者是刻意依赖默认行为以获取性能上的提升。
v-bind
html 属性不能使用双大括号形式绑定,只能使用 v-bind 指令。
<input type="button" value="{{value}}"> {{ramdom}}
可以发现 {{value}} 并没有被解析。正确的写法是:
<input type="button" v-bind:value="value"> {{ramdom}}
在将 v-bind 用于 class 和 style 时,Vue 作了专门的增强。表达式结果的类型除了字符串之外,还可以是对象或数组。
绑定 HTML Class
我们可以传给 v-bind:class 一个对象,以动态地切换 class:
<div id="app"> <div v-bind:class="activeClass"></div> <div v-bind:class="errorClass"></div> <div v-bind:class="[activeClass, errorClass]"></div> </div> <script> var app = new Vue({ el: "#app", data: { activeClass: 'active', errorClass: ['text-danger', 'text-error'] } }) </script>
渲染后的效果:具有active和hasError的样式。
我们可以传给 v-bind:class 一个对象,以动态地切换 class:
<div v-bind:class="{ active: isActive }"></div>
上面的语法表示 active 这个属性存在与否将取决于数据属性 isActive 的 truthiness( 所有的值都是真实的,除了false,0,“”,null,undefined和NaN)。
可以在对象中传入更多属性来动态切换多个 class。此外,`v-bind:class` 指令也可以与普通的 class 属性共存。如下模板:
<div class="static" v-bind:class="{ active: isActive, 'text-danger': hasError }"> </div> <script> data: { isActive: true, hasError: false } </script>
绑定style样式
v-bind:style 的对象语法十分直观——看着非常像 CSS,但其实是一个 JavaScript 对象。CSS 属性名可以用驼峰式 (camelCase) 或短横线分隔 (kebab-case,记得用引号括起来) 来命名:
数组语法
数组语法可以将多个样式对象应用到同一个元素上:
<div v-bind:style="[baseStyles, overridingStyles]"></div> <script> data: { baseStyles: {'background-color': 'red'}, overridingStyles: {border: '1px solid black'} } </script>
渲染后的结果:
<div style="background-color: red; border: 1px solid black;"></div>
对象语法
对象语法十分直观——看着非常像 CSS,但其实是一个 JavaScript 对象。CSS 属性名可以用驼峰式 (camelCase) 或短横线分隔 (kebab-case,记得用单引号括起来) 来命名:
<div v-bind:style="{ color: activeColor, fontSize: fontSize + 'px' }"></div> <script> data: { activeColor: 'red', fontSize: 30 } </script>
渲染后的效果:
<div style="color: red; font-size: 30px;"></div>
计算属性 computed
计算属性
计算属性允许我们对指定的视图,复杂的值计算。这些值将绑定到依赖项值,只在需要时更新。
例如下面的场景,我们有一个日期的数据,但是是毫秒值:
data:{
birthday:1529032123201 //毫秒值
}
我们在页面渲染,希望得到 yyyy-MM-dd 的样式:
<div id="app"> <h1>您的生日是:{{birthday}}</h1> </div> computed:{ birthday(){ const date = new Date(this.birthday); return date.getFullYear()+"年"+date.getMonth()+"月"+date.getDay()+"日" } }
computed 可以用来监控自己定义的变量
computed比较适合对多个变量或者对象进行处理后返回一个结果值,也就是多个变量中的某一个值发生了变化则我们监控的这个值也就会发生变化,举例:购物车里面的商品列表和总金额之间的关系,只要商品列表里面的商品数量发生变化,或减少或增多或删除商品,总金额都应该发生变化。这里的这个总金额使用computed属性来进行计算是最好的选择。
例如:
data:{ return{ results:[{name:'商品1',price:80},{name:'商品2',price:50},{name:'商品3',price:90}] } } computed:{ totalPrice: function (){ let total = 0 for(let i =0; i<this.results.length; i++){ total += this.results.price } return total } }
只要 results 还没有发生改变,多次访问 totalPrice 计算属性会立即返回之前的计算结果,而不必再次执行函数。
computed 的 get 和 set 方法
计算属性默认只有 getter ,不过在需要时你也可以提供一个 setter :
data:{ return{ firstName: null, lastName: null, } }, computed:{ fullName: { // 此时在get方法中计算出fullName get: function () { return this.firstName + this.lastName }, // 此时fullName的值的变化会响应给val set: function (val) { this.firstName = val[0] this.lastName: = val[1] } } }
与watch之间的区别:
watch主要用于监控vue实例的变化,它监控的变量当然必须在data里面声明才可以,computed 监控的变量不在data里面声明,直接在computed里面定义,然后就可以在页面上进行双向数据绑定展示出结果或者用作其他处理。
计算属性缓存
computed 可以关联多个实时计算的对象,当这些对象中的其中一个改变时都会出发这个属性。具有缓存能力,所以只有当数据再次改变时才会重新渲染,否则就会直接拿取缓存中的数据。
举个例子:
当我们想让div元素的背景色和文字颜色一致时,我们就可以使用computed属性。此时computed只会在初次渲染和文字颜色改变的情况下才会触发。其他情况下会直接从缓存中读取。
监听属性 watch
Vue 的 watch 属性可以用来监听 data 属性中数据的变化。
<div id="app"> <input type="text" v-model="message"/> </div>
监听 message 数据变化的监听器,它的名称也要叫 message。
每当 message 的数字发生改变,都会触发一次打印操作。
我们还可以直接在监听的 function 中使用参数来获取新值与旧值。
var vm = new Vue({ el: '#app', data: { message:{} }, watch: { message: { // 监视message属性的变化 deep: true, // deep为true,会监视message的属性及属性中的对象属性变化 handler(newVal,oldVal) { // 变化后的回调函数 console.log(newVal,oldVal); } } })
组件基础
组件(Component)是 Vue.js 最强大的功能之一。
组件可以扩展 HTML 元素,封装可重用的代码。
组件系统让我们可以用独立可复用的小组件来构建大型应用,几乎任意类型的应用的界面都可以抽象为一个组件树。
组件的命名
HTML 中的特性名是大小写不敏感的,所以浏览器会把所有大写字符解释为小写字符。这意味着当你使用 DOM 中的模板时,camelCase (驼峰命名法) 的 prop 名需要使用其等价的 kebab-case (短横线分隔命名) 命名。例如,我们命名了一个组件的名称为 nameTest,然后再其他组件里面引用 <nameTest></nameTest>,那么我们将找不到这个组件,因为这个组件已经将名字转换为nametest。所以对于自定义标签名,Vue.js 不强制要求遵循 W3C规则 (小写,并且包含一个短杠),尽管遵循这个规则比较好。
全局组件
全局组件通过Vue.component()方法注册。全局注册的可以在任何创建的实例中引用。
注册一个全局组件语法格式如下:
Vue.component(tagName, options)
tagName 为组件名称,options 为组件参数。注册后,我们可以使用以下方式来调用组件:
<tagName></tagName> <div id="faCounter"> <!--faCounter 就是组件元素counter的父元素,要把新建的vue实例绑定在这个父元素--> <counter></counter> <!--counter 就是新建的组件,也就是自定义的元素--> </div> <script> // 定义一个新的vue 组件。组件就是自定义的元素 // 1.组件名为"counter"; 2.data 写函数; 3.template 写组件的内容(元素和触发的事件) Vue.component('counter', { data:function(){ return { count:0 } }, // template 是模板的意思,在 html 里面是一个可以同时控制多个子元素的父元素。在这里定义了组件的内容 template:'<button v-on:click="count++">点击计算点击次数:{{count}}次</button>' }) // 定义一个新的 vue 实例,用 el 绑定组件元素(counter)的父元素 faCounter 元素上 new Vue({ el: '#faCounter' }) </script>
局部组件
一旦全局注册,就意味着即便以后你不再使用这个组件,它依然会随着 Vue 的加载而加载。因此,对于一些并不频繁使用的组件,我们会采用局部注册。局部组件直接通过component添加到vue实例对象上。这样组件只能在这个实例中使用:
<div id="app"> <runoob></runoob> </div> <script> var Child = { template: '<h1>自定义组件!</h1>' } // 创建根实例 new Vue({ el: '#app', data:function(){ return { name:'局部组件' } }, components: { // <runoob> 将只在父模板可用 'runoob': Child } }) </script>
data 必须是一个函数
当我们定义这个 <button-counter> 组件时,你可能会发现它的 data 并不是像这样直接提供一个对象:
data: { count: 0 }
取而代之的是,一个组件的 data 选项必须是一个函数,这是因为:
不使用 return 包裹的数据会在项目的全局可见,会造成变量污染,使用return包裹后数据中变量只在当前组件中生效,不会影响其他组件。
data: function () { return { count: 0 } }
如果 Vue 没有这条规则,点击一个按钮就可能会影响到其它所有实例。
组件通信
Props(父组件向子组件传值):
组件实例的作用域是孤立的。这意味着不能并且不应该在子组件的模板内直接引用父组件的数据。可以使用 props 把数据传给子组件。
在 Vue 中,父子组件的关系可以总结为 prop 向下传递,事件向上传递。父组件通过 prop 给子组件下发数据,子组件通过事件给父组件发送消息。
Prop是单向数据流。所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外改变父级组件的状态,从而导致你的应用的数据流向难以理解。额外的,每次父级组件发生更新时,子组件中所有的 prop 都将会刷新为最新的值。这意味着你不应该在一个子组件内部改变 prop。如果你这样做了,Vue 会在浏览器的控制台中发出警告。
子组件中data和props的区别:
- 子组件中的data数据,不是通过父组件传递的是子组件私有的,是可读可写的。
- 子组件中的所有 props中的数据,都是通过父组件传递给子组件的,是只读的。
具体步骤:
- 父组件使用子组件时,自定义属性(属性名任意,属性值为要传递的数据)
- 子组件利用props属性(通过自定义属性的属性名)接收父组件数据
<!--父组件--> <div id="app"> <!-- 1.父组件使用子组件,自定义了message属性,属性值为要传递的num属性 --> <child message="num"></child> <!-- 静态传递 --> <!-- 如果此处不使用v-bind指令,message的值就会是字符串"num",而不把它当成data中的数据解析 --> <child :message="num"></child> <!-- 动态传递 --> </div> <script> // 注册 Vue.component('counter', { // 2.使用props(通过自定义属性的属性名:message)来接收一个父组件传递的属性 props: ['message'], // 直接使用props接收到的自定义属性的属性名来渲染页面 template: '<button @click="message++"> 点击加一 {{message}} </button>' // 无论子组件中message如何变化,都不会影响到父组件中num的值 }) // 创建根实例 new Vue({ el: '#app', data:{num:0} }) </script>
一个组件默认可以拥有任意数量的 prop,任何值都可以传递给任何 prop。在上述模板中,你会发现我们能够在组件实例中访问这个值,就像访问 data 中的值一样。
Prop 验证
组件可以为 props 指定验证要求。
为了定制 prop 的验证方式,你可以为 props 中的值提供一个带有验证需求的对象,而不是一个字符串数组。例如:
Vue.component('my-component', { props: { // 基础的类型检查 (`null` 和 `undefined` 会通过任何类型验证) propA: Number, // 多个可能的类型 propB: [String, Number], // 必填的字符串 propC: { type: String, required: true }, // 带有默认值的数字 propD: { type: Number, default: 100 }, // 带有默认值的对象 propE: { type: Object, // 对象或数组默认值必须从一个工厂函数获取 default: function () { return { message: 'hello' } } }, // 自定义验证函数 propF: { validator: function (value) { // 这个值必须匹配下列字符串中的一个 return ['success', 'warning', 'danger'].indexOf(value) !== -1 } } } })
示例:
const myList = { template:'<li v-for="item in items" :key="item.id">{{item.id}}:{{item.name}}</li>' props:{ //定义需要从父组件中接收的属性 items:{ //是要接收的属性名称 type:Array, //限定父组件传递来的必须是数组 default:[], //默认值 required:true //是否必须 } } }
type 可以是下面原生构造器:
String
Number
Boolean
Array
Object
Date
Function
Symbol
自定义事件(子组件向父组件传值):
父组件是使用 props 传递数据给子组件,但如果子组件要把数据传递回去,就需要使用自定义事件!
我们可以使用 v-on 绑定自定义事件, 每个 Vue 实例都实现了事件接口(Events interface),即:
- 使用
$on(eventName)
监听事件 - 使用
$emit(eventName)
触发事件
<div id="app"> <!-- 父组件可以在使用子组件的地方直接用 v-on 来监听子组件触发的事件 --> <counter :message="num" @click="incr()"></counter> </div>
<script> Vue.component('counter', { props: ['message'], template: '<button @click="subIncr"> 点击加一 {{message}} </button>', methods:{ subIncr(){ this.$emit("incr"); //在子组件中通过方法名调用父组件对应的方法 } } }) new Vue({ el: '#app', data:{num:0}, method:{ incr(){ //在父组件中监听子组件 this.num++; } } }) </script>
data 必须是一个函数
上面例子中,可以看到 button-counter 组件中的 data 不是一个对象,而是一个函数:
data: function () { return { count: 0 } }
Vue.js 自定义指令
<div id="app"> <p>页面载入时,input 元素自动获取焦点:</p> <input v-focus> </div>
<script> // 注册一个全局自定义指令 v-focus Vue.directive('focus', { // 当绑定元素插入到 DOM 中。 inserted: function (el) { // 聚焦元素 el.focus() } }) // 创建根实例 new Vue({ el: '#app' }) </script>
我们也可以在实例使用 directives 选项来注册局部指令,这样指令只能在这个实例中使用:
<div id="app"> <p>页面载入时,input 元素自动获取焦点:</p> <input v-focus> </div>
<script> // 创建根实例 new Vue({ el: '#app', directives: { // 注册一个局部的自定义指令 v-focus focus: { // 指令的定义 inserted: function (el) { // 聚焦元素 el.focus() } } } }) </script>
vue-router 路由
Vue.js 路由允许我们通过不同的 URL 访问不同的内容。
通过 Vue.js 可以实现多视图的单页Web应用(single page web application,SPA)。
<router-link> 是一个组件,该组件用于设置一个导航链接,切换不同 HTML 内容。 to 属性为目标地址, 即要显示的内容。
<div id="app"> <p> <!-- 使用 router-link 组件来导航。通过传入 'to' 属性指定链接。 --> <router-link to="/foo">Go to Foo</router-link> <router-link to="/bar">Go to Bar</router-link> </p> <!-- 描点:路由出口 --> <!-- 路由匹配到的组件将渲染在这里 --> <router-view></router-view> </div>
// 0. 如果使用模块化机制编程,导入 Vue 和 VueRouter,要调用 Vue.use(VueRouter) // 1. 定义(路由)组件。 // 可以从其他文件 import 进来 const Foo = { template: '<div>foo</div>' } const Bar = { template: '<div>bar</div>' } // 2. 定义路由 // 每个路由应该映射一个组件。 其中"component" 可以是通过 Vue.extend() 创建的组件构造器, // 或者,只是一个组件配置对象。 const routes = [ { path: '/foo', //路由路径,必须以 "/" 开头 component: Foo //对应组件 }, { path: '/bar', component: Bar } ] // 3. 创建 router 实例,然后传 'routes' 配置。还可以传别的配置参数。 const router = new VueRouter({ routes // (缩写)相当于 routes: routes }) // 4. 创建和挂载根实例。 // 记得要通过 router 配置参数注入路由,从而让整个应用都有路由功能。 const app = new Vue({ el:"#app", router // 引用router })