React 和 Vue 全方位对比总结

717次阅读  |  发布于1年以前

前言

本文将从渐进式、时间线、跨平台及企业级框架情况多个维度对两个库进行对比。

从概念开始

一个说自己是库,一个说自己是框架,我们就先从这个细节说起。如下图

Vue说自己是框架,是因为官方提供了从声明式渲染到构建工具一整套东西。

React说自己是库,是因为官方只提供了React.js这个核心库,路由、状态管理这些都是社区第三方提供了,最终整合成了一套方案。

两者最终达到的效果是相同的,也就是渐进式的解决方案,根据需求复杂度的不同,Vue和React都可以提供相匹配的、合适的解决方案。

何为声明式渲染

何为声明式渲染,为何这种方式能在前端界大行其道呢?

声明式渲染指的是只需操作数据,不是操作视图(一般指DOM),数据就自然映射到视图上。当开发人员设定好数据和视图的映射关系后,操作数据就是声明式的编程。

没有声明式渲染,前端的业务逻辑和视图逻辑就要耦合在一起,难为开发和维护。声明式渲染将视图和业务逻辑拆分开,让开发人员只需要关注数据和业务逻辑,能应对前端负责的业务,也能保证数据准确的反应在视图上。

响应式数据 VS 不可变数据

Vue和React都要解决一个问题,如何让数据变化后,通知到视图更新。

Vue采用了响应式数据,对数据进行劫持,Vue2是对属性进行劫持,Vue有了新的API Proxy,可以对对象进行代理。当数据变化时,通知劫持的观察者更新视图。

React是调用方法通知视图更新,数据不可变的好处是在重新渲染时只需比对数据引用是否变化,就知道是否数据变化了,进而React决定是否要进行重渲染。

响应式数据的好处是可以准确的知道哪个组件受影响了,需要进行重渲染,进行精准更新,但也同时因为劫持数据和收集依赖要耗费额外的性能。不可变数据并没有监听哪些组件会受自己的改变而需要重新渲染,所以要自数据变动组件及所有子组件都需要重新渲染,需要在子组件中添加性能优化相关的代码,保证跟自己相关的数据没有变化时不重新渲染,这需要额外的心智负担,但也没有响应式数据那些额外开销。

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这种双向绑定,只是一种语法糖,不算两个库的关键区别。

组件化

组件化是两个库等编写复杂应用的又一基石特性。这里要提得就是单向数据流,数据都是从父组件流向子组件。数据等流向清晰,便于管理。

组件

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及其所有子组件进行重渲染。

路由

两个库的路由库的核心功能一样,暂时不多说,后续等我充分研究后这部分内容。

状态管理

什么情况下,要使用状态管理呢?

如下图所示,当数据不总是以一种线性的,单向的方式流动、许多不相关的组件以相同的方式更新状态等情况出现,就应该考虑使用状态管理了。

React使用Redux后示意图

如上图所示,使用状态管理后,数据的变化和流向又变得清晰。状态管理可以很好的配合复杂组件树的情况。

心智模型对比

Vuex仍然是基于响应式数据的,通过Action(异步)和Mutation(同步)的改变State。State绑定到视图的computed上,Action和Mutation绑定到视图的methods上,就打通了视图和状态管理。

Redux仍然是基于不可变数据的,是函数式的思维,通过Action来处理副作用,通过Reducer来更新State。通过store.subscribe回调通知外界State变化。

完整度对比

Vuex除了维护状态,也处理异步,同样提供了连通Vue的mapState等函数,相对来说比较完整。而且Vuex将State、Getters、Mutation、Action统一放在一个对象里,更加集中方便管理。

Redux的内容更加纯粹,只负责维护状态,如果需要处理异步,需要加入中间件,如果需要连通React,需要引入react-redux库。

其他感想

还有个Mbox状态管理库,也是基于响应式数据的,可以应用到React上,我个人目前感觉还是Redux更加适合React,因为React就尊崇函数式的思想,这点上Redux非常契合,可以用一样的心智模型来思考。

还有Pinia,相比于Vuex,Mutation不再存在更加简洁,且Pinia更加适合Vue3。从这我们也可以看出无论Vuex还是Pinia,都是为Vue量身打造的状态管理库。而Redux的设计专注于自身设计,再跟React进行组合。

Fiber架构

上面是从渐进式的脉络来对比了两个库,接下来我们看看React和Vue近年来的主要更新,再思考彼此是否有类似的更新,都做的怎么样。

React为什么要采用Fiber架构

首先要说的就是React 16推出的Fiber架构。说的是解决React渲染性能问题,我想说的是React 15的渲染性能很差吗?

我觉得即使没有Fiber架构,React 15的渲染性能也没有很差,在使用React 15进行实际项目的开发时,也是可以满足绝大部分项目的性能要求的,除非项目巨大,并且开发者对性能优化方面做得不够。

所以有种说法说因为Vue性能好,React性能差才要进行Fiber架构是没有道理的。Vue和React性能差不多,只不过是因为React要保持比较好的性能,在性能优化方面要做得工作,要比Vue多花些心思。

所以,react采用Fiber架构,不是因为性能差,而是因为追求卓越,让应用有更加流畅的表现。

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版本,我们大部分还是用同步更新就够了,除非你觉得有必要使用并发更新的时候才需要使用。

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进行映射,至于这过程中发生的其他事情,都是副作用。

跨平台方案对比

React和Vue最知名的两个跨端框架,就是React Native和Weex了。

先说说Weex

Weex发展历程

我们先捋一下Weex发展时间线,从出生到死亡。

实际使用Weex体验

特别要说的一点是,Weex不支持模版语法v-show。在我看来这能反应React API简洁的一个优势,API越简洁,基于它构建的框架需要兼容的API越少。

由于Weex看起来已经逐渐退出历史舞台,更多跨端开发体验,放在下面谈React Native时候说。

再说说React Native

RN(React Native简称)从2015年开源至今,一直处于活跃更新状态。所以要考虑跨平台应用开发,RN是值得被考虑的。

我们先看看两个方案的口号对比。

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方案的时候,也要跟上脚步,关注版本变化,进行框架升级。

Next.js vs Nuxt.js

接下来说说React和Vue的两个重磅框架,Next.js和Nuxt.js。

Next.js

主要特点

服务端组件

额外要说的是Next 13推出的服务端组件,让服务端渲染从页面级细化到组件级别,真是太厉害了。另外函数式组件再次在Next.js中发挥威力,在服务端是不支持类组件的。

Nuxt.js

可以说是Next.js的Vue版本,主要特性是类似的,服务端渲染,基于文件的路由,还有全栈能力,不过Nuxt目前还不支持服务端组件。

对比总结

概括的说,React是最求简洁和函数式的,这两点也确实在跨平台方案和大型框架如Next.js中发挥了威力。还有一个重要的影响点就是,给项目中的React升级是容易的,也得益于React对自身暴露API的谨慎。

Vue追求易用和上手,但在跨平台方案和大型框架落了下风。但如果是开发Web应用,Vue同样是值得选择的好技术栈。

Copyright© 2013-2020

All Rights Reserved 京ICP备2023019179号-8