vue 阶段性总结

  使用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状态作出修改?

    Vue是经过一点一点的截断的方式对模板文件进行解析的,例如:Vue在解析模板的过程中遇到了<div>{{msg}}</div>,首先会解析出来div标签作为一个node节点,而后接着去截取{{msg}}做为文本节点,而且发现这个文本节点中有特殊的{{}},就会将msg做为一个表达式,而且认为msg是本身须要绑定的UI数据。最后会解析到</div>来最终肯定div节点的闭合。实际过程当中Vue把模板解析完成以后会生成对应的虚拟DOM树,这个DOM树是存在于内存当中,而且会标记其中须要关心的变量和表达式,将这些须要关系的变量或者表达式与本身data做用域中的数据进行绑定,绑定的过程就是在对数据监听的get、set方法中来进行相应的VNODE修改,VNODE修改流程结束以后会触发render方法,从而达到改变指定UI的效果。

   3、组件数据共享

    分组件开发固然能简化咱们的工做,数据绑定又让咱们只用关注于数据,由于vue接管了咱们的UI,它能够将咱们操做的数据动做映射为UI状态的改变。因此咱们在平时开发的过程中须要关心数据的改变已经数据的传递。数据的传递指的是组件之间数据的传递,由于咱们在开发工做当中每每都会有多个组件共享数据的状况。这些组件之间的关系多是父子组件、兄弟组件、祖前后代组件这些关系。这里我想说一下最简单的父子组件之间的数据传递方式。由于兄弟组件和祖前后代组件这种组件关系Vue提供了Vuex和事件总线这种解决方案。

    从个人工做经验来讲父子组件之间的数据传递方式有两大类,一类就是Vue官方推荐的作法,父传子经过props来进行传递,子传父经过$emit这种发布订阅模式来进行。第二类就是父传子经过props进行传递,子传父经过改变对象属性引用的方式来进行.

若是多层级组件之间使用vue $emit来改变父组件的数据很闲的很繁琐,咱们可使用改变对象属性的方式来改变这一现象。
    
    在Vue中咱们不能直接在子组件中直接修改父组件的数据,这一行为Vue会发出警告。这是由于在Vue2.0 中只支持单项数据流,也就是说只能经过父组件传递数据给子组件,而且父组件改变数据时子组件能够同步监控到修改,从而引发UI的修改。可是咱们能够在父组件中传入一个对象到子组件,子组件不直接修改父组件传过来的对象的引用,而是修改传过来的对象的属性,这样Vue就不会发出警告,而且父组件能够得到子组件的修改。
// 父组件模板
<ChildComponent :parent-props="testProp" />
// 父组件数据
data: {
  testProp: {
    pro1: 'a',
    pro2: 'b'
  }
}

// 子组件
props: ['testProp'],
methods: {
  doing () {
    this.testProp.a = 'c'
  }
}
    经过以上步骤,能够实如今子组件中修改父组件传递过来的数据的目的,这种无论组件的层级有多深均可以实现数据的传递和修改,这在有些状况下是颇有用的。好比在 element-ui 这个组件库当中,就是经过这个数据传递的方式来实现 Form 组件的。
<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: [
      // ...
    ]
  }
}
    element-ui 就是首先传入一个 formData这个对象到 el-form 组件中的,而且经过prop属性给 el-form-item 组件,让该组件得以跟 formData.name 这条数据进行绑定,从而实现表单校验。
    (完)
相关文章
相关标签/搜索