爲何咱們放棄了 Vue?Vue 和 React 深度對比

原文:爲何咱們放棄了 Vue?Vue 和 React 深度對比
經做者受權轉載javascript

我使用 Vue 和 React 已經很長一段時間了,兩個框架上實踐代碼量都在 10 萬行以上。不得不說二者都是很 nice 的,幫助開發者減小不少工做量,這類框架是現代化前端開發必備的。然而 Vue 和 React 二者之間的選擇並不像選擇蘋果或香蕉同樣簡單,二者在工程實踐上的差距讓咱們逐漸放棄了 Vue。本文以不同的角度對二者進行深度對比。

react vs vue

常見搖擺問題、觀點

首先,我簡短談談常見對比項目、觀點的見解,這些部份內容能夠經過一些文章或者 Vue 官方對比文檔查到,主要目的是幫助小白解決入門搖擺問題。我會根據長期實踐經驗直接下結論,若是你反對,歡迎評論區留言 battle,反正我不會回答你這類問題。html

Vue 或 React 文檔更豐富?

二者都有豐富的文檔(包括中文文檔),Vue 文檔React 中文,因此不用擔憂你四六級都過不了,看不懂文檔,這都是有眼就行的事~固然,若是你提早懂點 javascript 相關知識也是大大滴好,ES6 語法更佳,能夠在這裏跟阮老師學習,免費的電子書。文檔和持續進階不是你在兩個框架間作選擇的緣由。前端

Vue 的話須要記住各類指令,還有屬性細節,免不了常常查文檔。React 相對簡單,記住:「函數入口是 props,出口是 html」就好了。vue

jquery 搞笑

React 學習門檻高?

這個也不是你選擇框架的緣由,若是這個也能夠做爲緣由的話,我以爲是由於你懶,給本身找了藉口。據我本身學習、實踐總結,兩個框架都很簡單,有手就行,有腦就會,不見得會 React 就比 Vue 牛逼不少。二者都提供了相應腳手架,方便用戶使用:java

Vuereact

npm install -g @vue/cli
vue create my-project

Reactjquery

npx create-react-app my-app
cd my-app
npm start

傻瓜式使用,無限 div 就完事了。git

大項目用 React,小項目用 Vue

這是我之前在華爲的時候,內部討論兩個框架對比時下的一個結論。
怎麼說呢?這個就是萬精油結論,沒有參考意義。你如何定義一個項目是大項目?超過 xx 萬行代碼?後端 API 超過 xxx 個?不管什麼項目,都有作「大」的可能,只要正常運營,你就得持續維護,補充新增的需求。框架的可持續演進更爲重要。
固然我能夠這麼跟你說,React 適不適合「小」項目我不知道,可是 Vue 不適合「大」項目,業務代碼超過 5 萬行以後問題明顯,後面會詳細說這點。es6

XX 大公司也在用 Vue,咱們跟隨就好了

不少新手在入門一些框架,或者選型組件、方案時會看看哪些大公司已經用了,避免本身踩坑。固然啦,我也這麼作滴。可是大公司可能只是「嚐鮮」、「實驗性」使用,這些項目對他們來講可有可無,選型失敗了,壓力給到開發工程師,996 重構就好了,而你,╮(╯▽╰)╭ 項目和屎同樣也得維護下去。github

舉個選型錯誤的例子,看看大公司怎麼拯救的。

之前在華爲作硬件項目的時候,用的原理圖軟件,那叫一個辣雞,用着用着總想砸掉電腦。歷史問題,選型錯誤,但無奈不少項目、基礎庫都在上面,只能硬着頭皮搞,遷移的成本過高,軟件廠商水平太差,可是華爲牛逼啊,把那個軟件大改特改,各類內部數據庫都集成在上面,各類自開發的輔助工具,仍是能夠開發出很牛逼的產品,部門作的單板連續11年全球第一。

