為什么我們放棄了 Vue?Vue 和 React 深度對比 - 碼道人 https://markdowner.net/article/79319258450055168
為什么我們放棄了 Vue?Vue 和 React 深度對比
我使用 Vue 和 React 已經很長一段時間了,兩個框架上實踐代碼量都在 10 萬行以上。不得不說兩者都是很 nice 的,幫助開發者減少很多工作量,這類框架是現代化前端開發必備的。然而 Vue 和 React 兩者之間的選擇並不像選擇蘋果或香蕉一樣簡單,兩者在工程實踐上的差距讓我們逐漸放棄了 Vue。本文以不一樣的角度對兩者進行深度對比。
常見搖擺問題、觀點
首先,我簡短談談常見對比項目、觀點的看法,這些部分內容可以通過一些文章或者 Vue 官方對比文檔查到,主要目的是幫助小白解決入門搖擺問題。我會根據長期實踐經驗直接下結論,如果你反對,歡迎評論區留言 battle,反正我不會回答你這類問題。
Vue 或 React 文檔更豐富?
兩者都有豐富的文檔(包括中文文檔),Vue 文檔、React 中文,所以不用擔心你四六級都過不了,看不懂文檔,這都是有眼就行的事~當然,如果你提前懂點 javascript 相關知識也是大大滴好,ES6 語法更佳,可以在這里跟阮老師學習,免費的電子書。文檔和持續進階不是你在兩個框架間做選擇的原因。
Vue 的話需要記住各種指令,還有屬性細節,免不了經常查文檔。React 相對簡單,記住:“函數入口是 props,出口是 html”就行了。
React 學習門檻高?
這個也不是你選擇框架的原因,如果這個也可以作為原因的話,我覺得是因為你懶,給自己找了借口。據我自己學習、實踐總結,兩個框架都很簡單,有手就行,有腦就會,不見得會 React 就比 Vue 牛逼很多。兩者都提供了相應腳手架,方便用戶使用:
Vue
npm install -g @vue/cli vue create my-project
React
npx create-react-app my-app
cd my-app npm start
傻瓜式使用,無限 div 就完事了。
大項目用 React,小項目用 Vue
這是我以前在華為的時候,內部討論兩個框架對比時下的一個結論。 怎么說呢?這個就是萬精油結論,沒有參考意義。你如何定義一個項目是大項目?超過 xx 萬行代碼?后端 API 超過 xxx 個?無論什么項目,都有做“大”的可能,只要正常運營,你就得持續維護,補充新增的需求。框架的可持續演進更為重要。 當然我可以這么跟你說,React 適不適合“小”項目我不知道,但是 Vue 不適合“大”項目,業務代碼超過 5 萬行之后問題明顯,后面會詳細說這點。
XX 大公司也在用 Vue,我們跟隨就行了
很多新手在入門一些框架,或者選型組件、方案時會看看哪些大公司已經用了,避免自己踩坑。當然啦,我也這么做滴。但是大公司可能只是“嘗鮮”、“實驗性”使用,這些項目對他們來說無關緊要,選型失敗了,壓力給到開發工程師,996 重構就行了,而你,╮(╯▽╰)╭ 項目和屎一樣也得維護下去。
舉個選型錯誤的例子,看看大公司怎么拯救的。
以前在華為做硬件項目的時候,用的原理圖軟件,那叫一個難用,用着用着總想砸掉電腦。歷史問題,選型錯誤,但無奈很多項目、基礎庫都在上面,只能硬着頭皮搞,遷移的成本太高,軟件廠商水平太差,但是華為牛逼啊,把那個軟件大改特改,各種內部數據庫都集成在上面,各種自開發的輔助工具,還是可以開發出很牛逼的產品,部門做的單板連續11年全球第一。
如果你覺得你能搞定選型錯誤帶來的問題,或者你在華為,那當我沒說。(PS:以前還用tcl 寫腳本呢,你也可以試試蛋不蛋疼)
Vue 模板簡單,React jsx 有學習成本
同上。兩者都很簡單,一學就會。連這點東東都叫學習成本,我只能說:“我不是針對你,我是說在座的各位都是......” ( Vue 的模板有很多工程實踐問題,后面詳說。)
性能對比
可以看看這個第三方基准測試,兩者都挺快的。不過我們實踐過程中發現有差異,大列表渲染、大量數據加載,不做進一步優化的話 Vue 明顯比 React 慢。有個網站我們用 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,自己封裝一下不是什么難事。可以找找有沒別人封裝好的,拿來主義。
小程序(划重點)
有小程序開發經驗的同學都知道,小程序原生開發是很蛋疼的,通常需要借助框架封裝,代碼轉換。常見的有幾個框架:
這些小程序開發框架都是基於 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. 代碼長、縮進多,組件復雜時難以理清邏輯,不好進行分割<br>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),這點兩個框架的設計不同,如下的問題解決思路也不同。
- 我如何修改數據?
- Vue 直接 this 引用數據對象,直接修改。
- React 使用 setState 方法修改
- 框架如何發現數據被修改?
- Vue 使用 es5 新方法 Object.defineProperty,劫持 setter、getter 實現數據監聽。
- React,你用了 setState,它通過這個函數就知道哪些數據變化了。
- 我如何發現數據被修改?
- Vue:使用 watcher,或者 computed 屬性發現
- React:componentWillUpdate、componentDidUpdate 中可以監聽變化,或者函數組件的依賴部分插入
- 框架何時渲染修改的數據,我如何知道已經渲染好了?
- Vue:在適當的時候渲染,你通過使用 watcher,或者 computed 屬性發現
- React:setState 調用后在適當的時候重新渲染,並調用相關生命周期鈎子
在組件狀態管理功能上兩者都沒有太多槽點,如果要說的話就是 Vue watcher 寫多了代碼一堆縮進,比較難看,React 也沒好多少。
Vue 的數據對象相比 React 的狀態對象在代碼膨脹的時候差距就來了。代碼少的時候 Vue 的寫法更為簡潔,但組件狀態很多,需要明確數據更新邏輯時,React 簡單的 setState({}, callback),就搞定了,Vue 有點讓人摸不到頭腦。
Vue 項目解決 bug 和疑難雜症三大定理
- 沒有什么是 deep watch 解決不了的,有就加 immediate
- 事件相關,dom 相關記得 nextTick
- 實在不行,就用 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 的源碼不多,可以看這里。可以看到代碼中有很多和 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 是 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 對此的抽象更為徹底。 (完)