梳理一下我的开发中遇到的一些vue问题,记录一下我的的理解。
======================相关文章和开源库=======================javascript
系列文章html
1.前端知识梳理-HTML,CSS篇
前端
2.前端知识梳理-ES5篇
vue
3.前端知识梳理-ES6篇
java
4.前端知识梳理-VUE篇
git
我的维护的开源组件库es6
2.树形组织结构组件
element-ui
3.bin-admin ,基于bin-ui的后台管理系统集成方案canvas
4.bin-data ,基于bin-ui和echarts的数据可视化框架
5.其他生态连接
========================================================
当你把一个普通的 JavaScript
对象传入 Vue 实例做为 data 选项,Vue 将遍历此对象全部的属性,并使用 Object.defineProperty
把这些属性所有转为 getter/setter。
Object.defineProperty
是 ES5 中一个没法 shim 的特性,这也就是 Vue 不支持 IE8 以及更低版本浏览器的缘由。 这里文档只推荐官方文档,你能遇到的问题几乎均可以在文档中找的答案,这部分只挑几个常见问题进行讲解说明。以便你们能够避免此类问题的发生。
单向数据流: 顾名思义,数据流是单向的。数据流动方向能够跟踪,流动单一,追查问题的时候能够更快捷。缺点就是写起来不太方便。要使UI发生变动就必须建立各类 action 来维护对应的 state
双向数据绑定:数据之间是相通的,将数据变动的操做隐藏在框架内部。优势是在表单交互较多的场景下,会简化大量与业务无关的代码。缺点就是没法追踪局部状态的变化,增长了出错时 debug 的难度
具体表如今,通常咱们使用自定义组件(如bin-ui中的按钮<b-button size=’small’ v-waves :disabled=’false’>我是按钮<b-button>
)中,size
和disabled
就是单项数据流的体现,父级组件传参给按钮组件,流动单一,按钮组件只须要对props
参数作显示便可,双向数据绑定多体如今form表单组件,如input,v-model是实现双向数据绑定的语法糖,本质的数据流动仍是单向的,即v-model
至关于绑定:value
并@input=‘’
监听返回值并更新。
vue特色:
传统的开发老是避免不了操做dom,咱们总会想到在数据返回后去操做dom元素,可是操做dom元素的开销也是比较大的,并且不容易将视图层和业务层分离,所以,须要改变习惯,当咱们获取数据完成时,只须要对当前vue状态值进行更新,剩下的刷新操做,就交给vue虚拟dom去完成吧
虚拟DOM是干什么的?这就要从浏览器自己讲起。
如咱们所知,在浏览器渲染网页的过程当中,加载到HTML文档后,会将文档解析并构建DOM树,而后将其与解析CSS生成的CSSOM树一块儿结合产生爱的结晶——RenderObject树
,而后将RenderObject树渲染成页面(固然中间可能会有一些优化,好比RenderLayer
树)。这些过程都存在与渲染引擎之中,渲染引擎在浏览器中是于JavaScript引擎(JavaScriptCore也好V8也好)分离开的,但为了方便JS操做DOM结构,渲染引擎会暴露一些接口供JavaScript调用。因为这两块相互分离,通讯是须要付出代价的,所以JavaScript调用DOM提供的接口性能不咋地。各类性能优化的最佳实践也都在尽量的减小DOM操做次数。
而虚拟DOM干了什么?它直接用JavaScript实现了DOM树(大体上)
。组件的HTML结构并不会直接生成DOM,而是映射生成虚拟的JavaScript DOM
结构,经过在这个虚拟DOM上实现了一个 diff
算法找出最小变动,再把这些变动写入实际的DOM中。这个虚拟DOM以JS结构的形式存在,计算性能会比较好,并且因为减小了实际DOM操做次数,性能会有较大提高
虚拟dom的概念这里只是给简单介绍一下,vue的template模板语法,包括jsx语法,本质上最后渲染时都会宣传成render
函数,而render
函数中渲染的内容,其实就是虚拟dom
,在前端开发中,类库的开发和组件开发中,render
函数的编写也是必须掌握的技能之一。
vue实例有一个完整的生命周期,生命周期也就是指一个实例从开始建立到销毁的这个过程
beforeCreate() 在实例建立之间执行,数据未加载状态
created() 在实例建立、数据加载后,能初始化数据,dom渲染以前执行
beforeMount() 虚拟dom已建立完成,在数据渲染前最后一次更改数据
mounted() 页面、数据渲染完成,真实dom挂载完成
beforeUpadate() 从新渲染以前触发
updated() 数据已经更改完成,dom 也从新 render 完成,更改数据会陷入死循环
beforeDestory() 和 destoryed() 前者是销毁前执行(实例仍然彻底可用),后者则是销毁后执行复制代码
vue 生命周期这里平常开发中常见的就是created和mounted,具体应用就是好比,我在页面加载时须要获取新闻列表和一些原始数据,这时候咱们就能够在created钩子函数中调用方法去加载数据,并进行绑定,若是有时候咱们须要手动实现图表(echarts),这个时候由于图表的实现机制,咱们须要确保dom元素已经渲染完成,图表必须挂载到真实dom元素时,就必须在mounted函数中去调用图表生成方法了。
使用了 v-if 的时候,若是值为 false ,那么页面将不会有这个 html 标签生成。
v-show 则是无论值为 true 仍是 false ,html 元素都会存在,只是 CSS 中的 display 显示或隐藏
使用技巧:
1.这两个在使用时会有一些小问题,好比v-if在使用的时候,若是子元素内有使用{{}}绑定的响应属性,如{{current.name}}
,这时候若是current
不存在,那么在渲染这个元素的时候“可能”会报错,为啥是可能,是由于v-if有可能判断为false而致使内层元素根本就没渲染,而使用v-show的话就会报错,由于不管是true或false,内层元素都会渲染,这时若是current没有初始化,则必定会报错。因此,咱们若是经过v-show来显示隐藏元素的时候,须要确保内层绑定值已经初始化完成了。
2.我的推荐,若是是内层元素不变化的,如图片,部分样式内容等开启隐藏的,能够用v-show来实现,配合transition能实现比较好的性能要求。若是是须要动态渲染的可使用v-if
3.有些状况因为刷新机制问题,咱们能够经过v-if来强制开启vue进行重绘元素,如element和bin-ui,表格的生成都是基于配置宽高动态生成的,这就须要监听窗口大小去调用组件提供的接口从新刷新大小,但还有个暴力的解决办法就是v-if,好比弹窗的时候再渲染表格,关闭时直接=false来强制清除元素,以便保证每次都刷新重绘元素。
this.$nextTick()函数,官方释义是下次DOM更新循环结束以后执行的延迟回调,通常咱们会在修改数据以后使用$nextTick(),则能够在回调后获取更新后的DOM元素。
这个函数的具体使用我举个例子,上文中说道element,包括个人bin-ui重的table都会提供一个刷新大小的接口函数来手动获取table组件并重绘大小,可是这个函数有时候你编码了确并未实现,那这是为何呢,缘由就是vue在更新DOM的时候是异步执行的,只要侦听到数据变化,vue将会开启一个队列,并缓冲在同一事件循环中发生的全部数据变动,若是同一个watcher被屡次触发,只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免没必要要的计算和DOM的操做是很是重要的。而后再下一个事件循环“tick”中,vue刷新队列并行执行实际(已去重)的工做。
例如,当你设置 vm.someData = 'new value'
,该组件不会当即从新渲染。当刷新队列时,组件会在下一个事件循环“tick”中更新。多数状况咱们不须要关心这个过程,可是若是你想基于更新后的 DOM 状态来作点什么,这就可能会有些棘手。虽然 Vue.js 一般鼓励开发人员使用“数据驱动”的方式思考,避免直接接触 DOM,可是有时咱们必需要这么作。为了在数据变化以后等待 Vue 完成更新 DOM,能够在数据变化以后当即使用 Vue.nextTick(callback)
。这样回调函数将在 DOM 更新完成后被调用。例如:
<div id="example">{{message}}</div>
var vm = new Vue({
el: '#example',
data: {
message: '123'
}
})
vm.message = 'new message' // 更改数据
vm.$el.textContent === 'new message' // false
Vue.nextTick(function () {
vm.$el.textContent === 'new message' // true
})
复制代码
在组件内使用 vm.$nextTick()
实例方法特别方便,由于它不须要全局 Vue
,而且回调函数中的 this
将自动绑定到当前的 Vue 实例上:
Vue.component('example', {
template: '<span>{{ message }}</span>',
data: function () {
return {
message: '未更新'
}
},
methods: {
updateMessage: function () {
this.message = '已更新'
console.log(this.$el.textContent) // => '未更新'
this.$nextTick(function () {
console.log(this.$el.textContent) // => '已更新'
})
}
}
})
复制代码
由于 $nextTick()
返回一个 Promise
对象,因此你可使用新的 ES2017 async/await 语法完成相同的事情:
methods: {
updateMessage: async function () {
this.message = '已更新'
console.log(this.$el.textContent) // => '未更新'
await this.$nextTick()
console.log(this.$el.textContent) // => '已更新'
}
}复制代码
回到上文所述的现象上,因为dom刷新是异步的,并且被加入到一个队列中去,相似setTimeout异步队列,咱们没法肯定你当前但愿刷新表格大小事件是否在当前渲染帧中执行。所以,在咱们动态计算了宽度/高度后,咱们须要准确的获取已经更新dom元素后的组件,咱们就要在nextTick
函数中去获取执行刷新函数,这样就能够保证元素重绘正常而不须要使用v-if强制刷新了。
注意这里使用this.$nextTick
是官方提供的方法,咱们也能够用setTimeout(func,20)
默认20毫秒来模拟nextTick函数,20毫秒是经验值,但仍是推荐使用nextTick函数。
当 Vue.js 用 v-for 正在更新已渲染过的元素列表时,它默认用“就地复用”策略。若是数据项的顺序被改变,Vue 将不会移动 DOM 元素来匹配数据项的顺序, 而是简单复用此处每一个元素,而且确保它在特定索引下显示已被渲染过的每一个元素。key的做用主要是为了高效的更新虚拟DOM。
这块的注意点主要在动态组件和v-for时,为了标识独有dom,key值通常咱们取相似id这种惟一且 不变的变量,若是仅为了区分dom,切元素不会频繁更新(增删改)则可以使用index索引
1.父组件向子组件通讯:子组件经过 props 属性,绑定父组件数据,实现双方通讯
2.子组件向父组件通讯:将父组件的事件在子组件中经过 $emit 触发
3.非父子组件、兄弟组件之间的数据传递
全部的 prop 都使得其父子 prop 之间造成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,可是反过来则不行。这样会防止从子组件意外改变父级组件的状态,从而致使你的应用的数据流向难以理解。
额外的,每次父级组件发生更新时,子组件中全部的 prop 都将会刷新为最新的值。这意味着你不应该在一个子组件内部改变 prop。若是你这样作了,Vue 会在浏览器的控制台中发出警告。
组件通讯,结合4.2的部分,如 <b-button size=’small’ v-waves :disabled=’false’>
我是按钮<b-button>
这其中size,:disabled
都是prop
传值,也就是父组件传值给子组件,这里有个注意点,静态数据绑定,相似size=’small’
:disabled=’false’
都属于静态绑定,即不会动态根据父组件 data中的响应值作变化,这里数据绑定有个规定,若是数据值为静态数据切为字符串,则能够省略v-bind:
即像size=’small’
,不然全部的数据绑定都须要使用v-bind:
,通常咱们会省略,已冒号开头,后面跟随的就是绑定值,这个值也能够是各类数据类型,表达式,甚至是函数返回,如 :data=’[1,2,3]’ :data=’list? list : []’ :data=’33’
等 若是是布尔值的传值还有个建议写法,如,直接在组件中写 <b-button disabled>
我是按钮<b-button>
这里 的disabled
就至关于 :disabled=’true’
这里简单介绍一个插槽概念,组件编写时能够提供props和插槽两种类型的传值方式,区别不一样于props传值的是,插槽 更加灵活多变,如上文的按钮
<b-button size=’small’ v-waves :disabled=’false’>我是按钮<b-button>
按钮中间的‘我是按钮’就是插槽,插槽容许你往其中插入你想要的任何内容,如
<b-button><i class=’iconfont close’></i><span>我是按钮</span><b-button>
你能够插入你想定制的任意内容给组件,当前前提是组件提供了一个<slot></slot>
的默认插槽 这就像你小时候玩的卡带游戏机,你想玩什么游戏就插入什么样的卡带便可
可是这样仍然不够强大,若是我想我插入内容时默认就有个字符或者元素在那呢,这里就是你须要在组件中<slot>….</slot>中间写入你想要的默认元素内容就好了
类比于电脑主板,不一样的生产厂商都会按照一种共同的标准提供不一样的接口插槽,为的就是方便用户按需扩展,vue的组件插槽也提供了这种方式。有时候咱们也会有多个插槽,如
<div class="container">
<header>
<!-- 咱们但愿把页头放这里 -->
</header>
<main>
<!-- 咱们但愿把主要内容放这里 -->
</main>
<footer>
<!-- 咱们但愿把页脚放这里 -->
</footer>
</div>
复制代码
对于这样的状况,<slot> 元素有一个特殊的特性:name。这个特性能够用来定义额外的插槽:
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
复制代码
一个不带 name 的 <slot> 出口会带有隐含的名字“default”。
在向具名插槽提供内容的时候,咱们能够在一个 <template> 元素上使用 v-slot 指令,并以 v-slot 的参数的形式提供其名称:
<base-layout>
<template v-slot:header>
<h1>Here might be a page title</h1>
</template>
<p>A paragraph for the main content.</p>
<p>And another one.</p>
<template v-slot:footer>
<p>Here's some contact info</p> </template> </base-layout> 复制代码
如今 <template> 元素中的全部内容都将会被传入相应的插槽。任何没有被包裹在带有 v-slot 的 <template> 中的内容都会被视为默认插槽的内容。
插槽不只有默认和具名插槽,还用做用域插槽,如父级插入子组件的插槽能够访问子组件的数据,这就是做用域插槽,具体使用参考bin-ui,element-ui中表格的用法
在新项目运用vue实现数据绑定的同事可能会发现,每一个vue实例下都写了一行
mixins:[mixin]
,那这个是干吗的呢
混入 (mixin) 提供了一种很是灵活的方式,来分发 Vue 组件中的可复用功能。一个混入对象能够包含任意组件选项。当组件使用混入对象时,全部混入对象的选项将被“混合”进入该组件自己的选项
mixin
混入在vue开发中十分常见,它的用法也很是的简单,你只须要记住,公共的方法函数属性通通均可以放置到mixin中,主要就是为了复用代码,减小代码冗余,如,公共的请求封装,公共的分页属性,公共的查询跳转等均可以。
mixin
的混入策略能够简单的理解为如下几点
1.数据对象,data中的属性,会进行递归合并,类比es6的展开运算符,若是mixin中存在的,泽一组件定义的数据优先。
2.同名的钩子函数会合并数组,如mounted ,若是组件和mixin都定义了,则都会执行,且组件内定义的后置。即混入的代码会优先执行。
3.同名的函数会覆盖。相似data中的属性,会将组件内函数和mixin中函数递归合并,同名覆盖,这点相似于es5的,同名函数覆盖,组件内函数覆盖混入函数。
jQuery时代的核心就是获取dom元素,并进行一系列的操做,可是vue数据驱动视图时若是有必要获取dom元素(如获取元素绘制echarts,绘制canvas)时该如何获取呢。
vue提供了一个方法,首先在dom元素中编写ref=’table’
,这其实就是相似设置id或class,只是为了给vue进行识别使用。
获取方式也很是简单,只须要this.$refs.table
或this.$refs[‘table’]
便可得到dom元素
若是你给一个vue组件设置ref并使用this.$refs
获取的则是这个组件的实例,你能够经过这个实例调用组件的内部方法,如上文提到的table刷新方法。
即this.$refs.table.handleResize()
这个问题通常有两种缘由。
1.没有设置响应式对象属性的添加删除
受现代 JavaScript 的限制 (并且 Object.observe
也已经被废弃),Vue 没法检测到对象属性的添加或删除。因为 Vue 会在初始化实例时对属性执行 getter/setter 转化,因此属性必须在 data
对象上存在才能让 Vue 将它转换为响应式的。例如:
var vm = new Vue({
data:{
a:1
}
})
// `vm.a` 是响应式的
vm.b = 2
// `vm.b` 是非响应式的
复制代码
对于已经建立的实例,Vue 不容许动态添加根级别的响应式属性。可是,可使用 Vue.set(object, propertyName, value)
方法向嵌套对象添加响应式属性。例如,对于:
Vue.set(vm.someObject, 'b', 2)复制代码
您还可使用 vm.$set
实例方法,这也是全局 Vue.set
方法的别名:
this.$set(this.someObject,'b',2)复制代码
有时你可能须要为已有对象赋值多个新属性,好比使用 Object.assign()
或 _.extend()
。可是,这样添加到对象上的新属性不会触发更新。在这种状况下,你应该用原对象与要混合进去的对象的属性一块儿建立一个新的对象。
// 代替 `Object.assign(this.someObject, { a: 1, b: 2 })`复制代码
this.someObject = Object.assign({}, this.someObject, { a: 1, b: 2 })复制代码
分析:这就是前面说说的对象拷贝问题,若是你只使用注释的内容来去扩展对象的话,这里a和b实际仍是至关于你直接写this.someObject.a=1 , this.someObject.b=2
,这里仍然不会触发更新。
若是采用下面的方式进行编码,本质上实际至关于建立了一个新的对象并变动了整个响应式对象someObject的引用地址。所以,vue会从新触发更新。
2.数组索引值设置一个项或数组长度改变
改变数组的长度或者设置一个项
var vm = new Vue({
data(){
return{
list:[{id:1,name:’张三’},{id:2,name:’李四’}]
}
}
})
this.list[1]= {id:3,name:’王五’}
this.list.length = 1
复制代码
实际输出的效果就是数据变化了但不会更新视图变化复制代码
数组问题的解决办法有两种
所以,咱们再遇到数组操做时,通常推荐建立新数组来操做并更新视图,这里就是上文中提到的深拷贝。由于咱们不能保证咱们当前操做的数组是否是包含对象或者其余引用类型。
最后结合上文提到的判断精准类型,来实现一个递归调用的深拷贝函数
function typeOf (obj) {
const toString = Object.prototype.toString
const map = {
'[object Boolean]': 'boolean',
'[object Number]': 'number',
'[object String]': 'string',
'[object Function]': 'function',
'[object Array]': 'array',
'[object Date]': 'date',
'[object RegExp]': 'regExp',
'[object Undefined]': 'undefined',
'[object Null]': 'null',
'[object Object]': 'object'
}
return map[toString.call(obj)]
}
// 深拷贝函数
function deepCopy (data) {
const t = typeOf(data)
let o
if (t === 'array') {
o = []
} else if (t === 'object') {
o = {}
} else {
return data
}
if (t === 'array') {
for (let i = 0; i < data.length; i++) {
o.push(deepCopy(data[i]))
}
} else if (t === 'object') {
for (let i in data) {
o[i] = deepCopy(data[i])
}
}
return o
}
复制代码