使用react的好处_Vue 和 React 的优点分别是什么?

感谢王雷知乎用户指出我的一个错误,错误地把制约性能的地方定位在diff而不是patch。

这个错误也显示了我作为一个Angular开发者的思维定式,是,我承认,也希望大家共勉——

Angular开发者错误思维定式之一:

因为Angular是操作真实DOM就以为virtaul到actual中的patch没有性能开销

不过观点依然如此,不能活在自己的领域内无法自拔。

比如其他答案下出现的一些矛盾:

-你看Reat有fiber所以vue一定要抄?

-抄fiber干嘛?实现都不一样…

-你看你拒绝时间分片,尤大都说要用时间分片…

-fiber什么时候变成时间分片了?

这种谬误可以等同于…

-我写的setInterval动画有点卡,该怎么优化?

-用fiber啊!

-…

-你看我用requestAnimationFrame优化了,现在一点也不卡~

-你看,我就跟你说用了fiber就会很好,浏览器渲染得都快了!你看你写个动画都要抄fiber~

用一个自己领域内的一个实现指代所有相关算法和相关实现,真是恐怖。本来vue的patch都在watcher中进去schedule,然后再提交,你可以说我用时间分片优化这个schedule里的提交队列。你说用fiber优化vue什么鬼?

看来cocos已经被fiber优化过了…

另外还有好多人没有意识到这种说法有什么问题,我再说明一下:

浏览器已经有事件循环了,已经有异步环境了,不然你不可能在js里处理事件。

react的scheduler里清清楚楚地requestAnimationFrame和自己实现requestAnimationFrameWithSettimeout,是在浏览器空闲的时候请求一个新的浏览器帧。

到你们这儿成生造一个协程系统了。

单线程里做异步,比如tornado,你在python圈子里用tornado指代事件循环,别人最多说一句其他平台也有事件循环罢了。

你在本来就事件循环的浏览器请求帧运行自己代码,然后说自己实现了协程,这种言论就有奇怪,然后说还直接用自己平台发明的词类比别人还说别人也得学?还用自己平台的词类比协程啊,时间分片啊之类的。

另外做游戏的框架哪个不实现这一套,又变成是React首发了?React领域内自己的更新,非要用来说服其他平台的人。

我想问是不是React平台的每一个人,都是这么狂妄,当然我也知道产生这样的思想有点不好,不过甚至有某些大厂的员工也有这种想法,还说我对浏览器一无所知,言下之意就是React能够突破浏览器的限制,做浏览器不许他做的事咯?。

这样的环境真的让我有些不由自主地怀疑这个技术栈,真的。

不过当年React想要摆脱由浏览器钳制自己的应用性能,使用virtual dom来让自己代码层面平台无关,这其实是一种有些hack的行为,也就是并不是浏览器中代码的正常工作流程,所以一句happy hacking在你写代码的时候放出来给你看,那些无端鼓吹React的人,并没有认识到这一点,应该不是React的真正主力,也不可能是。

平台内部的同学很多惯性思维特别严重,总以为大家用的框架实现原理都是一样的,然后你抄我,我抄你,然后我遇到的问题你们肯定都会遇到,你们都快来学我。

这种心态要不得。

现在理性客观分析vue和React的响应式流程,评论区敞开来说。

React reconciliation

首先附上连接,大家也可以根据文档纠正一下我的鄙陋,毕竟细节的理解是有区别的。

React的reconciliation(调和)是一个过程,描述的是React如何将virtual dom转化成actual dom的实现,为甚么要用virtual dom?因为document.createElement方法有性能损失(浏览器机制)。

局限在React框架下的朋友,很容易就得出结论——

document.createElement有性能损失,所以肯定要做virtual dom到actual dom之间的变更检测,并使得变更的部分能够被识别出来,然后用create和delete的api修改真实DOM。

实现的结果是这样的:首先setState函数告诉组件我要更新了,这个方法会把state放入状态队列中,如果一个事件循环状态队列没有更新,则视为不会更新,否者,更新组件(virtaul dom),并标记为脏(这部分不是reconciliation)。

当前virtual dom有脏节点,怎么办?react从当前节点出发,向下遍历所有子节点和子组件。找到变更的部分,patch(conmmit)也就是调用createElement或者removeElement或者重新赋值。当然这个遍历过程是有算法优化的,也就是diff算法,优化有:比较同级节点,比较节点类型——比如component编程了string,比较空和非空等等,将O(n3)的问题优化为近似O(n)的问题。(这就是reconciliation)

