第六章 前端开发学习——Vue框架
一、Vue介绍
二、Vue实例
三、Vue视图
四、Vue组件
五、Vue过度动画
一、Vue介绍
1.前端框架介绍
A)前端框架有
- React Facebook
- Angular Google
- Vue 全世界
B)Angular、Vue、React的区别
Vue与React
- React与Vue 都采用虚拟DOM
- 核心功能都在核心库中,其他类似路由这样的功能则由其他库进行处理
- React的生态系统更庞大,由ReactNative来进行混合App开发; Vue更轻量
- React由独特的JSX语法; Vue是基于传统的Web计数进行扩展(HTML、CSS、JavaScript),更容易学习
Vue与Angular
Angular1和Angular2以后的版本 是完全不同的两个框架; 一般Angular1被称作Angular.js, Angular之后的版本被称作 Angular
Vue与Angular的语法非常相似
Vue没有像Angular一样深入开发,只保证了基本功能。 Angular过于笨重
Vue的运行速度比Angular快得多
Angular的脏检查机制带来诸多性能问题
2.什么是Vue(what):
- Vue 是一套用于构建用户界面的渐进式框架
- 使用Vue框架,可以完全在浏览器端渲染页面,服务端只提供数据
- 使用Vue框架可以非常方便的构建 单页面应用 (SPA)
Vue与JQuery的区别:
jQuery 仍然是操作DOM的思想, 主要jQuery 用来写页面特效
Vue是前端框架(MVVM) ,对项目进行分层。 处理数据
Vue优点:
- 不存在依赖
- 轻便(25k min)
- 适用范围广(大中小型项目、PC、移动端、混合开发)
- 本土框架,社区非常活跃
- 语法简单、学习成本低
- 双向数据绑定(所见即所得)
MVVM
M 模型层 Model
V 视图层 View
VM (控制层) VIEW-MODEL
3.多页面应用与单页面应用
A)多页面应用(MultiPage Application,MPA)
多页面跳转刷新所有资源,每个公共资源(js、css等)需选择性重新加载,常用于 app 或 客户端等
B)单页面应用(SinglePage Web Application,SPA)
只有一张Web页面的应用,是一种从Web服务器加载的富客户端,单页面跳转仅刷新局部资源 ,公共资源(js、css等)仅需加载一次,常用于PC端官网、购物等网站
两者对比:
4.Vue入门
A)安装
方式一:直接<script>引入
- 开发环境版本 https://vuejs.org/js/vue.js 包含完整的警告和调试模式
- 生成环境版本 https://vuejs.org/js/vue.min.js 删除了警告、进行了压缩
方式二:CDN
https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.min.js
https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js
# 以手动指定版本号
方式三:NPM
在用 Vue 构建大型应用时推荐使用 NPM 安装[1]。NPM 能很好地和诸如 webpack 或 Browserify 模块打包器配合使用。同时 Vue 也提供配套工具来开发单文件组件。
npm install vue
方式四:构建工具 (CLI)
npm install -g @vue/cli
vue create my-project
二、Vue实例
1.创建实例
A)创建实例
var app = new Vue({ el: '#app', })
- 每个 Vue 应用都是通过用 Vue 函数创建一个新的 Vue 实例开始的
- 一个 Vue 应用由一个通过 new Vue 创建的根 Vue 实例,以及可选的嵌套的、可复用的组件树组成。
B)挂载元素
2.数据与方法
A)添加数据data
Vue({
data: {
}
})
- 当一个 Vue 实例被创建时,它向 Vue 的响应式系统中加入了其 data 对象中能找到的所有的属性。当这些属性的值发生改变时,视图将会产生“响应”,即匹配更新为新的值。
- 只有当实例被创建时 data 中存在的属性才是响应式的
- 如果你知道你会在晚些时候需要一个属性,但是一开始它为空或不存在,那么你仅需要设置一些初始值
B)实例方法
Vue 实例还暴露了一些有用的实例属性与方法。它们都有前缀 $,以便与用户定义的属性区分开来
- vm.$el
- vm.$data
- vm.$watch(dataAttr, fn)
3.计算属性和侦听器
A)添加方法methods
Vue({
methods: {
}
})
- methods用来装载可以调用的函数,你可以直接通过 Vue 实例访问这些方法,或者在指令表达式中使用。方法中的 this 自动绑定为 Vue 实例。
B)添加计算属性computed
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Vue</title> <style> p { border:1px solid #ccc; padding: 20px; 400px; } </style> </head> <body> <div id="app"> <h1>Vue计算属性</h1> <hr> firstName: <input type="text" v-model='firstName'> <br> lastName: <input type="text" v-model='lastName'> <br> <p> {{ fullName }} </p> </div> <script src="http://t.zoukankan.com/vue.js"></script> <script> //创建Vue实例 let vm = new Vue({ el:'#app', data: { firstName:'', lastName:'' }, computed: { fullName: function(){ return this.firstName + this.lastName } } }); //console.log(vm.fullName) //console.log(typeof vm.fullName) </script> </body> </html>
Vue({ computed: { 属性名: function(){ } } })
- 模板内的表达式非常便利,但是设计它们的初衷是用于简单运算的。在模板中放入太多的逻辑会让模板过重且难以维护,这时候应该使用计算属性
C)添加侦听属性watch
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Vue</title> <style> p { border:1px solid #ccc; padding: 20px; 400px; } </style> </head> <body> <div id="app"> <h1>Vue计算属性</h1> <hr> 请输入全名: <input type="text" v-model='fullName'> <p> lastName: {{ lastName }} </p> <p> firstName: {{ firstName }} </p> </div> <script src="http://t.zoukankan.com/vue.js"></script> <script> //创建Vue实例 let vm = new Vue({ el:'#app', data: { fullName:'', firstName:'', lastName:'' }, watch: { fullName: function(){ this.firstName = this.fullName.split(' ')[0] this.lastName = this.fullName.split(' ')[1] } } }) </script> </body> </html>
Vue({ watch: { 属性: function(){ } } })
- 虽然计算属性在大多数情况下更合适,但有时也需要一个自定义的侦听器。这就是为什么 Vue 通过 watch 选项提供了一个更通用的方法,来响应数据的变化。当需要在数据变化时执行异步或开销较大的操作时,这个方式是最有用的
D)方法,计算属性,侦听属性的区别
它们三者都是以函数为主体,但是它们之间却各有区别。
计算属性与方法:
我们可以将同一函数定义为一个方法而不是一个计算属性。两种方式的最终结果确实是完全相同的。然而,不同的是计算属性是基于它们的依赖进行缓存的。计算属性只有在它的相关依赖发生改变时才会重新求值。这就意味着只要 message 还没有发生改变,多次访问 reversedMessage 计算属性会立即返回之前的计算结果,而不必再次执行函数。
相比之下,每当触发重新渲染时,调用方法将总会再次执行函数。
计算属性与侦听属性:
- watch擅长处理的场景:一个数据影响多个数据
- computed擅长处理的场景:多个数据影响一个数据
4.delimiters
作用:改变纯文本插入分隔符。
5.生命周期
A)生命周期钩子函数(红色重要常用)
(1)beforeCreate
在实例初始化之后,数据观测(data observer)和 event/watcher 事件配置之前被调用。
(2)created:
数据创建成功 data methods computed watch
在这里从服务器获取数据
在实例创建完成后被立即调用。在这一步,实例已完成以下的配置:数据观测(data observer)、属性和方法的运算、watch/event 事件回调。然而,挂载阶段还没开始,$el 属性目前不可见。
(3)beforeMount
在挂载开始之前被调用,相关的 render 函数将首次被调用。
注意:该钩子在服务器端渲染期间不被调用。
(4)mounted:此时,Vue实例已经挂载到元素上。 操作DOM请在这里。
el 被新创建的 vm.el替换,并挂载到实例上去之后调用该钩子。如果root实例挂载了一个文档内元素,当mounted被调用时vm.el 也在文档内。页面渲染完成后初始化的处理都可以放在这里。
注意:mounted 不会承诺所有的子组件也都一起被挂载。
(5)beforeUpdate
数据更新时调用,发生在虚拟 DOM 重新渲染和打补丁之前。
你可以在这个钩子中进一步地更改状态,这不会触发附加的重渲染过程。
(6)updated
由于数据更改导致的虚拟 DOM 重新渲染和打补丁,在这之后会调用该钩子。
当这个钩子被调用时,组件 DOM 已经更新,所以你现在可以执行依赖于 DOM 的操作。然而在大多数情况下,你应该避免在此期间更改状态。如果要相应状态改变,通常最好使用计算属性或 watcher 取而代之。
注意:updated 不会承诺所有的子组件也都一起被重绘。
(7)activated
keep-alive 组件激活时调用。
(8)deactivated
keep-alive 组件停用时调用。
(9)beforeDestroy
实例销毁之前调用。在这一步,实例仍然完全可用。
(10)destroyed
Vue 实例销毁后调用。调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。
B)生命周期图示
三、Vue视图
1.基本模板语法
A)文本
- 数据绑定最常见的形式就是使用“Mustache”语法 (双大括号) 的文本插值
v-text
指令也可以用于数据绑定,如果要更新部分的 textContent ,需要使用 插值。- 通过使用
v-once
指令,你也能执行一次性地插值,当数据改变时,插值处的内容不会更新
B)原始HTML
双大括号会将数据解释为普通文本,而非 HTML 代码。为了输出真正的 HTML,你需要使用 v-html 指令
Tips:你的站点上动态渲染的任意 HTML 可能会非常危险,因为它很容易导致 XSS 攻击。请只对可信内容使用 HTML 插值,绝不要对用户提供的内容使用插值。
C)属性
- Mustache语法不能作用在 HTML 特性上,遇到这种情况应该使用 v-bind 指令
- 在布尔属性的情况下,它们的存在即暗示为 true, 如果值是 null、undefined 或 false,则属性不会被包含在渲染出来的
D)使用JavaScript表达式(不建议)
{{ number + 1 }}
{{ ok ? 'YES' : 'NO' }}
{{ message.split('').reverse().join('') }}
<div v-bind:id="'list-' + id"></div>
{{ var a = 1 }}
<!-- 这是语句,不是表达式 -->
{{ if (ok) { return message } }} <!-- 流控制也不会生效,请使用三元表达式 -->
E)防止刷新或加载闪烁
- v-cloak并不需要添加到每个标签,只要在el挂载的标签上添加就可以
2.指令
A)指令
- 指令 (Directives) 是带有 v- 前缀的特殊属性。
- 指令特性的值预期是单个 JavaScript 表达式
- 指令的职责是,当表达式的值改变时,将其产生的连带影响,响应式地作用于 DOM。
B)指令列表
- v-text
- v-html
- v-show
- v-if
- v-else
- v-else-if
- v-for
- v-on
- v-bind
- v-model
- v-pre
- v-cloak
- v-once
C)缩写
v-bind缩写
<!-- 完整语法 --> <a v-bind:href="http://t.zoukankan.com/url">...</a> <!-- 缩写 --> <a :href="http://t.zoukankan.com/url">...</a>
v-on缩写
<!-- 完整语法 --> <a v-on:click="doSomething">...</a> <!-- 缩写 --> <a @click="doSomething">...</a>
3.条件渲染与列表渲染
A)条件渲染
相关指令
- v-if
- v-else
- v-else-if
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Vue条件渲染</title> <style> .box { border:1px solid #ccc; padding: 10px; 600px; } </style> </head> <body> <div id="app"> <h1>条件渲染</h1> <hr> <button @click="isShow = !isShow">改变</button> <!-- <div v-if="true"> Lorem ipsum dolor sit amet, consectetur adipisicing elit. Rem quo saepe, eum nisi. Atque, pariatur ad sapiente alias, dignissimos tempora iusto ullam veritatis, obcaecati ipsa dicta sunt dolorem ducimus eos! </div> --> <template v-if="isShow"> <h2>锄禾</h2> <p>锄禾日党务</p> <p>锄禾日党务</p> <p>锄禾日党务</p> </template> <div v-else> HELLO 同志 </div> <hr> <input type="number" v-model="tag" max="3" min="0" step="1"> <div v-if="tag == 0" key="1"> 00000000000000000000000000 </div> <div v-else-if="tag == 1" key="2"> 1111111111111111111111111111 </div> <div v-else-if="tag == 2" key="3"> 222222222222222222222222222222 </div> <div v-else key="4"> else esle else else else else </div> <hr> <p v-show="false">Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ipsam incidunt perspiciatis, soluta repellendus ipsa placeat hic? Aspernatur modi, corporis incidunt deserunt accusantium laudantium, voluptates maxime eveniet maiores a labore nam.</p> </div> <script src="http://t.zoukankan.com/vue.js"></script> <script> new Vue({ el:'#app', data: { isShow: true, tag:0 } }) </script> </body> </html>
控制多个元素
<template v-if="ok">
<h1>Title</h1>
<p>Paragraph 1</p>
<p>Paragraph 2</p>
</template>
复用已有元素
- Vue 会尽可能高效地渲染元素,通常会复用已有元素而不是从头开始渲染
- 要想每次都重新渲染,只需添加一个具有唯一值的 key 属性
v-show
<h1 v-show="ok">Hello!</h1>
与 v-if 的区别
v-if
是“真正”的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建。v-show
就简单得多——不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 进行切换。- 一般来说,v-if 有更高的切换开销,而 v-show 有更高的初始渲染开销。因此,如果需要非常频繁地切换,则使用 v-show 较好;如果在运行时条件很少改变,则使用 v-if 较好。
B)列表渲染
遍历数组
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>列表渲染</title> </head> <body> <div id="app"> <h1>列表渲染</h1> <hr> <button @click="updateItemList()">更新数组</button> <ul> <li v-for="(item,index) in itemList"> {{index}} {{item}} </li> </ul> <table> <tr> <th>序号</th> <th>姓名</th> <th>年龄</th> <th>工作</th> <th>地址</th> </tr> <tr v-for="item in dataList" :key="item.id" v-if="item.id > 2"> <td>{{ item.id }}</td> <td>{{ item.name }}</td> <td>{{ item.age }}</td> <td>{{ item.job }}</td> <td>{{ item.address }}</td> </tr> </table> </div> <script src="http://t.zoukankan.com/vue.js"></script> <script> new Vue({ el:'#app', data: { itemList: ['曹操', '诸葛亮', '刘备', '孙权', '周瑜', '董卓'], dataList: [ {id:1, name:'曹操', age:19, job:'大王', address:'许都'}, {id:2,name:'诸葛亮', age:19, job:'丞相', address:'许都'}, {id:3,name:'刘备', age:19, job:'大王', address:'许都'}, {id:4,name:'孙权', age:19, job:'大王', address:'许都'}, {id:5,name:'董卓', age:19, job:'大王', address:'许都'} ] }, methods: { updateItemList: function(){ //this.itemList[1] = '贾宝玉' //this.itemList.push('贾宝玉'); //this.itemList.pop(); //this.itemList.reverse(); Vue.set(this.itemList, 1, '赵云') } } }) </script> </body> </html>
<ul id="example-1"> <li v-for="item in items"> {{ item.message }} </li> </ul>
v-for 还支持一个可选的第二个参数为当前项的索引
<ul id="example-2"> <li v-for="(item, index) in items"> {{ index }} - {{ item.message }} </li> </ul>
遍历对象
<!-- 只取值 --> <li v-for="value in object"> {{ value }} </li> <!-- 值、属性名 --> <div v-for="(value, key) in object"> {{ key }}: {{ value }} </div> <!--值、属性名、索引--> <div v-for="(value, key, index) in object"> {{ index }}. {{ key }}: {{ value }} </div>
key
- 当 Vue.js 用 v-for 正在更新已渲染过的元素列表时,它默认用“就地复用”策略。
- 这个默认的模式是高效的,但是只适用于不依赖子组件状态或临时 DOM 状态 的列表渲染输出。
- 为了给 Vue 一个提示,以便它能跟踪每个节点的身份,从而重用和重新排序现有元素,你需要为每项提供一个唯一 key 属性。因为在遍历,需要用 v-bind 来绑定动态值
- 建议尽可能在使用 v-for 时提供 key,除非遍历输出的 DOM 内容非常简单,或者是刻意依赖默认行为以获取性能上的提升。
更新检测
数组
由于 JavaScript 的限制,Vue 不能检测以下变动的数组:
当你利用索引直接设置一个项时,例如:vm.items[indexOfItem] = newValue 当你修改数组的长度时,例如:vm.items.length = newLength
对象
还是由于 JavaScript 的限制,Vue 不能检测对象属性的添加或删除
方法
Vue.set()
vm.$set()
遍历数字
<div>
<span v-for="n in 10">{{ n }} </span>
</div>
v-for on <tmplate>
<ul>
<template v-for="item in items">
<li>{{ item.msg }}</li>
<li role="presentation"></li>
</template>
</ul>
v-for 和 v-if
当它们处于同一节点,v-for 的优先级比 v-if 更高,这意味着 v-if 将分别重复运行于每个 v-for 循环中。
<li v-for="todo in todos" v-if="!todo.isComplete"> {{ todo }} </li>
3.样式
A)绑定HTML Class
对象语法
方法一:
<div v-bind:class="{ active: isActive }"></div>
方法二:
<div class="static" v-bind:class="{ active: isActive, 'text-danger': hasError }"> </div>
方法三:
<div v-bind:class="classObject"></div>
数组语法
<div v-bind:class="[activeClass, errorClass]"></div>
B)绑定内联样式
对象语法
方法一:
<div v-bind:style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>
方法二:
<div v-bind:style="styleObject"></div>
数组语法
数组语法可以将多个样式对象应用到同一个元素上
<div v-bind:style="[baseStyles, overridingStyles]"></div>
自动添加前缀
当 v-bind:style
使用需要添加浏览器引擎前缀的 CSS 属性时,如 transform,Vue.js 会自动侦测并添加相应的前缀。
4.事件绑定
A)事件绑定
监听事件
事件处理方法
<div id="example-2"> <!-- `greet` 是在下面定义的方法名 --> <button v-on:click="greet">Greet</button> </div>
内联调用方法
有时也需要在内联语句处理器中访问原始的 DOM 事件。可以用特殊变量 $event 把它传入方法
<button v-on:click="warn('Form cannot be submitted yet.', $event)">
Submit
</button>
B)事件修饰符
.stop
<!-- 阻止单击事件继续传播 阻止事件冒泡--> <a v-on:click.stop="doThis"></a>
.prevent
<!-- 提交事件不再重载页面 阻止默认事件--> <form v-on:submit.prevent="onSubmit"></form>
.capture
<!-- 添加事件监听器时使用事件捕获模式 --> <!-- 即元素自身触发的事件先在此处处理,然后才交由内部元素进行处理 --> <div v-on:click.capture="doThis">...</div>
.self
<!-- 只当在 event.target 是当前元素自身时触发处理函数 --> <!-- 即事件不是从内部元素触发的 --> <div v-on:click.self="doThat">...</div>
.once
<!-- 点击事件将只会触发一次 2.1.4新增--> <a v-on:click.once="doThis"></a>
.passive
<!-- 滚动事件的默认行为 (即滚动行为) 将会立即触发 2.3.0新增--> <!-- 而不会等待 `onScroll` 完成 --> <!-- 这其中包含 `event.preventDefault()` 的情况 --> <!--.passive 修饰符尤其能够提升移动端的性能。--> <div v-on:scroll.passive="onScroll">...</div>
补充:
①修饰符可以串联
<a v-on:click.stop.prevent="doThat"></a>
②允许只有修饰符
<form v-on:submit.prevent></form>
C)按键修饰符
数字
按键别名
- .enter(回车)
<!-- 回车键 --> <input v-on:keyup.enter="submit">
<!-- 只有在 `keyCode` 是 13 时(ASCII码13是回车键)调用 `vm.submit()` --> <input v-on:keyup.13="submit">
- .tab
- .delete (捕获“删除”和“退格”键)
- .esc
- .space
- .up
- .down
- .left
- .right
D)系统修饰键
可以用如下修饰符来实现仅在按下相应按键时才触发鼠标或键盘事件的监听器。
- .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 @keyup.alt.67="clear"> <!-- Ctrl + Click --> <div @click.ctrl="doSomething">Do something</div>
5.表单输入绑定
你可以用 v-model
指令在表单 <input>
及 <textarea>
元素上创建双向数据绑定。它会根据控件类型自动选取正确的方法来更新元素。
v-model
会忽略所有表单元素的 value、checked、selected 特性的初始值而总是将 Vue 实例的数据作为数据来源。你应该通过 JavaScript 在组件的 data 选项中声明初始值。
A)基础语法
①文本
<input v-model="message" placeholder="edit me"> <textarea v-model="message" placeholder="add multiple lines"></textarea>
②复选框
单个复选框
<input type="checkbox" v-model="checked"> <input type="checkbox" v-model="toggle" true-value="yes" false-value="no" >
多个复选框
<div id='example'> <input type="checkbox" value="Jack" v-model="checkedNames"> <label for="jack">Jack</label> <input type="checkbox" value="John" v-model="checkedNames"> <label for="john">John</label> <input type="checkbox" value="Mike" v-model="checkedNames"> <label for="mike">Mike</label> <br> <span>Checked names: {{ checkedNames }}</span> </div>
③单选按钮
绑定value对应的字符串
<div id="example"> <input type="radio" value="One" v-model="picked"> <label for="one">One</label> <br> <input type="radio" value="Two" v-model="picked"> <label for="two">Two</label> <br> <span>Picked: {{ picked }}</span> </div>
④选择框
单选,绑定对应所选的值
<div id="example"> <select v-model="selected"> <option disabled value="">请选择</option> <option>A</option> <option>B</option> <option>C</option> </select> <span>Selected: {{ selected }}</span> </div>
多选时 ,绑定到一个数组
<div id="example"> <select v-model="selected" multiple style=" 50px;"> <option>A</option> <option>B</option> <option>C</option> </select> <br> <span>Selected: {{ selected }}</span> </div>
B)修饰符
.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">
四、Vue组件
什么是组件(what):
组件(Component)是 Vue.js 最强大的功能之一。组件可以扩展 HTML 元素,封装可重用的代码。在较高层面上,组件是自定义元素,Vue.js 的编译器为它添加特殊功能。在有些情况下,组件也可以表现为用 is 特性进行了扩展的原生 HTML 元素。
所有的 Vue 组件同时也都是 Vue 的实例,所以可接受相同的选项对象(除了一些根级特有的选项)并提供相同的生命周期钩子。
如何理解组件:
简单理解,每个Page就是一个组件 (注册组件、登录组件、商品列表组件),页面的组成部分 (轮播图,选项卡,下拉菜单...),组件其实就是一个独立的 HTML,它的内部可能有各种结构、样式、逻辑,某些地方来说有些像 iframe,它都是在页面中引入之后展现另一个页面的内容,但实际上它与 iframe 又完全不同,iframe 是一个独立封闭的内容,而组件既是一个独立的内容,还是一个受引入页面控制的内容。
通常一个应用会以一棵嵌套的组件树的形式来组织:
例如,你可能会有页头、侧边栏、内容区等组件,每个组件又包含了其它的像导航链接、博文之类的组件。
为什么要使用组件(why):
举个简单的列子,最近我的项目中有一个日历模块,多个页面都要用这个日历,而每个页面的日历都存在一些差别,如果不使用组件,我要完成这个项目,做到各个页面的日历大体一致,而部分地方存在差异,我可能就需要写几套日历代码了。
而使用组件呢?一套代码,一个标签,然后分别在不同地方引用,根据不同的需求进行差异控制即可。
<calendar></calendar>
我可以通过给 calendar 传递值实现在本页面对日历的控制,让它满足我这个页面的某些单独需求。
有人会问,你 calendar 标签是什么鬼?前面有这么一句话,组件是自定义元素。calendar 就是我自定义的元素,它就是一个组件。所以在项目中,你会发现有各种五花八门的标签名,他们就是一个个组件。
组件的构成:
data
methods
computed
watch
template 模板
钩子函数
...
1.组件基础
A)注册组件
我们把创建一个组件称为注册组件,如果你把组件理解成为变量,那么注册组件你就可以理解为声明变量。我们通过 Vue.component 来注册一个全局组件。
Vue.component(componentName, { //选项 })
对于自定义组件的命名,Vue.js 不强制遵循 W3C 规则(小写,并且包含一个短杠),尽管这被认为是最佳实践。
*根实例
一个组件就是一个 vue实例
根实例(new Vue()) 其实根实例 就是根组件
B)组件选项
与创建Vue示例时的选项相同(除了一些根级特有的选项)
一个组件的 data 选项必须是一个函数 (每个组件实例具有自己的作用域,组件复用不会互相影响)
C)组件使用
组件可以复用
D)组件模板
每个组件模板必须只有一个根元素
模板形式一:内联模板 (不推荐)
<my-component inline-template>
<div>
<p>These are compiled as the component's own template.</p>
<p>Not parent's transclusion content.</p>
</div>
</my-component>
模板形式二:template 选项指定字符串 (模板字符串)
模板形式三:X-Templates模板
<script type="text/x-template" id="hello-world-template"> <p>Hello hello hello</p> </script> Vue.component('hello-world', { template: '#hello-world-template' })
模板形式四:单文件组件(.vue)(最优,工作中使用较多)
E)全局组件与局部组件
全局组件
局部组件
F)父组件与子组件
父子组件判定只在组件使用时考虑
下图根组件模板内使用其它组件,所以这些组件都是子组件
2.组件之间的嵌套使用与互相通信
组件设计初衷就是要配合使用的,最常见的就是形成父子组件的关系:组件 A 在它的模板中使用了组件 B。它们之间必然需要相互通信:父组件可能要给子组件下发数据,子组件则可能要将它内部发生的事情告知父组件。
每个组件的作用域都是独立的,所以在组件嵌套使用的时候子组件不能直接使用父组件中的数据。
A)父组件通过Prop向子组件传递数据
基本使用
在子组件中声明 prop,然后添加一个 message
<child message="hello!"></child>
Vue.component('child', { // 声明 props props: ['message'], // 就像 data 一样,prop 也可以在模板中使用 // 同样也可以在 vm 实例中通过 this.message 来使用 template: '<span>{{ message }}</span>' })
一个组件默认可以拥有任意数量的 prop,任何值都可以传递给任何 prop。我们能够在组件实例中访问这个值,
然后直接传入值就可以在子组件中使用 message。
Tips:若某个组件需要在根组件中使用,组件(Vue.component)必须先注册到Vue类中再实例化
其它形式:
a)
b)
Prop 的大小写
HTML 中的特性名是大小写不敏感的,所以浏览器会把所有大写字符解释为小写字符。这意味着当你使用 DOM 中的模板时,camelCase (驼峰命名法) 的 prop 名需要使用其等价的 kebab-case (短横线分隔命名) 命名
传入一个对象的所有属性
<blog-post v-bind="post"></blog-post>
等价于
<blog-post v-bind:id="post.id" v-bind:title="post.title" ></blog-post>
Prop验证
我们可以为组件的 prop 指定验证要求
Vue.component('my-component', { props: { // 基础的类型检查 (`null` 匹配任何类型) 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 } } } })
类型列表:
- String
- Number
- Boolean
- Array
- Object
- Date
- Function
- Symbol
- 自定义的构造函数
B)子组件通过事件向父组件传递数据
on(eventName)+on(eventName)+emit(eventName) 实现通讯
在父组件中使用 on(eventName)监听事件,然后在子组件中使用on(eventName)监听事件,然后在子组件中使用emit(eventName) 触发事件,这样就能实现子组件向父组件传值。
①子组件定义模板,定义事件addCount()
②子组件向父组件发送事件this.$emit
③父组件的模板中的子组件通过自定义事件监听
④父组件定制对应方法
使用事件抛出一个值
有的时候用一个事件来抛出一个特定的值是非常有用的。这时可以使用 $emit
的第二个参数来提供这个值
3.插槽slot
A)通过插槽分发内容
Something bad happened.· 会替换掉 slot标签
Vue.component('alert-box', { template: ` <div class="demo-alert-box"> <strong>Error!</strong> <slot></slot> </div> ` })
<alert-box>
Something bad happened.
</alert-box>
B)模板中多个插槽
组件模板
<div class="container"> <header> <slot name="header"></slot> </header> <main> <slot></slot> </main> <footer> <slot name="footer"></slot> </footer> </div>
调用组件
<base-layout> <template slot="header"> <h1>Here might be a page title</h1> </template> <p>A paragraph for the main content.</p> <p>And another one.</p> <p slot="footer">Here's some contact info</p> </base-layout>
C)插槽默认内容
<button type="submit"> <slot>Submit</slot> </button>
4.动态组件
A)实现动态组件
在不同组件之间进行动态切换
<component is="组件名" class="tab"></component>
components: { 'login-component': { template: ` <form action="#"> <div class="form-group"> <label for="#">用户名</label> <input type="text" /> </div> <div class="form-group"> <label for="#">密码</label> <input type="password" /> </div> <button class="btn btn-default btn-block">登录</button> </form> ` }, 'register-component': { template: ` <form action="#"> <div class="form-group"> <label for="#">用户名</label> <input type="text" /> </div> <div class="form-group"> <label for="#">密码</label> <input type="password" /> </div> <div class="form-group"> <label for="#">确认密码</label> <input type="password" /> </div> <button class="btn btn-primary btn-block">注册</button> </form> ` }, 'no-login-component': { template:` <div> 我是李刚,我不用登录,我牛逼 </div> ` } }
B)在动态组件上使用 keep-alive
包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们
主要用于保留组件状态或避免重新渲染
<!-- 基本 --> <keep-alive> <component :is="view"></component> </keep-alive> <!-- 多个条件判断的子组件 --> <keep-alive> <comp-a v-if="a > 1"></comp-a> <comp-b v-else></comp-b> </keep-alive>
C)绑定组件选项对象
动态组件可以绑定 组件选项对象(有component属性的对象),而不是已注册组件名的示例
var tabs = [ { name: 'Home', component: { template: '<div>Home component</div>' } }, { name: 'Posts', component: { template: '<div>Posts component</div>' } }, { name: 'Archive', component: { template: '<div>Archive component</div>', } } ] new Vue({ el: '#dynamic-component-demo', data: { tabs: tabs, currentTab: tabs[0] } })
<component v-bind:is="currentTab.component" class="tab" > </component>
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>动态组件</title> <link rel="stylesheet" href="http://t.zoukankan.com/dist/css/bootstrap.css"> <style> .panel { border-top: none; } </style> </head> <body> <div id="app"> <div class="container"> <div class="page-header"> <h1>动态组件</h1> </div> <div class="row"> <div class="col-md-6"> <ul class="nav nav-tabs"> <li v-for="item,index in tabs" : @click="setTab(index)"><a href="javascript:0">{{ item.tabName }}</a></li> </ul> <div class="panel"> <div class="panel-body"> <keep-alive> <component :is="tabs[tab].tabComponent"></component> </keep-alive> </div> </div> </div> </div> </div> </div> <script src="http://t.zoukankan.com/dist/js/vue.js"></script> <script> //创建根实例 new Vue({ el:'#app', data: { tabs: [ {'tabName':'登录', 'tabComponent':'login-component'}, {'tabName':'注册', 'tabComponent':'register-component'}, {'tabName':'免登录', 'tabComponent':'no-login-component'}, ], tab: 0 }, methods: { isTab(index) { return this.tab === index; }, setTab(index) { this.tab = index; } }, components: { 'login-component': { template: ` <form action="#"> <div class="form-group"> <label for="#">用户名</label> <input type="text" /> </div> <div class="form-group"> <label for="#">密码</label> <input type="password" /> </div> <button class="btn btn-default btn-block">登录</button> </form> ` }, 'register-component': { template: ` <form action="#"> <div class="form-group"> <label for="#">用户名</label> <input type="text" /> </div> <div class="form-group"> <label for="#">密码</label> <input type="password" /> </div> <div class="form-group"> <label for="#">确认密码</label> <input type="password" /> </div> <button class="btn btn-primary btn-block">注册</button> </form> ` }, 'no-login-component': { template:` <div> 我是李刚,我不用登录,我牛逼 </div> ` } } }) </script> </body> </html>
5.组件的其他特性
A)解析DOM模板时的注意事项
有些 HTML 元素,诸如 <ul>
、<ol>
、<table>
和 <select>
,对于哪些元素可以出现在其内部是有严格限制的。而有些元素,诸如 <li>
、<tr>
和 <option>
,只能出现在其它某些特定的元素内部。
<table> <blog-post-row></blog-post-row> </table>
上面的写法,渲染效果会不甚理想,可以采用以下写法
<table> <tr is="blog-post-row"></tr> </table>
需要注意的是如果我们从以下来源使用模板的话,这条限制是不存在的:
- 字符串 (例如:template: '...')
- 单文件组件 (.vue)
<script type="text/x-template">
B)Prop的一些问题
Prop的属性名问题
HTML 中的特性名是大小写不敏感的,所以浏览器会把所有大写字符解释为小写字符。这意味着当你使用 DOM 中的模板时,camelCase (驼峰命名法) 的 prop 名需要使用其等价的 kebab-case (短横线分隔命名) 命名
如果你使用字符串模板,那么这个限制就不存在了。
非Prop属性
组件上定义的非Prop属性 会传递到 组件模板的根元素上
class 和 style 特性会非常智能,即两边的值会被合并起来
对prop重新赋值
子组件中,对prop重新赋值,会报警告
C)组件的相关问题
将原生事件绑定到组件
想要在一个组件的根元素上直接监听一个原生事件。这时,你可以使用 v-on 的 .native 修饰符
<base-input v-on:focus.native="onFocus"></base-input>
.sync 修饰符
在有些情况下,我们可能需要对一个 prop 进行“双向绑定”
推荐以 update:my-prop-name 的模式触发事件
//子组件中 this.$emit('update:title', newTitle) <!-- 上级组件 模板中 --> <text-document v-bind:title="doc.title" v-on:update:title="doc.title = $event" ></text-document>
以上写法可以换成下列写法
<text-document v-bind:title.sync="doc.title"></text-document>
五、Vue过度动画
【待补充】