从js运行机制角度分析vue数据追踪和react状态管理的异同

前言

之前一直用react框架来写前端工程,最近单位一个项目因使用了vue框架,于是对照着react项目,选择对应的生命周期函数、router、 redux/vuex、jsx/template、async await/axios,三下五除二组装起了vue项目,当然,也因为对vue整体没有太多的了解,在有些复杂页面的如组件的复用遇到了些问题,重点就是不知道数据如何复写到页面上,于是便有了这篇文章。

一.js原生写法对DOM节点上数据的改变

从最基本最原始js说起,当html上有个dom节点p标签时候,我们可以通过js来改变p标签的内容

//html代码
<a id='a1'>a标签</a>
<p id='p2'>p标签</p>
<button id='btn3' onClick='btnClick'>按钮</button >

//js代码
btnClick(){
  $("#p1").html("新内容");
}

我们知道,浏览器的刷新按钮是对整个页面进行重新渲染,而上述代码,我们通过jquery实现了对P标签内容的改变,并且这个改变是局部的改变,并不会引起a标签和button按钮,也不会引起整个页面重新渲染
记住两点,浏览器刷新是整个页面渲染jquery是局部渲染

二.Ajax/setTime等具有延迟操作特性函数对DOM节点上数据的改变

我们请求ajax获取数据后,对页面上p标签数据的改变跟上面js改变其实在本质上并没有什么改变,都是局部刷新。但往往我们一个页面有多个ajax请求,这些请求都是异步的,而我们知道JavaScript是单线程,这是怎么实现的?

浏览器线程

js运作在浏览器中,是单线程的,即js代码始终在一个线程上执行,这个线程称为js引擎线程。
浏览器是多线程的,除了js引擎线程,它还有:
UI渲染线程,用于渲染页面
浏览器事件触发线程,用于控制交互,响应用户
http请求线程,用于处理请求,ajax是委托给浏览器新开一个http线程
EventLoop轮询的处理线程,处理线程用于轮询消息队列
……..
当我们页面运行在页面上,展现在我们眼前的是js引擎线程,当我们操作时,虽然处理代码是写在了js里面,但最终还是会触发浏览器事件线程,并将该事件放在消息队列里,如果该操作包含一个网络请求,则会调用http请求线程(异步),获取数据改变页面数据则会调用UI渲染线程。同理,setTime也是将操作放入消息队列里,改变数据,调用UI渲染线程。
记住一点,当一个页面运行在浏览器上,至少有四五个线程协作才能完成数据变化页面刷新

三.vue的数据追踪

基于js的前端页面,包含三个部分html、style、script,而基于vue的页面也是包含这个三个部分,template对应html,script和style;基于js的页面只有一些window.onload可以被归类为生命周期函数,而基于vue的页面极大的丰富了生命周期函数,其中的watch和computed就用于来追踪数据的变化。
下面我们就来分析vue是怎么追踪数据的
1.在data()函数中定义数据初始值
2.在template模板中用{{}}对某个控件(一个实例,如p标签)实现数据绑定。这里要介绍ES5中一个神奇的方法 Object.defineProperty,他有三个参数,实例对象、需要定义的属性或方法的名字、目标属性所拥有的特性。在第三个参数中,vue.js悄悄的给实例添加了setter和getter方法。这个函数对数据实现的拦截,当我们给数据赋值的时候,setter方法会实现2个操作:1.给数据赋值 2.发送一个事件给浏览器,告诉浏览器的UI渲染线程,我需要渲染页面了。个人认为这是vue数据追踪最核心的地方。
3.vue也可以用watch和computed来追踪数据的变化。watch用来监测当A数据变化时候,其它非A数据有没有什么变化(一对一,一对多,A是前者,监测前者的变化来改变后者),computed用来监测当非B数据或者多个非B数据变化时,B数据有什么变化(一对一,多对一,B是后者,监测前者的变化来改变前后者)。一般我们程序中要computed即可,很少会使用到watch。这里在程序中我们对每一个绑定的数据通过computed来追踪变化。

//templa代码
<a id='a1'>{{a1}}</a>
<p id='p2'>{{p2}}</p>
<button id='btn3' onClick='btnClick3'>按钮3</button >
<button id='btn4' onClick='btnClick4'>服务器取值</button >

