? 沒有對比就沒有傷害,今天突發(fā)奇想想對Vue和React的使用體驗(yàn)進(jìn)行一下總結(jié)餐蔬,隨便比較下兩者。網(wǎng)絡(luò)上對比兩者比較的各種的文章一大把佑附,眾說紛紜樊诺。接觸和使用Vue和React已經(jīng)很長時(shí)間了,給自己最直接的感覺是:兩者并沒有太明顯的差別音同,畢竟完成的是同一件事情(業(yè)務(wù)頁面開發(fā))词爬,但如果是先用Vue,再用React的权均,會發(fā)現(xiàn)之前感覺Vue那種模板化和數(shù)據(jù)視圖分離的編碼方式很先進(jìn)很牛逼顿膨,而寫熟了React的JSX語法,和組件化的思想叽赊,一下子又覺得React的哲學(xué)思想又先進(jìn)一點(diǎn)恋沃。所以總結(jié)下來,好比飯局上喝紅酒還是白酒必指,不同時(shí)期給人感覺不一樣囊咏。下文,不帶偏見的聊聊兩者塔橡。
發(fā)展歷史
百科一把:
React 起源于 Facebook 的內(nèi)部項(xiàng)目梅割,因?yàn)樵摴緦κ袌錾纤?JavaScript MVC 框架,都不滿意葛家,就決定自己寫一套户辞,用來架設(shè)Instagram 的網(wǎng)站。做出來以后癞谒,發(fā)現(xiàn)這套東西很好用底燎,就在2013年5月開源了。
Vue 是一套用于構(gòu)建用戶界面的漸進(jìn)式JavaScript框架扯俱。與其它大型框架不同的是书蚪,Vue 被設(shè)計(jì)為可以自底向上逐層應(yīng)用。Vue 的核心庫只關(guān)注視圖層迅栅,方便與第三方庫或既有項(xiàng)目整合殊校。
? 從介紹來看,兩個(gè)框架都是MVC模式的延續(xù)读存,或者叫改進(jìn)为流。React更側(cè)著于V呕屎,正如介紹的“React主要用于構(gòu)建UI”,React更關(guān)心的是UI的組件化敬察,而Vue則在MVC的基礎(chǔ)上提出了MVVM的思想秀睛。
國內(nèi)使用情況:
? BAT主流使用React多一點(diǎn),普通互聯(lián)網(wǎng)和傳統(tǒng)軟件公司還是Vue多一點(diǎn)(沒有統(tǒng)計(jì)數(shù)據(jù)支撐莲祸,屬于筆者意淫)蹂安。
原理分析
Vue代碼:
main.js
import Vue from 'vue'
import App from './App'
new Vue({
el: '#app',
components: { App },
render: h => h(App)
})
App.vue
<template>
<div id="app">
<ToList />
</div>
</template>
<script>
import ToList from './components/ToList'
export default {
components: { ToList },
name: 'App'
}
</script>
ToList.vue
<template>
<div>
<div>
<input v-model="inputValue" />
<button @click="handleSubmit">提交</button>
</div>
<ul>
<li v-for="(item,index) of list"
:key="index">
{{item}}
</li>
</ul>
</div>
</template>
<script>
export default {
name: 'ToList',
data () {
return {
inputValue: '', list: []
}
},
methods: {
handleSubmit: function () {
this.list.push(this.inputValue)
this.inputValue = ''
}
}
}
</script>
? 雖然編寫代碼的時(shí)候分了三個(gè)文件,但是通過webpack的vue-loader等插件锐帜,最終build完田盈,其實(shí)只生成了一個(gè)有效的JS文件:app.XXXX.js〗裳郑可以這么理解允瞧,瀏覽器其實(shí)識別不了vue這樣的文件的,瀏覽器能解析的就是js\html\css蛮拔。vue內(nèi)部的script部分我們可以理解述暂,那么我們來分析分析template最終會變成什么樣的格式。取消webpack混淆壓縮插件:UglifyJsPlugin建炫,最終摳出來的轉(zhuǎn)換后腳本如下:
var ToList = {
name: 'ToList',
data: function data () {
return {
inputValue: '',
list: []
}
},
methods: {
handleSubmit: function handleSubmit () {
this.list.push(this.inputValue)
this.inputValue = ''
}
}
}
var ToList_render = function () {
var _vm = this
var _h = _vm.$createElement
var _c = _vm._self._c || _h
//templete最終變成了js版本的vnode結(jié)構(gòu)
return _c('div', [
_c('div', [
_c('input', {
directives: [
{
name: 'model',
rawName: 'v-model',
value: _vm.inputValue,
expression: 'inputValue'
}
],
domProps: { value: _vm.inputValue },
on: {
input: function ($event) {
if ($event.target.composing) {
return
}
_vm.inputValue = $event.target.value
}
}
}),
_vm._v(' '),
_c('button', { on: { click: _vm.handleSubmit } }, [_vm._v('提交')])
]),
_vm._v(' '),
_c(
'ul',
_vm._l(_vm.list, function (item, index) {
return _c('li', { key: index }, [
_vm._v('\n ' + _vm._s(item) + '\n ')
])
}),
0
)
])
}
? 可以發(fā)現(xiàn)畦韭,template其實(shí)最終轉(zhuǎn)換為用JS描述的Dom結(jié)構(gòu)(ToList_render),而這個(gè)結(jié)構(gòu)其實(shí)就是虛擬節(jié)點(diǎn)(虛擬Dom)踱卵。并且v-model這類指令的標(biāo)簽廊驼,Vue自動給我們注冊了input事件,值發(fā)生變化后自動賦值給綁定的數(shù)據(jù)惋砂。頁面加載后妒挎,通過ToList_render函數(shù),虛擬DOM就把JS-DOM渲染到了真實(shí)的DOM西饵。
? 那么既然是MVVM酝掩,數(shù)據(jù)發(fā)生變化,怎么更新到標(biāo)簽控件呢眷柔。上面的代碼當(dāng)然不能解釋期虾,閱讀Vue源碼其實(shí)可以大致發(fā)現(xiàn),Vue采用了一個(gè)JS方法:Object.defineProperty()驯嘱,通過vm的data對象镶苞,代理了真實(shí)data,從而感知data的get和set鞠评,然后通過上面的虛擬dom茂蚓,改變具體的標(biāo)簽值,再通過虛擬DOM庫Diff到真實(shí)Dom。
React代碼
/*
1)拆分組件: 拆分界面,抽取組件
2)實(shí)現(xiàn)靜態(tài)組件: 使用組件實(shí)現(xiàn)靜態(tài)頁面效果
3)實(shí)現(xiàn)動態(tài)組件
① 動態(tài)顯示初始化數(shù)據(jù)
② 交互功能(從綁定事件監(jiān)聽開始)
*/
// 應(yīng)用組件
class App extends React.Component {
constructor (props) {
super(props)
// 初始化狀態(tài)
this.state = {
todos: ['吃飯', '睡覺', '打豆豆']
}
this.add = this.add.bind(this)
}
add (todo) {
const {todos} = this.state
todos.unshift(todo)
//更新狀態(tài)
this.setState({todos})
}
render () {
const {todos} = this.state
return (
<div style={{color:'red'}}>
<TodoAdd add={this.add} count={todos.length} />
<TodoList todos={todos} />
</div>
)
}
}
// 添加todo組件
class TodoAdd extends React.Component {
constructor (props) {
super(props)
this.addTodo = this.addTodo.bind(this)
}
addTodo () {
// 讀取輸入數(shù)據(jù)
const text = this.input.value.trim()
// 查檢
if(!text) {
return
}
// 保存到todos
this.props.add(text)
// 清除輸入
this.input.value = ''
}
render () {
return (
<div>
<h2>Simple TODO List</h2>
<input type="text" ref={input => this.input=input}/>
<button onClick={this.addTodo}>Add #{this.props.count}</button>
</div>
)
}
}
TodoAdd.propTypes = {
add: PropTypes.func.isRequired,
count: PropTypes.number.isRequired
}
// todo列表組件
class TodoList extends React.Component {
render () {
const {todos} = this.props
return (
<ul>
{
todos.map((todo, index) => <li key={index}>{todo}</li>)
}
</ul>
)
}
}
TodoList.propTypes = {
todos: PropTypes.array.isRequired
}
// 渲染應(yīng)用組件標(biāo)簽
ReactDOM.render(<App />, document.getElementById('root'))
? 首先React入門解釋就說明了一個(gè)問題聋涨,render函數(shù)里面返回的是一個(gè)JSX語法的代碼晾浴,而JSX的作用就是用來描述html的DOM結(jié)構(gòu)的,也就是說React在build的完后牍白,其實(shí)JSX已經(jīng)轉(zhuǎn)換為JS結(jié)構(gòu)的DOM描述脊凰。如果沒法直觀理解,可以參考下React官方介紹 jsx:
? 那么頁面加載完成后茂腥,ReactJS做的事情狸涌,似乎和Vue是一樣的,把JS-Dom 掛載到真實(shí)頁面础芍,也就是Diff掛載杈抢。那么問題來了,React既然不是MVVM這類雙向的數(shù)據(jù)綁定仑性,自稱是單向的,它單向又如何實(shí)現(xiàn)呢右蹦。第一次接觸React诊杆,其實(shí)就發(fā)現(xiàn)了一個(gè)特點(diǎn)setState函數(shù)。個(gè)人感覺何陆,這是一個(gè)設(shè)計(jì)局限問題晨汹,React沒法像Vue那樣自然的修改data的值,它為了實(shí)現(xiàn)數(shù)據(jù)變化改變視圖贷盲,委婉的約定了改變數(shù)據(jù)用setState函數(shù)淘这。其實(shí)setState里面就間接的完成了Vue里面的數(shù)據(jù)發(fā)生變化的感知過程(這當(dāng)然是廢話,用戶直接調(diào)用函數(shù)去修改數(shù)據(jù)了巩剖,當(dāng)然直接感知了)铝穷。setState里面重新通過render函數(shù)渲染了虛擬Dom,然后再diff更新到真實(shí)dom。React既然都委婉的采用了setState佳魔,那自然可以對setState下些功夫了曙聂,異步更新(隊(duì)列式更新),合并更新鞠鲜,借此來宣稱一些性能上的優(yōu)化宁脊,當(dāng)然也由此產(chǎn)生了各種不便和問題。
對比點(diǎn)
- 數(shù)據(jù)驅(qū)動
都采用數(shù)據(jù)驅(qū)動視圖的思想贤姆,上來就是data數(shù)據(jù)和state數(shù)據(jù)榆苞,讓開發(fā)人員一目了然的明白,我們干的事情就是數(shù)據(jù)來驅(qū)動視圖的變化霞捡,業(yè)務(wù)開發(fā)的核心是數(shù)據(jù)坐漏。
- 模板&虛擬Dom
雖然虛擬Dom化,Vue從2.0版本才引入了虛擬Dom,但到目前為止兩者這方面的思想是一致的仙畦。通過Template也好输涕,或者JSX也好,做的同一件事情慨畸,把html標(biāo)簽搞成js描述的Dom結(jié)構(gòu)莱坎。其實(shí)回頭想想,也只能用javascript語言來描述這個(gè)dom寸士。為啥檐什?這樣可以寫很多邏輯在里面,而帶邏輯的描述語言弱卡,在瀏覽器端時(shí)候也只有js合適乃正。另外要配合差異化更新(diff),似乎也只有采用內(nèi)存比較才高效。當(dāng)然Diff算法婶博,兩者還是有差異的瓮具,但思想還是一樣的。
- MVVM&單向
我目前為止了解到的凡人,React為啥只實(shí)現(xiàn) 了單向名党,可以理解為簡單化,并非100%的數(shù)據(jù)都需要雙向挠轴,而且單向的過程簡單明了传睹,況且開發(fā)人員可以自行實(shí)現(xiàn)雙向綁定。而Vue的雙向岸晦,就是MVVM的核心體現(xiàn)了欧啤,這是它的優(yōu)勢,當(dāng)然的確也帶來了很多便利启上,尤其是表單開發(fā)過程邢隧。
- 組件化
說完MVVM和單向,組件化過程兩者的確都實(shí)現(xiàn)了碧绞,但React的組件化才把組件的概念體現(xiàn)的淋漓盡致府框。甚至函數(shù)即組件的概念,能夠直白的讓開發(fā)員理解啥是組件讥邻。而Vue的組件明顯是在MVVM的思想影響下形成的迫靖,少許牽強(qiáng)。說起組件化兴使,不得不聊一點(diǎn)系宜。開始學(xué)習(xí)React,發(fā)現(xiàn)到處都是js发魄,連HTML標(biāo)簽也用js編寫盹牧,會覺得變扭俩垃,但真正做項(xiàng)目寫慣了,豁然感覺汰寓,這么寫比Vue更直白口柳,更爽。當(dāng)然也正是因?yàn)榻M件化有滑,React的難度比Vue大跃闹,比如ES6的類組件,什么高階函數(shù)啊毛好,入門過程比較漫長望艺。
- 代碼風(fēng)格
Vue文件把html\js\css按照三塊進(jìn)行組織,這種風(fēng)格把數(shù)據(jù)和視圖分開的很直接肌访,而React就一個(gè)JS找默,html部分只是Render函數(shù)的返回值(JSX),似乎感覺分離吼驶,但似乎有合在一起惩激。所以站在代碼風(fēng)格角度Vue的感覺更合理,但站在組件化的角度React的合并又更貼切蟹演。