若是你以爲你能搞定選型錯誤帶來的問題,或者你在華爲,那當我沒說。(PS:之前還用 tcl 寫腳本呢,你也能夠試試蛋不蛋疼)

Vue 模板簡單,React jsx 有學習成本

同上。二者都很簡單,一學就會。連這點東東都叫學習成本,我只能說:「我不是針對你,我是說在座的各位都是......」
( Vue 的模板有不少工程實踐問題,後面詳說。)

性能對比

能夠看看這個第三方基準測試,二者都挺快的。不過咱們實踐過程當中發現有差別,大列表渲染、大量數據加載,不作進一步優化的話 Vue 明顯比 React 慢。TaskHub 這個網站咱們之前就是用 Vue 寫的,後來直接遷移到 React 前端性能大大提高,用戶體驗有明顯差距(數據結構、後臺不變)。

深度對比

原本想簡單寫寫,沒想到前面寫了那麼多了,╮(╯▽╰)╭,下面是重頭戲,寫寫實踐過程當中發現的問題,兩個框架的解決思路。若是你仍是小白,下面的一些東西可能沒接觸過,能夠看下這篇文章:【譯】經過建立相同的 APP,對比 React 和 Vue,親自實現一下,瞭解基礎知識。

市場佔比

相關 npm 下載量見上圖,市場已經用腳投票了。看到這裏,若是你只想知道選型結論,你能夠走了。若是你還說 xx 大公司在用 Vue,跟着就行。能夠這麼說吧,大公司更多用的是 React,用 Vue 更多的目的是保留相關技術棧能力,多一個選擇,避免 React license 事件再次發生。

固然,尤大也在這裏說過,看npm下載量沒用,實際使用應該參考 devTool 的下載量。可是...爲啥我打開的不少網站下面這個標都是亮的?

開發生態

客觀來講,做爲核心團隊成員,顯然咱們會更偏心 Vue,認爲對於某些問題來說用 Vue 解決會更好。若是沒有這點信念,咱們也就不會成天爲此忙活了。可是在此,咱們想盡量地公平和準確地來描述一切。其餘的框架也有顯著的優勢,例如 React 龐大的生態系統

by Vue 官方

生態上的差距是明顯的,這點 Vue 官方也認可的,不少人由於生態這點遷移到 React,不過我本人不是很在乎,Vue 生態也不差,若是說你用了 React 生態的東西就以爲很牛逼,你的競爭對手也會用,這點並不能給你產品帶來多大增值,競爭力仍是要靠本身手碼出來的好。下面簡單帶過:

UI 組件

二者的周邊 UI 庫都挺豐富的,React 稍微多一點,不過這不是選型的關鍵,本身手寫 UI 庫也不是什麼難事,偶爾封裝一下原生標籤也是很簡單的。之前用 Vue 的時候尚未太多 UI 庫,手動寫了一個功能比較全的 UI 庫,用 rollup 打包,也就 2 萬行代碼左右,有手就行。

dom 相關的第三方庫

Vue 和 React 都有 ref 能夠操做 dom,本身封裝一下不是什麼難事。能夠找找有沒別人封裝好的,拿來主義。

小程序(劃重點)

有小程序開發經驗的同窗都知道,小程序原生開發是很蛋疼的,一般須要藉助框架封裝,代碼轉換。常見的有幾個框架:

  • Taro (React 技術棧,推薦使用)
  • wepy(Vue 技術棧,強烈不推薦使用)
  • uni-app(Vue 技術棧,可使用)

這些小程序開發框架都是基於 Vue 或者 React 的二次封裝,簡化小程序開發。

vue 的一些周邊庫和 Vue 強綁定,而不是以一個獨立 js 庫的形式存在。致使代碼難以複用,相關 Bug、問題也帶到了二次開發的框架中。

這種強依賴致使的問題會給之後項目升級、遷移帶來不少問題。
好比 vuex 做爲 Vue 官方推薦的狀態管理方案,只能在 Vue 上面使用,不能在 React 上面使用。Redux 狀態管理在 React 上用的多,這個卻能用在 Vue 上面。
相似的問題不少,你會發現 React 周邊的東西能夠用於 Vue,Vue 的東西不能用在 React 上。

