react与vue的比较一直是一个比较引战与容易引起争议的话题,或许每个前端都或多或少的参与到过这场辩论中,但是在这场巨大的辩论中产出的有价值的内容却一直比较稀缺。在这里我无意再次引起争吵,只是结合我自己的经验希望尽可能客观的阐述一些我认为的两个框架上一些差别与优劣。事实上如果没有真的在生产环境中较多的使用过两个框架而只是根据文档与一些demo来做对比几乎是没有什么实际的意义的。我在前一年的工作中几乎只使用vue,因为当时上一家公司部门的技术栈只是vue,虽然我也学习了react并且根据文档做了一些demo,但当我当时真正去想知道react与vue之间的具体差别是什么从而去找一些react的文章来读时会发现,因为没有实践与系统的研究过react很难真正的弄清楚这两者的真正差异。而如今当我大概真正写了3个月左右的react后,最大的发现其实是很多差异你写着写着它自己就会慢慢的浮到水面上,所以建议部门技术栈宽松的同学还是多去尝试,弄清楚问题的最好的方式应该是自己试着解答它。
以下只代表个人的意见,并不保证观点全部正确
diff、patch与update
react: 在react中如果某个组件的状态发生改变,react会把此组件以及此组件的所有后代组件重新渲染,不过重新渲染并不代表会全部丢弃上一次的渲染结果,react中间还是会通过diff去比较两次的虚拟dom最后patch到真实的dom上。虽然如此,如果组件树过大,diff其实还是会有一部分的开销,因为diff的过程依然是比较以此组件为根的整颗组件树。react提供给我们的解决方案是shouldComponentUpdate
,以此函数的返回结果来判断是否需要执行后面的diff、patch与update。再实际的开发过程中我们常常会用pureComponent
来帮助我们做这一层逻辑判断,但需要注意的是pureComponent
的shouldComponentUpdate
也只是浅比较,假设比较的类型是object,如果object仅属性发生变化,但是其引用没发生变化那么shouldComponentUpdate
会认为两者之间没有任何变化。
vue: vue的响应式使用的是Object.defineProperty
api,并且由于在getter中实现了依赖收集,所以不会像react一样去比较整颗组件树,而是更加细粒度的去更新状态有变化的组件,同时defineProperty
也不存在像shouldComponentUpdate
中比较引用的问题。
对比: vue的更新要比react粒度要更细也更加不用去人为的关心,虽然react可以通过shouldComponentUpdate
实现同样的效果,然而如果state的层级结构比较深那么相应的手动去优化这部分代码也会更加费力,所以在react中我们需要尽量保证整体结构的扁平,去让pureComponent
帮助我们自动的对此作出优化。
状态数据的更改方式
react: 由于react与redux推崇的都是函数式编程,所以类似的状态更改都类似如下代码
state = {
obj: {a: 1, b: 2},
arr: [1, 2, 3],
}
// 修改obj.b
modifyObj = () => {
this.setState({
obj: {
...this.state.obj,
b: 3,
}
})
}
// 修改arr
modifyArr = () => {
this.setState({
arr: [...this.state.arr, 4],
})
}
复制代码
实际上我们就算直接修改对象或是数组也是可以的,比如直接修改obj.b = 3
,然后setState({ obj })
,一些情况下这并不会引起什么错误,但是第一由于react与redux本身就是推崇函数式编程的,所以官方是不推荐直接修改对象或数组。第二还记的我们上面说的pureComponent
吗,由于shouldComponentUpdate
是浅比较,如果直接修改对象或者数组的话会导致组件并不会发生更新。
vue: vue在修改状态数据来说相对是比较简单的,对于对象我们可以直接this.obj.b = 3
就可以了,此时会触发初次渲染时设置的setter,然后会调用notify方法去通知所有watcher执行update。但是对于数组的修改来说vue就并没有这么简洁了,虽然我们依然可以通过push,unshift等方法去修改数组,但是类似arr[index] = someVal
这种方式就行不通了,此时或者使用splice方法,或者通过类似react的方式map一个新的数组重新赋值。最近vue3也逐渐浮出了水面,再最近这几次的vueConf中宣布了vue3采用的是proxy方案,所以在vue3中数组的改变就可以使用arr[index]
的方式了。
对比: 在data为对象的情况下,vue的代码方式要更加简单方便,而数组的情况下对于vue与react其实并没有什么太大的差别,相当多的时候我们依然要使用map,filter等函数。但是注意vue的简单方便并不代表本身更好,react与redux之所以推崇永远用新的值代替旧值为的是更加容易debug与一些时间旅行等功能,但对于vue来说这并不冲突,如果你喜欢你同样可以用这种方式去书写你的vue代码。另外一提,虽然看起来Object.defineProperty
这个api帮助了vue很多,但也不是全无副作用的,当状态数据庞大时,由于此api需要递归的去不断的对状态数据执行监听,所以vue的初始消耗有可能更多,对应的也就是白屏时间相对react来说更长,此问题也会在vue3中解决,因为基于proxy的监听是类似lazy这种形式的。
关于逻辑复用
react: 关于逻辑复用是一块很有意思的内容,真的要感慨一下react的社区与社区贡献的高质量方案,从mixin到高阶组件再到renderProps,我们这里主要拿renderProps来举例,高阶组件我们留到后面说。所谓的逻辑复就是把通用的一些逻辑复用到需要这些逻辑的组件上,我们来看下react官方文档为我们提供的案例。
这是一个实时显示鼠标位置的组件,对于这个组件来说主要逻辑在于鼠标事件的处理,但是这一部分又恰恰是比较难复用的一个地方,假设我们希望在另一个组件中渲染一张猫的图片,同时它的位置由鼠标位置决定,那么这部分鼠标事件与状态的代码我们基本上又要再写另一遍,来看下renderProps是怎么解决问题的。
第一次看这样的代码真的是要稍微感叹一下这他妈怎么想到的,抽象出Mouse组件负责行为,然后将state作为参数传入外部返回组件的函数中,真的是感觉相当牛逼。顺带一提,并不一定要用render这种props,比如这个例子里我们完全可以写成如代码的形式
<Mouse>
{
mouse => <Cat mouse={mouse} />
}
</Mouse>
// Mouse组件
<div onMouseMove={this.handleMouse}>{this.props.children(this.state)}</div>
复制代码
相对应的我们可以做出很多这样的封装,比如模态框的开关,比如Input,Select等控件关于onChange与setState的封装,简单举一个例子
<Container>
{
(value, changeValue) => <Input value={value} onChange={changeValue} />
}
</Container>
复制代码
想象一个常见的场景,4个Input外加一个Button搜索按钮,然后将搜索出的数据展示到表格上,使用这种封装可以省下相当多的样板代码。
vue: 我们直接看看vue怎么实现上面的鼠标复用逻辑把。
// 指令
Vue.directive('mouse', {
binding: function (el, binding) {
function mouseMove(e) {
binding.value.x = e.clientX;
binding.value.y = e.clientY;
}
el.__mouseMove = mouseMove;
el.addEventListener('mousemove', mouseMove);
},
unbinding: function (el) {
el.removeEventListener('mousemove', el.__mouseMove);
}
})
// 组件中
<template>
<div v-model="data">
{{ data.x }}, {{ data.y }}
</div>
</template>
复制代码
直接注册一个v-mouse指令,我们就可以在任意组件去复用这段逻辑了,其实单论这两段代码我个人认为vue的实现是更好的,v-mouse这种声明式的指令非常的清晰与优雅,但是但是但是我们必须要清楚这只是很简单的一小段逻辑,如果逻辑更加复杂,毫无疑问的是react灵活性与维护性上都要比指令的方式好上非常多。
对比: react的renderProps虽然稍为繁琐一点但是在灵活性与可维护性上来说是要绝对优于vue的指令的。但是在一些简单的场景下vue的指令也确实会相当的方便,比如最常用的内置v-model,依然想一下我们之前描述的场景,多个Input加一个按钮,vue对应的只是4个v-model="value"
。
数据的流向
其实这一部分并不算两者有差异的部分,但是很多人一看到v-model就下意识的反应双向数据绑定。很多比较vue与react的回答直到今天还会说由于vue是双向数据绑定,导致数据容易变得混乱在大型项目中不易维护,这实在是有些搞笑的。其实再组件间vue与react的数据流向都是单向的由父向子,并且子组件不允许改变props,两者并没有什么区别,而类似input中的v-model不过是逻辑复用的一种方式或者说是一种声明式的语法糖,同样的为组件使用v-model依然是一种语法糖,但是数据的流向仍然是清晰的单向流动。
高阶组件
react: 高阶组件是另一种复用逻辑的形式,对比renderProps,高阶组件其实更多的是选择传入什么,而renderProps则是暴漏什么。所谓的高阶组件其实跟高阶函数是一个意思,只不过函数接收的是一个组件然后再返回一个组件, 举一个常用的例子,假设我们有一个接口是获取公司的组织树,这个组织树在很多场景下都要使用,我们就可以抽出一个公用的高阶组件
function HOC(wrapperComponent) {
return class getOrgTree extends React.Component {
state = { orgTree: [] }
componentDidMount() {
fetch('xxxxxxx').then(orgTree => this.setState({ orgTree }))
}
render() {
return <wrapperComponent orgTree={this.state.orgTree} {...this.props} />
}
}
}
复制代码
通过这个函数我们可以把任意我们自己的组件传入函数使其拥有orgTree的props,除此之外我们还可以通过高阶组件去减少或增加各种props等拓展性的操作
vue: vue的类似操作几乎只能通过mixin来实现(容易污染,也不容易debug),更是很难去拓展使用mixin的组件,这两者的区别像是react的组件像是穿上了一层装甲变了身,vue的组件只是把东西拿过来放在口袋里本质上还是自己。
const myMixin = {
created: function () {
fetch('xxxxx').then(orgTree => this.orgTree = orgTree)
},
}
复制代码
对比: 在高阶函数这个阶段react通过面向对象的方式依然达到了一个高扩展性与灵活性,vue在高阶函数中几乎没什么发挥余地,因为vue实际上是面向配置(options)的,虽然我们可以利用render函数楞写一个貌似高阶组件的东西,但是第一render书写的代码可读性还是很差的(虽然貌似也可以使用babel写jsx,但那样为什么不直接用react),第二这也几乎背离了vue的开发模式,所以其实从一些模式上的应用与逻辑上的复用角度来讲react是领先的。不过好消息是vue3的组件也是class了,这或许能帮助vue在这个短板上提升。
TypeScript支持
对比: vue2虽然支持,但是使用上并不良好,落后react很多个等级。依然是vue3解决的问题。
社区与周边生态
对比: 事实上我认为真正react与vue之间的差距很大一部分是在社区与一些生态上,react的社区贡献真的是相当的活跃,各种各样的方案,各种成熟的组件(不只是UI库),相对而言vue则显得平静很多,有很多组件的质量也还不够高,这个就并不是vue3能解决的问题了,唯一能依靠的只有时间。
总结
结论与很多对比一样,vue适合小而精的项目,react则更适用于偏大的项目,但是立住这个结论的支点是不一样的,vue的组件由于一些复杂逻辑的复用方式和组件可应用模式的不足,以至于在大型项目中复用性与设计性是不如react的,但是在小而精的项目里,vue更友好的书写方式,更简化的代码与更声明式的指令都是很棒的优势与特点,而相反的react在中大型项目中能更好的完成vue的不足项,但是也更需要团队技术整体比较给力,领头大佬的设计与把关能力要更优秀,不然糟糕的设计或许不如没有设计。有一些比较总会认为vue由于api较多书写更灵活导致最后的代码难以维护,事实上react或许才是更灵活的一个,因为react几乎没有api导致无论怎样的写代码都是可行的,团队中有10个人可能就有10种方式去理解react,并且无论是状态管理还是异步中间件再加上路由等等,react的社区都提供了更多的选项,怎么选型,采用什么方案也是稍令人头疼的事情之一。
最后
都读到这了,觉得还行给个star总不过分~~~