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的一些主要概念和實現方式,主要是爲了加深本身理解,有些東西本身水平有限,很差表述,大概就是堆砌一些知識點而已。