React 和 Vue 全方位對比總結

作者:檸檬豆腐腦 

https://juejin.cn/post/7250834664260829243

前言

本文將從漸進式、時間線、跨平臺及企業級框架情況多個維度對兩個庫進行對比。

從概念開始

一個說自己是庫,一個說自己是框架,我們就先從這個細節說起。如下圖 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 同樣是值得選擇的好技術棧。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/Z_FHSi1uGHj9as9bkhDgaQ