不吹不黑比對下React與Vue的差異與優劣
react與vue的比較一直是一個比較引戰與容易引起爭議的話題,或許每個前端都或多或少的參與到過這場辯論中,但是在這場巨大的辯論中產出的有價值的內容卻一直比較稀缺。在這裡我無意再次引起爭吵,只是結合我自己的經驗希望儘可能客觀的闡述一些我認為的兩個框架上一些差別與優劣。事實上如果沒有真的在生產環境中較多的使用過兩個框架而只是根據文件與一些demo來做對比幾乎是沒有什麼實際的意義的。我在前一年的工作中幾乎只使用vue,因為當時上一家公司部門的技術棧只是vue,雖然我也學習了react並且根據文件做了一些demo,但當我當時真正去想知道react與vue之間的具體差別是什麼從而去找一些react的文章來讀時會發現,因為沒有實踐與系統的研究過react很難真正的弄清楚這兩者的真正差異。而如今當我大概真正寫了3個月左右的react後,最大的發現其實是很多差異你寫著寫著它自己就會慢慢的浮到水面上,所以建議部門技術棧寬鬆的同學還是多去嘗試,弄清楚問題的最好的方式應該是自己試著解答它。
以下只代表個人的意見,並不保證觀點全部正確
diff、patch與update
react: 在react中如果某個元件的狀態發生改變,react會把此元件以及此元件的所有後代元件重新渲染,不過重新渲染並不代表會全部丟棄上一次的渲染結果,react中間還是會通過diff去比較兩次的虛擬dom最後patch到真實的dom上。雖然如此,如果元件樹過大,diff其實還是會有一部分的開銷,因為diff的過程依然是比較以此元件為根的整顆元件樹。react提供給我們的解決方案是shouldComponentUpdate
,以此函式的返回結果來判斷是否需要執行後面的diff、patch與update。再實際的開發過程中我們常常會用pureComponent
pureComponent
的shouldComponentUpdate
也只是淺比較,假設比較的型別是object,如果object僅屬性發生變化,但是其引用沒發生變化那麼shouldComponentUpdate
會認為兩者之間沒有任何變化。
vue: vue的響應式使用的是Object.defineProperty
api,並且由於在getter中實現了依賴收集,所以不會像react一樣去比較整顆元件樹,而是更加細粒度的去更新狀態有變化的元件,同時defineProperty
也不存在像shouldComponentUpdate
中比較引用的問題。
對比: vue的更新要比react粒度要更細也更加不用去人為的關心,雖然react可以通過shouldComponentUpdate
實現同樣的效果,然而如果state的層級結構比較深那麼相應的手動去優化這部分程式碼也會更加費力,所以在react中我們需要儘量保證整體結構的扁平,去讓pureComponent
幫助我們自動的對此作出優化。
狀態資料的更改方式
react: 由於react與redux推崇的都是函數語言程式設計,所以類似的狀態更改都類似如下程式碼
state = {
obj: {a: 1, b: 2},
arr: [1, 2, 3],
}
// 修改obj.b
modifyObj = () => {
this.setState({
obj: {
...this.state.obj,
b: 3,
}
})
}
// 修改arr
modifyArr = () => {
this.setState({
arr: [...this.state.arr, 4],
})
}
複製程式碼
實際上我們就算直接修改物件或是陣列也是可以的,比如直接修改obj.b = 3
,然後setState({ obj })
,一些情況下這並不會引起什麼錯誤,但是第一由於react與redux本身就是推崇函數語言程式設計的,所以官方是不推薦直接修改物件或陣列。第二還記的我們上面說的pureComponent
嗎,由於shouldComponentUpdate
是淺比較,如果直接修改物件或者陣列的話會導致元件並不會發生更新。
vue: vue在修改狀態資料來說相對是比較簡單的,對於物件我們可以直接this.obj.b = 3
就可以了,此時會觸發初次渲染時設定的setter,然後會呼叫notify方法去通知所有watcher執行update。但是對於陣列的修改來說vue就並沒有這麼簡潔了,雖然我們依然可以通過push,unshift等方法去修改陣列,但是類似arr[index] = someVal
這種方式就行不通了,此時或者使用splice方法,或者通過類似react的方式map一個新的陣列重新賦值。最近vue3也逐漸浮出了水面,再最近這幾次的vueConf中宣佈了vue3採用的是proxy方案,所以在vue3中陣列的改變就可以使用arr[index]
的方式了。
對比: 在data為物件的情況下,vue的程式碼方式要更加簡單方便,而陣列的情況下對於vue與react其實並沒有什麼太大的差別,相當多的時候我們依然要使用map,filter等函式。但是注意vue的簡單方便並不代表本身更好,react與redux之所以推崇永遠用新的值代替舊值為的是更加容易debug與一些時間旅行等功能,但對於vue來說這並不衝突,如果你喜歡你同樣可以用這種方式去書寫你的vue程式碼。另外一提,雖然看起來Object.defineProperty
這個api幫助了vue很多,但也不是全無副作用的,當狀態資料龐大時,由於此api需要遞迴的去不斷的對狀態資料執行監聽,所以vue的初始消耗有可能更多,對應的也就是白屏時間相對react來說更長,此問題也會在vue3中解決,因為基於proxy的監聽是類似lazy這種形式的。
關於邏輯複用
react: 關於邏輯複用是一塊很有意思的內容,真的要感慨一下react的社群與社群貢獻的高質量方案,從mixin到高階元件再到renderProps,我們這裡主要拿renderProps來舉例,高階元件我們留到後面說。所謂的邏輯復就是把通用的一些邏輯複用到需要這些邏輯的元件上,我們來看下react官方文件為我們提供的案例。
這是一個實時顯示滑鼠位置的元件,對於這個元件來說主要邏輯在於滑鼠事件的處理,但是這一部分又恰恰是比較難複用的一個地方,假設我們希望在另一個元件中渲染一張貓的圖片,同時它的位置由滑鼠位置決定,那麼這部分滑鼠事件與狀態的程式碼我們基本上又要再寫另一遍,來看下renderProps是怎麼解決問題的。
第一次看這樣的程式碼真的是要稍微感嘆一下這他媽怎麼想到的,抽象出Mouse元件負責行為,然後將state作為引數傳入外部返回元件的函式中,真的是感覺相當牛逼。順帶一提,並不一定要用render這種props,比如這個例子裡我們完全可以寫成如程式碼的形式
<Mouse>
{
mouse => <Cat mouse={mouse} />
}
</Mouse>
// Mouse元件
<div onMouseMove={this.handleMouse}>{this.props.children(this.state)}</div>
複製程式碼
相對應的我們可以做出很多這樣的封裝,比如模態框的開關,比如Input,Select等控制元件關於onChange與setState的封裝,簡單舉一個例子
<Container>
{
(value, changeValue) => <Input value={value} onChange={changeValue} />
}
</Container>
複製程式碼
想象一個常見的場景,4個Input外加一個Button搜尋按鈕,然後將搜尋出的資料展示到表格上,使用這種封裝可以省下相當多的樣板程式碼。
vue: 我們直接看看vue怎麼實現上面的滑鼠複用邏輯把。
// 指令
Vue.directive('mouse', {
binding: function (el, binding) {
function mouseMove(e) {
binding.value.x = e.clientX;
binding.value.y = e.clientY;
}
el.__mouseMove = mouseMove;
el.addEventListener('mousemove', mouseMove);
},
unbinding: function (el) {
el.removeEventListener('mousemove', el.__mouseMove);
}
})
// 元件中
<template>
<div v-model="data">
{{ data.x }}, {{ data.y }}
</div>
</template>
複製程式碼
直接註冊一個v-mouse指令,我們就可以在任意元件去複用這段邏輯了,其實單論這兩段程式碼我個人認為vue的實現是更好的,v-mouse這種宣告式的指令非常的清晰與優雅,但是但是但是我們必須要清楚這只是很簡單的一小段邏輯,如果邏輯更加複雜,毫無疑問的是react靈活性與維護性上都要比指令的方式好上非常多。
對比: react的renderProps雖然稍為繁瑣一點但是在靈活性與可維護性上來說是要絕對優於vue的指令的。但是在一些簡單的場景下vue的指令也確實會相當的方便,比如最常用的內建v-model,依然想一下我們之前描述的場景,多個Input加一個按鈕,vue對應的只是4個v-model="value"
。
資料的流向
其實這一部分並不算兩者有差異的部分,但是很多人一看到v-model就下意識的反應雙向資料繫結。很多比較vue與react的回答直到今天還會說由於vue是雙向資料繫結,導致資料容易變得混亂在大型專案中不易維護,這實在是有些搞笑的。其實再元件間vue與react的資料流向都是單向的由父向子,並且子元件不允許改變props,兩者並沒有什麼區別,而類似input中的v-model不過是邏輯複用的一種方式或者說是一種宣告式的語法糖,同樣的為元件使用v-model依然是一種語法糖,但是資料的流向仍然是清晰的單向流動。
高階元件
react: 高階元件是另一種複用邏輯的形式,對比renderProps,高階元件其實更多的是選擇傳入什麼,而renderProps則是暴漏什麼。所謂的高階元件其實跟高階函式是一個意思,只不過函式接收的是一個元件然後再返回一個元件, 舉一個常用的例子,假設我們有一個介面是獲取公司的組織樹,這個組織樹在很多場景下都要使用,我們就可以抽出一個公用的高階元件
function HOC(wrapperComponent) {
return class getOrgTree extends React.Component {
state = { orgTree: [] }
componentDidMount() {
fetch('xxxxxxx').then(orgTree => this.setState({ orgTree }))
}
render() {
return <wrapperComponent orgTree={this.state.orgTree} {...this.props} />
}
}
}
複製程式碼
通過這個函式我們可以把任意我們自己的元件傳入函式使其擁有orgTree的props,除此之外我們還可以通過高階元件去減少或增加各種props等拓展性的操作
vue: vue的類似操作幾乎只能通過mixin來實現(容易汙染,也不容易debug),更是很難去拓展使用mixin的元件,這兩者的區別像是react的元件像是穿上了一層裝甲變了身,vue的元件只是把東西拿過來放在口袋裡本質上還是自己。
const myMixin = {
created: function () {
fetch('xxxxx').then(orgTree => this.orgTree = orgTree)
},
}
複製程式碼
對比: 在高階函式這個階段react通過面向物件的方式依然達到了一個高擴充套件性與靈活性,vue在高階函式中幾乎沒什麼發揮餘地,因為vue實際上是面向配置(options)的,雖然我們可以利用render函式楞寫一個貌似高階元件的東西,但是第一render書寫的程式碼可讀性還是很差的(雖然貌似也可以使用babel寫jsx,但那樣為什麼不直接用react),第二這也幾乎背離了vue的開發模式,所以其實從一些模式上的應用與邏輯上的複用角度來講react是領先的。不過好訊息是vue3的元件也是class了,這或許能幫助vue在這個短板上提升。
TypeScript支援
對比: vue2雖然支援,但是使用上並不良好,落後react很多個等級。依然是vue3解決的問題。
社群與周邊生態
對比: 事實上我認為真正react與vue之間的差距很大一部分是在社群與一些生態上,react的社群貢獻真的是相當的活躍,各種各樣的方案,各種成熟的元件(不只是UI庫),相對而言vue則顯得平靜很多,有很多元件的質量也還不夠高,這個就並不是vue3能解決的問題了,唯一能依靠的只有時間。
總結
結論與很多對比一樣,vue適合小而精的專案,react則更適用於偏大的專案,但是立住這個結論的支點是不一樣的,vue的元件由於一些複雜邏輯的複用方式和元件可應用模式的不足,以至於在大型專案中複用性與設計性是不如react的,但是在小而精的專案裡,vue更友好的書寫方式,更簡化的程式碼與更宣告式的指令都是很棒的優勢與特點,而相反的react在中大型專案中能更好的完成vue的不足項,但是也更需要團隊技術整體比較給力,領頭大佬的設計與把關能力要更優秀,不然糟糕的設計或許不如沒有設計。有一些比較總會認為vue由於api較多書寫更靈活導致最後的程式碼難以維護,事實上react或許才是更靈活的一個,因為react幾乎沒有api導致無論怎樣的寫程式碼都是可行的,團隊中有10個人可能就有10種方式去理解react,並且無論是狀態管理還是非同步中介軟體再加上路由等等,react的社群都提供了更多的選項,怎麼選型,採用什麼方案也是稍令人頭疼的事情之一。
最後
都讀到這了,覺得還行給個star總不過分~~~