若是你以爲這個問題不嚴重,當你把 Vue 代碼遷移到小程序 wepy 框架時發現,wepy 不支持 Vuex (bug 異常多),狀態管理只能用 redux,欲哭無淚。
一樣的問題,若是你用的是 React 相關技術棧,React 遷移到 Taro 小程序框架異常簡單,並且還能一次性生成微信小程序、支付寶小程序、字節跳動小程序等,代碼複用率高。

APP 生態

weex、rn 這塊我沒有比較好的實踐經驗,二者用於生產方案都要慎重考慮。rn 比 weex 成熟這點是明確的。

邏輯代碼組織

茴字的寫法

Vue 三種組件寫法對比(Js 部分)

Object API 29 lines

import Vue, { PropOptions } from 'vue'

interface User {
  firstName: string
  lastName: number
}

export default Vue.extend({
  name: 'YourComponent',

  props: {
    user: {
      type: Object,
      required: true
    } as PropOptions<User>
  },

  data () {
    return {
      message: 'This is a message'
    }
  },

  computed: {
    fullName (): string {
      return `${this.user.firstName} ${this.user.lastName}`
    }
  }
})

Class API 17 lines

import { Vue, Component, Prop } from 'vue-property-decorator'

interface User {
  firstName: string
  lastName: number
}

@Component
export default class YourComponent extends Vue {
  @Prop({ type: Object, required: true }) readonly user!: User

  message: string = 'This is a message'

  get fullName (): string {
    return `${this.user.firstName} ${this.user.lastName}`
  }
}

Function API 25 lines

import Vue from 'vue'
import { computed, value } from 'vue-function-api'

interface User {
  firstName: string
  lastName: number
}

interface YourProps {
  user?: User
}

export default Vue.extend({
  name: 'YourComponent',

  setup ({ user }: YourProps) {
    const fullName = computed(() => `${user.firstName} ${user.lastName}`)
    const message = value('This is a message')

    return {
      fullName,
      message
    }
  }
})
寫法 優勢 缺點
Object API Vue 官方寫法,方便Vue直接處理組件 1. 代碼長、縮進多,組件複雜時難以理清邏輯,很差進行分割
2. 混入較多Vue的概念,新手學習成本高
Class API 相關概念能夠用class的思路理解,能夠更好地描述Vue的混入、data、computed,生命週期鉤子等概念。Vue 3.0 將原生支持class寫法 用到了修飾器語法特性,目前還在實驗階段(typescript可使用helper函數解決兼容問題,問題不大)
Function API 無狀態(部分場景),更好的單元測試、並行化 函數式寫法很容易寫出回調地獄,致使代碼可讀性、可維護性差,目前純粹function api 寫法較少見

React 兩種組件寫法對比(Js 部分)

class 組件 34 lines

import React, { Component } from 'react';

interface P {}

interface S {}

class Index extends Component<P, S> {

  constructor(props: Readonly<P>) {
    super(props);
    this.state = {};
  }

  static defaultProps = {};

  componentDidMount() {}

  componentDidUpdate(prevProps: Readonly<P>) {}

  componentWillUnmount() {}

  render() {
    return (
      <div>
      </div>
    );
  }
}

export default Index;

函數組件 15 lines

import React, { FC } from "react";

interface Props {}

const Index: FC<Props> = (props) => {
  // js 代碼

  return (
    <div></div>
  );
};

Index.defaultProps = {};

export default Index;

在 js 邏輯部分二者寫法沒毛病,都須要用到框架特定的生命週期鉤子,Vue 的 class 寫法最爲簡潔(3種對比),React 的 function 寫法最爲清晰(所有寫法對比)。這部分不是選擇關鍵,怎麼寫是我的喜愛。

組件內狀態管理

