vue2.x入坑总结—回顾对比angularJS/React的一统

Posted zhoulujun

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了vue2.x入坑总结—回顾对比angularJS/React的一统相关的知识,希望对你有一定的参考价值。

从感性的角度讲,我是不屑于用VUE,觉得react套件用起来更顺手,但是vue现在越来火,所以也不得入vue(杂烩汤)的坑。vue/anguarJS/React,三者对关系现在就是:

技术分享图片

https://www.zhoulujun.cn/uploadfile/images/2018/0626/20180626214906428779269.jpg

自己ps了下,觉得深有道理,骚年们自己体悟,然后再问军哥^_^

不过回归真题,看vue还是先了解下https://cdn.zhoulujun.cn/vue.jpg(太大,自己打开)

vue生命周期及相关主题

组件实例周期

vue所有功能的实现都是围绕其生命周期进行的,在生命周期的不同阶段调用对应的钩子函数可以实现组件数据管理和DOM渲染两大重要功能。学习实例的生命周期,能帮助我们理解vue实例的运行机制,更好地利用钩子函数完成我们的业务代码。

create 和 mounted 相关 的函数有:

beforecreated》created》beforeMount》mounted》beforeDestroy

  • beforecreated:el 和 data 并未初始化,  案例:可以在这加个loading事件 及获取路由参数,但是this.(data|computed|methods)参数均为undefind(无法访问到 el 属性和 data 属性等

  • 在beforeCreate和created之间:在这个生命周期之间,进行初始化事件,进行数据的观测,可以看到在created的时候数据已经和data属性进行绑定(放在data中的属性当值发生改变的同时,视图也会改变)

  • created:组件实例创建完成,属性已绑定,但 DOM 还未生成,$el 属性还不存在(this.$refs.XXX===undfined)。案例:在这结束loading,还做一些初始化,如根据父组件props计算当前组件数据

  1. created和beforeMount之间:首先会判断对象是否有el选项。如果有的话就继续向下编译,如果没有el选项,则停止编译,也就意味着停止了生命周期,直到在该vue实例上调用vm.$mount(el)。再次判断template参数选项的有无(因为vue需要通过el找到对应的outer template):

    (1)如果vue实例对象中有template参数选项,则将其作为模板编译成render函数。

    (2)如果没有template选项,则将外部html作为模板编译。

    (3)可以看到template中的模板优先级要高于outer HTML的优先级。

    如果没有template,则通过render传人的createElement编译

    综合排名优先级:render函数选项 > template选项 > outer HTML.

    这里没有看懂,可以看一下官方文档 独立构建和运行时构建

  • beforeMount:完成了 el 和 data 初始化 。坑:如果直接使用{{msg}} 在生命周期beforeMount期间,此刻的msg数据尚未编译至{{msg}}中,用户能看到一瞬间的{{msg}}  v-cloak

  • beforeMount和mounted之间:给vue实例对象添加$el成员,并且替换掉挂在的DOM元素

  • mounted :完成挂载    案例: 在这发起后端请求,拿回数据,配合路由钩子做一些事情

  • beforeUpdate:可以监听到data的变化但是view层没有被重新渲染,view层的数据没有变化

  • beforeUpdate和update之间:当vue发现data中的数据发生了改变,会触发对应组件的重新渲染(重新渲染虚拟 dom,并通过 diff 算法对比 vnode 节点差异更新真实 dom (virtual DOM re-render and patch)),先后调用beforeUpdate和updated钩子函数(beforeUpdate:可以监听到data的变化但是view层没有被重新渲染,view层的数据没有变化,updated: view层才被重新渲染,数据更新。此处可回顾下react对更新函数。 

  • updated:候 view层才被重新渲染,数据更新

  • beforeDestroy    组件销毁之前,案例:你确认删除XX吗?第二个:比如走马灯文字,路由跳转之后,因为组件已经销毁了,但是setInterval还没有销毁,还在继续后台调用,控制台会不断报错,如果运算量大的话,无法及时清除,会导致严重的页面卡顿。解决办法:在组件生命周期beforeDestroy停止setInterval

  • destroyed :当前组件已被删除,清空相关内容 。实例销毁后虽然 dom 和属性方法都还存在,但改变他们都将不再生效!

这这里顺便回顾下react生命周期

  • getDefaultProps()+getInitialState() es5≈ es6 contruct()

    函数初始化。定时init state,也可访问props。 这个阶段,相当于 vue的create 函数该做的事情。

  • componentWillMount()

    组件初始化时只调用,以后组件更新不调用,整个生命周期只调用一次,在客户端也在服务端,此时可以修改state。这个阶段应该相当于 vue的 beforeMount()

  • render()

    react最重要的步骤,创建虚拟dom,进行diff算法,更新dom树都在此进行。此时就不能更改state了。 这个相当于 vue  mounted

  • componentDidMount()()

    组件渲染之后调用,只调用一次,只在客户端。这个阶段,一般的异步数据放在这个函数内处理

  • componentWillReceiveProps(nextProps)

    组件初始化时不调用,组件接受新的props时调用。

  • shouldComponentUpdate(nextProps, nextState)

    react性能优化非常重要的一环。组件接受新的state或者props时调用,我们可以设置在此对比前后两个props和state是否相同,如果相同则返回false阻止更新,因为相同的属性状态一定会生成相同的dom树,这样就不需要创造新的dom树和旧的dom树进行diff算法对比,节省大量性能,尤其是在dom结构复杂的时候。但是state依然会保持更新。

    这这里,是vue PK react 的重点。

    Vue宣称可以更快地计算出Virtual DOM的差异,这是由于它在渲染过程中,会跟踪每一个组件的依赖关系,不需要重新渲染整个组件树。

    而对于React而言,每当应用的状态被改变时,全部子组件都会重新渲染。当然,这可以通过shouldComponentUpdate这个生命周期方法来进行控制,但Vue将此视为默认的优化。

    小结:如果你的应用中,交互复杂,需要处理大量的UI变化,那么使用Virtual DOM是一个好主意。如果你更新元素并不频繁,那么Virtual DOM并不一定适用,性能很可能还不如直接操控DOM。

  • componentWillUpdata(nextProps, nextState)

    组件初始化时不调用,只有在组件将要更新时才调用,此时可以修改state。这个一般没有生命卵用。可能toast一下。 大致相当于vue的beforeUpdate

  • componentDidUpdate()

    组件初始化时不调用,组件更新完成后调用,此时可以获取dom节点。

  • componentWillUnmount()

    组件将要卸载时调用,一些事件监听和定时器需要在此时清除。相当于vue的beforeDestroy

相比来讲,觉得react的生命周期更加清爽。建议参看:重谈react优势——react技术栈回顾

路由钩子

路由是项目等重点,很多事情可以在路由里面处理好。路由和store等规划项目基础架构核心,没有好的规划,工程就是一坨屎。

全局路由钩子

作用于所有路由切换,一般在main.js里面定义

  • beforeEach:一般在这个勾子的回调中,对路由进行拦截。比如,未登录的用户,直接进入了需要登录才可见的页面,那么可以用next(false)来拦截,使其跳回原页面等,值得注意的是,如果没有调用next方法,那么页面将卡在那。

    next的四种用法

    1. next() 跳入下一个页面

    2. next(‘/path‘) 改变路由的跳转方向,使其跳到另一个路由

    3. next(false)  返回原来的页面

    4. next((vm)=>{})  仅在beforeRouteEnter中可用,vm是组件实例。

  • afterEach :在所有路由跳转结束的时候调用,和beforeEach是类似的,但是它没有next方法,这里比如做修改标签标题: document.title = to.meta.title等工作。

组件路由勾子

和全局勾子不同的是,它仅仅作用于某个组件,一般在.vue文件中去定义。

beforeRouteEnter

这个是一个很不同的勾子。因为beforeRouterEnter在组件创建之前调用,所以它无法直接用this来访问组件实例。

为了弥补这一点,vue-router开发人员,给他的next方法加了特技,可以传一个回调,回调的第一个参数即是组件实例。

一般我们可以利用这点,对实例上的数据进行修改,调用实例上的方法。

我们可以在这个方法去请求数据,在数据获取到之后,再调用next就能保证你进页面的时候,数据已经获取到了。没错,这里next有阻塞的效果。你没调用的话,就会一直卡在那

beforeRouteLeave

在离开路由时调用。可以用this来访问组件实例。但是next中不能传回调。

beforeRouteUpdate:

这个方法是vue-router2.2版本加上的。因为原来的版本中,如果一个在两个子路由之间跳转,是不触发beforeRouteLeave的。这会导致某些重置操作,没地方触发。在之前,我们都是用watch $route来hack的。但是通过这个勾子,我们有了更好的方式。

 

指令周期

bind:只调用一次,指令第一次绑定到元素时调用,用这个钩子函数可以定义一个在绑定时执行一次的初始化动作。

inserted:被绑定元素插入父节点时调用(父节点存在即可调用,不必存在于 document 中)。

实际上是插入vnode的时候调用。

update:被绑定元素所在的模板更新时调用,而不论绑定值是否变化。通过比较更新前后的绑定值,可以忽略不必要的模板更新。

慎用,如果在指令里绑定事件,并且用这个周期的,记得把事件注销

componentUpdated:被绑定元素所在模板完成一次更新周期时调用。

unbind:只调用一次, 指令与元素解绑时调用。

一个页面跳转,发生的事情

  1. 路由勾子 (beforeEach、beforeRouteEnter、afterEach)

  2. 根组件 (beforeCreate、created、beforeMount)

  3. 组件 (beforeCreate、created、beforeMount)

  4. 指令 (bind、inserted)

  5. 组件 mounted

  6. 根组件 mounted

  7. beforeRouteEnter的next的回调

  8. nextTick

结论:

路由勾子执行周期非常早,甚至在根实例的渲染之前

具体的顺序 router.beforeEach > beforeRouteEnter > router.afterEach

tip:在进行路由拦截的时候要避免使用实例内部的方法或属性。

在开发项目时候,我们脑门一拍把,具体拦截的程序,写在了根实例的方法上了,到beforeEach去调用。结果导致整个拦截的周期,推迟到实例渲染的之后。

因此对于一些路由组件的beforeRouteEnter里的请求并无法拦截,页面看上去好像已经拦截下来了。

实际上请求依然发了出去,beforeRouteEnter内的函数依然执行了。

指令的绑定在组件mounted之前,组件的beforeMount之后

不得不提的, beforeRouteEnter的next勾子

beforeRouteEnter的执行顺序是如此靠前,而其中next的回调勾子的函数,执行则非常靠后,在mounted之后!!

我们通常是在beforeRouteEnter中加载一些首屏用数据,待数据收到后,再调用next勾子,通过回调的参数vm将数据绑定到实例上。

因此,请注意next的勾子是非常靠后的。

nextTick:越早注册的nextTick触发越早

 

上文讲了这么多的router,顺势总结下:

router-link属性

:to :相当于a标签中的"herf"属性,后面跟跳转链接所用

replace:replace在routre-link标签中添加后,页面切换时不会留下历史记录

tag:具有tag属性的router-link会被渲染成相应的标签

active-class:这个属性是设置激活链接时class属性,也就是当前页面所有与当前地址所匹配的的链接都会被添加class属性

exact:开启router-link的严格模式

用了vue-router,证明项目工程还是蛮大的,建议使用vuex来做全局数据管理(可能用redux习惯了吧!)

Vuex下Store的模块化拆

vuex的store天生自带modules概念,同时也需要thunk中间件,action处理异步数据。下面copy一点我项目code demo:

const store = new Vuex.Store({
  modules: {
    authForm: authFormStore,
    BankAdd:BankAddFormStore
  }
});

下面是store的示范

const authFormStore = {
  state: {
    name: ""
  },
  mutations: {
    update: function (state, obj) { //我原来也是写了N多个update函数,图样图森破啊
      state[obj.name] = obj.value;
    },
  },
  action: {
    setData: function (context, obj) {
      //TODO
      Vue.http.post("api", params, {emulateJSON: true}).then(function (res) {
        // 处理业务
        // 调用上面setAgree方法更新点赞数
        context.commit(obj.name, obj.value);
      }, function () {
      })
    },
  },
  getters: {
    getNews(state){
      //TODO  return new value
    }
  }
}

在vue组件mapState

computed: {
  memberType: function () {
    let memberType = this.$route.params.memberType;
    this.initWeChat(window.location.href, memberType);
    return memberType;
  },
  ...mapState({
    authForm: state => state.authForm,
    epCertType: state => state.authForm.epCertType
  })
},

这里有坑:表单的v-model属性值是Vuex的state时,如果时严格模式,因为用户输入时,v-model会试图修改v-model的值,由于修改并非mutation执行的,严格模式下会抛出错误。

针对这种情况,有两个处理方法:一个是双向绑定的计算属性,一个是给表单绑定value,然后侦听input或change事件,在事件中调用action。

computed:{
    message:{
      get(){
        return this.$store.obj.message
      },
      set(value){
        this.$store.commit(‘updateMessage‘,value)
      }
    }
}

第二种方法

computed:{
 ...mapState({
  message: state => state.obj.message
 })
},
methods:{
 updateMessage(e){
  this.$store.commit(‘updateMessage‘,e.target.value)
 }
}
mutations:{ //store mutation函数
 updateMessage(state,message){
  state.obj.message=message
 }
}

 

 Vue.js 最核心的功能有两个,一是响应式的数据绑定系统,二是组件系统

所谓双向绑定,指的是vue实例中的data与其渲染的DOM元素的内容保持一致,无论谁被改变,另一方会相应的更新为相同的数据。这是通过设置属性访问器实现的。

关于vueangluar eact的数据绑定:双向绑定和单向数据流

Vue 的依赖追踪是【原理上不支持双向绑定,v-model 只是通过监听 DOM 事件实现的语法糖】

vue的依赖追踪是通过 Object.defineProperty 把data对象的属性全部转为 getter/setter来实现的;当改变数据的某个属性值时,会触发set函数,获取该属性值的时候会触发get函数,通过这个特性来实现改变数据时改变视图;也就是说只有当数据改变时才会触发视图的改变,反过来在操作视图时,只能通过DOM事件来改变数据,再由此来改变视图,以此来实现双向绑定

双向绑定是在同一个组件内,将数据和视图绑定起来,和父子组件之间的通信并无什么关联;

组件之间的通信采用单向数据流是为了组件间更好的解耦,在开发中可能有多个子组件依赖于父组件的某个数据,假如子组件可以修改父组件数据的话,一个子组件变化会引发所有依赖这个数据的子组件发生变化,所以vue不推荐子组件修改父组件的数据,直接修改props会抛出警告

这里推荐阅读《Vue.js双向绑定的实现原理

react没有数据双向绑定

react是单向数据流:对应任何可变数据理应只有一个单一“数据源”,数据源状态提升至父组件中

react中通过将state(Model层)与View层数据进行双向绑定达数据的实时更新变化,具体来说就是在View层直接写JS代码Model层中的数据拿过来渲染,一旦像表单操作、触发事件、ajax请求等触发数据变化,则进行双同步

angular也是双向数据绑定(一次做完所有数据变更,然后整体应用到界面上)

安利下:再谈angularJS数据绑定机制及背后原理—angularJS常见问题总结

三者中,我还是更推崇react+redux模式(自上而下的数据流,业务聚焦于数据树设计)

这里面不得不提的就是,vuejs对data中数组的原生方法进行了封装,所以在改变数组时能够触发视图更新。这个我在写日期控件对时候遇到很多坑,比如:

  1. 通过索引直接修改数组的元素,例如vm.items[0] = {title: ‘title‘}

  2. 无法直接修改数组的长度,例如vm.items.length = 0

解决方案: 对于第一种vue提供了set方法vm.items.set(0,{title: ‘title’}) 或vm.$set(‘items[0]’,{title: ‘title’})。另外一个列表渲染的时候的有一个性能的小技巧: 如果数组中本身自带一个唯一的标识 id ,那么在渲染的时候,通过trace-by给数组设定唯一的标识,这样vuejs在渲染过程中会尽量重复原有对象的作用域和dom元素。

 

关于用“箭头函数精简你的 Vue 模块”(建议点击阅读),精简出来就是:

methods: {
  method(){//todo}  //不用: method:()=>{//todo}   method:function(){//todo}
}

 

参考文章:

Vue2.0 探索之路——生命周期和钩子函数的一些理解

详解vue生命周期

vue生命周期

vue生命周期探究

vue安装,router-link的一些属性,用法,tag active-class,to,replace,exex等等

Vuex下Store的模块化拆分实践

Vue.js与React的全面对比

Vue.js 2.0源码解析之前端渲染篇

用箭头函数精简你的 Vue 模块 – dotdev

 转载请注明来源,vue2.x入坑总结-回顾对比angularJS/React - vue入坑总结 - 周陆军的个人网站,:https://www.zhoulujun.cn/html/webfront/ECMAScript/vue/8125.html。不妥之处,望告之,谢谢!

以上是关于vue2.x入坑总结—回顾对比angularJS/React的一统的主要内容,如果未能解决你的问题,请参考以下文章

简单对比vue2.x与vue3.x响应式及新功能

Vue2.x和Vue3.x使用上的差异对比-示例

Vue2.x和Vue3.x使用上的差异对比-示例

vue3,对比 vue2 有什么优点?

Vue2.x踩坑与总结

vue3,对比 vue2 有什么优点?