vue 文档
生命周期
vue的生命周期分为四个阶段: 创建前/创建后 挂载前/挂载后 更新前/更新后 销毁前/销毁后
所有的生命周期钩子自动绑定this上下文到实例中,因此可以访问数据,对属性和方法运算。不能使用箭头函数来定义一个生命周期。因为箭头函数绑定了父上下文,会报错。
-
beforeCrete创建前:在实例初始化之后,数据观测(data observer)和event/watcher事件配置之前被调用 -
creted创建后:在实例创建完成后被立即调用。在这一步,实例已完成以下配置:数据观测(data observer),属性和方法的运算,watch/event事件回调。挂载还没开始,$el属性不可用 -
beforeMount挂载前:在挂载开始之前被调用:相关的render函数首次被调用 -
mounted挂载后:实例被挂载后调用,这是el被新创建的vm.el也在文档内。mounted不会保证所有的子组件也都一起被挂载,如果希望整个视图都渲染完毕,可以在mounted内部使用vm.$nextTick
-
beforeUpdate更新前:数据更新时被调用,发生在虚拟DOM更新之前,这里适合在更新之前访问现有的DOM,比如手动移出已添加的事件监听器 -
updated更新后:由于数据更改导致虚拟DOM重新渲染,在这之后会调用该钩子。当这个钩子被调用时,组件DOM已经更新,所以现在可以执行依赖DOM的操作。但是应该避免在此时更改状态,最好使用计算属性和watch来替代 -
beforeDstory销毁前:实例销毁之前调用。这一步实例仍然可用 -
destoryed销毁后:实例销毁后调用。被调用后,该对应vue实例的所有指令都被解绑,所有事件监听器都被移除。所有的子实例也都被销毁
一上来就执行的只有前四个 beforeCreate created beforeMount mounted
服务器端渲染只有beforeCreate created执行
计算属性和侦听器
计算属性: 在模板中放入太多的逻辑会让模板过重且难以维护,对于模板内任何复杂逻辑都可以使用计算属性
计算属性 vs 方法
两种方式都可以实现相同的效果。 不同的是计算属性是依赖数据进行缓存的。只有相关依赖的数据发生变化时才会重新计算求值,数据不变的话,多次访问会立即返回之前的计算结果。相比之下,每当触发重新渲染时,调用方法总会再次执行函数。 方法可以接收参数,而计算属性不可以
为什么需要缓存? 假设有一个性能开销比较大的计算属性A,需要遍历一个巨大数数组并做大量的计算,然后我们可能有其他计算属性依赖于A。如果没有缓存,我们将不可避免多次执行A的getter。主要就是优化性能,减少计算。如果你不希望有缓存,可以用方法来替代。
计算属性和侦听属性
计算属性的setter
计算属性默认只有getter,需要时也可以提供一个setter:
computed:{
fullName:{
get:function(){
return this.firstName + ' ' + this.lastName
},
set:function(newValue){
var names = newValue.split(' ')
this.firstName = names[0];
this.lastName = names[names.lengtg-1]
}
}
}
vm.fullName = 'john Doe' 设置值时setter会被调用,vm.firstName 和vm.lastName 也会被更新
侦听器
数据变化时想要执行异步操作时,可以使用watch
class和style绑定
对象语法 数组语法
条件渲染
用key管理可复用的元素
切换input不会重新渲染,给一个key值,来表达这两个元素是完全独立的,不要复用他们。 只需要添加一个key属性即可
请问 v-if 和 v-show 有什么区别
-
共同点:
v-if和v-show都是动态显示DOM元素
-
不同点:
1.编译过程:v-if是真正的条件渲染;v-show的元素始终会被渲染并保留DOM,v-show只是简单的切换display属性 2.编译条件:v-if是惰性的,如果初始渲染条件为假,则什么也不做,直到条件第一次变为真,才开始渲染条件块代码。v-show不管初始条件是什么,元素总是会被渲染,并且只是简单的基于css进行切换。 3.性能消耗:v-if有更高的切换消耗。v-show有更高的初始渲染消耗 4.应用场景:v-if适合运行时条件很少改变时使用。v-show适合频繁切换
v-for和v-if的优先级
当他们处于同一节点,v-for的优先级比v-if更高,这意味着v-if将分别重复运行与每个v-for循环中。
列表渲染
遍历数组 v-for="(item,index) in list" :key="item.id"
遍历对象 v-for="(value,name,index) in object" :key="item.id"
维护状态
vue使用v-for渲染元素列表时,它默认使用'就地更新'的策略,如果数据项的顺序被改变了,vue不会移动DOM元素来匹配数据项的顺序,而是就地更新每个元素,并且确保他们在每个索引位置正确渲染。这个默认的模式是高效的,但是只适用于不依赖子组件状态或临时DOM(表单输入值)的列表渲染输出。 为了让vue能跟踪每个节点的身份,从而重用和重新排序现在元素,你需要为每项提供一个唯一的key属性
数组更新检测
变异方法
变异方法,顾名思义,会改变调用了这些方法的原始数组
- pop()
- push()
- shift()
- unshift()
- sort()
- reverse()
- splice()
替换数组
非变异方法:不会改变原始数组,而总是返回一个新数组。 当使用非变异方法时,可以用新数组替换旧数组
- filter()
- concat()
- slice()
注意事项
1.利用索引直接修改一个数组项目 2.当你修改数组长度的时候
vm.items[1] = 'x' //不是响应的 vm.items.length = 2 //不是响应的
可以使用set(vm.items,indexOfItem,newValue)
vm.items.splice(indexOfItem,1,newValue)
vm.items.splice(newLength)
对象变更检测注意事项
vue不能检测对象属性的添加和删除 但是可以使用Vue.set(object,keyName,value) 向实例添加响应式属性
Vue.set(vm.user,'age',27);
vm.$set(vm.user,'age',27)
给已有对象赋值多个属性
- Object.assign()
vm.user = Object.assign({},vm.user,{
age:27,
height:180
})
显示过滤/排序后的结果
想要显示一个数组经过过滤或排序后的列表,而不实际改变或重置原始数组。 这种情况下可以创建一个计算属性,来返回过滤或排序后的数组
事件处理
- 绑定事件
<div @click="handleClick">do something</div>
方法定义在methods中,this指向的是当前的实例。event是原生DOM事件
- 访问原始DOM事件
直接在方法中传入$event
<button @click="warn('something',$event)"></button>
methods:{
warn:function(message,event){
if(event){
event.preventDefault()
}
alert(message);
}
}
事件修饰符
- .stop
- .prevent
- .capture
- self
- once
- passive
//阻止单机事件继续
<a @click.stop="handleClick"></a>
// 阻止默认行为提交事件不在重载页面
<form @click.prevent="handleSubmit"></form>
// 修饰符可以串联
<a @click.stop.prevent="doThat"></a>
// 只有修饰符
<form @submit.prevent></form>
// 事件捕获模式 先处理此事件,然后再处理内部事件
<div @click.capture="doThis"></div>
// 只在当event.target是当前元素自身时才会触发
<div @click.self="doThat"></div>
// 绑定事件只触发一次
<a @click.once="doThis"></a>
//滚动事件的默认行为将会立即触发 不会等待onScroll完成 可以提升移动端的性能
<div @scroll.passive="onScroll"></div>
使用修饰符顺序很重要
@click.prevent.self 会阻止所有的点击
@click.self.prevent 只会住址自身的点击
按键修饰符
- .enter
- .tab
- .delete
- .esc
- .space
- .up
- .down
- .left
- .right
自定义按键修饰符别名: Vue.config.keyCodes.f1 = 112
系统修饰符
按下相应键时才触发鼠标和键盘事件的监听器 和keyup一起用时,时间出发时修饰键必须处于按下状态
- .ctrl
- .alt
- .shift
- .meta
// alt + C
<input @keyup.alt.67="clear">
// ctrl +click
<div @click.ctrl="doSomething"></div>
.exact修饰符
.exact 修饰符允许你控制由精确的系统修饰符组合触发的事件
// alt或shift被一同按下时也会触发
<button @click.ctrl="onClick"></button>
//尤其只有ctrl被按下时才触发
<button @click.ctrl.exact="onCtrlClick"></button>
// 没有任何系统修饰符被按下时才触发
<button @click.exact="onClick"></button>
鼠标修饰符
- .left
- .right
- .middle
为什么在HTML中监听事件
- 扫一眼HTML模板便能轻松定位在js代码中对应的方法
- 无需再js里手动绑定事件,和DOM解耦,更易于测试
- 当一个viewModel 被销毁时,所有的事件处理器都会自动被删除,无需担心如何清理它们
表单输入绑定
v-model本质上是语法糖 负责监听用户的输入时间以更新数据 v-model会忽略所有表单元素的value,checked,selected v-model 需要在data中声明初始值
:value @input
<input type="text" :value="value" @input="handler">
<textarea :value="value" @input="handler"></rextarea>
:value @change
<input type="checkbox" :checked @change="handle">
<input type="radio" :checked @change="handle">
<select :value="value" @change="handle"></select>
select多选可以加multiple 参数
<select v-model="value" multiple></select>
修饰符
- .lazy
- .number
- tirm
组件基础
声明一个组件
// 1
Vue.component('button-component',{
data(){
return {
count:0
}
},
template:`<button @click="count++">{{count}}</button>`
})
// 2
<div id="root"></div>
new Vue({
el:"#root"
})
组件的复用
组件可以进行任意次数的复用
每用一次组件,就会创建一个新的实例
data必须是一个函数
因为我们每使用一次组件,就会创建一个新的实例,每个实例都单独维护一份被返回对象的独立的拷贝,如果data不是一个函数,所有的实例都是修改同一个data里的数据,会影响所有的组件
通过prop向子组件传递数据
//传递静态数据
<blog-post title="this is title!"></blog-post>
//传递动态数据
<blog-post :title="title"></blog-post>
单个根元素
包裹组件的模板必须有一个根元素
监听子组件事件
使用组件的地方绑定自定义事件 然后在组件内部方法中用$emit触发组件绑定的自定义事件
组件上使用v-model
<custom-input v-model="text"></custom-input>
Vue.component('custom-input',{
props:['value'],
template:`<input :value="value" @input="$emit('input',$event.target.value)"/>`
})
通过插槽分发内容
<alert-box>
this is message!
</alert-box>
Vue.component('alert-box',{
template:`
<div>
<strong>title</strong>
<slot></slot>
</div>
`
})
解析DOM模板的注意事项
ul ol table select 这些元素内部有指定的元素 可以使用is 属性
<table>
<tr is="blog-post"></tr>
</table>
深入了解组件
组件名大小写
Vue.component('my-component-name',{})
Vue.component('myComponentName',{})
但是在模板中使用的时候只可以使用<my-component-name></my-component-name>这一种方法
全局注册
Vue.component('my-component-name',{//选项})
全局注册的组件,可以用在任何新创建的Vue根实例(new Vue)的模板中
Vue.component('component-a', { /* ... */ })
Vue.component('component-b', { /* ... */ })
Vue.component('component-c', { /* ... */ })
new Vue(el:'#app')
<div id="app">
<component-a></component-a>
<component-b></component-b>
<component-c></component-c>
</div>
局部注册
声明一个组件
var CompoentA = {}
注册
new Vue({
components:{
'component-a':ComponentA
}
})
使用
<div>
<component-a></component-a>
</div>
模块系统
在模块系统中局部注册
导入
import ComponentA from './ComponentA'
注册
export default {
components:{
ComponentA
}
}
使用
<template>
<ComponentA></ComponentA>
</template>
prop
prop的大小写
因为HTML的attribute名是大小写不敏感的。所以浏览器会把所有大写属性名转换为小写。 所有在HTML模板中要用短横线分隔命名法,组件接收的时候可以用驼峰命名
<blog-post post-title="hello!"></blog-post>
Vue.component('blog-post',{
props:['postTitle']
})
prop传值的类型
- 数字
- 字符串
- 布尔值
- 数组
- 对象
- 一个对象的所有属性
实际上任何类型的值都可以传给一个prop
单项数据流
所有的prop使得其父子prop之间形成了一个单向向下绑定: prop的更新会向下流动到子组件中,但是反过来不行。就是为了防止从子组件意外改变父组件的状态,从而导致你的数据流向难以理解
父组件发生更新时,子组件中的所有prop都将会刷新为最新的值。所以你不应该在一个子组件内部改变prop
prop验证
export default {
props:{
propA:Number,
propB:[String,Number],
propC:{
type:String,
requires:true
},
propD:{
type:Number,
default:100
},
propE:{
type:Object,
default:function(){
return {message:'hello'}
}
},
propF:{
validator:function(value){
return ['sucess','warning','danger'],indexOf(value)!== -1
}
}
}
}
type的值可以是原生构造函数中的一个:
- String
- Number
- Boolean
- Array
- Object
- Date
- Function
- Symbol
禁用Attribute继承
export default{
inhertAttrs:false
}
this.$attrs可以获取组件上的属性值
自定义事件
事件名
推荐使用短横线命名法 kebab-case
将原生事件绑定到组件上
在一个组件上监听一个原生的事件
可以使用.native修饰符
<base-input @focus.native="onFocus"></base-input>
listener`将所有的事件监听器指向这个组件的某个特定的子元素
.sync修饰符
当我们用一个对象同时设置多个prop的时候,也可以将.sync修饰符和v-bind配合使用:
<text-document v-bind:title.sync="doc.title"></text-document>
这样会把doc对象中的每一个属性,都作为一个单独的prop穿进去,然后各自添加用于更新的v-on监听器
插槽
插槽内容
<slot>元素作为承载分发内容的出口
<my-component>
this is content
</my-component>
<template>
<a :href="url">
<slot></slot>
</a>
</template>
当组件渲染的时候,<slot></slot>将会被替换为this is content
插槽内可以包含任何模板代码,包括HTML,甚至其他的组件
如果my-component没有包含一个slot标签,则该组件包裹的任何内容都会被抛弃
编译作用域
父级模板里的所有内容都在父级作用域中编译的,子模板里的所有内容都是在子作用域中编译的。在动态渲染slot的时候,是取不到包裹组件的值的
后备内容
<slot>submit</slot> //这里的submit会作为后备的内容
<button type="submit">
submit
</button>
但是如果要提供了内容
<submit-button>
Save
</submit-button>
则会替代后备内容
<button>Save</button>
具名插槽
组件内部用slot标签占位,slot有name属性
<template>
<div>
<header>
<slot name="head"></slot>
</header>
<div>this is content</div>
<footer>
<slot name="foot"></slot>
</footer>
</div>
</template>
使用组件的时候,用<template>标签v-slot来指定传给哪个具名的slot
v-slot只能添加在<template>上
<base-layout>
<template v-slot:foot>
header
</template>
hello
<template v-slot:head>
footer
</template>
</base-layout>
作用域插槽
模板里slot先v-bind绑定好user对象
<template>
<div class="user">
<slot :user="user">{{ user.first }}</slot>
</div>
</template>
// 使用组件的地方去访问模板内的数据
<current-user>
<template v-slot:default="slotProps">
{{slotProps.user.last}}
</template>
</current-user>
需要使用<template>标签上绑定v-slot:default="slotProps"
然后就可以访问到模板里的user数据{{slotProps.user.last}}
独占默认插槽的缩写语法
具名插槽写法v-slot:head
作用域插槽写法:v-slot:default="slotProps"
缩写语法:v-slot="slotProps"
<current-user v-slot="slotProps">
{{slotProps.user.first}}
</current-user>
只要出现多个插槽,一定要为插槽使用完正的基于<template>语法
<current-user>
<template v-slot:default="slotProps">
{{slotProps.user.first}}
</template>
<template v-slot:other="otherProps">
{{otherProps.other.keys}}
</template>
</current-user>
解构插槽prop
作用域插槽的内部工作原理就是将你的插槽内容包括在一个传入单个参数的函数里
function(slotProps){
// ...
}
也可以使用解构来传入具体的插槽prop
<current-user v-slot="{user}">
{{user.first}}
</current-user>
<current-user v-slot="{user:person}">
{{person.first}}
</current-user>
<current-user v-slot="{user={first:'guest'}}">
{{user.first}}
</current-user>
动态插槽名
<base-layout>
<template v-slot:[dynamicSlotName]>
</template>
</base-layout>
具名插槽的缩写
v-slot:缩写语法#
<base-layout>
<template v-slot:head>
</template>
</base-layout>
缩写语法
<base-layout>
<template #head>
</template>
</base-layout>
动态组件和异步组件
在动态组件上使用keep-alive
当组件之间进行切换的时候,又想保持这些组件的状态,可以避免反复重渲染导致的性能问题
tab切换实例:每次切换新标签的时候,vue都创建了一个新的currentTabComponent实例
我们更希望那些标签的组件实例切换的时候能够被缓存下来。
可以使用<keep-alive></keep-alive>元素将其动态组件包裹起来,这样切换的时候,之前的tab的状态会被保存下来
异步组件
// 异步加载组件
new Vue({
components:{
my-component:()=>import('./my-async-component')
}
})
异步加载状态
加载异步组件的时候,还可以传递参数
const AayncComponent = () =>({
component:import('./MyComponent.vue'),
loading:LoadingComponent,
error:ErrorComponent,
delay:200,
timeout:3000
})
new Vue({
components:{
'async-component':AayncComponent
}
})
处理边界情况
访问元素和组件
访问根组件
根组件就是顶级new Vue()的实例
可以用$root属性进行访问
this.$root.name
this.$root.name = 'hlf'
this.$root.baz()
访问父级组件实例
$parent :访问父级组件实例
this.$parent.name
this.$parent.name = 'hlf'
this.$parent.getData()
访问子组件实例或子元素
ref:通过ref属性为子组件赋予一个id引用
<my-component ref="myComponent"></my-component>
this.$refs.myComponent//获取当前实例
refs
依赖注入
provide:提供给后代组件的数据/方法
inject:任何后代组件里,可以使用inject来接受通过provide定义的属性和方法
依赖注入所提供的属性是非响应式的
程序化的事件侦听器
$emit可以被v-on侦听,vue实例同时在其事件接口中提供了其他方法
- $on(eventName,eventHandle)侦听一个事件
- $once(eventName,eventHandle)一次性侦听一个事件
- $off(eventName,eventHandle)停止侦听一个事件
一般不会用到这些,但是当你需要在一个组件实例上手动侦听事件时,可以派上用场
循环引用
递归组件
组件是可以是在自己的模板中调用自身的。
给自己组件定义name属性
<base-component></base-component>
new Vue({
name:'myself',
template:`<div><myself></myself></div>`
})
使用递归组件确保调用的时候是有条件性的,稍有不慎,会导致无限循环
组件之间的循环引用
X-template
<script type="text/x-template" id="hello-teamplte">
<p>hello</p>
</script>
new Vue({
template:'#hello-teamplte'
})
这中生成模板的方式避免使用
控制更新
强制更新
$forceUpdate:强制更新
不推荐使用
通过v-once创建低开销的静态组件
有时候可能有一个组件,这个组件包含了大量静态内容。
这种情况下,可以在根元素上添加v-once,确保这些内容只计算一次然后缓存起来
但是不要过度使用这个模式,除非你非常留意渲染变慢了,不然完全是没有必要的
进入/离开和列表过渡
单元素/组件的过渡
transition:vue提供了<transition></transition>的封装组件,可以给任何元素和组件添加进入/离开状态
- 条件渲染使用v-if
- 条件展示使用v-show
- 动态组件
- 组件根节点
<div id="demo">
<button @click="show = !show">Toggle</button>
<transition>
<p v-if="show">hello</p>
</transition>
</div>
.fade-enter-active,.fade-leave-active{
transition:opacity .5s
}
,fade-enter,.fade-leave-to{
opacity:0
}
过渡的类名
在进入/离开的过渡中,会有6个class切换
v-enter:定义进入过渡的开始状态v-enter-active:定义进入过渡生效时的状态v-enter-to:定义进入过渡的结束状态v-leave:定义离开过渡的开始状态v-leave-active:定义离开过渡生效时的状态v-leave-to:定义离开过渡的结束状态
css过渡
可复用性
混入
一个混入对象可以包含人已组建选项。 当组件使用混入对象时,所有混入对象的选项将被混合进入该组件本身的选项
var myMixin = {
created:function(){
this.hello()
},
methods:{
hello(){
console.log('hello');
}
}
}
定义一个使用混入对象的组件
var Component = Vue.extend({
mixins:[myMixin]
})
vat component = new Component(); // 'hello frin mixin!'
选项合并
当组件和混入对象含有同名选项时,这些选项将会进行合并 data数据对象在内部会进行递归合并,并在发生冲突时以组件数据优先 同名钩子函数将合并成一个数组,所以都会被调用。 混入对象的钩子将在组件自身钩子之前调用
值为对象的选项,如methods、components、detrctives将被合并成同一个对象
两个对象键名冲突时,取组件对象的键值对
var mixin = {
data(){
return{
message:'hello',
foo:'abc'
}
}
}
new Vue({
mixin:[mixin],
data(){
return{
message:'goodbye',
bar:'def'
}
},
created(){
console.log(this.data); // {message:'goodbye',foo:'abc','def'}
}
})
---
var mixin = {
create(){
console.log('混入对象的钩子被调用');
}
}
new Vue({
mixins:[mixin],
created(){
console.log('自身组件钩子被调用');
}
})
//'混入对象的钩子被调用'
//'自身组件钩子被调用'
---
var mixin = {
methods:{
foo(){
console.log('foo');
},
conflicting(){
console.log('from mixin');
}
}
}
var vm = new Vue({
mixins:[mixin],
methods:{
bar(){
console.log('bar');
},
conflicting(){
console.log('from self');
}
}
})
vm.foo() //'foo'
vm.bar() //'bar'
vm.conflicting() // from self
全局混入
混入也可以进行全局注册。但是使用时要格外小心! 一旦使用全局混入,它将影响每一个之后创建的Vue实例. 使用恰当是,这可以用来为自定义选项注入处理逻辑
Vue.mixin({
created(){
var myOption = this.$options.myOption
if(myOption){
console.log(myOption);
}
}
})
new Vue({
myOption:'hello!'
})
//'hello'
自定义选项合并策略
自定义选项将使用默认策略,即简单的覆盖已有值。
如果想让自定义选项以自定义逻辑合并,可以向Vue.config.optionMergeStrategies添加一个函数
Vue.config.optionMergeStrategies.myOption = function(toVal,fromVal){
//返回合并后的值
}
对于多数值为对象的选项,可以适用与`methods`相同的合并策略
var strategies = Vue.config.optionMergeStategies;
strategies.myOption = strategies.methods;
自定义指令
除了核心功能内置的指令v-model 和 v-show vue也允许注册自定义指令 需要对普通DOM元素进行底层操作,这时候,就会用到自定义指令。
Vue.directive('focus',{
inserted:function(el){
el.focus()
}
})
局部注册,组件中也可以声明directives 选项
directives:{
focus:{
//指令的定义
inserted:function(el){
el.focus()
}
}
}
<input v-focus>
钩子函数
一个指令定义对象可以提供如下几个钩子函数:
- bind : 只调用一次,指令第一次绑定到元素时调用
- inserted : 被绑定元素插入父节点时调用
- update : 所在组件VNode更新时调用,可以发生在其子VNode更新之前
- componentUpdated : 指令所在组件的VNode及其子Vnode全部更新后调用
- unbind : 只调用一次,指令与元素解绑时调用
钩子函数参数
-
el:指令所绑定的元素,可以用来直接操作DOM
-
binding:一个对象包含以下属性:
- name:指令名,不包含
v-前缀 - value:指令的绑定值
- oldValue:指令绑定的前一个值.仅在
update和componentUpdated可用 - expression:字符串形式的表达式
- arg:传给指令的参数
- modifiers:一个包含修饰符的对象
- name:指令名,不包含
-
vnode:Vue编译生成的虚拟节点
-
oldVnode:上一个虚拟节点
除了el之外,其他参数都应该是只读的,切勿进行修改
动态指令参数
过滤器
过滤器可以用在两个地方“双花括号插值和v-bind表达式” 过滤器应该被添加在kavascript表达式的尾部,由管道符号指示
{{ message | capitalize }}
<div v-bind:id="rawId | formatId"></div>
局部过滤器
filters:{
capitalize:function(value){
if(!value)return ''
value = value.toString()
return value.charAt(0).toUpperCase() + value.slice(1)
}
}
全局过滤器
在创建 Vue 实例之前全局定义过滤器
Vue.filter('captalize',function(value){
if(!value) return ''
value = value.toString()
return value.charAt(0).toUpperCase() + value.slice(1)
})
new Vue({
//...
})
过滤器函数第一个参数就是表达式的值 capitalize过滤器将会受到message的值作为第一个参数 过滤器可以串联
{{ message | filterA | filterB }}
过滤器就是js函数,因此可以接受参数
{{ message | filterA('arg1',arg2) }}
message默认作为第一个参数,'arg1'作为第二个,arg2作为第三个参数