Vue也已经升级到2.0版本了,到如今为止(2016/11/19)比较流行的MVVM框架有AngularJS(也有人认为其为MVC)、ReactJS和VueJS,这三个框架中,以我如今的状况来讲(AngularJS2尚未去接触),ReactJS和VueJS的对比应该是比较适合的,感受这哥俩就是好基友,无论是单向数据流、组件化思想、仍是构建大型应用的路由和状态管理都有些许类似之处。而AngularJS与Jquery对比我我的觉着比较合适。css
为何如今MVVM框架这么火呢?JQuery挺好用的呀,为何要去代替它?...html
可能会产生这样的疑问,在我看来,MVVM框架的流行是由于随着前端技术的发展对于要求愈来愈高和前端面对的场景愈来愈复杂致使了现实对于前端性能的要求也愈来愈高,这样像JQuery那样频繁的操做DOM节点的方式显然就不太合适了。因此MVVM开始逐渐流行开来,另外我认为JQuery目前来看仍是不会被代替的,由于对于一些对性能要求不是很高的前端项目,是用JQuery来开发仍是很是爽的。前端
废话有点多了,进入正题。接下来从数据双向绑定、组件及数据流、路由、状态管理等方面来分别对比一下怎样去使用Vue和React。vue
我理解的数据双向绑定是,MVVM框架中的View层和Model层的数据相互影响。那么,那些行为会引发数据变更呢?
首先,View层(即页面上)的表单操做、触发事件可能会引发数据变更;ajax请求也可能会引发数据变更,这个变更我认为更多在Model层;还有一种状况就是,某一数据变更引发另外关联数据的改变。
无论是哪种数据改变,都会致使View层和Model层的变化,View层变更引发页面变化,Model层变更保存数据。react
在Vue中,View层中与数据绑定有关的有插值表达式、指令系统、*Class和Style、事件处理器和表单控件,ajax请求和计算属性也和数据变化有关,下面咱们分别说一下这些知识点设计的一些数据绑定问题。ios
在Vue中,插值表达式和指令对于数据的操做又称为模板语法。git
Vue.js 使用了基于 HTML 的模版语法,容许开发者声明式地将 DOM 绑定至底层 Vue 实例的数据。全部 Vue.js 的模板都是合法的 HTML ,因此能被遵循规范的浏览器和 HTML 解析器解析。github
在底层的实现上, Vue 将模板编译成虚拟 DOM 渲染函数。结合响应系统,在应用状态改变时, Vue 可以智能地计算出从新渲染组件的最小代价并应用到 DOM 操做上。ajax
关于插值表达式的使用,在Vue官网模板语法的插值部分有详细的使用说明,不在赘述,须要注意的是,过滤器在插值中的使用有时能够起到意想不到的效果。算法
Vue重的指令估计是从Angular那里学来的,有不少类似的地方,可是也不彻底相同。
Vue中的指令我觉着很是的简单,而且就12个,很容易记忆:
[v-cloak] { display: none }
一块儿用时,这个指令能够隐藏未编译的 Mustache 标签直到实例准备完毕。大概列举一下,详细使用请参考Vue API 指令和Vue 指南的Class与Style绑定、条件渲染、列表渲染、事件处理器、表单控件绑定部份内容。
Vue为了方便操做控制元素的样式,专门加强了v-bind:class和v-bind:style,经过加强的这两个指令能够实用对象语法或者数组语法对元素的样式进行变更,这也不是本文重点,Vue官方Class与Style绑定已经说得很详细了。
条件渲染和列表渲染在Vue模板中动态建立模板很不错,让我里面想到了JSP中的EL表达式和Struts中的JSTL(后端模板语言中基本都有),这就能够方便的根据后端传过来的数据进行模板建立了。你懂得,详细语法和使用仍是参考Vue文档列表渲染和条件渲染,本篇主题是对比,并非讲解基础语法,Vue的官方文档我觉着很是给力,简单明了。
在Vue中咱们能够经过v-on来给元素注册事件,完成事件注册,Vue中的事件处理和平时使用的事件处理不一样之处就是,既能够绑定数据处理函数,也可使用内联处理器。而且,Vue还讲经常使用的事件方法,如preventDefault()等经过修饰符的方式来方便使用。
你能够用v-model指令在表单控件元素上建立双向数据绑定。它会根据控件类型自动选取正确的方法来更新元素。
Vue中对于表单控件提供的v-model*指令很是的使用,能够方便的返回或者设置表单控件的信息。
在Vue中引入了计算属性来处理模板中放入太多的逻辑会让模板太重且难以维护的问题,这样不但解决了上面的问题,并且也同时让模板和业务逻辑更好的分离。
Vue计算属性
在Vue1.x的版本中,官方推荐的ajax数据请求库是vue-resource,可是在Vue2.0的版本中,再也不推荐使用,该推荐使用axios。
其实这些ajax数据请求的使用都大差不差,随你选择,而且vue-resource仍是继续支持使用的,在Vue2.0中。
以上八个方面,我我的认为都是和数据绑定相关的一些Vue基本项,只是简单列举,具体内容请查看Vue文档或者API。
为何这么多呢?由于Vue中有个模板的概念,因此,数据和模板进行数据绑定须要分别来作,而在接下来的React中,你会发现,React的数据绑定虽然也是这八项,可是,不会展开八项来讲明。
在上面的Vue中,咱们已经说起了有八个方面能够影响到数据的改变,可是React中就没有必要展开了说明了,由于在React中没有Vue中模板的概念,由于人家有一个JSX语法呀,能够将HTML和JS还有CSS混合写在一块儿呀,,这样写成的组件感受还不错,组件化也是ReactJS的重要特色之一。
React中经过将state(Model层) 与View层数据进行双向绑定达到数据的实时更新变化,具体来讲就是在View层直接写JS代码将Model层中的数据拿过来渲染,一旦像表单操做、触发事件、ajax请求等触发数据变化,则进行双向同步。
因此说React的特色是组件化,也就是说,接下来的小节才是React的重点部分。
前端发展到如今,为了提升开发效率,组件化已经成为一个必然的趋势。而MVVM框架中,若是没有组件化的思想,它都不敢说拿出来宣传(纯属我的意淫,哈哈)。下面咱们再分别简单介绍一下VueJS和ReactJS中组件思想和组件之间的数据流。
上一节中提到过,React中的组件化是其重要的特色之一,由于在Angular1.x的出现,并无明确提出组件的思想,只是有一个相似的指令思想来实现组件化的方式。因此,当React中明确提出组件思想后,前端好像重生了(吹的有点大了)。
React中实现组件有两种方式,一种是createClass方法,另外一种是经过ES2015的思想类继承React.Component来实现。
import React from 'react'; export default React.createClass({ render() { return ( <div>hello5</div> ) } })
这样,一个组件就建立完成,而且经过ES2015的模块化思想将其暴露出去了,其余组件就能够引入并使用了。以下:
import React from 'react'; import ReactDom from 'react-dom'; import Hello from './hello.jsx'; ReactDom.render( <Hello/>, document.getElementById('app'); );
OK,这样就使用简单使用了一个组件。
import React from 'react'; class CommentItem extends React.Component { render() { return ( <div className="comment"> <span>{ this.props.author }</span> <span>{ this.props.date }</span> <div>{ this.props.children }</div> </div> ) } } export { CommentItem as default };
须要注意的是,这样建立组件的时候,组件名称首字母必须大写(如:CommentItem)。一样,咱们使用一下这个组件。
import React from 'react'; import CommentItem from './comment-item'; class CommentList extends React.Component { render() { let CommentNodes = this.props.data.map((comment, index) => { return ( <CommentItem key={index} author={comment.author} date={comment.date}> {comment.content} </CommentItem> ) }); return ( <div> { CommentNodes } </div> ) } } export { CommentList as default };
这样咱们又建立了一个组件,而且在这个组件中咱们使用了上面建立的那个组件。
在上面类继承React.Component来实现一节中,咱们能够看出例子中出现了组件嵌套的状况,仔细想一想,组件之间传递信息确定是必然的。那么React是怎样进行组件之间的数据通讯的呢?
回答这个问题以前,咱们须要考虑一下,组件之间有几种数据通讯。首先,第一种比较容易想到,那就是父子组件之间的数据通讯。第二种也就天然而然的出来了----非父子组件之间的数据通讯。
父子组件之间的数据通讯细分其实还有两种:父与子之间和子与父之间。
在React中,父与子之间的数据通讯是经过props属性就行传递的;
而子与父之间的数据通讯能够经过父组件定义事件,子组件触发父组件中的事件时,经过实参的形式来改变父组件中的数据来通讯;
下面咱们来分别经过例子来再现一下这种场景:
父组件:
import React from 'react'; import CommentItem from './comment-item'; class CommentList extends React.Component { render() { let CommentNodes = this.props.data.map((comment, index) => { return ( <CommentItem key={index} author={comment.author} date={comment.date}> {comment.content} </CommentItem> ) }); return ( <div> { CommentNodes } </div> ) } } export { CommentList as default };
子组件:
import React from 'react'; class CommentItem extends React.Component { render() { return ( <div className="comment"> <span>{ this.props.author }</span> <span>{ this.props.date }</span> <div>{ this.props.children }</div> </div> ) } } export { CommentItem as default };
经过上面咱们能够看出,子组件CommentItem须要父组件传过来的值进行展现,而父组件是这样的:
<CommentItem key={index} author={comment.author} date={comment.date}> {comment.content} </CommentItem>
在父组件中添加了key
、author
、date
属性来向子组件传值。想对象的,子组件经过props对象来获取父组件传过来的值,以下:
<span>{ this.props.author }</span> <span>{ this.props.date }</span> <div>{ this.props.children }</div>
好的,咱们再来看一下另外一种子与父之间的通讯。
父组件:
import React from 'react'; import CommentList from './comment-list'; import CommentForm from './comment-form'; class CommentBox extends React.Component { constructor(props) { super(props); this.state = {data: []}; } handleCommentSubmit(comment) { let comments = this.state.data; comments.push(comment); this.setState({data: comments}); } render() { return ( <div className="m-index"> <div> <h1>评论</h1> </div> <CommentList data={this.state.data} /> <CommentForm onCommentSubmit={this.handleCommentSubmit.bind(this)} /> </div> ) } } export { CommentBox as default };
子组件:
import React from 'react'; class CommentForm extends React.Component { handleClick(){ let author = this.refs.author.value, content = this.refs.content.value; this.props.onCommentSubmit({author, content, date:new Date().getTime()}); this.refs.author.value = ""; this.refs.content.value = ""; } render() { return ( <div className="yo-list yo-list-a"> <label className="item item-input"> <input type="text" className="yo-input flex" ref="author" placeholder="发布人" /> </label> <label className="item item-input"> <textarea className="yo-input flex" ref="content" placeholder="留言内容"></textarea> </label> <label> <button onClick={this.handleClick.bind(this)} className="yo-btn yo-btn-l">发表评论</button> </label> </div> ) } } export { CommentForm as default };
简单解释一下,子组件是一个表单组件,父组件中引用了该表单子组件,而后子组件中点击button按钮,触发子组件中的处理函数,处理函数经过refs获取到表单输入值而后调用父组件中传过来的函数,从而触发父组件中的函数执行改变data数据,data数据变更直接影响的是另外一个组件CommentList的变化。
须要注意的是,在获取表单控件内的数据时,咱们利用了一个refs对象,该对象用于获取真实DOM结构。具体来讲就是,在React中组件并非真实的 DOM 节点,而是存在于内存之中的一种数据结构,叫作虚拟 DOM (virtual DOM,这是React探索性的创新)。只有当它插入文档之后,才会变成真实的 DOM 。根据 React 的设计,全部的 DOM 变更,都先在虚拟 DOM 上发生,而后再将实际发生变更的部分,反映在真实 DOM上,这种算法叫作 DOM diff (详细了解diff 算法),它能够极大提升网页的性能表现。
在这里点击button时,input和textarea元素仍是虚拟DOM,因此违法获取到输入的值,须要经过refs对象获取一下。
React中在处理非父子组件之间的通讯时,简单的,嵌套不深的非父子组件(如:兄弟组件)能够仍然使用上一节非父子组件之间通讯中的事件函数,传形参的方式来实现。如子组件CommentList 和子组件CommentFrom之间的通讯就是这样实现的。
若是,须要通讯的两个非父子组件之间嵌套比较深,可使用Flux和Redux来实现状态管理,这里不作详细阐述,下面会详细对比vue的状态管理进行说明。想先了解的能够看一下阮一峰老师的blog:
Flux 架构入门教程
Redux 入门教程(一):基本用法
Redux 入门教程(二):中间件与异步操做
Redux 入门教程(三):React-Redux 的用法
上面这张图已经很清楚的展现了react组件的声明周期了,就不过多介绍了。这张图摘自React组件生命周期小结,对于理解React组件的声明周期钩子函数颇有帮助。
Vue比React出来的要晚一些,天然顺应了前端组件化的大潮,而且我的以为借鉴了部分React的思想来实现其组件化思想。
Vue默认的是单向数据流,这是Vue直接提出来讲明的,父组件默承认以向子组件传递数据,可是子组件向父组件传递数据就须要额外设置了。
父子组件之间的数据通讯是经过Prop和自定义事件实现的,而非父子组件可使用订阅/发布模式实现(相似于Angualr中的非父子指令之间的通讯),再复杂一点也是建议使用状态管理(vuex)。
我一直以为Vue的官方文档是我看过最直接、易懂的技术文档,因此就直接给你们贴一个中文连接,本身去跟随尤雨溪学习吧。
上面对比React和Vue的组件及数据流的时候,都提到了当非父子组件之间嵌套过深的时候都建议使用状态管理来维护数据的变化,那么到底它们之间的状态管理有什么区别呢?
先放个官方中文连接,仍是建议直接看官方文档。而后在放一下小例子去体会一下。
先简单说明一下,vuex状态管理的几个核心概念:
例子来了:
store.js
import Vue from '../libs/vue.js'; import Vuex from '../libs/vuex.min.js'; Vue.use(Vuex); const state = { loginPrePath:['/'] }; const mutations ={ LOGINPREPATH(state,path){ state.loginPrePath.unshift(path); }, LOGINPREPATHSHIFT(state){ state.loginPrePath.shift(); } }; export default new Vuex.Store({ state, mutations });
actions.js:
export default { loginPrePath: ({dispatch,state},path)=>{ console.log('actions loginPrePath:' +path); dispatch('LOGINPREPATH',path); }, loginPrePathShift: ({dispatch,state})=>{ console.log('delete....'); dispatch('LOGINPREPATHSHIFT'); } }
getter.js:
export default { loginPrePath: state => state.loginPrePath };
login.vue:
<template> ... </template> <script> import Vue from '../libs/vue.js'; import VueResource from '../libs/vue-resource.js'; import Vuex from '../vuex/actions.js'; import VuexGet from '../vuex/getters.js'; Vue.use(VueResource); export default { data(){ return { username: '', password: '', loginBtn: 0 } }, vuex: { actions: { setLoginPrePath: Vuex.loginPrePath }, getters:{ getLoginPrePath: VuexGet.loginPrePath } }, methods: { forget(){ //使用vuex,修改状态值 this.setLoginPrePath({path:this.$route.path,title:'忘记密码'}); this.$router.go({path:'/index2/forget.json'}); }, submit(){ if(this.loginBtn === 3){ if(this.checked){ this.$http.post('/zhixiao/password.json',{password:this.password}).then( (res)=>{ if(res.ok){ console.log("注册成功,正在跳转登陆页面"); setTimeout(()=>{ //获取状态值,经过getter var path = this.getLoginPrePath[0].path; this.loginPrePathShift(); this.$router.go(path); },1500); } },(res)=>{ console.log('网络错误,请稍后重试'); } ) }else{ console.log('请选择赞成用户协议'); } }else{ console.log('请填写验证码'); } } } } </script>
上面的例子并没有实际效果,是我从之前项目中拼出来的(vuex1.0),只是为了说明loginPrePath这个状态值在vuex中的使用方式。详细请看Vue官方文档。
React中官方提供的状态管理是Flux,可是貌似如今第三方开发的Redux更强大,可是相比较使用的难度和学习曲线就有点陡峭了。
我的感受Flux和Vue中的vuex思想基本相同,由于Vuex就是借鉴的Flux。
因此说,如今再来讲Flux就简单了。
回想一下,在vuex中若是咱们想修改一个状态值,咱们应该怎么办呢?
在组件中配置vuex对象属性里面的actions和getters属性数组,而后配置的其实是调用了Actions中的方法,Actions做用是将修改操做派生给store中的mutations,mutations真正处理业务逻辑而且修改状态值。
其实Flux也是如此,只不过在vuex中的Actions层执行了一个dispatcher方法将状态操做转发给了mutations,在Flux中直接须要显示的配置一层Dispatcher层进行转发。而且实现方式有所不一样,vuex中mutations中隐藏了一些事件触发的操做,而Flux中直接经过咱们本身编写代码实现,毕竟Flux是年纪大了,不如小弟vuex年轻呀。
例子:
components.js
import React from 'react'; import MyButton from './MyButton.jsx'; import ButtonActions from '../actions/ButtonActions.js'; import ListStore from '../stores/ListStore.js'; export default React.createClass({ getInitialState() { return { items: ListStore.getAll() } }, createNewItem() { ButtonActions.addNewItem('New Item'); }, componentDidMount() { ListStore.addChangeListener(this._onChange); }, componentWillUnmount() { ListStore.removeChangeListener(this._onChange); }, _onChange() { this.setState({ items: ListStore.getAll() }) }, render() { return ( <MyButton onClick={this.createNewItem} items={this.state.items} /> ) } });
ButtonActions.js:
import AppDispatcher from '../dispatchers/AppDispatcher.js'; export default { addNewItem(text) { AppDispatcher.dispatch({ actionType: 'ADD_NEW_ITEM', text: text }) } }
AppDispatcher.js':
import { Dispatcher } from 'flux'; import ListStore from '../stores/ListStore.js'; let AppDispatcher = new Dispatcher(); AppDispatcher.register(action => { switch( action.actionType ) { case 'ADD_NEW_ITEM': ListStore.addNewItemHandle(action.text); ListStore.emitChange(); break; } }); export default AppDispatcher;
ListStore.js:
import { EventEmitter } from 'events'; export default Object.assign({}, EventEmitter.prototype, { items: [], getAll(){ return this.items; }, addNewItemHandler(text) { this.items.push(text); }, emitChange() { this.emit('change'); }, addChangeListener(callback) { this.on('change', callback); }, removeChangeListener(callback) { this.removeListener('change', callback); } });
仔细按照例子走一遍工做流程,相信你就理解Flux实现状态管理的思想了。
要想实现SPA,路由是个不可避免的话题,做为主流的MVVM框架,怎么可能没有官方路由呢,二者的路由也很类似,都是利用各自的组件实现思想来实现的。
仍是先贴官方连接,简单易懂。
再给个例子(vue-router1.0),仔细看一下:
app.js
//引用component import index from './components/index.vue'; import main from './components/main.vue'; import my from './components/my.vue'; //APP route import Vue from './libs/vue.js'; import VueRouter from './libs/vue-router.js'; Vue.use(VueRouter); router.map({ '/':{ component: index, subRoutes:{ '/':{ component: main }, '/my':{ name:'my', component: my }, '/results/:key':{ name:'results', component:results } } } }); //启动router router.start(App,'body');
index.vue
<template> <div class="box"> <div class="index-container"> <router-view> </router-view> </div> <footer id="footer"> <ul> <li v-bind:class="getIndex == $index ? 'active' : ''" v-for="data in navList" v-on:click="changePage($index)" v-link="{path:data.path,exact: true}"> <i class="iconfont">{{{data.icon}}}</i> <p>{{{data.name}}}</p> </li> </ul> </footer> </div> </template> <script> var Vue = require('../libs/vue.js'); var VueResource = require('../libs/vue-resource.js'); import {getIndex} from '../vuex/getters.js'; import {changeIndexPage} from '../vuex/actions.js'; Vue.use(VueResource); export default { vuex: { actions:{ changeIndexPage }, getters:{ getIndex } }, data(){ return { cur: 0, navList:[ {path:'/',icon:'',name:'主页'}, {path:'/lee',icon:'',name:'排行榜'}, {path:'/search',icon:'',name:'发现'}, {path:'/my',icon:'',name:'个人'} ] } }, methods:{ changePage:function(i){ this.changeIndexPage(i); this.cur = this.getIndex; } } } </script>
大概就这样,感受像是配置的感受,其实这就是利用的vue中组件思想来实现的,详细看官方文档。
React中的路由只须要安装插件react-router便可。
再来看例子:
app.jsx
'use strict'; import '../styles/usage/page/app.scss'; import React from 'react'; import ReactDOM from 'react-dom'; // import router import { Router, Route, hashHistory, IndexRoute, Redirect } from 'react-router'; // router components import App from './components/router/router-app.jsx'; import TV from './components/router/router-tv.jsx'; import Shows from './components/router/router-show.jsx'; import ShowIndex from './components/router/router-show-index.jsx'; let app = document.getElementById('app'); let handleEnter = () => { console.log('entered'); } let handleLeave = () => { console.log('leaved'); } ReactDOM.render(( <Router history={hashHistory}> <Route path="/" component={App}> <Route path="tv" component={TV}> <IndexRoute component={ShowIndex}></IndexRoute> <Route path="/shows/:id" onEnter={handleEnter} onLeave={handleLeave} component={Shows} /> <Redirect from="shows/:id" to="/shows/:id" /> </Route> </Route> </Router> ), app);
router-app.jsx:
'ure strict'; import React from 'react'; // import router import { Link } from 'react-router'; export default React.createClass({ render() { return ( <div> <div> <Link to="/">首页</Link> | <Link to="/tv">电视剧</Link> </div> {this.props.children} </div> ) } });
router-tv.jsx
'use strict'; import React from 'react'; export default React.createClass({ render() { return ( <div> {this.props.children} </div> ) } });
router-show.jsx
'use strict'; import React from 'react'; export default React.createClass({ render() { return ( <h1> 节目内容 {this.props.params.id} </h1> ) } });
router-show-index.jsx
'use strict'; import React from 'react'; import { Link } from 'react-router'; export default React.createClass({ render() { return ( <div> <Link to="tv/shows/2">电视节目列表</Link> </div> ) } });
例子很简单,可是将经常使用的路由操做基本都涵盖了。
大概经过本身的理解,对比了一下Vue和React的一些主要概念和实现方式,主要是为了加深本身理解,有些东西本身水平有限,很差表述,大概就是堆砌一些知识点而已。