Vue 使用的是數據對象(data),React 使用的是狀態對象(不可變 state),這點兩個框架的設計不一樣,以下的問題解決思路也不一樣。

  1. 我如何修改數據?

    • Vue 直接 this 引用數據對象,直接修改。
    • React 使用 setState 方法修改
  2. 框架如何發現數據被修改?

    • Vue 使用 es5 新方法 Object.defineProperty,劫持 setter、getter 實現數據監聽。
    • React,你用了 setState,它經過這個函數就知道哪些數據變化了。
  3. 我如何發現數據被修改?

    • Vue:使用 watcher,或者 computed 屬性發現
    • React:componentWillUpdate、componentDidUpdate 中能夠監聽變化,或者函數組件的依賴部分插入
  4. 框架什麼時候渲染修改的數據,我如何知道已經渲染好了?

    • Vue:在適當的時候渲染,你經過使用 watcher,或者 computed 屬性發現
    • React:setState 調用後在適當的時候從新渲染,並調用相關生命週期鉤子

在組件狀態管理功能上二者都沒有太多槽點,若是要說的話就是 Vue watcher 寫多了代碼一堆縮進,比較難看,React 也沒好多少。

Vue 的數據對象相比 React 的狀態對象在代碼膨脹的時候差距就來了。代碼少的時候 Vue 的寫法更爲簡潔,但組件狀態不少,須要明確數據更新邏輯時,React 簡單的 setState({}, callback),就搞定了,Vue 有點讓人摸不到頭腦。

Vue 項目解決 bug 和疑難雜症三大定理

  1. 沒有什麼是 deep watch 解決不了的,有就加 immediate
  2. 事件相關,dom 相關記得 nextTick
  3. 實在不行,就用 setTimeout

(來自某個師兄)

React 的不可變(immutable)狀態在應用複雜時表現出的透明、可測試性更佳。

以上內容對比下來,感受二者都OKOK的,功能也健全,Vue 生態差一點,可是能夠本身動手豐衣足食。下面幾點是咱們真正棄用 Vue 的緣由。

沃蘇艾德布耀布耀德說過:一樣的問題,在語言層面上的解決方案纔是最佳解決方案。語言生命週期長於框架生命週期

模板語法 VS JSX

上下文丟失

Vue 的單文件組件,使用<template><script> 對代碼進行分割,直接致使的問題就是上下文丟失。
舉個例子,你封裝了一些經常使用的函數,在 Vue 文件中 import 進來。你這個函數能在 template 中直接使用嗎?

// filter.js 文件
export function isNickname(value) {
  return /^[\s\S]{1,50}$/.test(value);
}
<template>
  <div>
    {{ a }}
    <button @click="a = isNickname('abc')">Test</button>
    {{ b }}
  </div>
</template>

<script>
// eslint-disable-next-line no-unused-vars
import {isNickname} from '../fn/filter';


export default {
  name: 'HelloWorld',
  props: { msg: String },
  data: () => {
    return {
      a: false,
      b: 1,
      c: 1,
    }
  },
  methods: {
    isNickname1() {
      return isNickname('abc');
    }
  }
}
</script>

<style scoped></style>

上述代碼會報錯:

[Vue warn]: Property or method "isNickname" is not defined on the instance but referenced during render

因此你只能將方法定義在methods中,再引用進來。模板語法並不知道你有 isNickname 這個函數,簡單的操做多了 3 行代碼。

模板語法不是圖靈完備的,必須轉換爲js代碼(render 函數),放在component語境下才行。
相似的例子還有不少,你會發現,你寫的代碼與 Vue 強綁定了,哪天Vue核心庫崩了,你代碼也崩了。Vue 核心庫升級了,周邊依賴庫也得跟着升級。

模板分割

好的代碼組織能將常變與不變的部分進行分割解耦

