使用vue开发已经有一段时间了,本文主要是记录平时使用过程当中踩过的一些坑,以及一些心得。一方面能够自我总结提升,另外一方面能够将本身的经验分享出来。css
1、为何要用框架html
如今前端行业发展飞快,咱们在选用技术栈的时候,一方面要易于上手,另外一方面要适合本身的项目。vue就是这样一个前端框架,易于上手,有成熟的文档能够参考、一样有成熟的社区能够讨论问题。最不济能够阅读源码,vue的源码仍是比较易读的。前端
为何咱们开发的时候须要使用框架?之前的jquery很差吗?原生js开发很差吗?使用这些框架可以解决哪些痛点?我就来讲说我本身的一些浅见。vue
一、组件化node
组件化开发能够解耦,便于复用,我跟人而言组件化是模块化更进一步的解耦。最初咱们在开发前端项目的时候,浏览器原生并不支持模块开发,咱们须要使用一些库来给模块化的js提供一个运行平台,例如以前的 requireJs、seaJs等。若是你经历过原始的开发再转型到模块化开发,你就会明白模块化的好处。由于当你在没有模块的开发过程中,你会发现全部的变量都是全局的,全部的方法都是全局的(固然局部变量除外)。当你在你的js文件当中使用了某个变量的时候,你都找不到这个变量是定义在当前的js文件当中仍是定义在其余的js文件中,这就很头疼了。还有就是你引用了别人的js文件,你没法确认你写的方法是否是覆盖了别人的方法。当你使用了模块化开发的时候,你就能经过模块引入的方式来进行你须要依赖的方法、类、对象、常量、变量的引入。而且模块中全部定义的方法变量都不是全局的,这样你会少了不少麻烦。jquery
上面简要谈了谈模块化的好处,模块化对你的js逻辑来讲基本上是够用了,可是咱们前端不只有js逻辑,还有html、css这些界面样式。这些东西怎么去解耦呢?怎么拥有本身的局部做用域呢?怎么去复用呢?组件就很好地解决了这个问题。试想一下你须要在页面上作一个拥有提交功能的按钮,你须要在html中写入元素,而后再在js中给这个按钮添加事件。当你的页面上有不少这种按钮的时候,你须要去复制多份,这样一没有起到解耦的效果,二没法复用。如今若是浏览器厂商特地为你的需求定制了一个标签,你只须要在你的html文件中使用这个标签就能完成你所须要的功能,这样是否是很简单,若是你须要多份这样的按钮,你要作的只是将这个标签写到其余地方。固然浏览器不可能为你定制这样一个标签,因此你须要本身为本身定制这样一个标签。这就是组件化的思想。web
二、数据绑定element-ui
这个能够说是前端三大框架真正可以解决痛点的地方了,由于前面所说的组件化,浏览器立刻就要原生支持 webcomponent 了。在原来的开发模式中咱们修改页面中的某个元素中的value值,咱们须要获取到相应的元素,而后再修改元素的value。获取元素的过程就是操做DOM的过程,浏览器为js提供了操做DOM的接口,前端就是展现页面,也就是改变html及css。改变html和css须要使用脚本语言才能动态地修改,这就是js存在的重要意义。可是操做DOM的过程是繁琐的,而数据绑定能够帮咱们从操做DOM的过程当中解放出来。试想一下咱们若是只须要改变 value的值就可让页面发生相应的更改,这样咱们就能够免去操做DOM的麻烦。咱们称这种改变UI的方式为数据绑定。数组
无论框架是如何实现数据绑定的,咱们须要关心的只有数据,由于UI是被数据所驱动的。只要数据发生改变UI就能发生相应的更改。其实细想起来,咱们作的UI页面就是一个html DOM树,这颗DOM树彻底能够经过内存中的对象来实现数据映射。框架要作的工做就是将内存中的对象与DOM树发生绑定关系,其实内存中的这个对象就是咱们常常看到的虚拟DOM。浏览器
2、vue的数据绑定原理
vue这个框架很好地实现了以上两个优势。我曾经读过一些vue的源码,下面说说我对vue发生数据绑定的一些理解,若是掌握了vue的工做原理,咱们能够在工做当中很好地解释一些难以理解的问题,我认为仍是颇有必要的。
通常来讲咱们改变了数据浏览器并不知道须要从新渲染哪些DOM,浏览器只知道当咱们显式地改变了DOM的时候才会去从新渲染。很显然改变数据而后通知浏览器去渲染就是咱们的vue起的做用。那么vue是怎么知道咱们改变了数据的呢?
第一个问题:vue如何知道咱们修改了数据?
其实vue并非万能的,它并不知道我随手写的一个变量是否发生了改变。也就是说vue所监控的变量是有要求的,这个要求就是显式地声明在data和props中的对象属性。也就是说咱们改变data对象或者对象中的属性的时候vue是知道咱们修改了这些属性的。vue是如何知道的呢?
在咱们实例化Vue这个类的时候,它会递归地对data对象设置监听,监听的方式是 使用浏览器支持的 Object.defineProperty() 这个类静态方法对这些属性或对象设置get、set方法。这两个方法有点相似钩子函数,当你去改变data中的某个属性时就会触发set方法,当你去获取data中的某个属性时就会触发get方法。很显然你能够在get和set方法中执行一些操做去修改页面中的DOM。
vue就是这样对咱们的数据进行监控的。前面说了 vue是在初始化的时候递归地对属性进行监听的,当你改变了data中某个属性的引用的时候vue会从新对新的对象进行递归地监听。注意这里说的是改变引用的时候才会触发监听绑定,也就是说当你的对象引用没有发生改变,只是给对象增长了一个属性的时候vue是没法对新的属性进行监听的。这也就是为何你在没有在data中声明属性而是后面添加的属性vue没有办法监听到的缘由。一般咱们还会犯一个错误,就是咱们把开始声明的对象引用改变了,可是新的引用中的属性跟原来的引用的属性有所区别,这样原来的属性就会丢失引用。例如
1 data () { 2 return { 3 pro1: { // 初始声明的引用具备a b两个属性 4 a: 1, 5 b: 2 6 } 7 } 8 } 9 10 ... 11 12 // 后面的某个时刻 13 this.pro1 = {a: 3, c: 4}; 14 15 // 在这里你会发现原来 模板中与 pro1.b 发生绑定关系已经被丢失了
就上面的问题而言,咱们平时的工做当中如何避免这种状况呢。现阶段我采用的方法是 使用 Object.assign()静态方法或者本身编写一个merge方法,进行数据的合并操做。因为Object.assign()方法存在局限性,咱们本身编写的merge方法能够更加灵活,因此咱们会采用merge方法进行vue的数据变动。merge方法的大概实现以下:
/** * 数据合并方法 * @param {Object} target 目标对象 * @param {Object} origin 源对象 * @param {String} stand 合并标准,默认为左树标准 * @returns {void} 无返回值 */ export function mergeData (target, origin, stand = 'left') { if (!target || !origin) { return } if (Utils.dataType(target) !== Utils.dataType(origin)) { console.error('目标对象与源对象的数据类型不一样,没法实现合并') return } let flag = stand === 'left' for (let prop in target) { if (Utils.dataType(target[prop]) === 'object') { // target[prop] = (target[prop].constructor === Array) ? [] : {}// 三元运算,将s[prop]初始化为数组或者对象 mergeData(target[prop], origin[prop]) } else if (Utils.dataType(target[prop]) === 'array') { // 兼容处理 if (!origin[prop]) { origin[prop] = [] } if (origin[prop].length > 0) { // 该条件是为了剔除重复的数据 target[prop].length = 0 } target[prop].push(...origin[prop]) } else { let defaultVal switch (Utils.dataType(target[prop])) { case 'object': defaultVal = flag ? (target[prop] || {}) : (origin[prop] || {}) break case 'array': // defaultVal = target[prop] || [] defaultVal = flag ? (target[prop] || []) : (origin[prop] || []) break case 'string': // defaultVal = target[prop] || '' defaultVal = flag ? (target[prop] || '') : (origin[prop] || '') break case 'number': // defaultVal = target[prop] || 0 defaultVal = flag ? (target[prop] || 0) : (origin[prop] || 0) break case 'boolean': // defaultVal = target[prop] || false defaultVal = flag ? (target[prop] || false) : (origin[prop] || false) break } target[prop] = (origin[prop] || defaultVal) } }; }
第二个问题:Vue怎么知道咱们数据改变以后须要对哪些UI状态作出修改?
3、组件数据共享
分组件开发固然能简化咱们的工做,数据绑定又让咱们只用关注于数据,由于vue接管了咱们的UI,它能够将咱们操做的数据动做映射为UI状态的改变。因此咱们在平时开发的过程中须要关心数据的改变已经数据的传递。数据的传递指的是组件之间数据的传递,由于咱们在开发工做当中每每都会有多个组件共享数据的状况。这些组件之间的关系多是父子组件、兄弟组件、祖前后代组件这些关系。这里我想说一下最简单的父子组件之间的数据传递方式。由于兄弟组件和祖前后代组件这种组件关系Vue提供了Vuex和事件总线这种解决方案。
从个人工做经验来讲父子组件之间的数据传递方式有两大类,一类就是Vue官方推荐的作法,父传子经过props来进行传递,子传父经过$emit这种发布订阅模式来进行。第二类就是父传子经过props进行传递,子传父经过改变对象属性引用的方式来进行.
// 父组件模板 <ChildComponent :parent-props="testProp" /> // 父组件数据 data: { testProp: { pro1: 'a', pro2: 'b' } } // 子组件 props: ['testProp'], methods: { doing () { this.testProp.a = 'c' } }
<el-form :model="formData" :rules="formRules"> <el-form-item prop="name"> <el-input v-model="formData.name" /> </el-form-item> </el-form> // 数据 data: { formData: { name: '' }, formRules: { name: [ // ... ] } }