由於沒有明確的界定,這裏不討論正確與否,只表達我的對前端MV*架構模式理解見解,再比較React和Vue兩種框架不一樣.
寫完以後我知道這文章好水,特別是框架對比部分都是別人說爛的,而我也是打算把這做爲長期文章來寫,慢慢梳理深刻,每次有新的理解就更新文章,我挺期待以後到了超過字數限制不得不寫成系列文章的那一天.css
2018/04/28 新增聲明式渲染 && 生命週期對比 && 狀態(State) OR 屬性(Data) && Props組件通訊
2018/05/02 新增狀態管理
2018/06/07 新增mobx狀態管理
2018/11/06 補充更新機制和狀態管理對比前端
MVC全名是Model View Controller,把應用程序分紅三部分分別是:vue
(這些簡單的東西我就懶得特地畫圖了,直接百度圖片找張清晰的拿來用的..)
(更多內容請自行查閱,本節到此爲止了.)react
還有一種狀況: MVC容許在不改變視圖外觀的狀況下改變視圖對用戶輸入的響應方式,只要用不一樣種類的controller實例替換便可。例如改變URL觸發hashChange事件,用戶不通過View直接到達Controller最後再影響回View.vuex
MVP全名是Model-View-Presenter,從經典的模式MVC演變而來,分兩種狀況:編程
Presenter佔據絕對主導地位,掌控著Model和View,然後二者之間互不聯繫.redux
(這些簡單的東西我就懶得特地畫圖了,直接百度圖片找張清晰的拿來用的..)
(更多內容請自行查閱,本節到此爲止了.)segmentfault
Presenter依舊佔據主導地位,可是會把一部分簡單的視圖邏輯(如雙向綁定)交還給View和Model進行處理,自身負責其餘複雜的視圖邏輯.數組
MVC和MVP(Supervising Controller)區別:
1, 視圖支持Presenter和View兩種途徑更新;瀏覽器
1, 模型與視圖高度分離,咱們能夠修改視圖而不影響模型;
2, 能夠更高效地使用模型,由於全部的交互都發生在一個地方——Presenter內部;
3, 能夠將一個Presenter用於多個視圖,而不須要改變Presenter的邏輯;
4, 若是把邏輯放在Presenter中,就能夠脫離用戶接口來測試這些邏輯(單元測試);
1, 因爲對視圖的渲染放在了Presenter中,因此View和Presenter的交互會過於頻繁而且難以維護;
MVVM全名是Model-View-ViewModel,本質上就是MVC的改進版,也能夠說是MVP的改良版,把應用程序分紅三部分分別是:
MVP和MVVM區別: 它使用 數據綁定(Data Binding)
、依賴屬性(Dependency Property)
、命令(Command)
、路由事件(Routed Event)
來搞定與view層的交互, 當ViewModel對Model進行更新的時候,會經過數據綁定更新到View.
(這些簡單的東西我就懶得特地畫圖了,直接百度圖片找張清晰的拿來用的..)
(更多內容請自行查閱,本節到此爲止了.)
兩個框架是如今最熱門的選擇之一,它們既相似又不一樣.
React就是MVC裏的V,只專一視圖層,而Vue算是MVVM框架,雙向綁定是特點之一.
咱們先看看它們本身的官方介紹:
React is a JavaScript library for building user interfaces.
React是一個用於構建用戶界面的Javascript庫.
Vue (pronounced /vjuː/, like view) is a progressive framework for building user interfaces. Unlike other monolithic frameworks, Vue is designed from the ground up to be incrementally adoptable. The core library is focused on the view layer only, and is easy to pick up and integrate with other libraries or existing projects. On the other hand, Vue is also perfectly capable of powering sophisticated Single-Page Applications when used in combination with modern tooling and supporting libraries.
Vue.js (讀音 /vjuː/,相似於 view) 是一套構建用戶界面的漸進式框架。與其餘重量級框架不一樣的是,Vue 採用自底向上增量開發的設計。Vue 的核心庫只關注視圖層,它不只易於上手,還便於與第三方庫或既有項目整合。另外一方面,當與單文件組件和 Vue 生態系統支持的庫結合使用時,Vue 也徹底可以爲複雜的單頁應用程序提供驅動。
React 組件實現一個 render() 方法,它接收輸入數據並返回顯示的內容。此示例使用相似XML的語法,稱爲 JSX 。輸入數據能夠經過 this.props 傳入組件,被 render() 訪問。
class HelloMessage extends React.Component { render() { return ( <div> Hello {this.props.name} </div> ); } } ReactDOM.render( <HelloMessage name="Taylor" />, mountNode );
Vue.js 的核心是一個容許採用簡潔的模板語法來聲明式的將數據渲染進 DOM 的系統
<div id="app"> {{ message }} </div> // --------省略-------- var app = new Vue({ el: '#app', data: { message: 'Hello Vue!' } })
他是 JavaScrip 的一種擴展語法。 React 官方推薦使用這種語法來描述 UI 信息。JSX 可能會讓你想起某種模板語言,可是它具備 JavaScrip 的所有能力,從本質上講,JSX 只是爲 React.createElement(component, props, ...children) 函數提供的語法糖。
JSX 對使用React 不是必須的。
Vue.js 使用了基於 HTML 的模板語法,容許開發者聲明式地將 DOM 綁定至底層 Vue 實例的數據。全部 Vue.js 的模板都是合法的 HTML ,因此能被遵循規範的瀏覽器和 HTML 解析器解析。
在底層的實現上,Vue 將模板編譯成虛擬 DOM 渲染函數。結合響應系統,在應用狀態改變時,Vue 可以智能地計算出從新渲染組件的最小代價並應用到 DOM 操做上。
事實上 Vue 也提供了渲染函數,甚至支持 JSX。然而,默認推薦的仍是模板。
我的感受二者其實上手速度都挺快,相比之下JSX除了修改部分屬性名字跟普通HTML變化不算大,Templates額外添加不少自定義功能幫助開發者作更多的事,框架痕跡也比較重.
咱們能夠把組件區分爲兩類:一類是偏視圖表現的 (presentational)推薦使用模板,一類則是偏邏輯的 (logical)推薦使用 JSX 或渲染函數。
//Jsx寫法 <div className="list"> <ol> { todos.map(item =><li>{todo.text}</li>) } </ol> </div>
//Templates寫法 <div class="list"> <ol> <li v-for="todo in todos"> {{ todo.text }} </li> </ol> </div>
React 中推薦經過 CSS-in-JS 的方案實現的 (好比 styled-components、glamorous 和 emotion),雖然在構建時將 CSS 提取到一個單獨的樣式表是支持的,但 bundle 裏一般仍是須要一個運行時程序來讓這些樣式生效。當你可以利用 JavaScript 靈活處理樣式的同時,也須要權衡 bundle 的尺寸和運行時的開銷
var styleObj = { color:"blue", fontSize:40, fontWeight:"normal" }; --------省略-------- <h1 style={styleObj} className="alert-text">Hello</h1>
Vue 設置樣式的默認方法是單文件組件裏相似 style 的標籤。讓你能夠在同一個文件裏徹底控制 CSS,將其做爲組件代碼的一部分。
<style scoped> @media (min-width: 250px) { .list-container:hover { background: orange; } } </style>
這個可選 scoped
屬性會自動添加一個惟一的屬性 (好比 data-v-21e5b78) 爲組件內 CSS 指定做用域,編譯的時候 .list-container:hover 會被編譯成相似 .list-container[data-v-21e5b78]:hover。
最後,Vue 的單文件組件裏的樣式設置是很是靈活的。經過 vue-loader,你可使用任意預處理器、後處理器,甚至深度集成 CSS Modules——所有都在 <style> 標籤內。
各有好壞吧,React侵入式作法不太喜歡,Vue組件式作法倒也還行,我的而言更傾向獨立css樣式外部引入易於管理.
State是私有的,而且由組件自己徹底控制。
// 錯誤 this.state.comment = 'Hello'; // 正確 this.setState({comment: 'Hello'});
componentDidMount() { fetchPosts().then(response => { this.setState({ posts: response.posts }); }); fetchComments().then(response => { this.setState({ comments: response.comments }); }); }
// 錯誤 this.setState({ counter: this.state.counter + this.props.increment, }); // 正確 //另外一種 setState() 的形式,它接受一個函數而不是一個對象。這個函數將接收前一個狀態做爲第一個參數,應用更新時的 props 做爲第二個參數 this.setState((prevState, props) => ({ counter: prevState.counter + props.increment }));
當一個 Vue 實例被建立時,它向 Vue 的響應式系統中加入了其 data 對象中能找到的全部的屬性。當這些屬性的值發生改變時,視圖將會產生「響應」,即匹配更新爲新的值。
// 有效 data: { visitCount1: 0 } --------省略-------- // 觸發任何視圖的更新 vm.visitCount1 = 2 // 不會觸發任何視圖的更新 vm.visitCount2 = 2
React是屬於單向控制,即只能是經過改變State從而改變視圖,咱們能夠利用JS方法像表單等場景模擬雙向綁定的效果,實際仍是由State去觸發視圖更新
Vue是屬於雙向綁定,原理是經過 Object.defineProperty 監聽劫持data對象的getter/setter屬性來實現的
一個組件能夠選擇將數據向下傳遞,做爲其子組件的 props(屬性).
//父組件傳遞數據 <Child num={this.state.num} /> //--------省略-------- //子組件讀取數據 <h2>It is {this.props.num}.</h2>
//父組件傳遞數據 <Child num={this.state.num} /> //--------省略-------- constructor(props) { super(props); this.state = { //初始化成state num: this.props.num, }; }
//傳遞修改函數 class Father extends Component { construtor(props) { super(props); this.state = { num: 1, }; } onChangeState(val) { this.setState(val); } render() { <Child num={this.state.num} onClicked={this.onChangeState.bind(this)} />; } } //調用修改函數添加入參 class Child extends Component { render() { <button onClicked={() => this.props.onClicked({num: 2})}> It is {this.props.num}. </button>; } }
//父組件傳遞數據 <child message="hello!"></child> //--------省略-------- //子組件要顯式地用 props 選項聲明它預期的數據 Vue.component('child', { // 聲明 props props: ['message'], // 就像 data 同樣,prop 也能夠在模板中使用 // 一樣也能夠在 vm 實例中經過 this.message 來使用 template: '<span>{{ message }}</span>' })
//父組件傳遞數據 <child message="hello!"></child> //--------省略-------- //1, 定義一個局部變量,並用 prop 的值初始化它: props: ['message'], data: function () { return { msg: this.message } } template: '<span>{{ msg }}</span>' //2, 定義一個計算屬性,處理 prop 的值並返回: props: ['message'], computed: { msg: function () { return this.message } } template: '<span>{{ msg }}</span>'
//父組件傳遞數據 <child v-bind:message="message" v-on:update:message="message = $event"></child> //--------可用.sync 修飾符替代-------- //後面傳遞的message是變量,非字符串 //<child :message.sync="message"></child> //--------省略-------- //子組件 props: ['message'], data: function () { return { msg: this.message } } watch: { //監聽msg變化自動通訊父組件更改 msg(val) { this.$emit('update:message', newMsg) }, },
React生命週期 | 做用 |
---|---|
getDefaultProps | 做用於組件類,只調用一次,返回對象用於設置默認的props,對於引用值,會在實例中共享 |
getInitialState | 做用於組件的實例,在實例建立時調用一次,用於初始化每一個實例的state,此時能夠訪問this.props。 |
componentWillMount | 在完成首次渲染以前調用,此時仍能夠修改組件的state |
render | 必選的方法,建立虛擬DOM,該方法具備特殊的規則: 1) 只能經過this.props和this.state訪問數據; 2) 能夠返回null、false或任何React組件; 3) 只能出現一個頂級組件(不能返回數組); 4) 不能改變組件的狀態; 5) 不能修改DOM的輸出; |
componentDidMount | 真實的DOM被渲染出來後調用,在該方法中可經過this.getDOMNode()訪問到真實的DOM元素。此時已可使用其餘類庫來操做這個DOM。在服務端中,該方法不會被調用。 |
componentWillReceiveProps | 組件接收到新的props時調用,並將其做爲參數nextProps使用,此時能夠更改組件props及state |
shouldComponentUpdate | 組件是否應當渲染新的props或state,返回false表示跳事後續的生命週期方法,一般不須要使用以免出現bug。在出現應用的瓶頸時,可經過該方法進行適當的優化。在首次渲染期間或者調用了forceUpdate方法後,該方法不會被調用 |
componentWillUpdate | 接收到新的props或者state後,進行渲染以前調用,此時不容許更新props或state。 |
componentDidUpdate | 完成渲染新的props或者state後調用,此時能夠訪問到新的DOM元素。 |
componentWillUnmount | 組件被移除以前被調用,能夠用於作一些清理工做,在componentDidMount方法中添加的全部任務都須要在該方法中撤銷,好比建立的定時器或添加的事件監聽器 |
componentDidCatch | 16.x新增,捕獲全局異常來進行頁面的友好提示 |
Vue生命週期 | 做用 |
---|---|
beforeCreate | 在實例初始化以後,數據觀測 (data observer) 和 event/watcher 事件配置以前被調用 |
created | 在實例建立完成後被當即調用。在這一步,實例已完成如下的配置:數據觀測 (data observer),屬性和方法的運算,watch/event 事件回調。然而,掛載階段還沒開始,$el 屬性目前不可見 |
beforeMount | 在掛載開始以前被調用:相關的 render 函數首次被調用。該鉤子在服務器端渲染期間不被調用 |
mounted | el 被新建立的 vm.$el 替換,並掛載到實例上去以後調用該鉤子。若是 root 實例掛載了一個文檔內元素,當 mounted 被調用時 vm.$el 也在文檔內. 注意 mounted 不會承諾全部的子組件也都一塊兒被掛載。若是你但願等到整個視圖都渲染完畢,能夠用 vm.$nextTick 替換掉 mounted |
beforeUpdate | 數據更新時調用,發生在虛擬 DOM 從新渲染和打補丁以前。你能夠在這個鉤子中進一步地更改狀態,這不會觸發附加的重渲染過程。該鉤子在服務器端渲染期間不被調用 |
updated | 因爲數據更改致使的虛擬 DOM 從新渲染和打補丁,在這以後會調用該鉤子。當這個鉤子被調用時,組件 DOM 已經更新,因此你如今能夠執行依賴於 DOM 的操做。然而在大多數狀況下,你應該避免在此期間更改狀態。若是要相應狀態改變,一般最好使用計算屬性或 watcher 取而代之,注意 updated 不會承諾全部的子組件也都一塊兒被重繪。若是你但願等到整個視圖都重繪完畢,能夠用 vm.$nextTick 替換掉 updated:該鉤子在服務器端渲染期間不被調用 |
activated | keep-alive 組件激活時調用。該鉤子在服務器端渲染期間不被調用 |
deactivated | keep-alive 組件停用時調用。該鉤子在服務器端渲染期間不被調用 |
beforeDestroy | 實例銷燬以前調用。在這一步,實例仍然徹底可用。該鉤子在服務器端渲染期間不被調用。 |
destroyed | Vue 實例銷燬後調用。調用後,Vue 實例指示的全部東西都會解綁定,全部的事件監聽器會被移除,全部的子實例也會被銷燬 |
errorCaptured | 當捕獲一個來自子孫組件的錯誤時被調用。此鉤子會收到三個參數:錯誤對象、發生錯誤的組件實例以及一個包含錯誤來源信息的字符串。此鉤子能夠返回 false 以阻止該錯誤繼續向上傳播 |
在React裏渲染機制是以組件單位更新的,也就是說當數據發生改變,當前視圖包括其中的組件子組件和底下的子組件都會一塊兒更新,這種違反性能的機制確定是有問題的,因此React提供了生命週期shouldComponentUpdate方法讓你決定當前組件是否更新,還有一個PureComponent方法會自動檢測到state或者props發生變化時,纔會調用render方法.可是隻是淺比較,若是搭配ImmutableJs持久化數據聽說性能大大的提高.除此以外還能節省大量的手動比較的代碼和時間,
簡單描述過程
比較先後兩棵Dom樹同層級的節點區別,非同層級節點包括所屬子節點整個直接刪除從新建立;
在 Vue 應用中,組件的依賴是在渲染過程當中自動追蹤的,因此係統能精確知曉哪一個組件確實須要被重渲染,由於Vue是使用 Object.defineProperty對綁定屬性進行數據劫持的,因此比起React組件式更新它可以精確接收到哪些組件纔是須要渲染的.
這是React-Router3的模板寫法,實際到了React-Router4的API和思想都有些大的差別
import React from 'react' import { render } from 'react-dom' // 首先咱們須要導入一些組件... import { Router, Route, Link } from 'react-router' // 而後咱們從應用中刪除一堆代碼和 // 增長一些 <Link> 元素... const App = React.createClass({ render() { return ( <div> <h1>App</h1> {/* 把 <a> 變成 <Link> */} <ul> <li><Link to="/about">About</Link></li> <li><Link to="/inbox">Inbox</Link></li> </ul> {/* 接著用 `this.props.children` 替換 `<Child>` router 會幫咱們找到這個 children */} {this.props.children} </div> ) } }) // 最後,咱們用一些 <Route> 來渲染 <Router>。 // 這些就是路由提供的咱們想要的東西。 React.render(( <Router> <Route path="/" component={App}> <Route path="about" component={About} /> <Route path="inbox" component={Inbox} /> </Route> </Router> ), document.body)
Vue-Router3的模板寫法
// 0. 若是使用模塊化機制編程,導入Vue和VueRouter,要調用 Vue.use(VueRouter) // 1. 定義(路由)組件。 // 能夠從其餘文件 import 進來 const Foo = { template: '<div>foo</div>' } const Bar = { template: '<div>bar</div>' } // 2. 定義路由 // 每一個路由應該映射一個組件。 其中"component" 能夠是 // 經過 Vue.extend() 建立的組件構造器, // 或者,只是一個組件配置對象。 // 咱們晚點再討論嵌套路由。 const routes = [ { path: '/foo', component: Foo }, { path: '/bar', component: Bar } ] // 3. 建立 router 實例,而後傳 `routes` 配置 // 你還能夠傳別的配置參數, 不過先這麼簡單著吧。 const router = new VueRouter({ routes // (縮寫)至關於 routes: routes }) // 4. 建立和掛載根實例。 // 記得要經過 router 配置參數注入路由, // 從而讓整個應用都有路由功能 const app = new Vue({ router }).$mount('#app') // 如今,應用已經啓動了!
狀態管理庫有不少種,我只是舉出我用過的例子,並非必須的.下面只會展現最基本的代碼,想跑完整流程還得看文檔.
Redux 能夠用這三個基本原則來描述:
Actions: 把數據從應用傳到store的有效載荷。它是store數據的惟一來源.
/* * action 類型 */ export const ADD_TODO = 'ADD_TODO'; /* * action 建立函數 */ export function addTodo(text) { return { type: ADD_TODO, text } }
Reducers: 指定了應用狀態的變化如何響應actions併發送到store的,記住actions只是描述了有事情發生了這一事實,並無描述應用如何更新state。
import { combineReducers } from 'redux' import { ADD_TODO, } from './actions' function todos(state = [], action) { switch (action.type) { case ADD_TODO: return [ ...state, { text: action.text, completed: false } ] default: return state } } const todoApp = combineReducers({ todos }) export default todoApp
Store: 就是把Actions和Reducers聯繫到一塊兒的對象.
import { createStore } from 'redux' import todoApp from './reducers' let store = createStore(todoApp)
單向數據流
(這些簡單的東西我就懶得特地畫圖了,直接百度圖片找張清晰的拿來用的..)
進階:
另外一種實現方式,具體可看Mobx4.X狀態管理入門
Vuex 是一個專爲 Vue.js 應用程序開發的狀態管理模式。它採用集中式存儲管理應用的全部組件的狀態,並以相應的規則保證狀態以一種可預測的方式發生變化。Vuex 也集成到 Vue 的官方調試工具 devtools extension,提供了諸如零配置的 time-travel 調試、狀態快照導入導出等高級調試功能。
Mutation: 必須是同步函數,更改Vuex的store中的狀態的惟一方法是提交mutatio
// mutation-types.js export const SOME_MUTATION = 'SOME_MUTATION' // store.js import Vuex from 'vuex' import { SOME_MUTATION } from './mutation-types' const store = new Vuex.Store({ state: { ... }, mutations: { // 咱們可使用 ES2015 風格的計算屬性命名功能來使用一個常量做爲函數名 [SOME_MUTATION](state) { // mutate state } } })
Action 相似於mutation,不一樣在於:
actions: { incrementAsync ({ commit }) { setTimeout(() => { commit('increment') }, 1000) } }
Getter: 從store中的state中派生出一些狀態
getters: { // ... doneTodosCount: (state, getters) => { return getters.doneTodos.length } }
State: 包含了所有的應用層級狀態。至此它便做爲一個「惟一數據源 (SSOT)」而存在.
const app = new Vue({ el: '#app', // 把 store 對象提供給 「store」 選項,這能夠把 store 的實例注入全部的子組件 store, components: { Counter }, template: ` <div class="app"> <counter></counter> </div> ` })
Module: Vuex 容許咱們將 store 分割成模塊(module)。每一個模塊擁有本身的 state、mutation、action、getter、甚至是嵌套子模塊——從上至下進行一樣方式的分割:
const moduleA = { state: { ... }, mutations: { ... }, actions: { ... }, getters: { ... } } const moduleB = { state: { ... }, mutations: { ... }, actions: { ... } } const store = new Vuex.Store({ modules: { a: moduleA, b: moduleB } }) store.state.a // -> moduleA 的狀態 store.state.b // -> moduleB 的狀態
單向數據流
(官網來的)
Vuex
Redux指定了應用狀態的變化如何響應actions併發送到store的reducer,且必須爲純函數.Vuex其實是使用mutation更新狀態
(更多內容請自行查閱,本節到此爲止了.)
React 官方提供了 create-react-app,詬病的地方比較多.
更多人選擇本身搭建或者使用民間打包庫.
Vue 提供了 Vue-cli 腳手架,能讓你很是容易地構建項目,包含了 Webpack,Browserify,甚至 no build system,可是有些設置例如Scss預處理器等自定義配置須要本身搞,總的來講至關實用.
React正常來講須要搭配Jsx和Es6語法和構建環境;Vue能夠直接引入js庫就能開發,並且內置的功能屬性比React多得多