Vue 的模板嚴重限制了這一點。
舉個例子,前端有個下拉菜單,功能不斷增長,並且對於不一樣的人要顯示不一樣菜單(權限管理)。在 Vue 中,爲了實現 html 代碼(綁定在 template 中)的分割,你只能再搞一個組件。在 React 中,能夠直接這樣寫:

const menu = <div>abc<div>;

可單獨作一個組件(低開銷函數組件),也可當作變量,放在當前代碼中。相對靈活不少。

JSX 手寫 render 渲染函數自帶下面的優點

  • 完整的 js 功能來構建視圖頁面,可使用臨時變量、js 自帶的控制流、以及直接引用當前 js 做用域中的值
  • 開發工具對 jsx 的支持比現有 vue 模板先進(linting、typescript、編譯器自動補全)

JSX 能夠用於 Vue 能夠用於 React,就像 Redux 同樣。這種語言是與框架解耦的。

「雖然模板語法有那麼多問題,可是 Vue 也支持 JSX 呀。」

我猜到你會這麼說,但就像上面所說的,既然我必定要用JSX/TSX、Redux了,那我爲何不用 React?

"基於 HTML 的模板使得將已有的應用逐步遷移到 Vue 更爲容易"

不會更容易,只會更麻煩。
首先,下面會說到的 template 中沒法很好 linting、type 推斷,代碼遷移過去不少 bug 沒法及時發現。其次代碼遷移很大部分都是 js 邏輯的遷移(這個更重要),遷移到 vue 中,你須要填鴨式拆分原先代碼,放到 computed、menthods 中,工做量不小且代碼和 Vue 強綁定。最後,原代碼 class、@click 這些東西,有現代化的編輯器,批量 replace 成 className、onClick 不是很簡單的事情嗎?

Typescript、linting 支持

這點更是致命, Typescript 已成爲咱們前端開發必需。類型檢測、推斷對於代碼重構很是重要,哪天后端字段改了,前端能夠很方便設配改動,明確知道代碼改動點在哪,構建前就能發現大量錯誤。

而 Vue 的模板不支持 typescript(官方還在加強),在模板上支持要不少「hack」操做,原始框架更爲複雜。
Vue.extend 對象中編寫代碼很難有比較好的 ts 推斷,爲了更好的支持 Typescript,咱們之前都是使用 Vue 的 Class 寫法(參考上文)。前端配合後臺改動接口,然而不少未提早檢查出的錯誤都出如今模板代碼中。

可測試性、重構

Vue 須要新建一個.vue 文件

<template>
  <div>
    {{hello}}
  </div>
</template>

<script>
export default {
  name: 'Test',
  props: {
    hello: String
  },
  created() {
    console.log(this.hello);
  }
}
</script>

<style scoped></style>

React 操做都在 jsx 環境下執行,放的位置隨意,寫法比模板更容易測試,迭代:

function Test(props: { hello: string }) {
  console.log(props);
  return <div>{props.hello}</div>
}

Vue 與 React 測試成本的差距明顯。React 手起刀落,一個函數就搞定了,要測試什麼內容清晰可見。若是要重構 hello 字段,Vue 要記得 template 中的代碼也要手動改,React 直接 typescript 類型、屬性重構就好了,編輯器自動化。

複雜狀態、Action 管理

全局狀態管理方案選型是很重要的,畢竟 95% 以上的 API 對接代碼都在這裏,這部分代碼佔全局代碼很大一部分比例,可否複用、重構、測試成爲選擇的關鍵。

Vue 推薦的方案只有強耦合的 Vuex(Redux 遷移到 Vue 等不算在內)
React 周邊方案有 Redux、Mobx 等。這些庫不會與 React 有太強的耦合(能夠獨立存在)。
兩個框架的狀態管理思想差很少,都是單向數據流、單例模式(Vuex & Redux)。

Vuex

Vuex