这样做有什么好处?你自己随意书写变量,函数,等编程元素,只要在需要变更视图的时候,setState就可以了

你可以有更多方式声明组件和节点,什么hoc啊,pure啊之类的,能够有更加灵活的编程体验,适合更多的编程范式。

这样做有什么坏处?直接setInterval(()=>{setState(...)},1),就能发现——如果我不停地将节点标记为脏,不停地运行那个diff过程,导致不停重绘,当前事件循环会被阻塞——也就是卡。

setState导致你在编程过程中,虽然不用考虑类似getElement,removeElement之类的操作,但是setState无形之中还是用两个”树“制约了你的思想,也就是React惯性思维的一种——一定要等数据都变更完成了再渲染啊?

脏节点以下所有子组件都要渲染,如果页面大了,顶层数据一改变,diff的工作量就会变大,也会导致大量重绘,同样也会出现阻塞。

接下来,React想到一种方式优化这个操作,这个方式就是——requestAnimationFrame。

用过requestAnimationFrame的同学应该也深有体会,一旦将setTimeout的动画用requestAnimationFrame写,就可以很顺畅了,为什么?

因为底层逻辑不同:setTimeout是将函数绑定到事件循环的底部,导致一旦线程阻塞,运行会被推迟:

2.而requestAnimationFrame是将帧暴露给你,当前帧内浏览器空闲就执行你的代码。

而这个requestAnimationFrame,就是浏览器的时间分片,有没有办法让React容易阻塞的diff过程在这种机制下运行呢?

于是有了fiber这样的数据结构:将单纯的diff算法中每层的比较记录下来——以前是只标记改变的值:

function FiberNode(

tag: WorkTag,

pendingProps: mixed,

key: null | string,

mode: TypeOfMode,

) {

// Instance this.tag = tag;

this.key = key;

this.elementType = null;

this.type = null;

this.stateNode = null;

// Fiber this.return = null;

this.child = null;

this.sibling = null;

this.index = 0;

this.ref = null;

this.mode = mode;

// Effects this.effectTag = NoEffect;

this.nextEffect = null;

this.firstEffect = null;

this.lastEffect = null;

}

将Effect标记在fiber这样的结构中,并且他的child和sibling都已经被记录,这样变更和位于virtual dom中的位置都会被记录。

2. 然后,用workInPregress封装fiberNode

// This is used to create an alternate fiber to do work on.

