没有对比就没有伤害,今天突发奇想想对Vue和React的使用体验进行一下总结,随便比较下两者。网络上对比两者比较的各种的文章一大把,众说纷纭。接触和使用Vue和React已经很长时间了,给自己最直接的感觉是:两者并没有太明显的差别,毕竟完成的是同一件事情(业务页面开发),但如果是先用Vue,再用React的,会发现之前感觉Vue那种模板化和数据视图分离的编码方式很先进很牛逼,而写熟了React的JSX语法,和组件化的思想,一下子又觉得React的哲学思想又先进一点。所以总结下来,好比饭局上喝红酒还是白酒,不同时期给人感觉不一样。下文,不带偏见的聊聊两者。
发展历史
百科一把:
React 起源于 Facebook 的内部项目,因为该公司对市场上所有 JavaScript MVC 框架,都不满意,就决定自己写一套,用来架设Instagram 的网站。做出来以后,发现这套东西很好用,就在2013年5月开源了。
Vue 是一套用于构建用户界面的渐进式JavaScript框架。与其它大型框架不同的是,Vue 被设计为可以自底向上逐层应用。Vue 的核心库只关注视图层,方便与第三方库或既有项目整合。
从介绍来看,两个框架都是MVC模式的延续,或者叫改进。React更侧着于V,正如介绍的“React主要用于构建UI”,React更关心的是UI的组件化,而Vue则在MVC的基础上提出了MVVM的思想。
国内使用情况:
BAT主流使用React多一点,普通互联网和传统软件公司还是Vue多一点(没有统计数据支撑,属于笔者意淫)。
原理分析
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>
虽然编写代码的时候分了三个文件,但是通过webpack的vue-loader等插件,最终build完,其实只生成了一个有效的JS文件:app.XXXX.js。可以这么理解,浏览器其实识别不了vue这样的文件的,浏览器能解析的就是js\html\css。vue内部的script部分我们可以理解,那么我们来分析分析template最终会变成什么样的格式。取消webpack混淆压缩插件:UglifyJsPlugin,最终抠出来的转换后脚本如下:
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结构
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
)
])
}
可以发现,template其实最终转换为用JS描述的Dom结构(ToList_render),而这个结构其实就是虚拟节点(虚拟Dom)。并且v-model这类指令的标签,Vue自动给我们注册了input事件,值发生变化后自动赋值给绑定的数据。页面加载后,通过ToList_render函数,虚拟DOM就把JS-DOM渲染到了真实的DOM。
那么既然是MVVM,数据发生变化,怎么更新到标签控件呢。上面的代码当然不能解释,阅读Vue源码其实可以大致发现,Vue采用了一个JS方法:Object.defineProperty(),通过vm的data对象,代理了真实data,从而感知data的get和set,然后通过上面的虚拟dom,改变具体的标签值,再通过虚拟DOM库Diff到真实Dom。
React代码
/*
1)拆分组件: 拆分界面,抽取组件
2)实现静态组件: 使用组件实现静态页面效果
3)实现动态组件
① 动态显示初始化数据
② 交互功能(从绑定事件监听开始)
*/
// 应用组件
class App extends React.Component {
constructor (props) {
super(props)
// 初始化状态
this.state = {
todos: ['吃饭', '睡觉', '打豆豆']
}
this.add = this.add.bind(this)
}
add (todo) {
const {todos} = this.state
todos.unshift(todo)
//更新状态
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 () {
// 读取输入数据
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
}
// 渲染应用组件标签
ReactDOM.render(<App />, document.getElementById('root'))
首先React入门解释就说明了一个问题,render函数里面返回的是一个JSX语法的代码,而JSX的作用就是用来描述html的DOM结构的,也就是说React在build的完后,其实JSX已经转换为JS结构的DOM描述。如果没法直观理解,可以参考下React官方介绍 jsx:
那么页面加载完成后,ReactJS做的事情,似乎和Vue是一样的,把JS-Dom 挂载到真实页面,也就是Diff挂载。那么问题来了,React既然不是MVVM这类双向的数据绑定,自称是单向的,它单向又如何实现呢。第一次接触React,其实就发现了一个特点setState函数。个人感觉,这是一个设计局限问题,React没法像Vue那样自然的修改data的值,它为了实现数据变化改变视图,委婉的约定了改变数据用setState函数。其实setState里面就间接的完成了Vue里面的数据发生变化的感知过程(这当然是废话,用户直接调用函数去修改数据了,当然直接感知了)。setState里面重新通过render函数渲染了虚拟Dom,然后再diff更新到真实dom。React既然都委婉的采用了setState,那自然可以对setState下些功夫了,异步更新(队列式更新),合并更新,借此来宣称一些性能上的优化,当然也由此产生了各种不便和问题。
对比点
- 数据驱动
都采用数据驱动视图的思想,上来就是data数据和state数据,让开发人员一目了然的明白,我们干的事情就是数据来驱动视图的变化,业务开发的核心是数据。
- 模板&虚拟Dom
虽然虚拟Dom化,Vue从2.0版本才引入了虚拟Dom,但到目前为止两者这方面的思想是一致的。通过Template也好,或者JSX也好,做的同一件事情,把html标签搞成js描述的Dom结构。其实回头想想,也只能用javascript语言来描述这个dom。为啥?这样可以写很多逻辑在里面,而带逻辑的描述语言,在浏览器端时候也只有js合适。另外要配合差异化更新(diff),似乎也只有采用内存比较才高效。当然Diff算法,两者还是有差异的,但思想还是一样的。
- MVVM&单向
我目前为止了解到的,React为啥只实现 了单向,可以理解为简单化,并非100%的数据都需要双向,而且单向的过程简单明了,况且开发人员可以自行实现双向绑定。而Vue的双向,就是MVVM的核心体现了,这是它的优势,当然的确也带来了很多便利,尤其是表单开发过程。
- 组件化
说完MVVM和单向,组件化过程两者的确都实现了,但React的组件化才把组件的概念体现的淋漓尽致。甚至函数即组件的概念,能够直白的让开发员理解啥是组件。而Vue的组件明显是在MVVM的思想影响下形成的,少许牵强。说起组件化,不得不聊一点。开始学习React,发现到处都是js,连HTML标签也用js编写,会觉得变扭,但真正做项目写惯了,豁然感觉,这么写比Vue更直白,更爽。当然也正是因为组件化,React的难度比Vue大,比如ES6的类组件,什么高阶函数啊,入门过程比较漫长。
- 代码风格
Vue文件把html\js\css按照三块进行组织,这种风格把数据和视图分开的很直接,而React就一个JS,html部分只是Render函数的返回值(JSX),似乎感觉分离,但似乎有合在一起。所以站在代码风格角度Vue的感觉更合理,但站在组件化的角度React的合并又更贴切。