Vue介绍
- 作者: 尤雨溪
- Vue是一款渐进式JavaScript框架,通过简洁的API提供高效的数据绑定和灵活的组件系统。
- 渐进式:声明式渲染 — 组件系统 — 客户端路由 — 集中式状态管理 — 项目构建
- Vue 的核心库只关注视图层
- 官方文档
Vue安装
1 2 3 4 5 6 7
|
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
|
声明式渲染
Vue.js 的核心是一个允许采用简洁的模板语法来声明式地将数据渲染进 DOM 的系统:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| <div id="app"> <div>{{msg}}</div> <div>{{1 + 2}}</div> <div>{{msg + '---' + 123}}</div> </div> <script type="text/javascript" src="js/vue.js"></script>
<script type="text/javascript">
new Vue({ el: '#app', data: { msg: 'HelloWorld' } }) </script>
|
Vue模板语法
- 插值表达式
- 指令
- 事件绑定
- 属性绑定
- 样式绑定
- 分支循环结构
插值表达式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| 插值表达式是Vuejs中实现数据渲染到页面方式,不用进行dom操作可以直接将数据从模型到视图。
插值表达式就是{{ }},括号里面可以执行简单的js代码 。将模型变量中的属性直接放到插值表达式中可以实现数据渲染到页面的效果。
常规变量 number,string,boolean等 四则运 +,-,*,/,% 逻辑运算 或与非 三目运算 全局函数 {{ Math.random() }} 对象 {{ {name:“abc”,age:13} }} 数组 {{ [,5,6,7,8,9] }}
防止xss攻击 xss:Cross Site Scripting,跨域脚本攻击; 危害:盗取cookie,破坏页面结构; 防止csrf攻击 csrf:Cross-site request forgery,跨站请求伪造 危害:在用户不知情的情况下,模拟用户的操作,购买商品,发邮件,甚至转账
|
指令
- 指令的本质就是 Vue 提供的特殊自定义属性
- 指令的格式:以
v-
开头
常用基本指令
v-cloak
插值表达式存在的问题:“闪烁”
代码加载的时候先加载HTML 把插值语法当做HTML内容加载到页面上 当加载完js后才把插值语法替换掉 所以我们会看到闪烁问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <style type="text/css"> [v-cloak]{ display: none; } </style> <div id="app">
<div v-cloak >{{msg}}</div> </div>
|
v-text
- 作用与插值表达式类似,但是没有闪烁问题
- 相比插值表达式更加简洁
- 如果数据中有HTML标签会将html标签一并输出
v-html
- 用法和v-text 相似 但是他可以将HTML片段填充到标签中
- 可能有安全问题, 一般只在可信任内容上使用 v-html,永不用在用户提交的内容上
- 它与v-text区别在于v-text输出的是纯文本,浏览器不会对其再进行html解析,但v-html会将其当html标签解析后输出。
v-pre
- 显示原始信息跳过编译过程
- 跳过这个元素和它的子元素的编译过程。
- 一些静态的内容不需要编译加这个指令可以加快渲染
1 2 3
| <span v-pre>{{ msg }}</span>
{{ msg }}
|
v-once
1 2
| <span v-once>{{ msg }}</span>
|
数据的响应式:数据的变化导致页面内容的变化(数据驱动视图)
v-model(双向数据绑定)
使用场景:限制在 <input>
、<select>
、<textarea>
、components
中使用
1 2 3 4 5 6 7
| <div id="app"> <div>{{msg}}</div> <div> <input type="text" v-model='msg'> </div> </div>
|
双向数据绑定
- 当数据发生变化的时候,视图也就发生变化
- 当视图发生变化的时候,数据也会跟着同步变化
- 双向绑定底层原理:
Object.defineProperty()
的用法
可以通过v-bind和v-on实现v-model的功能
1
| <input v-bind:value="msg" v-on:input="msg=$event.target.value">
|
事件绑定
v-on指令(事件绑定)
1 2 3 4 5 6 7 8
| <input type='button' v-on:click="fn($event)"/> <input type='button' @click="fn"/>
methods: { fn(e){} }
.native
|
事件修饰符
在事件处理程序中调用 event.preventDefault()
或 event.stopPropagation()
是非常常见的需求。
Vue 不推荐我们操作DOM 为了解决这个问题,Vue.js 为 v-on
提供了事件修饰符
1 2 3 4 5 6 7 8 9 10 11 12 13
| <a v-on:click.stop="handle">跳转</a>
<a v-on:click.prevent="handle">跳转</a>
<a v-on:click.stop.prevent="handle"></a>
<div v-on:click.self="handle">...</div>
使用修饰符时,顺序很重要;相应的代码会以同样的顺序产生。因此,用 v-on:click.prevent.self 会阻止 所有的点击,而 v-on:click.self.prevent 只会阻止对元素自身的点击。
|
按键修饰符
在做项目中有时会用到键盘事件,在监听键盘事件时,我们经常需要检查详细的按键。Vue 允许为 v-on
在监听键盘事件时添加按键修饰符
1
| <input v-on:keyup.13="submit">
|
按键别名
1 2 3 4 5 6 7 8 9 10 11
| .enter => enter键 .tab => tab键 .delete (捕获“删除”和“退格”按键) => 删除键 .esc => 取消键 .space => 空格键 .up => 上 .down => 下 .left => 左 .right => 右
<input @keyup.enter="submit">
|
修饰键
1 2 3 4 5 6 7 8 9
| .ctrl .alt .shift .meta
<input @keyup.alt.67="clear">
<div @click.ctrl="doSomething">Do something</div>
|
与按键别名不同的是,修饰键和 keyup 事件一起用时,事件引发时必须按下正常的按键。换一种说法:如果要引发 keyup.ctrl,必须按下 ctrl 时释放其他的按键;单单释放 ctrl 不会引发事件。
1 2 3 4 5
| <input @keyup.alt.67="clear">
<input @keyup.alt="other">
|
自定义按键修饰符
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <div id="app"> <input type="text" v-on:keydown.f5="prompt()"> </div>
<script> Vue.config.keyCodes.f5 = 116;
let app = new Vue({ el: '#app', methods: { prompt: function() { alert('我是 F5!'); } } }); </script>
|
属性绑定
v-bind指令(绑定指令)
v-bind指令被用来响应地更新 HTML 属性
1 2 3 4 5 6 7
| <a v-bind:href='url'>跳转</a>
<img v-bind:src="imgUrl">
<a :href='url'>跳转</a>
|
class类名绑定
对象语法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| <style type="text/css"> .red { color:red; } .blue { color:blue; } .yellow { color:yellow; } </style>
<div v-bind:class="{red: isActive,blue: isError}">测试样式</div> ...... data: { isActive: true, isError: true, isFlag: true } ......
|
数组语法
1
| <div v-bind:class="['red', 'blue']">测试样式</div>
|
对象绑定和数组绑定的区别
- 绑定对象的时候 对象的属性 即要渲染的类名 对象的属性值对应的是 data 中的数据
- 绑定数组的时候数组里面存的是data 中的数据
混合使用
1
| <div v-bind:class="['red', 'blue', {yellow: isFlag}]">测试样式</div>
|
默认样式
1 2
| <div class="red blue" :class="['yellow']"></div>
|
样式绑定
对象语法
1 2 3 4 5 6 7
|
<div v-bind:style='{ "font-size": "12px", backgroundColor: "gray" }'></div>
|
数组语法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| <div v-bind:style='[objStyles, overrideStyles]'></div> <script> ...... data: { objStyles: { border: '1px solid green', width: '200px', height: '100px' }, overrideStyles: { border: '5px solid orange', backgroundColor: 'blue' } } ...... </script>
|
分支循环结构
条件绑定
使用场景
- 通过条件判断展示或者隐藏某个元素或者多个元素
- 进行两个视图之间的切换
v-if
1 2 3 4 5
| <div v-if='score>=90'>优秀</div> <div v-else-if='score<90&&score>=80'>良好</div> <div v-else-if='score<80&&score>60'>一般</div> <div v-else>比较差</div>
|
v-show
1 2
| <div v-show='flag'>测试v-show</div>
|
v-if和v-show的区别
v-if是动态的向DOM树内添加或者删除DOM元素
- 惰性渲染:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块
- v-if切换有一个局部编译/卸载的过程,切换过程中条件块内的事件监听器和子组件适当地被销毁和重建
v-show控制元素是否显示(控制css的display属性)
- 不管初始条件是什么,元素总是会被渲染
- v-show只编译一次,后面其实就是控制css
v-if 有更高的切换开销,而 v-show 有更高的初始渲染开销。因此,如果需要非常频繁地切换,则使用 v-show 较好;如果在运行时条件很少改变,则使用 v-if 较好。
循环绑定
v-for
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| <ul>
<li v-for='item in fruits'>{{item}}</li>
<li v-for='(item, index) in fruits'>{{item + '---' + index}}</li> <li v-for='(item, index) in myFruits'> <span>{{item.ename}}</span> <span>-----</span> <span>{{item.cname}}</span> </li>
</ul>
|
1 2 3 4 5 6 7 8 9
| <li v-for='(item, index) in fruits' :key='index'>{{item + '---' + index}}</li>
<li :key='item.id' v-for='(item, index) in myFruits'> <span>{{item.ename}}</span> <span>-----</span> <span>{{item.cname}}</span> </li>
|
1 2 3 4 5 6
|
<div v-for='(v,k,i) in obj'>{{v + '---' + k + '---' + i}}</div>
|
v-for和v-if一起使用
- 不推荐同时使用
v-if
和 v-for
- 当
v-if
与 v-for
一起使用时,v-for
具有比 v-if
更高的优先级。如果每一次都需要遍历整个数组,将会影响速度,尤其是当只需要渲染很小一部分的时候。
表单输入绑定
- 常见表单元素:
- input 输入框
- textarea 文本域输入框
- radio 单选框
- checkbox 复选框
- select 下拉菜单
- textarea和input用法一致
radio
- 每个单选框必须要有各自的value
- 每个单选框都需要通过v-model 绑定同一个值(data中的属性)
- v-model 绑定的数据默认可以是空,也可以是其中一个单选框的value
- 如果默认是其中一个单选框的value,则该单选框默认选中
- 当点击选中某一个单选框的时候, v-model绑定的数据就会变成当前的 value值
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <input type="radio" id="male" value="1" v-model='gender'> <label for="male">男</label>
<input type="radio" id="female" value="2" v-model='gender'> <label for="female">女</label>
<script> new Vue({ data: { gender: 2, }, }) </script>
|
checkbox
- 用法跟radio类似,区别是checkbox中v-model必须绑定一个数组
- 每个复选框必须要有各自的value
- 每个复选框都需要通过v-model 绑定同一个值(data中的属性)
- v-model 绑定的数据默认可以是空,也可以是其中某些复选框的value
- 当切换复选框的时候, v-model绑定的数据(该数组)就会添加或删除当前的 value值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| <div> <span>爱好:</span> <input type="checkbox" id="ball" value="1" v-model='hobby'> <label for="ball">篮球</label> <input type="checkbox" id="sing" value="2" v-model='hobby'> <label for="sing">唱歌</label> <input type="checkbox" id="code" value="3" v-model='hobby'> <label for="code">写代码</label> </div> <script> new Vue({ data: { hobby: ['2', '3'], }, }) </script>
|
select
- 每个option必须要有各自的value
- 给select通过v-model 绑定一个值
- v-model 绑定的数据默认可以是空,也可以是其中一个option的value
- 如果默认是其中一个option的value,则该option默认选中
- 当点击选中某一个option的时候, v-model绑定的数据就会变成当前的value值
- 如果select是多选下拉框,则v-model需要绑定一个数组
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| <select v-model='occupation'> <option value="0">请选择职业...</option> <option value="1">教师</option> <option value="2">软件工程师</option> <option value="3">律师</option> </select>
<select v-model='occupation2' multiple> <option value="0">请选择职业...</option> <option value="1">教师</option> <option value="2">软件工程师</option> <option value="3">律师</option> </select>
<script> new Vue({ data: { occupation: '2', occupation2: ['2', '3'] }, }) </script>
|
表单修饰符
1 2 3 4 5 6 7 8
| <input v-model.number="age" type="number">
<input v-model.trim="msg">
<input v-model.lazy="msg" >
|
表单验证
1 2 3 4 5
| <!-- 用表单提交事件 --> <form @submit="checkForm"></form>
<!-- 禁用表单默认的校验规则 --> <from novalidate="true"></from>
|
自定义指令 directive
第一个参数:指令名称
第二个参数:配置项
全局指令
1 2 3 4 5 6 7 8 9 10 11
| <input type="text" v-focus>
<script> Vue.directive('focus', { inserted: function (el) { el.focus(); } }); </script>
|
局部指令
- 局部指令只能在当前组件里面使用
- 当全局指令和局部指令同名时以局部指令为准
1 2 3 4 5 6 7 8 9 10 11
| new Vue({ directives: { focus: { inserted: function (el) { el.focus(); } } } });
|
自定义指令的钩子函数
- bind
- inserted
- update
- componentUpdated
- unbind
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <script> Vue.directive('color', { bind: function(){}, inserted: function(){}, update: function(){}, componentUpdated: function(){}, unbind: function(){}, }); </script>
|
带参数的自定义指令
- 钩子函数接收el、binding、vnode等参数
- el:绑定的dom元素。真实的dom,可对el进行dom操作
- binding:一个对象,包含指令的数据
- name:指令名,不包括 v‐ 前缀
- value:指令的绑定值,例如:v‐color=”myColor” 中,绑定值为myColor对应的值
- vnode:虚拟dom节点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <input type="text" v-color="myColor">
<script> Vue.directive('color', { bind: function(el, binding){ console.log(binding.name) console.log(binding.value) el.style.backgroundColor = binding.value; } }); new Vue({ el: '#app', data: { myColor: 'blue' } }) </script>
|
计算属性 computed
模板内的表达式非常便利,但是设计它们的初衷是用于简单运算的。在模板中放入太多的逻辑会让模板过重且难以维护。
计算属性用法
- 计算属性通过computed配置
- 每个计算属性是一个方法
- 方法里面需要renturn数据
- 例如:
return this.msg.split('').reverse().join('')
- return的数据依赖data中的值,上面的数据依赖this.msg
- return的数据就是计算属性的值
- 计算属性可以当成data中属性一样使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| <div>{{reversedMessage}}</div> <div v-text="reversedMessage"></div>
<script> new Vue({ el: '#app', data: { msg: 'Nihao' }, computed: { reversedMessage: function () { return this.msg.split('').reverse().join('') } } }) </script>
|
计算属性和methods方法对比
相同点:可以达到同样的效果
不同点:
- 计算属性可以缓存 只有计算属性依赖的data发生变化时才会重新计算
- methods方法多次调用或页面重新渲染的时候会重复计算
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| <div id="app"> <div>{{reverseString}}</div> <div>{{reverseString}}</div> <div>{{reverseMessage()}}</div> <div>{{reverseMessage()}}</div> </div> <script type="text/javascript">
var vm = new Vue({ el: '#app', data: { msg: 'Nihao', num: 100 }, methods: { reverseMessage: function(){ console.log('methods') return this.msg.split('').reverse().join(''); } }, computed: { reverseString: function(){ console.log('computed') var total = 0; for(var i=0;i<=this.num;i++){ total += i; } return total; } } }); vm.num = 101; </script>
|
计算属性的setter
计算属性默认只有 getter,不过在需要时你也可以提供一个 setter:
1 2 3 4 5 6 7 8 9 10 11 12 13
| reversedMsg: { get: function (){ return this.message.split('').reverse().join(''); }, set: function (){ this.message = value.split('').reverse().join(''); } }
|
计算属性传参
1 2 3 4 5 6 7
| computed: { myfilter() { return function(index){ return this.arr[index].username.match(this.name)!==null; } } }
|
侦听器 watch
侦听器用法
- 监听data变化执行异步(定时器、ajax等)操作或开销较大(复杂运算等耗时操作)的操作
- 一定是监听data中已经存在的数据
- 侦听器只有数据变化时才触发,如果想要默认触发一次侦听器,就要使用对象写法,并设置immediate
- immediate: true 立即执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
|
data: { firstName: 'zhang', lastName: 'san', fullName: '' }, watch: {
firstName: { handler: function(newValue, oldValue) { this.fullName = newValue + ' ' + this.lastName; }, immediate: true, }, lastName: { handler: function(newValue, oldValue) { this.fullName = this.firstName + ' ' + newValue; }, immediate: true, }, }
|
深度侦听
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| data: { obj: { a: 123 } }, watch: { obj: { deep: true, handler(newName, oldName) { console.log('obj.a changed'); }, } }
watch: { 'obj.a': { handler(newName, oldName) { console.log('obj.a changed'); } } }
|
侦听器和计算属性的区别
- 侦听器适用于:当一个数据变化时,要修改多个数据或者要进行异步操作
- 计算属性适用于:某个数据的值是一个或多个其它数据
运算
的结果
过滤器 filters
基本使用
- 过滤器可以用在两个地方:双花括号插值和v-bind表达式
- 过滤器应该被添加在表达式的尾部,由管道符
|
隔开
- 支持级联操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <div>{{msg | upper}}</div>
<div>{{msg | upper | lower}}</div>
<div :abc='msg | upper'>测试数据</div>
<script>
data: { msg: 'abc' },
</script>
|
局部过滤器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <div>{{msg | upper}}</div>
<script> new Vue({ data: { msg: 'abc' }, filters: { upper: function(val) { return val.charAt(0).toUpperCase() + val.slice(1); } } }) </script>
|
全局过滤器
1 2 3 4 5
| <script> Vue.filter('lower', function(val) { return val.charAt(0).toLowerCase() + val.slice(1); }); </script>
|
过滤器的参数
1 2 3 4 5 6 7 8 9
| <div>{{message | filterA('a', 'b')}}</div>
<script> Vue.filter('filterA', function(value, arg1, arg2){ }) </script>
|
生命周期
事物从出生到死亡的过程
Vue实例从创建 到销毁的过程
- 每个 Vue 实例在被创建时都要经过一系列的初始化过程——例如,需要设置数据监听、编译模板、将实例挂载到 DOM 并在数据变化时更新 DOM 等。同时在这个过程中也会运行一些叫做生命周期钩子的函数。
生命周期经历的主要阶段
挂载 - 初始化相关属性(data、methods等)
① beforeCreate
② created
③ beforeMount
④ mounted
更新 - DOM元素或组件的更新
① beforeUpdate
② updated
销毁(销毁相关属性)
① beforeDestroy
② destroyed
常用的钩子函数
beforeCreate |
在实例初始化之后,数据观测和事件配置之前被调用 此时data 和 methods 以及页面的DOM结构都没有初始化 什么都做不了 |
created |
在实例创建完成后被立即调用此时data 和 methods已经可以使用 但是还没有编译模板 |
beforeMount |
在挂载开始之前被调用 此时模板编译完成,但是页面还没有渲染,页面上还看不到真实数据 只是一个模板页面而已 |
mounted |
el被新创建的vm.$el替换,并挂载到实例上去之后调用该钩子。 数据已经真实渲染到页面上 在这个钩子函数里面我们可以使用一些第三方的插件或者请求后台数据 |
beforeUpdate |
数据更新时调用,发生在虚拟DOM打补丁之前。 data已经更新,但是页面上数据还是旧的 |
updated |
由于数据更改导致的虚拟DOM重新渲染和打补丁,在这之后会调用该钩子。 页面上数据已经替换成最新的 |
beforeDestroy |
实例销毁之前调用 |
destroyed |
实例销毁后调用 |
其他钩子函数
activated 被 keep-alive 缓存的组件激活时调用。
deactivated 被 keep-alive 缓存的组件停用时调用。
errorCaptured 当捕获一个来自子孙组件的错误时被调用。此钩子会收到三个参数:错误对象、发生错误的组件实例以及一个包含错误来源信息的字符串。此钩子可以返回 false
以阻止该错误继续向上传播。
响应式
变更方法(变异方法)
更改了原始数组
- push()
- pop()
- shift()
- unshift()
- splice()
- sort()
- reverse()
替换数组(非变异方法)
不更改原始数组,返回一个新数组
- filter
- concat
- slice
- map()
$set修改响应式数据
对于数组
Vue不能监听数组的变化,但是我们调用push、pop等变异方法都是响应式的,因为以上方法都是被Vue重写之后的方法
通过Vue.set修改响应式数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| <script> var vm = new Vue({ data: { items: ['a', 'b', 'c'] } })
Vue.set(vm.items, 1, 'x')
vm.items.splice(2)
</script>
|
对于对象
- Vue 无法检测 property 的添加或移除
由于 Vue 会在初始化实例时对 property 执行 getter/setter 转化,所以 property 必须在 data 对象上存在才能让 Vue 将它转换为响应式的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <script> var vm = new Vue({ data:{ a:1, obj: { name: 'zhang san' } } })
vm.b = 2
vm.obj.age = 18
</script>
|
- 对于已经创建的实例,Vue 不允许动态添加根级别的响应式 property。但是,可以使用
Vue.set(object, propertyName, value)
方法向嵌套对象添加响应式 property。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| <script> var vm = new Vue({ data:{ a:1, obj: { name: 'zhang san' } } }) Vue.set(vm.obj, 'age', 18) this.obj = Object.assign({}, this.obj, { age: 18 }) this.obj = { ...this.obj, age: 18 } </script>
|
Vue组件化开发
组件注册
全局组件
全局组件就是可以在任何vue实例中使用的组件
1 2 3 4 5 6 7 8 9
| <script> Vue.component('my-component', { template: '<div>A custom component!</div>' }) </script>
|
通过html标签使用组件
- 组件注册后是通过html标签来使用,标签名称就是组件名称
1 2 3 4
| <div id="app"> <my-component></my-component> </div>
|
组件名称命名方式
kebab-case命名方式:例如my-component
PascalCase命名方式:例如MyComponent
- 使用此种方式命名组件时,使用组件时注意:
- 在template中使用时PascalCase方式和kebab-case都可以使用:
<MyComponent></MyComponent>
<my-component></my-component>
- 直接在DOM中使用要写成kebab-case的方式:
<my-component></my-component>
总之建议使用kebab-case方式命名和使用组件
template使用注意
- template模板必须包含在一个根元素中
- template模板可以是模板字符串
添加data数据
1 2 3 4 5 6 7 8 9 10
| <script> Vue.component('button-counter', { data: function () { return { count: 0 } }, template: '<button v-on:click="count++">点击了{{ count }}次</button>' }) </script>
|
- 注意: data必须是一个函数,并且返回一个对象
- 因为组件可以被复用,如果data是个对象,那每个组件实例都共享该data,就会导致在一个实例里面修改data影响所有的实例。
- 如果data是个函数,每个实例可以维护一份被返回对象的独立的拷贝
- 根组件的data不需要配置成函数,因为根组件不会被复用
局部组件
局部组件就是只能在当前实例中使用的组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <div id="app"> <my-component></my-component> </div>
<script> new Vue({ components: { 'my-component': { template: '<div>A custom component!</div>' } } }) </script>
|
template的其他使用方式
1 2 3 4 5 6 7 8 9 10 11
| <template id="temp"> <div> 123 </div> </template>
<script> Vue.component('my-component', { template: '#temp' }) </script>
|
1 2 3 4 5 6 7 8 9 10 11
| <script type="text/x-template" id="temp"> <div> 123 </div> </script>
<script> Vue.component('my-component', { template: '#temp' }) </script>
|
组件通信
父组件与子组件
我们注册的组件肯定是需要在某个地方使用的,我们称作在父组件
中使用子组件
- 子组件可以接收父组件传递的数据
- 比如有一个子组件是显示学生信息,父组件传递不同的数据,子组件就可以显示不同的学生信息
父组件向子组件传值
- 子组件可以配置props属性,父组件就可以通过props属性向子组件传递数据
- props配置的属性是自定义属性
- 如果传递的是静态值(就是不是data中配置的数据),除字符串外,其余的还是要使用v-bind绑定
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| <my-component :msg="message"></my-component>
<script> Vue.component('my-component', { props: ['msg'], template: '<div>{{ msg }}</div>' })
new Vue({ data: { message: "来自父组件的数据" } }) </script>
|
props属性值类型
- 字符串 String
- 数值 Number
- 布尔值 Boolean
- 数组 Array
- 对象 Object
1 2 3 4 5 6 7 8 9 10 11 12
| props:{ list:{ type: Object, default: function () { return { message: 'hello' } }, required: true } }
|
props的属性名规则
由于html标签属性不区分大小写,onClick
属性会被转成onclick
属性,所以:
- 如果子组件的props中一定要配置驼峰式的属性,父组件中使用的时候需要使用短横线
-
的形式
子组件向父组件传值
$emit
- 子组件通过$emit一个自定义事件向父组件传递信息
- 父组件通过v-on监听子组件的自定义事件
v-on:自定义事件名=方法
- 自定义事件的名称就是$emit的第一个参数
- 自定义事件绑定的方法默认接收
$emit
的第二个参数,如果需要显式传参,使用**$event**
1 2 3
| this.$emit('子组件的自定义事件',值)
@子组件的自定义事件="父组件的事件($event)"
|
兄弟组件传值
EventBus事件中心
1 2 3
| <script> var hub = new Vue() </script>
|
1 2 3
| <script> hub.$emit('custom-event', 123); </script>
|
1 2 3 4 5
| <script> hub.$on('custom-event', (val) => { console.log(val) }); </script>
|
1 2 3
| <script> hub.$off('custom-event') </script>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
| <div id="app"> <div>父组件</div> <div> <button @click='handle'>销毁事件</button> </div> <test-tom></test-tom> <test-jerry></test-jerry> </div> <script> var hub = new Vue();
Vue.component('test-tom', { data: function(){ return { num: 0 } }, template: ` <div> <div>TOM:{{num}}</div> <div> <button @click='handle'>点击</button> </div> </div> `, methods: { handle: function(){ hub.$emit('jerry-event', 2); } }, mounted: function() { hub.$on('tom-event', (val) => { this.num += val; }); } }); Vue.component('test-jerry', { data: function(){ return { num: 0 } }, template: ` <div> <div>JERRY:{{num}}</div> <div> <button @click='handle'>点击</button> </div> </div> `, methods: { handle: function(){ hub.$emit('tom-event', 1); } }, mounted: function() { hub.$on('jerry-event', (val) => { this.num += val; }); } }); var vm = new Vue({ el: '#app', methods: { handle: function(){ hub.$off('tom-event'); hub.$off('jerry-event'); } } }); </script>
|
组件插槽
插槽的作用
1 2 3
| <alert-box> bug </alert-box>
|
slot标签
- 子组件通过slot标签接收父组件传递的html数据
1 2 3 4 5 6 7 8 9 10
| <script> Vue.component('alert-box', { template: ` <div class="demo-alert-box"> <strong>Error!</strong> <slot></slot> </div> ` }) </script>
|
- 如果slot标签中有内容
- 父组件不传数据时:默认显示slot中的内容
- 父组件传了数据时:会覆盖slot中的数据
匿名插槽
<slot></slot>
是匿名插槽,因为slot标签没有名字
- 匿名插槽也叫默认插槽
具名插槽
1
| <slot name="header"></slot>
|
- 向具名slot传递数据
- 通过template标签包裹需要传递的内容
- template标签上通过v-slot绑定slot的name
1 2 3 4 5
| <base-layout> <template v-slot:header> <h1>这是header</h1> </template> </base-layout>
|
注意:匿名插槽不需要template标签,所有没有包裹在template标签中的数据都会被匿名插槽接收
注意事项
- 向具名插槽传递数据需要template标签,匿名插槽不需要
- 匿名插槽其实是省略了外部的
<template v-slot:default></template>
- 具名插槽可以有多个,匿名插槽只能一个
- 父组件中给不同插槽传递数据时,顺序无所谓
作用域插槽
父组件也可以接收slot传递的数据
- slot标签通过v-bind绑定传递给父组件的数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <script> Vue.component('alert-box', { data: { message: 123 }, template: ` <div class="demo-alert-box"> <strong>Error!</strong> <slot name="header" v-bind:msg="message"> {{message}} </slot> </div> ` }) </script>
|
- 父组件在template标签的v-slot上接收数据
1 2 3 4 5 6
|
<template v-slot:header="data"> {{data.msg + 456}} </template>
|
- 接收匿名插槽的数据
- 需要把省略的
<template v-slot:default></template>
补上
1 2 3 4 5
| <slot v-bind:msg="message">{{message}}</slot>
<template v-slot:default="data"> {{data.msg + 456}} </template>
|
1 2 3 4 5 6
| <template #header></template> <template #header="data"></template>
<template #default></template> <template #default="data"></template>
|
模块化
ES6模块化语法:
1 2 3 4 5 6 7
| 默认导入:import xxx from 'xxx'; 默认导出:export default {};
按需导入:import { a, b, c } from 'xxx'; 按需导出:export const a = 1; export function sayHi() {}
只导入执行,不需要获取模块内部的数据:import 'xxx';
|
- 默认导出语法 export default 默认导出的成员
- 默认导入语法 import 接收名称 from ‘模块标识符’
1 2 3 4 5 6 7 8 9 10 11 12 13 14
|
let a = 10 let c = 20
let d = 30 function show() {} export default { a, c, show }
|
1 2 3 4 5
| import m1 from './m1.js' console.log(m1)
|
按需导出 与 按需导入
1 2 3 4 5 6 7
|
export let s1 = 'aaa'
export let s2 = 'ccc'
export function say = function() {}
|
1 2 3 4 5
| import { s1, s2 as ss2, say } from './m1.js' console.log(s1) console.log(ss2) console.log(say)
|
直接导入并执行模块代码
1 2 3 4 5
|
for(let i = 0; i < 3; i++) { console.log(i) }
|
默认导出 和 按需导出 一起使用
1 2 3 4 5 6 7 8 9 10 11
| let b = 'bbb';
export let a = 'aaa'
export function fn(){ console.log("fn"); }
export default{ b }
|
1 2 3 4 5
| import test,{a,fn} from "./test.js"
console.log(a); console.log(test.b); fn()
|
MVVM
1 2 3 4 5 6 7 8 9 10 11 12 13
| M model 模型|数据|data V view 视图|模板|el|HTML VM view-model 视图模型
MVVM是 Model-View-ViewModel 的缩写。 MVVM是一种软件设计模式,由MVC演变而来。实现了数据层、视图层、控制器的代码分离。 Model 层代表数据模型,也可以在Model中定义数据修改和操作的业务逻辑; Vue中数据层都放在data里面 View 代表UI 组件,它负责将数据模型转化成UI 展现出来, Vue中view即我们的HTML页面 ViewModel 是一个同步View 和 Model的对象(桥梁)。 Vue中ViewModel即Vue的实例
在MVVM架构下,View 和 Model 之间并没有直接的联系,而是通过ViewModel进行交互,Model 和 ViewModel 之间的交互是双向的, 因此View 数据的变化会同步到Model中,而Model 数据的变化也会立即反应到View 上。
ViewModel 通过双向数据绑定把 View 层和 Model 层连接了起来,而View 和 Model 之间的同步工作完全是自动的,无需人为干涉,因此开发者只需关注业务逻辑,不需要手动操作DOM, 不需要关注数据状态的同步问题,复杂的数据状态维护完全由 MVVM 来统一管理
|