Vue的入门知识我分成了如上四个顶层概念。
vue实例、指令、组件、模板。vue官方文档已经很易懂了,但是我希望把我的理解以及一些容易疏忽的地方记下来,方便我日后更轻松的使用它。vue是什么
vue是一套构建用户界面的渐进式框架。我对渐进式框架的理解是,你可以充分的使用vue的功能,包括简单的功能和复杂的功能,完全按照vue的模式去构建程序。当然你也可以只使用类似数据响应的单一功能。他不强制要求你按照某个模式去书写,也就是说你可以在原有的代码基础上将少量功能用vue去实现也是可行的。
最喜欢的vue的两个特点是,响应式数据和组件。响应式数据省去了很多监听数据的代码,可以专注在构建界面上。而组件作为模块化思想下延伸出的一个手段,不仅可以很好的进行解耦使组件独立存在便于管理,而且可以很好的配合模块化技术进行模块化开发。ES6已经支持模块化。
vue和MVVM
data = Model
模板 + 指令 = Viewnew Vue() = ViewModel模板
五种创建模板的方法:
DOM元素模板(Vue实例用)
script标签 + x-template
template属性
.vue文件中template标签
inline-template
DOM模板
直接用DOM元素作为模板的方式。仅限vue实例控制区域使用。下面四个是组件模板创建方式。
说明:受HTML限制,标签名不能用驼峰(html大小写不敏感)必须用aa-bb形式。另外受html标准影响,特殊元素内只允许存在特定元素,如tr内只能是td或th,如果想再类似tr里解析组件,要用is属性声明组件。
script标签 + x-template
将script内的内容作为模板的方式。注意组件必须有一个根元素。比如下面那个无用div。而且模板与组件分离了,不在同一个位置,不便于管理。不推荐。
Vue.component('hello-world', { template: '#hello-world-template'})
template属性
最常规做法,也是最恶心的做法,在js语法环境写html代码,没有代码提示,没有格式化,恶心程度可想而知。
Vue.component('my-component', { template: 'A custom component!'})
.vue文件的template标签
This will be pre-compiled
inline-template
直接在插入组件的位置,声明模板。不过和script模板一样,分离了,不便于管理。不推荐。
These are compiled as the component's own template.
Not parent's transclusion content.
vue实例
vue实例也就是MVVM中的VM(ViewModel)。负责连接view和model。
既然是VM,那么vue实例主要作用是操作数据和自身。
vue实例提供了几个用来操作数据的选项。
data
作为MVVM中的M(Model),data属性用来存储未经处理的原始数据对象。
特性:
新添加的属性不会被响应
这个特性包括两点。
一点是指属性必须在 data 对象上存在它才是响应的。var vm = new Vue({ data:{ a:1 }})// `vm.a` 是响应的vm.b = 2// `vm.b` 是非响应的
一点是指Vue 不允许在已经创建的实例上动态添加新的根级响应式属性。既不能将响应属性添加到嵌套的对象上。
vm.a.b = 2
添加响应式新属性的替代方案(2种)如下:
// 第一种Vue.set(vm.a, 'b', 2)this.$set(this.a,'b',2)// 第二种vm.a = Object.assign({}, vm.a, { b: 2 })this.a = Object.assign({}, this.a, { b: 2 })
不能响应的数组操作及对应替代方法
// 不能响应vm.items[indexOfItem] = newValue// 替代方法Vue.set(example1.items, indexOfItem, newValue)// 不能响应vm.items.length = newLength// 替代方法example1.items.splice(indexOfItem, 1, newValue)
computed
vue称他为计算属性,用来对data数据进行处理。因为是对data进行计算,所以为了便于维护和理解,计算属性不要去修改data数据,仅仅引用即可。
特性:
用return的数据渲染view
数据是响应式的
基于依赖缓存计算结果。计算属性只有在它的相关依赖发生改变时才会重新求值。
比如两个div都靠计算属性A来渲染,那么渲染第二个div时不会重新执行计算属性A,而是直接返回渲染第一个div时计算的值getter 和 setter。
默认只有一个getter,作用是依赖data变化来执行getter。可以自己设一个setter,作用是直接给计算属性赋值时会触发setter。
methods
区别于computed的方法,通常用来作为事件的回调函数,或者不希望使用computed的缓存特性时作为computed的替代品。methods方法同样是数据响应式的。
特性:
用return的数据渲染view
数据是响应式的
计算结果不缓存
问题:
如何在methods函数内引用事件元素本身?通常直接用this就可以引用事件元素本身。
el.onclick = function(){ console.log(this)}
但是在Vue里this指向实例或组件自身,所以在Vue里这样引用事件元素本身。
warn: function (message, event) { // 现在我们可以访问原生事件对象 if (event) event.preventDefault() alert(message) console.log(event.currentTarget) // 绑定事件的元素本身}
这里需要说明的是,引用的event对象是原生event对象,不是类似jQuery包装过的event对象。而且之所在模板里传入$event是因为,event对象的兼容性问题。在IE和chrome内可以通过window.event引用event对象,也就是不需要event形参,但是火狐不可以,火狐必须有event形参,而且将event对象作为实参传入,当然只有一个event形参时不需要显示的将event作为实参传入。问题在于event形参如果是非第一位参数,就必须将event对象显示的作为实参传入。在原生JS中可以如下实现。显然vue提供了很好的解决方法。
asd.onclick = function(event) { var ee = event function a (msg, event) { console.log(event) alert(msg) } a(window.value,ee)}
watch
当你想要在数据变化响应时,执行异步操作或开销较大的操作时请使用watch
filter
被用作一些常见的文本格式化。
生命周期
vue实例从创建到销毁的过程。在这个过程中可以通过配置生命周期钩子来处理一些逻辑。
属性代理
vue实例创建后,为了便于访问数据,vue实例对象会代理一些数据。
如vm.a
直接访问data数据、vm.$data直接访问整个data对象 指令
用来在view层表明数据的用途
v-if 和 v-show
v-show是条件显示,只是切换display: none/block。
v-if是条件渲染。特殊属性
is
用来声明该模板是哪个组件。解决两个问题。
动态切换组件
解决HTML元素限制
// bad...
key
vue有一个最大限度复用原则,可以复用的元素会经过少量修改继续复用。
key属性可以屏蔽复用机制。有key属性的元素,每次都会重新渲染。说明:
key值相同的元素之间也会复用,但是绑定key的元素被重新渲染时(渲染了非相同key的元素)原来的缓存会被清除,不再被复用
ref
vue实例对象可以通过变量名访问。但是组件没有变量名,所以没有可以访问的接口。
ref提供了访问组件的接口。访问
vm.$refs.child
组件
vue组件是实施前端组件化的一个手段。一个完整的前端组件应该包括属于某一前端UI模块HTML CSS JS部分。但是显然入门教程里的注册方式有模板和JS,但是少了CSS,有点残次品的感觉。所以条件允许我还是更喜欢.vue文件的方式封装组件。
注册组件
三种祖册方式
全局注册(不说了)
局部注册(不说了)
用.vue文件封装组件,并用作为模块进行require
引用组件
给组件取个名
访问组件
var parent = new Vue({ el: '#parent' })// 访问子组件var child = parent.$refs.child
内容分发
简单理解就是,父组件模板里的内容应该如何处置。
内容分发规则有以下几点:
没有slot属性的内容分发给匿名slot
有slot属性的内容分发给对应有相同name属性的slot
slot内的内容在没有被分发内容时显示,并不用任何标签进行包裹
组件组合方式
父子组件
递归组件
循环引用组件(A组件套了B组件,B组件里又套了A,A引用B B引用A)
问题:
递归组件:组件递归调用自身时会进入死循环,所以需要用类似v-if,并最终v-if值最终返回false循环引用组件:由于互相引用,导致引用关系混乱。在用webpack构建时,webpack无法判断先引用谁。如A引用B B引用A时首先导致混乱的是B组件。Vue提供了一个解决方法。
beforeCreate: function () { this.$options.components.TreeFolderContents = require('./B.vue')}
组件通讯
vue组件最长提的是解耦。每个组件只负责自己的部分,他只关心接收的数据。所以我觉得组件间的通讯是vue组件最重要的部分。
父组件给子组件传递数据
父组件给子组件传递数据遵循一个模式。
在父组件模板中声明传入的数据
在子组件中用props属性接收传入的数据
这样一来,子组件完全解耦,他只负责接收。
父组件模板传入
在子组件接收
Vue.component('child', { // 声明 props props: ['message'], template: '{ { message }}'})
说明:
传入数据是基本类型时,在子组件修改props会报错
传入数据是对象时,在子组件修改props的情况,因为对象是引用类型,修改props会同事修改到父组件
子组件通知父组件
子组件给父组件传递数据遵循一个模式。
在父组件模板绑定并监听一个事件,该事件触发父组件方法。
在子组件触发监听的事件,进而触发父组件方法,并把数据作为参数传入该方法。
具体实现:
在父组件模板监听事件在子组件触发事件
methods: { increment: function () { this.counter += 1 this.$emit('increment', data) }}
非父子组件通讯
非父子组件通讯,由于两个组件独立存在,不能像父子间那样在父模板传入或监听。
非父子组件通讯通过eventBus也就是一个空的vue实例对象作为中转站进行通讯。具体实现如下。
创建一个空的vue实例
在组件生命周期前期(通常是mounted)在中转站上声明一个事件监听,表示谁触发这个事件就相当于通知我了。
在另一个组件,触发这个事件,和子通知父一样。
所以非父子和子传父的区别在于,声明监听事件的方式不同。
具体实现:
创建一个中转站var eventBus = new Vue()
在A组件声明周期内,在中转站监听一个事件,并把该事件中的this指向自身(A组件本身)
mounted: function () { eventBus.$on('addBar', function () { this.a++ }.bind(this))}
在B组件中触发事件来通知A组件
methods: { aaaAdd: function () { eventBus.$emit('addBar') }}
slot内传入数据
与父传子一样的模式,在slot传入,在分发内容接收。
具体实现:
传入接收,具有特殊属性 scope 的 <template>
元素必须存在
hello from parent { { props.text }}
有没有发现?四种不同的通讯方式各不相同,用了四种不同的模式,而且有些在html文档里声明数据传入,有些在js文档里。我觉的很不优雅也不易理解和管理。
动态切换组件并缓存组件
原理很简单,通过内置的component元素绑定is来实现切换。
但是切换出去的组件不会被缓存,重新切回来会重新渲染,如果想缓存。