//script中代码
data(){
  a1:'a标签';
  p2:'p标签';
}
watch:{
  p2:function(){
    this.a1='改变了';
  }
},
computed(){
  p2:function(){
    return vuex中.state.p2;
  }
},
methods:{
  btnClick3(){
    this.p2='新内容';
  }
  btnClick4(){
    发送服务器请求getdata();
  }
}
//vuex中代码
state:{
 p2:'';
}
mutations:{
  getdata(state,res){
    const data=服务器返回数据;
    state.p2=data;
  }
}

点击页面按钮3进行赋值的时候,会默认调用p标签的setter方法完成1.更新数据2.发送事件给浏览器,调用UI渲染线程局部渲染页面
点击服务器请求按钮,获取数据后,computed中p2:function(){}也是一个赋值行为,从vuex中获取数据后,跟按钮3完成同样的更新数据及局部渲染页面;此外,watch监听到了p2的变化,也同步改变了a1的值,渲染了页面。这里实际上是局部渲染了两次页面。
记住一点,vue是追踪每一条通过{{}}绑定的数据,每次赋值后局部渲染

react状态管理

与vue数据追踪不同的是,react采用的时候状态变化来引起页面的变化。
我们先剔除状态里的函数,单纯讨论数据部分。
初学react,要比vue难理解的多,什么一组确定的状态对应一个确定的页面,什么更新组件的状态然后根据新的状态重新渲染用户界面,感觉跟大学里辩证唯物唯心主义一样绕人。
先看代码

//render代码
<a id='a1'>{a1}</a>
<p id='p2'>{p2}</p>
<button id='btn3' onClick='btnClick3'>按钮3</button >
<button id='btn4' onClick='btnClick4'>服务器取值</button >

//class中代码
state:{
  a1:'a标签';
  p2:'p标签';
}
btnClick3(){
  const p='新内容';
  this.setState({p2:p});
}
btnClick4(){
  发送服务器请求getdata();
}
componentWillReceiveProps(nextProps):function{
  if(nextProps.p!=this.pros.p2) const a='改变了';
  const p=nextProps.p;
  this.setState({p2: p,a1:a});
}
//redux中代码
  state: {
    p:"",
  },
 effects: {
    *getdata({payload}, {call, put}){
      const p=服务器返回数据;
      yield put({type: 'savedata',payload: {p}});
    },
}

在react中,state的作用有点类似vue中data(不考虑state里的函数),componentWillReceiveProps作用相当于vue的computed,我们可以发现,不管是在按钮3本页面直接赋值还是从服务器取值用componentWillReceiveProps接受新数据后,都需要显式的调用用this.setState,它完成2个动作:1.更新state里的数据 2.通知浏览器调用UI渲染线程来渲染页面。这里跟vue中最大的区别就是,vue是通过{{}}自动的给p标签实例添加setter,setter会自动的告诉浏览器数据变化了,而react需要手动的调用this.setState来通知浏览器我们要改变数据了。此外,vue是每条数据单独实现局部自动更新,而react数据改变后,手动整体更新

五.总结

vue和react说到底还是js,既然是js,刷新页面数据必然少不了浏览器重绘(repaint)和重排(reflow),事实上,vue的setter和react的setState给浏览器发送事件调用UI渲染线程的时候就是告诉浏览器我要重绘还是重排。

vue逐条更新和react整体更新到底谁效率更高?
重排是告诉浏览器我要重新布局,而重绘是根据已经布置好的布局实现样式外观等改变,所以说重排的消耗要比重绘多的多。
react是整体更新,不管你什么数据变了,我都是不计代价的触发一次重排
vue是逐条更新,理论上只是根据数据的变化,每次触发一下重绘即可,可能10次重绘都赶不上react的一次重排,看上去似乎vue效率更高。但是!vue有个v-if属性,这个是很讨厌的东西,它会根据值来影藏这个显示那个,实际上调用了两次display:block/none,而给display赋值必然会引起重排,在复杂页面中,我们必然会使用v-if,每个v-if都会重排两次,开销很大。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,884评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,755评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,369评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,799评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,910评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,096评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,159评论 3 411
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,917评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,360评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,673评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,814评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,509评论 4 334
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,156评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,882评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,123评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,641评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,728评论 2 351

推荐阅读更多精彩内容