export function createWorkInProgress(

current: Fiber,

pendingProps: any,

expirationTime: ExpirationTime,

): Fiber {

let workInProgress = current.alternate;

if (workInProgress === null) {

// We use a double buffering pooling technique because we know that we'll

// only ever need at most two versions of a tree. We pool the "other" unused

// node that we're free to reuse. This is lazily created to avoid allocating

// extra objects for things that are never updated. It also allow us to

// reclaim the extra memory if needed.

workInProgress = createFiber(

current.tag,

pendingProps,

current.key,

current.mode,

);

workInProgress.elementType = current.elementType;

workInProgress.type = current.type;

workInProgress.stateNode = current.stateNode;

}

3.scheduler中实现了requestAnimationFrame一样的机制,并做了个响应式的tracing(目前他的说明是希望做平台无关的,但是源码中有浏览器相关api)。

4.在enableSchedulerTracing为真的时候,开始workingPregress的处理。

这样就保证了React的渲染virtual dom总是能够异步地进行,将那些React的setState带来的坏处降低到用户感知不到的程度。(不过造成了新的问题,那是后话)

但是肯定要做virtual dom到actual dom之间的变更检测么?

相继出现的Vue找到了另一种优化方法——组件响应式。

原理是,我为什么要把setState这样会引起性能开销的api交给用户呢?即——为什么要用户setState的时候再进行diff呢?并且,如果我准确知道了哪一个组件会变更,我直接修改这个组件就行了,为什么还要遍历所有子组件树?

很快Vue给出了解决方案:不进行组件间的diff递归,只在组件内进行diff,摒弃掉函数式组件等其他的方式,规定统一api——vue.component。

用代理模式,将data中的值用getter和setter绑定起来,并设置响应式watcher,压入队列。如果watcher发现了变更,启动组件内的diff,直接将新的组件patch到dom。

这里的watcher回调就是有数据变更组件的变更后virtual dom,粒度大小与优化后的一个React fiber相同。

这样做的优点是:不需要在思维上想象diff过程,因为我修改一个组件,diff被限制在了组件内,用户的使用过程中不需要思考性能优化的问题。

组建内的diff可以直接使用jsx,vue的vnode也支持了jsx的编译,可以利用React的生态。

这样做的缺点是:灵活度下降,不能支持很多编程范式(给出了强制api)。

组件相关变量被限制在了data中,setState的问题相对容易察觉,但是循环修改data值虽然性能开销小得多,但是更加难以发现。但是patch带来的性能开销依然存在,下一步的优化方向,也有考虑使用requestAnimationFrame的方式优化watcher的绑定和组件的patch。

但是真的真的需要virtual dom么?

直接在dom里响应式?

有请render2

以及,ivy

另一种思考方式,我为什么要用数据驱动的方式思考问题?该干啥干啥不行?

然后有些自以为是的React平台内的朋友,觉得Vue就该学fiber,两个问题fiber是个数据结构,描述的是一次统计diff节点操作的快照。

就算你说的是时间分片,你也要说时间分片,不是什么fiber!fiber不代表时间分片。

fiber是个数据结构不假,你甚至可以说fiber架构代表了用时间分片异步化diff流程以及patch流程的一种实现方式,你说fiber代表了时间分片?乖乖……

这暴露了一个问题,React社区很容易形成以自我为中心的思考方式,总结以下思维误区以供参考:一定需要virsual dom到actual dom的diffing 过程(全局的)

fiber和轮询调用是同义词

fiber和时间切片是同义词

类型声明或者class就是面向对象

template一定要写在js中

shadow dom是react实现的

react的某些优化可以加速浏览器渲染

redux就是函数式

数据驱动是前端的发展趋势

....

是是是,啥概念都应该以React的为准!

应用领域不一样,完全不不构成竞争关系,跟你说React比Vue牛皮的人,下次怼回去,职业素养真的太低。

首先React和Vue有非常多的相似之处,比如:都是数据驱动

都能使用jsx进行编程

但是两者数据驱动实现不同,React是传统消息驱动模式,setState通知变更检测,Vue是proxy模式。

实现不同,必然导致编程风格的差异:getter,setter对用户不可见,必然会隐藏很多渲染细节,好处是让用户更专注于逻辑部分,坏处是渲染时间和运算不可控。setState虽然需要用户手动进行,暴露了一些细节,但是用户权限更多,对性能的控制更强。

Vue的jsx只能实现模板的组装(比如你想要动态注入一段jsx,只能像iview一样写render函数了),本质和html一样,React的jsx能实现更多的技巧。既Vue结构确定,是一种声明式的v-vm映射工具,而React更偏命令式一点,是一种由vm驱动v的工具(hooks的发布更加坐实了这一点)。

优劣点有哪些呢?声明式的Vue上手更加简单(对象映射template,直观),开发效率高,但是api更多,约束也更多。

React上手偏难一点点(命令式反直觉),开发效率要低一点,但是api少,约束也少,更加灵活。

生态的话:状态管理方面React要丰富一点,毕竟消息驱动是它的根,状态管理就是它的命,不可能不重视。

SSR个人认为nuxt要比next完善很多。

UI库是因为antd才有React UI库质量高的映像,iview也不错但是逐利心较重(没办法一个是kpi一个要活下去)。

对Typescript的支持因为Vue声明式编程要差一点,不过可能大家误解了Typescript,不是用了静态类型写几个class,interface就叫工程化了,感兴趣的话还是建议多多阅读spring或者.net平台的代码,面向对象不是类型检查器,因此React也并没有比Vue强多少。

根本就不是谁牛皮的问题,而是面对一个项目需求谁更适合的问题。

你可以试试事件驱动的ng,change after check劝退你~zone暴力事件代理,响应式流约束数据副作用,让你明白什么是止不住的忧伤。

为防止大家产生歧义

声明式和命令式指的是状态变更

你说react是声明式啊?你是指的描述ui,这不废话?html本来就是声明式的

vue是修改data对象自然就patch

react是调用setState函数

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值