Vuex 的源碼很少,能夠看這裏。能夠看到代碼中有不少和 Vue 強綁定的東東,脫離了 Vue,這東西就無法用了。你可能會說我就用 Vue,什麼 React 不去用不就完了?考慮如下場景:

  • 項目經理要把 Vue 的代碼遷移支持小程序,忽然!有的框架不支持Vuex,腦殼嗡嗡叫
  • 項目經理說要設配 APP 端,忽然!一堆Bug!腦殼嗡嗡叫
  • 項目經理腦抽,要把 React 項目遷移到 Vue,忽然!redux!用的仍是 saga!腦殼嗡嗡叫
  • 狀態管理出現竟態問題!臥槽要寫一堆爛碼去解決。新人看了腦殼嗡嗡叫

怎麼辦?!!!這部分的代碼比 Vuex 源碼都多?
這些問題都是狀態管理庫和框架強綁定致使的,框架上的問題也會影響到周邊庫。

if (version >= 2) {
    Vue.mixin({ beforeCreate: vuexInit })
  } else {
    // override init and inject vuex init procedure
    // for 1.x backwards compatibility.
    const _init = Vue.prototype._init
    Vue.prototype._init = function (options = {}) {
      options.init = options.init
        ? [vuexInit].concat(options.init)
        : vuexInit
      _init.call(this, options)
    }
  }

能夠看到,Vue 核心升級,這些伴隨的庫也得升級、測試。在非瀏覽器環境下運行時,因爲 Vue (或類Vue 框架)的初始化等機制須要改動,會致使相關庫,如 Vuex 不可用,多了一個代碼分支,相關代碼沒法複用、測試、重構負擔重。

Redux

redux

Redux 是 React 上比較經常使用的狀態管理方案,其設計思想很是簡單(見上圖),能夠獨立使用,相關代碼容易遷移到不一樣平臺。衍生出周邊異步方案也不少:

選型能夠參考這裏 & 這裏,咱們用 saga 比較多,處理竟態問題等比較簡單,起步多看看文檔就能夠,也不難。下面這張圖能夠幫助你理解幾個方案之間的關係,利弊權衡。


相關插件也很豐富,參考:Redux Middleware。你會發現不少你想要的東西 Vuex 都木有!

「既然這樣,我在 Vue 上用 Redux 就好了」

也行,畢竟這樣之後遷移到 React 會簡單點。

萬惡之源 this 指針

寫過 React 函數組件的同窗都知道,相比 class 組件,函數組件少了 this 指針,代碼簡化、清晰很多。而這個問題在 Vue 上更爲嚴重。全局 this !

有人以爲這是優勢,方便使用。等你代碼量上去了再來講話。

當項目多人協做的時候,或者承接某某祖傳代碼,你不全局搜索,你都不知道 this 上面掛了羊頭仍是狗肉。

  • this.ajax
  • this.http
  • this.message
  • this.wtf......

正如一位網友評論:

那東西就是全局做用域。拿「容許在全局做用域上隨便放東西很方便」做爲優勢的話,和「容許隨地大小便會很方便」有什麼區別……

寫 C 語言的新手都知道全局變量不要隨意用,這滿天飛的 this,張三讀不懂,李四看不懂,IDE 也不懂。並且這是官方推薦寫法,╮(╯▽╰)╭(說全局不太準確,應該說是組件做用域)

想起我之前寫的 Vue UI 庫,叫 SUE-UI,sue~很快的樣子。爲了不之後和其餘插件衝突,插件使用都是:

  • this.$su_message
  • this.$su_modal
  • this.$su_toast

往事不堪回首啊!

最後

框架功能上,暫時沒有發現 Vue 作的來 React 作不來的事情,反過來也同樣,兩個框架都能知足功能需求。
工程實踐上,因爲耦合性、代碼組織靈活性、平滑升級、測試、重構讓咱們最終放棄了 Vue。

在 Vue 中你操做的是定義好的對象,React 中你操做的是一個函數。所謂前端開發,本質就是在編寫下面幾個函數。

S = async(A1)
S = sync(A2)
UI = f(S)

顯然,React 對此的抽象更爲完全。(完)

相關文章
相關標籤/搜索