原文来源于:程序员成长指北;作者:柠檬豆腐脑
如有侵权,联系删除
前言
本文将从渐进式、时间线、跨平台及企业级框架情况多个维度对两个库进行对比。
1. 从概念开始
-
React官网说:React是用于构建用户界面的Javascript库。
-
Vue官网说:Vue是用于构建用户界面的Javascript渐进式框架。
一个说自己是库,一个说自己是框架,我们就先从这个细节说起。如下图
Vue说自己是框架,是因为官方提供了从声明式渲染到构建工具一整套东西。
React说自己是库,是因为官方只提供了React.js这个核心库,路由、状态管理这些都是社区第三方提供了,最终整合成了一套方案。
两者最终达到的效果是相同的,也就是渐进式的解决方案,根据需求复杂度的不同,Vue和React都可以提供相匹配的、合适的解决方案。
2. 何为声明式渲染
何为声明式渲染,为何这种方式能在前端界大行其道呢?
声明式渲染指的是只需操作数据,不是操作视图(一般指DOM),数据就自然映射到视图上。当开发人员设定好数据和视图的映射关系后,操作数据就是声明式的编程。
没有声明式渲染,前端的业务逻辑和视图逻辑就要耦合在一起,难为开发和维护。声明式渲染将视图和业务逻辑拆分开,让开发人员只需要关注数据和业务逻辑,能应对前端负责的业务,也能保证数据准确的反应在视图上。
2.1 响应式数据 VS 不可变数据
Vue和React都要解决一个问题,如何让数据变化后,通知到视图更新。
Vue采用了响应式数据,对数据进行劫持,Vue2是对属性进行劫持,Vue有了新的API Proxy,可以对对象进行代理。当数据变化时,通知劫持的观察者更新视图。
React是调用方法通知视图更新,数据不可变的好处是在重新渲染时只需比对数据引用是否变化,就知道是否数据变化了,进而React决定是否要进行重渲染。
响应式数据的好处是可以准确的知道哪个组件受影响了,需要进行重渲染,进行精准更新,但也同时因为劫持数据和收集依赖要耗费额外的性能。不可变数据并没有监听哪些组件会受自己的改变而需要重新渲染,所以要自数据变动组件及所有子组件都需要重新渲染,需要在子组件中添加性能优化相关的代码,保证跟自己相关的数据没有变化时不重新渲染,这需要额外的心智负担,但也没有响应式数据那些额外开销。
2.2 JSX VS 模版
声明式渲染的另一端是视图,其实JSX和模版咋一看区别不大,都是XML样子的东西。
JSX可以最大程度利用JS的能力,比如下面这段代码,逻辑判断、循环甚至递归都可以自由使用。
function TreeNode(props) {
const { children } = props;
if(!children) return <div>暂无节点</div>;
return (
<div>
{children
? children.map(child => <TreeNode key={child.id} {...child} />)
: "暂无节点"}
</div>
);
}
相比之下模版语法,要想实现逻辑判断或循环,就需要借助模版语法v-if、v-for等。
同一个问题,比如组件要接收父组件传递过来的视图片段,来看一下React和Vue分别是怎样解决呢。答案是JSX会使用props.children或render prop,模版会使用语法slot。
可以看到JSX是简洁的,一些功能的实现都是利用JS原生的能力,而Vue是提供便捷的语法帮助你解决问题。
还有一点区别是,模版会在编译时编译成render函数,在运行时供Vue使用,这给了Vue优化的空间,在Vue3中,针对模版的编译有了标记元素、静态提升等优化手段,后续在运行时更快的创建虚拟DOM和diff对比。而JSX就是JS,没有了编译过程这个优化空间。
至于Vue有v-model这种双向绑定,只是一种语法糖,不算两个库的关键区别。
3. 组件化
组件化是两个库等编写复杂应用的又一基石特性。这里要提得就是单向数据流,数据都是从父组件流向子组件。数据等流向清晰,便于管理。
组件
Vue的组件更像一个对象,无论我们使用Vue2的Options API 还是Vue3的Composition API,都是填充组件对象的内容,扩展组件的功能。
React分函数组件和类组件。函数组件更符合React整体的调调,也就是函数式。函数式的思维,把React抽象为UI=Fn(State),React将State映射成UI,这是纯的部分,这个过程中发生的其他事情,被称之为副作用。我认为这也是函数式的精髓所在,它总是告诉你自己的主线是什么,保持主线的纯粹,其他方便的东西都是副作用,是主线的支线流程。这样,我们的应用就有了清晰的逻辑。
对比之下,React也更倾向让组件单一职责,分容器组件和展示组件,而这在Vue中没有刻意的强调,Vue就是让组件完成独立的功能即可。
在具体实践中,React组件要更注意性能优化,也就是控制自身要不要重新渲染,而Vue却不需要。这还是响应式数据带来的区别,Vue能准确的知道数据变更后会触发哪些组件进行更新。而React中State变化以后,没有优化的情况下,会导致使用State及其所有子组件进行重渲染。
4. 路由
两个库的路由库的核心功能一样,暂时不多说,后续等我充分研究后这部分内容。
5. 状态管理
什么情况下,要使用状态管理呢?
如下图所示,当数据不总是以一种线性的,单向的方式流动、许多不相关的组件以相同的方式更新状态等情况出现,就应该考虑使用状态管理了。
React使用Redux后示意图
所示,使用状态管理后,数据的变化和流向又变得清晰。状态管理可以很好的配合复杂组件树的情况。
5.1 心智模型对比
Vuex仍然是基于响应式数据的,通过Action(异步)和Mutation(同步)的改变State。State绑定到视图的computed上,Action和Mutation绑定到视图的methods上,就打通了视图和状态管理。
Redux仍然是基于不可变数据的,是函数式的思维,通过Action来处理副作用,通过Reducer来更新State。通过store.subscribe回调通知外界State变化。
5.2 完整度对比
Vuex除了维护状态,也处理异步,同样提供了连通Vue的mapState等函数,相对来说比较完整。而且Vuex将State、Getters、Mutation、Action统一放在一个对象里,更加集中方便管理。
Redux的内容更加纯粹,只负责维护状态,如果需要处理异步,需要加入中间件,如果需要连通React,需要引入react-redux库。
5.3 其他感想
还有个Mbox状态管理库,也是基于响应式数据的,可以应用到React上,我个人目前感觉还是Redux更加适合React,因为React就尊崇函数式的思想,这点上Redux非常契合,可以用一样的心智模型来思考。
还有Pinia,相比于Vuex,Mutation不再存在更加简洁,且Pinia更加适合Vue3。从这我们也可以看出无论Vuex还是Pinia,都是为Vue量身打造的状态管理库。而Redux的设计专注于自身设计,再跟React进行组合。
6. Fiber架构
上面是从渐进式的脉络来对比了两个库,接下来我们看看React和Vue近年来的主要更新,再思考彼此是否有类似的更新,都做的怎么样。
6.1 React为什么要采用Fiber架构
首先要说的就是React 16推出的Fiber架构。说的是解决React渲染性能问题,我想说的是React 15的渲染性能很差吗?
我觉得即使没有Fiber架构,React 15的渲染性能也没有很差,在使用React 15进行实际项目的开发时,也是可以满足绝大部分项目的性能要求的,除非项目巨大,并且开发者对性能优化方面做得不够。
所以有种说法说因为Vue性能好,React性能差才要进行Fiber架构是没有道理的。Vue和React性能差不多,只不过是因为React要保持比较好的性能,在性能优化方面要做得工作,要比Vue多花些心思。
所以,react采用Fiber架构,不是因为性能差,而是因为追求卓越,让应用有更加流畅的表现。
6.2 Fiber架构产生的后续影响
Fiber架构的调度逻辑是复杂的,从React16 到React 18在不断优化中,而Fiber架构带来给开发者的问题是,是否要在我们的应用中利用Fiber架构的优势,开启并发模式。
下面看看各版本如何开启并发模式。
// 对于React 16和17,如果使用ReactDOM.render,是不开启并发模式的
ReactDOM.render(<App />, rootNode);
// 对于React 16和17,如果使用ReactDOM.createRoot(rootNode).render(<App />),是开启并发模式的
ReactDOM.createRoot(rootNode).render(<App />);
// 对于React 18,改变了策略,统一使用ReactDOM.createRoot(rootNode).render(<App />)来启动应用。
// 但只有当开发者使用跟并发相关的新特性useDeferredValue和useTransition,才会开启并发模式,否则还是同步模式
const App = () => {
const [count, updateCount] = useState(0);
const [isPending, startTransition] = useTransition();
const onClick = () => {
// 使用了并发特性useTransition
startTransition(() => {
// 本次更新是并发更新
updateCount((count) => count + 1);
});
};
return <h3 onClick={onClick}>{count}</h3>;
};
所以正如我上头所说,无论16到18版本,我们大部分还是用同步更新就够了,除非你觉得有必要使用并发更新的时候才需要使用。
7. Hooks VS Composition API
React 16.8发布了Hooks,Vue3也紧随其后推出了Composition API。无论Hooks和Composition API,都很大程度的改变了开发者代码的书写方式。
在写组件代码时,两者达到的效果有异曲同工之处,就是让逻辑更紧密的业务代码可以放在一块,在组件之间可以复用状态逻辑。
// 在Vue中,可以利用Composition API将逻辑紧密的代码放在一块
import { ref, onMounted, onUnmounted } from 'vue'
// 按照惯例,组合式函数名以“use”开头
export function useMouse() {
// 被组合式函数封装和管理的状态
const x = ref(0)
const y = ref(0)
// 组合式函数可以随时更改其状态。
function update(event) {
x.value = event.pageX
y.value = event.pageY
}
// 一个组合式函数也可以挂靠在所属组件的生命周期上
// 来启动和卸载副作用
onMounted(() => window.addEventListener('mousemove', update))
onUnmounted(() => window.removeEventListener('mousemove', update))
// 通过返回值暴露所管理的状态
return { x, y }
}
// 同理,在React中,利用Hooks将逻辑紧密的代码放在一块
import React, { useState, useEffect } from 'react';
function FriendStatus(props) {
const [isOnline, setIsOnline] = useState(null); useEffect(() => { function handleStatusChange(status) { setIsOnline(status.isOnline); } ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange); return () => { ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange); }; });
if (isOnline === null) {
return 'Loading...';
}
return isOnline ? 'Online' : 'Offline';
}
但不同的是,Hooks对于React有更重要的意义,就是扩展了函数式组件的能力,让函数式组件拥有状态,让函数式组件可以做到一切。让函数式组件成为了React中一等一的公民。也更加符合React的调调,用函数式的思维来思考。UI不过是经过函数将State进行映射,至于这过程中发生的其他事情,都是副作用。
8. 跨平台方案对比
React和Vue最知名的两个跨端框架,就是React Native和Weex了。
8.1 先说说Weex
Weex发展历程
我们先捋一下Weex发展时间线,从出生到死亡。
-
2015年阿里无限内部使用。
-
2016年4月阿里巴巴在Qcon大会上宣布开源
-
2016 年底加入了 Apache 软件基金会
-
2017年12月从Apache Incubator退休,也就是不符合Apache孵化器的所有要求,没能成为一个成熟的开源项目。主要原因是没有足够的开源贡献者。Weex 将会回归到 alibaba/weex 继续维护。
-
我查看github上的 alibaba/weex 项目,最近的release版本是2017年5月的0.30版本。可以说没有再继续维护了,凉凉。
-
2021年面试阿里一个团队,说他们内部在使用Weex 2.0,不是个开源版本,更多信息不得而知。
实际使用Weex体验
特别要说的一点是,Weex不支持模版语法v-show。在我看来这能反应React API简洁的一个优势,API越简洁,基于它构建的框架需要兼容的API越少。
由于Weex看起来已经逐渐退出历史舞台,更多跨端开发体验,放在下面谈React Native时候说。
8.2 再说说React Native
RN(React Native简称)从2015年开源至今,一直处于活跃更新状态。所以要考虑跨平台应用开发,RN是值得被考虑的。
我们先看看两个方案的口号对比。
-
RN:Learn Once, Write Anythere。(RN只是降低的是学习成本,针对不同平台可能还需要单独开发)
-
Weex:Write once, Run Everywhere。(写一次,在哪都能跑)
RN口号是严谨的,它考虑到了跨平台的复杂性且难以统一,编写跨平台应用的代码,本身就是要具备一些挑战性的。Weex就显得有些吹牛皮了。
实际使用RN体验
相较于开发Web,RN的组件类似于DOM,但不同于DOM,RN组件会被翻译成原生组件。
同样的,RN的样式也是CSS-Like,也跟CSS相似,但不完全一样。React和RN在编程体验相同的地方是都是React的逻辑。关于组件和样式的情况,Weex也是类似的,毕竟两个方案的解决思路是相同的。
import React from 'react';
import { Text, View } from 'react-native';
const YourApp = () => {
return (
<View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}>
<Text>
Try editing me! 🎉
</Text>
</View>
);
}
export default YourApp;
RN另一个让人觉得充满挑战的点是频繁更新,原则上它每个月都会更新,它需要跟随原生平台的更新和变化,以确保应用程序的兼容性和性能。所以我们在使用RN方案的时候,也要跟上脚步,关注版本变化,进行框架升级。
9. Next.js vs Nuxt.js
接下来说说React和Vue的两个重磅框架,Next.js和Nuxt.js。
9.1 Next.js
主要特点
-
服务端渲染:支持SSR和SSG两种方式,有更好的SEO,首屏渲染性能。
-
基于文件的路由:简单灵活,可以实现页面之间的无缝跳转和传递参数,使用户在浏览网站时体验流畅且一致。
-
全栈能力:在Next.js框架下,同样可以定义API,可以说Next.js是一个全栈框架。
服务端组件
额外要说的是Next 13推出的服务端组件,让服务端渲染从页面级细化到组件级别,真是太厉害了。另外函数式组件再次在Next.js中发挥威力,在服务端是不支持类组件的。
9.2 Nuxt.js
可以说是Next.js的Vue版本,主要特性是类似的,服务端渲染,基于文件的路由,还有全栈能力,不过Nuxt目前还不支持服务端组件。
10. 对比总结
概括的说,React是最求简洁和函数式的,这两点也确实在跨平台方案和大型框架如Next.js中发挥了威力。还有一个重要的影响点就是,给项目中的React升级是容易的,也得益于React对自身暴露API的谨慎。
Vue追求易用和上手,但在跨平台方案和大型框架落了下风。但如果是开发Web应用,Vue同样是值得选择的好技术栈。暂时总结到这里,后续再学习,有新的心得再补充进来。