vue API 知识点(2) --- 选项 总结
一、选项 / 数据
1.data
当一个组件被定义,data 必须声明为返回一个初始数据对象的函数,因为组件可能被用来创建多个实例,如果 data 仍然是一个纯碎的对象,则所有的实例将被共享引用同一个数据对象。通过提供 data 函数,每次创建一个新实例后,我们能够调用 data 函数,从而返回初始数据的一个全新副本数据对象。
2.props
props 可以是数组或者对象,用于接收来自父组件的数据。props 中使用对象可以配置高级选项,类型检测,自定义验证,设置默认值
可以使用以下选项
type:可以是原生构造函数中的一种:String、Number、Boolean、Array、Object、Date、Function、Symbol、任何自定义构造函数,或上述内容组成的数组。会检查一个 prop 是否是给定的类型,否则抛出警告。
default:为 prop 指定一个默认值。如果该 prop 没有被传入,则换做用这个值。对象或数组的默认值必须从一个工厂函数返回
required:Boolean 定义该 prop 是否是必填项
validator:function 自定义验证函数会将该 prop 的值作为唯一的参数带入。返回 false 就给默认值,控制台有警告
props: { // 检测类型 height: Number, // 检测类型 + 其他验证 age: { type: Number, default: 0, required: true, validator: function (value) { return value >= 0 } } }
3.propsData
只用于 new 创建的实例中,创建实例时传递 props,主要作用时方便测试(开发的过程中基本用不到)
4.computed 计算属性
计算属性将被混入到 Vue 实例中,所有 geter 和 setter 的 this 上下文自动地绑定为 Vue 实例
注意如果你为一个计算属性使用了箭头函数,则 this 不会指向这个组件的实例,不过你仍然可以将其实例作为函数的第一个参数来访问。
计算属性的结果会被缓存,除非依赖的响应式 property 变化才会重新计算。注意,如果某个依赖(必须非响应式 property)在该实例范畴之外,则计算属性是不会被更新的。
<p>a + b 的结果是:{{ count }}</p>
computed: { count(){ return this.a + this.b } }
计算属性 vs 方法
<p>a + b 的结果是:{{ count() }}</p>
methods: { count(){ return this.a + this.b } }
我们可以将同一函数定义为一个方法而不是一个计算属性,两种方式的最终结果确实是完全相同的。不同的是计算属性是基于他们的响应式依赖进行缓存的。只在相应响应式依赖发生改变时他们才会重新计算求值。就是说只要 a 或者 b 还没有发生改变,多次访问 count 计算属性会立即返回之前的计算结果,而不必再次执行函数。
相比之下,每当触发重新渲染时,调用方法将总会再次执行函数,
我们为什么需要缓存? 假设我们有一个性能开销比较大的计算属性 A,他需要遍历一个巨大的数组并做大量的计算,然后我们可能有其他的计算属性依赖于 A。如果没有缓存我们将不可避免的多次执行 A 的getter。如果不希望有缓存出现,请用方法代替。
计算属性的 setter
计算属性默认只有 getter ,不过在需要时也可以提供一个 setter
computed: { str: { get: function() { return this.a + this.b }, set: function(newVlaue) { var abs = newValue.split(' ') this.a = abs[0] this.b = abs[abs.length - 1] } } }
现在运行 this.str = '2 3 4 5'时,setter 会被调用,this.a 和 this.b 也会相应地被更新
5.methods 事件处理
在模板中通过 v-on 定义方法名,在 methods 中定义方法
有时在内联语句处理器中访问原始的 DOM 事件,可以用特殊变量 $event 把它传入方法
<button @click="count(200, $event)">按钮</button> <button @click="count2">按钮2</button>
methods: { count: function(val, event) { if(event) { event.preventDefault() } // ... }, count2: function(event) {// 如果模板中什么都不传递,在这里可以默认直接接受一个参数就是 dom 对象 if(event) { event.preventDefault() } // ... } }
6.watch 监听器
一个对象,键是需要观察的表达式,值是对应回调函数,值也可以是方法名,或者包含选项的对象。Vue 实例将会在实例化时调用 $watch(),遍历 watch 对象的每一个属性
值是包括选项的对象,有三个选项
handler:回调函数,即监听到变化时应该执行的函数
deep: Boolean,确认是否深度监听
为了发现对象内部值的变化,可以在选项参数中指定 deep: true 注意监听数组的变更不需要这么做
immediate:Boolean,为true时,将立即以表达式的当前值出发回调(进入页面就开始监听)
watch: { a(){}, // 该回调会在任何被监听的对象 property 改变时被调用,不论其被嵌套多深 b: { handler: function(){}, deep: true }, // 该回调将会在侦听开始之后立即调用 c: { handler: function(), immediate: true }, // 可以传入回调数组,他们会被一一调用 d: [ handle1, function handle2(){}, { handler: function handle3(){} } ] }
二、选项 / DOM
在开发的过程中很少需要考虑这些东西,看看官网熟悉一下
三、选项 / 生命周期钩子
每个 Vue 实例在被创建时都要经过一系列的初始化过程,设置数据监听,编译模板,实例挂载到 DOM ,数据变化时更新 DOM 等,这里面有一系列的函数,称之为生命周期钩子
1.beforeCreate
在实例初始化之后,数据观测(data observer) 和 event/watcher 事件配置之前被调用,此时组件的选项对象还没有创建,el 和 data 并未初始化,因此无法访问 methods,data,computed 等上的方法和数据
2.created
在实例创建完成之后被立即调用。这一步,实例已经完成以下配置:数据观测(data observer),property 和方法的运算,watch/event 事件回调。然而挂载阶段还没开始,$el property 目前尚不可用。这是一个常用的生命周期,这里面可以调用 methods 中的方法,改变 data 中的数据,并且修改可以通过 vue 的响应式绑定体现在页面上,获取 计算属性 等等,通常我们在这里请求接口中的数据,
3.beforeMount
在挂在之前被调用,相关的 render 函数首次被调用,实例已经完成一下的配置:编译模板,把 data 里面的数据和模板生成 html,完成了 el 和 data 初始化,注意此时还没有挂载到 html 页面上
4.mounted
实例被挂载后被调用,这时 el 被新创建的 vm.$el 替换了。如果跟实例挂载到了一个文档内的元素上,当 mounted 被调用时 vm.$el 也在文档内
注意 mounted 不会保证所有的子组件也一起被挂载,如果希望等到整个视图都渲染完毕,可以在 mounted 内部使用 vm.$nextTick
mounted(){ this.$nextTick(function(){ // ... }) }
5.beforeUpdate
数据更新时调用,发生在虚拟 DOM 打补丁之前,这里适合在更新之前访问现有的 DOM,比如手动移除已添加的事件监听器。
该钩子在服务器端渲染期间不被调用,因为只有初次渲染会在服务端进行
6.updated
由于数据更改导致的虚拟 DOM 重新渲染和打补丁,在这之后会调用该钩子。
当这个钩子被调用时,组件 DOM 已经更新,所以你现在可以执行依赖于 DOM 的操作。然而大多数情况下,你应该避免在此期间更改状态。如果要相应状态改变,通常最好使用 计算属性 或 watcher 取而代之。
注意 updated 不会保证所有子组件也都一起被重绘。如果你希望等到整个视图都重绘完毕,可以在 updated 里使用 vm.$nextTick(同上)
7.activated
被 keep-alive 缓存的组件激活时调用
该钩子在服务器端渲染期间不被调用
8.deactivated
被 keep-alive 缓存的组件停用时被调用
该钩子在服务器端渲染期间不被调用
9.beforeDestroy
实例销毁之前调用。在这一步,实例仍然完全可用
该钩子在服务器端渲染期间不被调用
10.destroyed
实例销毁后调用,该钩子被调用后,对应 Vue 实例的所有指令都被解绑,所有的事件监听器被移除,所有的子实例也都被销毁
该钩子在服务器端渲染期间不被调用
11.errorCaptured
当捕获一个来自子孙组件的错误时被调用,此钩子会收到三个参数:错误对象、发生错误的组件实例和一个包含错误来源信息的字符串。此钩子返回 false 以阻止该错误继续向上传播
可以在此钩子中修改组件的状态。因此在捕获错误时,在模板或渲染函数中有一个条件判断来绕过其他内容就很重要,不然该组件可能会进入一个无限的渲染循环
错误传播规则
1.默认情况下,如果全局的 config.errorHandler 被定义,所有的错误仍会发送它,因此这些错误仍然会向单一的分析服务的地方进行汇报。
2.如果一个组建的继承或父级从属链路中存在多个 errorCaptured 钩子,则它们将会被相同的错误一一唤起
3.如果此 errorCaptured 钩子自身抛出了一个错误,则这个新错误和原本被捕获的错误都会发送给全局的 config.errorHandler
4.一个 errorCaptured 钩子能够返回 false 以阻止错误继续向上传播。本质上是说’这个错误已经被搞定且应该被忽略‘。他会阻止其他任何会被这个错误唤起的 errorCaptured 钩子和全局的 config.errorHandler
四、选项 / 资源
1.directives 自定义指令
2.filters 过滤器
3.components 组件
directives、filters、components 详情在这里有介绍 vue API 知识点(1)---全局 API 总结
五、选项 / 组合
1.parent
指定已创建的实例之父示例,在两者之间建立父子关系。子实例可以用 this.$parent 访问父实例,子实例被推入到父实例的 $children 数组中
我们在开发中,基本上不会使用到 $parent 和 $children 。更多的是使用 props 和 events 实现父子组件通信,
2.mixins 混入
3.extends
允许声明扩展另一个组件,而无需使用 Vue.extend。这主要是为了便于扩展单文件组件
这和 mixins 有点类似
mixins、extends 在这篇文章中有详细的介绍 vue API 知识点(1)---全局 API 总结
4.provide / inject
provide 和 inject 主要在开发高阶插件/组件库时使用。并不推荐用于普通应用程序代码中。
这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在其上下游关系成立的时间里始终生效。
解析:就是组件的引入层次过多,我们的子孙组件想要获取祖先组件的资源,可以不使用 props 的方式依次传递给子孙组件,层次太多,代码结构容易混乱。那么就可以使用 provide/inject 来做传递
官方注解
provide 选项应该是一个对象或返回一个对象的函数。该对象包含可注入其子孙的 property。在该对象中你可以使用 Symbols 作为key,但是只在原生支持 Symbol 和 Reflect.ownKeys 的环境下可工作
inject 选项应该是一个字符串数组或一个对象,对象的key是本地的绑定名,
value 是 在可用的注入内容中搜索用的 key(字符串或 Symbol),或一个对象,该对象的
from property 是在可用的诸如内容中搜索用的 key(字符串或 Symbol)
default property 是降级情况下使用的 value
个人解析
provide 是一个对象,或者是一个返回对象的函数,里面就包含要给子孙后代的东西,也就是属性和属性值
inject 一个字符串数组,或者是一个对象。属性值可以是一个对象,包含 from 和 default 默认值
const Child = { inject: { foo: { from: 'bar', default: 'foo' } } }
from 表示在可用的注入内容中搜索用的 key,default 当然就是默认值
注意:provide 和 inject 绑定并不是可响应的。这是刻意为之的,然而,如果你传入了一个可监听的对象,那么其对象的 property 还是可响应的
官方示例
// 父组件 var Provider = { provide: { name: 'zhangning' }, // ... } // 子组件注入 var Child = { inject: ['name'], created() { console.log(this.name) // => 'zhangning' } }
利用 es 中的 Symbols、函数 provide 和对象 inject
const a = Symbol(); const Parent = { provide(){ return { [a]: 'zhangning187' } } } const Child = { inject: { a }, // ... }
在新版本中,使用一个注入的值作为一个 property 的默认值
const Child = { inject: ['name'], props: { bar: { default() { return this.name } } } }
使用一个注入的值作为数据入口
const Child = { inject: ['name'], data() { return { bar: this.name } } }
如果它需要从一个不同名字的 property 注入,则使用 from 来表示其源 property
const Child = { inject: { from: 'bar', default: 'name' } }
与 prop 的默认值类似,你需要对非原始值使用一个工厂方法
const Child = { inject: { name: { from: 'bar', default: () => [1, 2, 3] } } }
在日常的开发中,我们经常会使用 Vuex 做状态管理,但是 vuex 有时候太过于繁琐。如果我们不想使用 vuex,这个时候我们就可以使用 provide/inject 这个方式来代替vuex,在跟组件中传入变量,然后在后代组件中使用即可
// 在跟组件中提供一个非响应式变量给后代组件
export default { provide() { return { text: 'zhangning' } } } // 后代组件注入 export default { inject: ['text'], created() { this.text = 'zhangning187'// 在我们使用 text 的时候它依然是 zhangning,而不是zhangning187 } }
上面已经讲过,provide 和 inject 绑定并不是可响应的,这是可以为之。当然我们也可以传入一个可响应的对象,那么它的属性也就是可响应的了
也就是说,Vue 不会对 provide 中的变量进行响应式处理。所以要想 inject 接受的变量是响应式的,provide 提供的变量本身就需要是响应式的。
由于组件内部的各种状态就是可响应式的,所以我们直接在根组件中将组件本身注入 provide,此时,我们可以在后代组件中任意访问根组件中的所有状态,根组件就成为了全局状态的容器
// 根组件提供将自身提供给后代组件 export default { provide() { return { app: this } }, data() { return { text: 'zhangning' } } } // 后代组件 注入 export default { inject: ['app'], data(){ return{} }, created: { this.app.text = 'zhangning187'// 此时使用 text,它的值就是zhangning187 } }
当然,我们也可以通过 $root 来获取根节点,但是在团队开发的过程中,如果所有的全局变量都统一定义在根组件中并不合适,而使用 provide/inject 不同模块的入口组件传给各自的后代组件可以完美解决这个问题
Vuex 和 provide/inject 区别在于,Vuex 中的全局状态的每次修改都是可以追踪回溯的,而 provide/inject 中变量的修改是无法控制的,通俗说就是,你不知道是哪个组件修改了这个全局状态。
provide/inject 破坏了我们的单向数据流原则。如果多个后代组件同时依赖于一个祖先组件提供的状态,那么只要有一个组件修改了这个状态,则所有组件都会受到影响。这个使耦合度增加了,让我们的数据变化不可控,在多人的开发过程中,这个可能会变得特别混乱。
以上看起来使用 provide/inject 做全局状态管理好像很危险,所以我们要谨慎使用这个方式。
但是在组件开发的时候,vue 官方提倡我们使用这种方法。例如 elment-ui
六、选项 / 其他
1.name
只有作为组件选项时起作用
允许组件模板递归地调用自身,注意,组件在全局用 Vue.component() 注册时,全局 ID 自动作为组件的 name
指定 name 选项的好处就是便于调试,有名字的组件有更友好的警告信息,当在 vue-devtools 下,未命名组件将显示成<AnonymousComponent>,这很没有语义,通过提供 name 选项,可以获得更有语义信息的组件树
2.delimiters
了解一下就行,通常不会去更改插入分隔符
类型 Array<string>
默认值 ["{{", "}}"]
限制 这个选项只在完整构建版本中的浏览器内编译时可用
改变纯文本插入分隔符
示例
new Vue({ delimiters: ["${", "}"] }) // 分隔符变成了 ES6 模板字符串的风格
3.functional
了解一下,通常用不到
类型 Boolean
使组件组状态(没有 data)和无实例(没有 this 上下文)。他们用一个简单的 render 函数返回虚拟节点,是他们渲染的代价更小
4.model
允许一个自定义组件在使用 v-model 时定制 prop 和 event。默认情况下,一个组件上的 v-model 会把 value 用作 prop 且把 input 用作 event,但是一下输入类型比如单选框和复选框按钮可能想使用 value prop 来达到不同的目的。使用 model 选项可以回避这些情况产生的冲突
// 定义一个组件
Vue.component('my-check', { model: { prop: 'checked', event: 'change' }, props: { value: String, checked: { type: Number, default: 0 } } })
使用
<my-check v-model='foo' value='some value'></my-check>
以上代码相当于
<my-check :checked='foo' @change='value=>{foo = val}' value='some value'></my-check>
我们先了解一下 input 中的 v-model 是怎么实现双向绑定的。
<input type='text' v-model='msg'>
v-model 只是语法糖真正的实现方式
<input type='text' :value='msg' @input='msg=$event.target.value'>
真正的双向绑定是通过监听 input 事件
v-model 在内部为不同的输入元素使用不同的属性并抛出不同的事件
text 和 textarea 元素使用 value 属性和 input 事件
checkbox 和 radio 使用 checked 属性和 change 事件
select 使用 value 和 change 事件
我们可以自定义输入框组件的 v-model,双向绑定
Vue.component('my-input', { template: `<input :value='value' @input='updateValue($event.target.value)' type='text'></input>`, props: ['value'], methods: { updateValue(val) { this.$emit('input', val) } } }) let app = new Vue({ el: '#app', data(){ message: '' }, methods: { handleInput(val) { this.message = val } } })
<div id='app'> <my-input :value='message' @input='handleInput'></my-input> </div>
上面的示例我们可以了解到,默认情况下,一个组件上的 v-model 会把 value 用作 prop 且把 input 用作 event,所以当我们在一个自定义组件上使用 v-model 并不能实现双向绑定,因为自定义的组件并没有默认的 value 和 input 事件,在使用时,我们需要按照上面那样显式的去声明定义这些东西,这时,选项 model 就派上用场了,在定义组件的时候指定 prop 的值和监听的事件(看到这里,上面写的官网给出的例子也应该可以看明白了)
举例
Vue.component('my-input', { model: { prop: 'uname', event: 'handleChange' }, props: { uname: { type: String, default: 'zhangning' } }, methods: { updateVal(val) { this.$emit('handleChange', val) } } })
模板
<template> <div> <input type='text' :value='uname' @input='updateVal($event.target.value)'> </div> </template>
使用组件
<my-input v-model='name' value='some value'></my-input>
等价于
<my-input :uname='name' @handleChange='val=>{foo = val}' value='some value'></my-input>
上面的示例中我们就可以在自定义的组件中使用 v-model 来进行双向绑定了(多看一下示例就明白了)
5.inheritAttrs
默认情况下父作用域的不被认作 props 的 attribute 绑定(attribute bindings)将会 回退 且作为普通的 HTML attribute 应用在子组件的根元素上,当撰写包裹一个目标元素或另一个组件的组件时,这可能不会总是符合预期行为。通过设置 inheritAttrs 为 false,这些默认行为将会被去掉,通过实例 property $attrs 可以让这些 attribute 生效,且可以通过 v-bind 显性的绑定到非根元素上。
vue 中一个比较令人烦恼的事情是属性只能从父组件传递给子组件,这也就意味着当你想向嵌套层级比较深组件数据传递,只能由父组件传递给子组件,子组件在传递给孙组件... 如果这样的传递属性只有一两个还行,如果要传递很多个,这样就会很繁琐。这时我们要去想别的方法来解决这个问题,通过 vuex 可以解决,通过上面的 provide/inject 也可以解决。现在还有一个方法,就是我们现在所说的,通过 inheriAttrs 选项,以及实例属性 $attrs
好像有点不好理解,下面看下示例应该就明白了
parent组件
<template> <div> <span>我是父组件</span> <childrenCom :name='name' :age='age' :sex='sex'></childrenCom> </div> </template> <script> export default { name: 'parent', components: { childrenCom: ()=> import('./children') }, data(){ return { name: 'zhangning', age: 24, sex: '男' } } }
children组件
<template> <div> <span>我是children</span> <div>得到的name:{{ name }}</div> </div> </template> <script> export default { name: 'children', props: ['name'], data(){ return{} }, created: function() { console.log(this.$attrs) } } </script>
上面的代码我们在 children 组件中只接收了 name 一个属性,age 和 sex 属性都没用,在浏览器渲染出来的结果是
我们可以看到:组件内没有被注册的属性将作为普通 html 元素被渲染,如果想让属性能够向下传递,即使 prop 组件没有被使用,你也需要在组件上注册,这样做会使组件预期功能变得模糊不清,同时也难以维护组件的 DRY。在 Vue2.4 ,可以在组件定义中添加 inheritAttrs: false,组件将不会把未被注册的 props 呈现为普通的 html 属性,但是在组件里我们可以通过其 $attrs 可以获取到没有使用的注册属性,如果需要,我们在这可以往下继续传递。
如果我们设置 inheritAttrs: false;
export default { name: 'children', props: ['name'], inheritAttrs: false, data(){ return{} }, created: function() { console.log(this.$attrs) } }
页面渲染如下
没有注册的 props 属性没有显示出来
下面讲一下 $attrs 的使用
一个页面由父组件,子组件,孙组件构成,如下
父组件
<template> <div> <span>我是父组件</span> <childrenCom :name='name' :age='age' :sex='sex'></childrenCom> </div> </template> <script> export default { name: 'parent', components: { childrenCom: ()=> import('./children') }, data(){ return { name: 'zhangning', age: 24, sex: '男' } } } </script>
子组件
<template> <div> <span>我是children</span> <div>得到的name:{{ name }}</div> <ChildrenChildren v-bind='$attrs'></ChildrenChildren> </div> </template> <script> export default { name: 'children', props: ['name'], inheritAttrs: false, components:{ ChildrenChildren: ()=>import('./childrenChildren') }, data(){ return{} }, created: function() { console.log('子组件') console.log(this.$attrs) } } </script>
孙组件
<template> <div> <span>我是孙组件</span> <div>{{ $attrs }}</div> </div> </template> <script> export default { name: 'childrenChildren', inheritAttrs: false, created(){ console.log('孙组件') console.log(this.$attrs) } } </script>
页面渲染效果
如果 attrs 被绑定在子组件 children 上,我们就可以在孙组件里获取到 this.$attrs 的值,这个值就是父组件中传递下来的 props (除了子组件中 props 声明的)
记住孙子组件里获取到 this.$attrs 的值是除了子组件注册的元素,除了子组件声明的元素,上面例子我们在子组件 props 注册声明了 name ,那么我们在孙组件里获取到的 $attrs 就不包含 name 属性,this.$attrs = {age: 24, sex: '男'}
讲一下 $attrs 的方便之处,以上面例子说下,如果我们想要在子组件中获取父组件的 name 属性值,在孙组件中获取父组件的 age 属性值,用 props 的话就必须从父组件把属性值传递给子组件,在传递给孙组件,显得有些臃肿,有些麻烦,但是用 $attrs 就不用这么麻烦,子组件绑定 $attrs ,孙组件就能获取到除了 name 属性外所有由父组件传递下来的属性。如果孙子组件也想获取到name属性,那么再绑定个 name 就可以了
<ChildrenChildren v-bind='$attrs' :name='name'></ChildrenChildren>
仔细体会一下,
举个例子说一下 inheritAttrs 到底有什么作用
<template> <children :name='name' :age='age' type='text'></children> </template> export default { name: 'tt', data() { return { name: 'zhangning', age: 23 } }, components: { 'children': { props: ['name, 'age'], template: '<input type="number">' } } }
上面的代码,我们在父组件中设置的 type='text' ,子组件里 input 上 type='number',最终在页面上的渲染 input 类型是 text ,父组件传递的 type='text' 覆盖了 input 上 type='number' ,这里就有问题了,把我们 input 类型改变了,这不是我们想要的结果,这里去体会官网的原话,是不是有点恍然大悟的感觉。
这里就用到了 inheritAttrs ,在子组件中设置 inheritAttrs 为 false就会得到我们想要的结果。这下应该明白了 inheritAttrs 的作用了把。在默认情况下 vue 会把父作用域的不被认作 props 的特性绑定且作为普通的 html 特性应用在子组件的根元素上。其实绑定一些属性是无所谓的,就是怕遇到这种特殊的,像上面 input 这种情况,这个时候 inheritAttrs: false 的作用就出来了。
当我们想要在孙组件中修改父组件中的数据,同步给父组件。使用 $listeners 属性
在孙组件中 通过 this.$emit('upData', name) 传递数据。
在子组件上绑定 v-on='$listeners'
在父组件中通过 @upData='fun' 接收,
6.comments
Boolean 默认值为 false
这个选项只在完整版构建版本中的浏览器内编译时可用
当设为 true 时,将会保留且渲染模板中的 html 注释,默认行为时舍弃他们
以上就是vue API 知识点 选项的总结