[轉(zhuǎn)] 淺談MVC,MVP,MVVM漸進變化及React與Vue比較
1 設計模式
MVC
MVC全名是Model View Controller,把應用程序分成三部分分別是:
- Model(業(yè)務模型): 用于管理應用程序數(shù)據(jù)處理邏輯的部分,通過觀察者模式(Pub&Sub / Events)發(fā)送消息給View;
- View(視圖界面): 用于處理數(shù)據(jù)顯示的部分,注冊并接收Model的數(shù)據(jù)更新視圖,通常視圖是依據(jù)模型數(shù)據(jù)創(chuàng)建的;
-
Controller(控制器): 用于連接模型和視圖控制應用程序的流程(事件綁定等),通常控制器負責響應View的事件(路由,鍵盤,鼠標等),調(diào)用Model的接口進行操作;
image.png
流程
- 當用戶在視圖界面中發(fā)生交互事件,View捕獲到這個操作會把處理的權(quán)利交移給Controller;
- Controller會對來自View數(shù)據(jù)進行預處理并決定調(diào)用Model的相關暴露接口;
- Model執(zhí)行相關的業(yè)務邏輯更改數(shù)據(jù)之后會通知有關的View重新渲染;
- View收到通知后從Model請求最新的數(shù)據(jù),然后重新渲染相關視圖界面;
還有一種情況: MVC允許在不改變視圖外觀的情況下改變視圖對用戶輸入的響應方式,只要用不同種類的controller實例替換即可。例如改變URL觸發(fā)hashChange事件,用戶不經(jīng)過View直接到達Controller最后再影響回View.
優(yōu)點:
- 耦合性低,MVC 分層有助于管理復雜的應用程序,同時也讓應用程序的測試更加容易;
- 重用性高,多個視圖能共享一個模型,可以做到多視圖同時更新;
- 生命周期成本低,MVC使開發(fā)和維護用戶接口的技術(shù)含量降低;
- 部署快,只需要部署對應部分代碼而不是完整項目;
- 可維護性高,分離視圖層和業(yè)務邏輯層也使得應用更易于維護和修改;
- 有利軟件工程化管理,可以使用控制器來聯(lián)接不同的模型和視圖去完成用戶的需求;
缺點:
- 沒有明確的定義,完全理解MVC并不是很容易,現(xiàn)存就有很多對MVC不同解讀實現(xiàn)的方式;
- 不適合小型,中等規(guī)模的應用程序,花費大量時間將MVC應用到規(guī)模并不是很大的應用程序通常會得不償失;
- 增加系統(tǒng)結(jié)構(gòu)和實現(xiàn)的復雜性,對于簡單的界面,會增加結(jié)構(gòu)的復雜性,并可能產(chǎn)生過多的更新操作,降低運行效率;
- 視圖與控制器間的過于緊密的連接,視圖沒有控制器的存在,其應用是很有限的,反之亦然,導致測試困難(依據(jù)模型數(shù)據(jù)創(chuàng)建部分);
- 視圖對模型數(shù)據(jù)的低效率訪問,依據(jù)模型操作接口的不同,視圖可能需要多次調(diào)用才能獲得足夠的顯示數(shù)據(jù);
- 觀察者模式由于事件觸發(fā)的隱式行為可能導致很難查找問題的來源并影響其解決;
MVP
MVP全名是Model-View-Presenter,從經(jīng)典的模式MVC演變而來,分兩種情況:
Passive View(被動視圖)
Presenter占據(jù)絕對主導地位,掌控著Model和View,而后兩者之間互不聯(lián)系.
- Model(業(yè)務模型): 用于管理應用程序數(shù)據(jù)處理邏輯的部分,通過觀察者模式(Pub&Sub / Events)發(fā)送消息給Presenter;
- View(視圖界面): 用于處理數(shù)據(jù)顯示的部分,傳遞事件和提供相關接口給Presenter;
-
Presenter(派發(fā)器): 作為中間層同步控制著Model數(shù)據(jù)修改和View視圖變化;
image.png
流程:
- 當用戶在視圖界面中發(fā)生交互事件,View捕獲到這個操作會把處理的權(quán)利交移給Presenter進行處理;
- Presenter需要時候可以獲取Model其中的數(shù)據(jù),并對Model進行操作更新;
- Model數(shù)據(jù)變化之后會通知Presenter;
- Presenter收到通知后會執(zhí)行View提供的相關接口重新渲染相關視圖界面;
MVC和MVP(Passive View)區(qū)別:
- 后者View和Model完全解耦,它們之間的通信是通過Presenter (MVC中的Controller)來進行的,所有的交互都發(fā)生在Presenter內(nèi)部;
- 前者Controller只能通過Model間接觸發(fā)View自行更新視圖,后者View不再負責更新視圖,而是提供接口給Presenter執(zhí)行;
Supervising Controller(監(jiān)督控制器)
Presenter依舊占據(jù)主導地位,但是會把一部分簡單的視圖邏輯(如雙向綁定)交還給View和Model進行處理,自身負責其他復雜的視圖邏輯.
- Model(業(yè)務模型): 用于管理應用程序數(shù)據(jù)處理邏輯的部分,通過觀察者模式(Pub&Sub / Events)發(fā)送消息給Presenter或者View;
- View(視圖界面): 用于處理數(shù)據(jù)顯示的部分和接管部分簡單的視圖邏輯,同步簡單的視圖和模型的狀態(tài),傳遞事件和提供相關接口給Presenter;
- Presenter(派發(fā)器): 作為中間層同步控制著Model數(shù)據(jù)修改和View視圖變化;
MVC和MVP(Supervising Controller)區(qū)別:
- 視圖支持Presenter和View兩種途徑更新;
優(yōu)點:
- 模型與視圖高度分離,我們可以修改視圖而不影響模型;
- 可以更高效地使用模型,因為所有的交互都發(fā)生在一個地方——Presenter內(nèi)部;
- 可以將一個Presenter用于多個視圖,而不需要改變Presenter的邏輯;
- 如果把邏輯放在Presenter中,就可以脫離用戶接口來測試這些邏輯(單元測試);
缺點:
- 由于對視圖的渲染放在了Presenter中,所以View和Presenter的交互會過于頻繁并且難以維護;
MVVM
MVVM全名是Model-View-ViewModel,本質(zhì)上就是MVC的改進版,也可以說是MVP的改良版,把應用程序分成三部分分別是:
- Model(業(yè)務模型): 用于管理應用程序數(shù)據(jù);
- View(視圖界面): 通過使用模板語法來聲明式的將數(shù)據(jù)渲染進DOM;
- ViewModel(視圖模型): 包含了領域模型(Domain Model)和視圖的狀態(tài)(State),核心就是雙向綁定技術(shù)(Two-Way-Data-Binding),View和Model之間數(shù)據(jù)同步操作交由給內(nèi)部的Binder/Data-binding engine處理;
MVP和MVVM區(qū)別: 它使用 數(shù)據(jù)綁定(Data Binding)、依賴屬性(Dependency Property)、命令(Command)、路由事件(Routed Event) 來搞定與view層的交互, 當ViewModel對Model進行更新的時候,會通過數(shù)據(jù)綁定更新到View.
優(yōu)點:
- 雙向綁定(data-binding):View的變動,自動反映在 ViewModel,反之亦然;
- 解放MVP大量手動同步狀態(tài)的問題,提高了代碼的可維護性;
- 簡化測試,Model正確就能保證View輸出;
缺點:
- 大型項目的綁定數(shù)據(jù)較多會提高維護成本;
- View里的數(shù)據(jù)綁定無法檢測斷點,只能從Model下手;
2 React VS Vue
兩個框架是現(xiàn)在最熱門的選擇之一,它們既類似又不同.
- 使用 Virtual DOM
- 提供了響應式 (Reactive) 和組件化 (Composable) 的視圖組件。
- 將注意力集中保持在核心庫,而將其他功能如路由和全局狀態(tài)管理交給相關的庫。
React就是MVC里的V,只專注視圖層,而Vue算是MVVM框架,雙向綁定是特色之一.
介紹
React
React is a JavaScript library for building user interfaces.
- Declarative: React makes it painless to create interactive UIs. Design simple views for each state in your application, and React will efficiently update and render just the right components when your data changes. Declarative views make your code more predictable, simpler to understand, and easier to debug.
- Component-Based: Build encapsulated components that manage their own state, then compose them to make complex UIs. Since component logic is written in JavaScript instead of templates, you can easily pass rich data through your app and keep state out of the DOM.
- Learn Once, Write Anywhere: We don't make assumptions about the rest of your technology stack, so you can develop new features in React without rewriting existing code. React can also render on the server using Node and power mobile apps using React Native.
翻譯:
React是一個用于構(gòu)建用戶界面的Javascript庫.
- 聲明式: React讓你無痛創(chuàng)建交互式UI界面,為你的App應用程序里的每個狀態(tài)設計簡單的視圖,并且當你的數(shù)據(jù)改變之后會進行高效地更新和正確地渲染對應組件,聲明式視圖讓你的代碼更可預測、更易于理解和更容易調(diào)試.
- 組件化: 構(gòu)建封裝組件管理它們自己的內(nèi)部狀態(tài),然后組合它們?nèi)?gòu)建復雜UI界面.因為組件邏輯寫在Javascript而不是模板里,你能輕松注入豐富的數(shù)據(jù)到你的App并且狀態(tài)脫離在Dom之外.
- 只需學習一次,就能用到任何地方,我們不對你的其余技術(shù)棧作出假設,所以你能在React里開發(fā)新的特性而不需要重寫你的現(xiàn)有代碼.React也能使用Nodejs進行服務器渲染和使用React Native進行移動端的豐富開發(fā).
Vue
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) 是一套構(gòu)建用戶界面的漸進式框架。與其他重量級框架不同的是,Vue 采用自底向上增量開發(fā)的設計。Vue 的核心庫只關注視圖層,它不僅易于上手,還便于與第三方庫或既有項目整合。另一方面,當與單文件組件和 Vue 生態(tài)系統(tǒng)支持的庫結(jié)合使用時,Vue 也完全能夠為復雜的單頁應用程序提供驅(qū)動。
聲明式渲染
React(官方寫法)
React 組件實現(xiàn)一個 render() 方法,它接收輸入數(shù)據(jù)并返回顯示的內(nèi)容。此示例使用類似XML的語法,稱為 JSX 。輸入數(shù)據(jù)可以通過 this.props 傳入組件,被 render() 訪問。
class HelloMessage extends React.Component {
render() {
return (
<div>
Hello {this.props.name}
</div>
);
}
}
ReactDOM.render(
<HelloMessage name="Taylor" />,
mountNode
);
Vue(官方寫法)
Vue.js 的核心是一個允許采用簡潔的模板語法來聲明式的將數(shù)據(jù)渲染進 DOM 的系統(tǒng)
<div id="app">
{{ message }}
</div>
// --------省略--------
var app = new Vue({
el: '#app',
data: {
message: 'Hello Vue!'
}
})
HTML
React JSX
他是 JavaScrip 的一種擴展語法。 React 官方推薦使用這種語法來描述 UI 信息。JSX 可能會讓你想起某種模板語言,但是它具有 JavaScrip 的全部能力,從本質(zhì)上講,JSX 只是為 React.createElement(component, props, ...children) 函數(shù)提供的語法糖。
- JSX 執(zhí)行更快,因為它在編譯為 JavaScript 代碼后進行了優(yōu)化。
- 它是類型安全的,在編譯過程中就能發(fā)現(xiàn)錯誤。
- 使用 JSX 編寫模板更加簡單快速。
JSX 對使用React 不是必須的。
Vue Templates
Vue.js 使用了基于 HTML 的模板語法,允許開發(fā)者聲明式地將 DOM 綁定至底層 Vue 實例的數(shù)據(jù)。所有 Vue.js 的模板都是合法的 HTML ,所以能被遵循規(guī)范的瀏覽器和 HTML 解析器解析。
在底層的實現(xiàn)上,Vue 將模板編譯成虛擬 DOM 渲染函數(shù)。結(jié)合響應系統(tǒng),在應用狀態(tài)改變時,Vue 能夠智能地計算出重新渲染組件的最小代價并應用到 DOM 操作上。
事實上 Vue 也提供了渲染函數(shù),甚至支持 JSX。然而,默認推薦的還是模板。
- 對于很多習慣了 HTML 的開發(fā)者來說,模板比起 JSX 讀寫起來更自然。這里當然有主觀偏好的成分,但如果這種區(qū)別會導致開發(fā)效率的提升,那么它就有客觀的價值存在。
- 基于 HTML 的模板使得將已有的應用逐步遷移到 Vue 更為容易。
- 這也使得設計師和新人開發(fā)者更容易理解和參與到項目中。
- 你甚至可以使用其他模板預處理器,比如 Pug 來書寫 Vue 的模板。
對比
個人感覺兩者其實上手速度都挺快,相比之下JSX除了修改部分屬性名字跟普通HTML變化不算大,Templates額外添加很多自定義功能幫助開發(fā)者做更多的事,框架痕跡也比較重.
我們可以把組件區(qū)分為兩類:一類是偏視圖表現(xiàn)的 (presentational)推薦使用模板,一類則是偏邏輯的 (logical)推薦使用 JSX 或渲染函數(shù)。
//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>
CSS
React
React 中推薦通過 CSS-in-JS 的方案實現(xiàn)的 (比如 styled-components、glamorous 和 emotion),雖然在構(gòu)建時將 CSS 提取到一個單獨的樣式表是支持的,但 bundle 里通常還是需要一個運行時程序來讓這些樣式生效。當你能夠利用 JavaScript 靈活處理樣式的同時,也需要權(quán)衡 bundle 的尺寸和運行時的開銷
var styleObj = { color:"blue", fontSize:40, fontWeight:"normal" };
--------省略--------
<h1 style={styleObj} className="alert-text">Hello</h1>
這個可選 scoped 屬性會自動添加一個唯一的屬性 (比如 data-v-21e5b78) 為組件內(nèi) CSS 指定作用域,編譯的時候 .list-container:hover 會被編譯成類似 .list-container[data-v-21e5b78]:hover。
最后,Vue 的單文件組件里的樣式設置是非常靈活的。通過 vue-loader,你可以使用任意預處理器、后處理器,甚至深度集成 CSS Modules——全部都在 <style> 標簽內(nèi)。
對比
各有好壞吧,React侵入式做法不太喜歡,Vue組件式做法倒也還行,個人而言更傾向獨立css樣式外部引入易于管理.
狀態(tài)(State) OR 屬性(Data)
React
State是私有的,并且由組件本身完全控制。
不要直接修改 state(狀態(tài)),setState() 代替;
// 錯誤
this.state.comment = 'Hello';
// 正確
this.setState({comment: 'Hello'});
state(狀態(tài))更新會被合并,React 為了優(yōu)化性能,有可能會將多個setState()調(diào)用合并為一次更新;
componentDidMount() {
fetchPosts().then(response => {
this.setState({
posts: response.posts
});
});
fetchComments().then(response => {
this.setState({
comments: response.comments
});
});
}
state(狀態(tài)) 更新可能是異步的,不能依賴他們的值計算下一個state(狀態(tài));
// 錯誤
this.setState({
counter: this.state.counter + this.props.increment,
});
// 正確
//另一種 setState() 的形式,它接受一個函數(shù)而不是一個對象。這個函數(shù)將接收前一個狀態(tài)作為第一個參數(shù),應用更新時的 props 作為第二個參數(shù)
this.setState((prevState, props) => ({
counter: prevState.counter + props.increment
}));
Vue
當一個 Vue 實例被創(chuàng)建時,它向 Vue 的響應式系統(tǒng)中加入了其 data 對象中能找到的所有的屬性。當這些屬性的值發(fā)生改變時,視圖將會產(chǎn)生“響應”,即匹配更新為新的值。
只有當實例被創(chuàng)建時 data 中存在的屬性是響應式的
// 有效
data: {
visitCount1: 0
}
--------省略--------
// 觸發(fā)任何視圖的更新
vm.visitCount1 = 2
// 不會觸發(fā)任何視圖的更新
vm.visitCount2 = 2
對比
React是屬于單向控制,即只能是通過改變State從而改變視圖,我們可以利用JS方法像表單等場景模擬雙向綁定的效果,實際還是由State去觸發(fā)視圖更新
Vue是屬于雙向綁定,原理是通過 Object.defineProperty 監(jiān)聽劫持data對象的getter/setter屬性來實現(xiàn)的
Props組件通信
一個組件可以選擇將數(shù)據(jù)向下傳遞,作為其子組件的 props(屬性).
React
父組件單向控制
//父組件傳遞數(shù)據(jù)
<Child num={this.state.num} />
//--------省略--------
//子組件讀取數(shù)據(jù)
<h2>It is {this.props.num}.</h2>
子組件自主控制
//父組件傳遞數(shù)據(jù)
<Child num={this.state.num} />
//--------省略--------
constructor(props) {
super(props);
this.state = {
//初始化成state
num: this.props.num,
};
}
父子組件雙向控制
//傳遞修改函數(shù)
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)} />;
}
}
//調(diào)用修改函數(shù)添加入?yún)?class Child extends Component {
render() {
<button onClicked={() => this.props.onClicked({num: 2})}>
It is {this.props.num}.
</button>;
}
}
Vue
父組件單向控制
//父組件傳遞數(shù)據(jù)
<child message="hello!"></child>
//--------省略--------
//子組件要顯式地用 props 選項聲明它預期的數(shù)據(jù)
Vue.component('child', {
// 聲明 props
props: ['message'],
// 就像 data 一樣,prop 也可以在模板中使用
// 同樣也可以在 vm 實例中通過 this.message 來使用
template: '<span>{{ message }}</span>'
})
子組件自主控制
//父組件傳遞數(shù)據(jù)
<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>'
父子組件雙向控制
//父組件傳遞數(shù)據(jù)
<child v-bind:message="message" v-on:update:message="message = $event"></child>
//--------可用.sync 修飾符替代--------
//后面?zhèn)鬟f的message是變量,非字符串
//<child :message.sync="message"></child>
//--------省略--------
//子組件
props: ['message'],
data: function () {
return { msg: this.message }
}
watch: {
//監(jiān)聽msg變化自動通信父組件更改
msg(val) {
this.$emit('update:message', newMsg)
},
},
生命周期對比
React
React生命周期 | 作用 |
---|---|
getDefaultProps | 作用于組件類,只調(diào)用一次,返回對象用于設置默認的props,對于引用值,會在實例中共享 |
getInitialState | 作用于組件的實例,在實例創(chuàng)建時調(diào)用一次,用于初始化每個實例的state,此時可以訪問this.props。 |
componentWillMount | 在完成首次渲染之前調(diào)用,此時仍可以修改組件的state |
render | 必選的方法,創(chuàng)建虛擬DOM,該方法具有特殊的規(guī)則: 1) 只能通過this.props和this.state訪問數(shù)據(jù); 2) 可以返回null、false或任何React組件; 3) 只能出現(xiàn)一個頂級組件(不能返回數(shù)組); 4) 不能改變組件的狀態(tài); 5) 不能修改DOM的輸出; |
componentDidMount | 真實的DOM被渲染出來后調(diào)用,在該方法中可通過this.getDOMNode()訪問到真實的DOM元素。此時已可以使用其他類庫來操作這個DOM。在服務端中,該方法不會被調(diào)用。 |
componentWillReceiveProps | 組件接收到新的props時調(diào)用,并將其作為參數(shù)nextProps使用,此時可以更改組件props及state |
shouldComponentUpdate | 組件是否應當渲染新的props或state,返回false表示跳過后續(xù)的生命周期方法,通常不需要使用以避免出現(xiàn)bug。在出現(xiàn)應用的瓶頸時,可通過該方法進行適當?shù)膬?yōu)化。在首次渲染期間或者調(diào)用了forceUpdate方法后,該方法不會被調(diào)用 |
componentWillUpdate | 接收到新的props或者state后,進行渲染之前調(diào)用,此時不允許更新props或state。 |
componentDidUpdate | 完成渲染新的props或者state后調(diào)用,此時可以訪問到新的DOM元素。 |
componentWillUnmount | 組件被移除之前被調(diào)用,可以用于做一些清理工作,在componentDidMount方法中添加的所有任務都需要在該方法中撤銷,比如創(chuàng)建的定時器或添加的事件監(jiān)聽器 |
componentDidCatch | 16.x新增,捕獲全局異常來進行頁面的友好提示 |
Vue生命周期 | 作用 |
---|---|
beforeCreate | 在實例初始化之后,數(shù)據(jù)觀測 (data observer) 和 event/watcher 事件配置之前被調(diào)用 |
created | 在實例創(chuàng)建完成后被立即調(diào)用。在這一步,實例已完成以下的配置:數(shù)據(jù)觀測 (data observer),屬性和方法的運算,watch/event 事件回調(diào)。然而,掛載階段還沒開始,$el 屬性目前不可見 |
beforeMount | 在掛載開始之前被調(diào)用:相關的 render 函數(shù)首次被調(diào)用。該鉤子在服務器端渲染期間不被調(diào)用 |
mounted | el 被新創(chuàng)建的 vm. |
beforeUpdate | 數(shù)據(jù)更新時調(diào)用,發(fā)生在虛擬 DOM 重新渲染和打補丁之前。你可以在這個鉤子中進一步地更改狀態(tài),這不會觸發(fā)附加的重渲染過程。該鉤子在服務器端渲染期間不被調(diào)用 |
updated | 由于數(shù)據(jù)更改導致的虛擬 DOM 重新渲染和打補丁,在這之后會調(diào)用該鉤子。當這個鉤子被調(diào)用時,組件 DOM 已經(jīng)更新,所以你現(xiàn)在可以執(zhí)行依賴于 DOM 的操作。然而在大多數(shù)情況下,你應該避免在此期間更改狀態(tài)。如果要相應狀態(tài)改變,通常最好使用計算屬性或 watcher 取而代之,注意 updated 不會承諾所有的子組件也都一起被重繪。如果你希望等到整個視圖都重繪完畢,可以用 vm.$nextTick 替換掉 updated:該鉤子在服務器端渲染期間不被調(diào)用 |
activated | keep-alive 組件激活時調(diào)用。該鉤子在服務器端渲染期間不被調(diào)用 |
deactivated | keep-alive 組件停用時調(diào)用。該鉤子在服務器端渲染期間不被調(diào)用 |
beforeDestroy | 實例銷毀之前調(diào)用。在這一步,實例仍然完全可用。該鉤子在服務器端渲染期間不被調(diào)用。 |
destroyed | Vue 實例銷毀后調(diào)用。調(diào)用后,Vue 實例指示的所有東西都會解綁定,所有的事件監(jiān)聽器會被移除,所有的子實例也會被銷毀 |
errorCaptured | 當捕獲一個來自子孫組件的錯誤時被調(diào)用。此鉤子會收到三個參數(shù):錯誤對象、發(fā)生錯誤的組件實例以及一個包含錯誤來源信息的字符串。此鉤子可以返回 false 以阻止該錯誤繼續(xù)向上傳播 |
React示意圖
Vue示意圖
再渲染性能
React
在React里渲染機制是以組件單位更新的,也就是說當數(shù)據(jù)發(fā)生改變,當前視圖包括其中的組件子組件和底下的子組件都會一起更新,這種違反性能的機制肯定是有問題的,所以React提供了生命周期shouldComponentUpdate方法讓你決定當前組件是否更新,還有一個PureComponent方法會自動檢測到state或者props發(fā)生變化時,才會調(diào)用render方法.但是只是淺比較,如果搭配ImmutableJs持久化數(shù)據(jù)據(jù)說性能大大的提升.除此之外還能節(jié)省大量的手動比較的代碼和時間,
簡單描述過程
- 調(diào)用render函數(shù)利用JS生成虛擬Dom樹,直到數(shù)據(jù)state/props發(fā)生改變的時候,render函數(shù)會被再次調(diào)用渲染出另外一棵虛擬Dom樹;
- 比較前后兩棵Dom樹同層級的節(jié)點區(qū)別,非同層級節(jié)點包括所屬子節(jié)點整個直接刪除重新創(chuàng)建;
- 不同的節(jié)點類型,直接替換.
- 相同節(jié)點類型當中的DOM節(jié)點,替換屬性.
- 相同類型當中的組件節(jié)點,繼續(xù)遞歸比較所屬子節(jié)點.
- DOM節(jié)點的遞歸children,繼續(xù)遞歸比較children.
- 列表比較.賦予唯一的key作比較.
- 更新視圖中差異地方;
Vue
在 Vue 應用中,組件的依賴是在渲染過程中自動追蹤的,所以系統(tǒng)能精確知曉哪個組件確實需要被重渲染,因為Vue是使用 Object.defineProperty對綁定屬性進行數(shù)據(jù)劫持的,所以比起React組件式更新它能夠精確接收到哪些組件才是需要渲染的.
- Vue 將遍歷此data對象所有的屬性,并使用 Object.defineProperty 把這些屬性全部轉(zhuǎn)為 getter/setter.
- 每個組件實例都有相應的 watcher 實例對象,它會在組件渲染的過程中把屬性記錄為依賴.
- 在屬性被訪問和修改時通知對應的組件.
- 對應的組件再次調(diào)動渲染函數(shù),生成虛擬Dom樹對比,實現(xiàn)更新.
路由
React-Router
這是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-Router
Vue-Router3的模板寫法
// 0. 如果使用模塊化機制編程,導入Vue和VueRouter,要調(diào)用 Vue.use(VueRouter)
// 1. 定義(路由)組件。
// 可以從其他文件 import 進來
const Foo = { template: '<div>foo</div>' }
const Bar = { template: '<div>bar</div>' }
// 2. 定義路由
// 每個路由應該映射一個組件。 其中"component" 可以是
// 通過 Vue.extend() 創(chuàng)建的組件構(gòu)造器,
// 或者,只是一個組件配置對象。
// 我們晚點再討論嵌套路由。
const routes = [
{ path: '/foo', component: Foo },
{ path: '/bar', component: Bar }
]
// 3. 創(chuàng)建 router 實例,然后傳 `routes` 配置
// 你還可以傳別的配置參數(shù), 不過先這么簡單著吧。
const router = new VueRouter({
routes // (縮寫)相當于 routes: routes
})
// 4. 創(chuàng)建和掛載根實例。
// 記得要通過 router 配置參數(shù)注入路由,
// 從而讓整個應用都有路由功能
const app = new Vue({
router
}).$mount('#app')
// 現(xiàn)在,應用已經(jīng)啟動了!
狀態(tài)管理
狀態(tài)管理庫有很多種,我只是舉出我用過的例子,并不是必須的.下面只會展示最基本的代碼,想跑完整流程還得看文檔.
Redux
Redux 可以用這三個基本原則來描述:
- 單一數(shù)據(jù)源: 整個應用的 state 被儲存在一棵 object tree 中,并且這個 object tree 只存在于唯一一個 store 中。
- State 是只讀的: 唯一改變 state 的方法就是觸發(fā) action,action 是一個用于描述已發(fā)生事件的普通對象。
- 使用純函數(shù)來執(zhí)行修改: 為了描述 action 如何改變 state tree ,你需要編寫 reducers。
Actions: 把數(shù)據(jù)從應用傳到store的有效載荷。它是store數(shù)據(jù)的唯一來源.
/*
* action 類型
*/
export const ADD_TODO = 'ADD_TODO';
/*
* action 創(chuàng)建函數(shù)
*/
export function addTodo(text) {
return {
type: ADD_TODO,
text
}
}
Reducers: 指定了應用狀態(tài)的變化如何響應actions并發(fā)送到store的,記住actions只是描述了有事情發(fā)生了這一事實,并沒有描述應用如何更新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聯(lián)系到一起的對象.
- 維持應用的 state;
- 提供 getState() 方法獲取 state;
- 提供 dispatch(action) 方法更新 state;
- 通過 subscribe(listener) 注冊監(jiān)聽器;
- 通過 subscribe(listener) 返回的函數(shù)注銷監(jiān)聽器。
import { createStore } from 'redux'
import todoApp from './reducers'
let store = createStore(todoApp)
單向數(shù)據(jù)流
- 定義Action描述對象;
- 通過dispatcher觸發(fā)Action對象;
- Reducer響應變化更新到Store狀態(tài)管理對象;
- 注入Store狀態(tài)的組件視圖更新;
- 界面交互觸發(fā)Action再次跑相應流程;
進階:
- Action利用中間件發(fā)起異步請求;
- Reducer邏輯拆分;
- 組件注入部分Store狀態(tài);
等等,Redux 中文文檔,(更多內(nèi)容請自行查閱,本節(jié)到此為止了.)
Mobx
另一種實現(xiàn)方式,具體可看Mobx4.X狀態(tài)管理入門
Vuex
Vuex 是一個專為 Vue.js 應用程序開發(fā)的狀態(tài)管理模式。它采用集中式存儲管理應用的所有組件的狀態(tài),并以相應的規(guī)則保證狀態(tài)以一種可預測的方式發(fā)生變化。Vuex 也集成到 Vue 的官方調(diào)試工具 devtools extension,提供了諸如零配置的 time-travel 調(diào)試、狀態(tài)快照導入導出等高級調(diào)試功能。
Mutation: 必須是同步函數(shù),更改Vuex的store中的狀態(tài)的唯一方法是提交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 風格的計算屬性命名功能來使用一個常量作為函數(shù)名
[SOME_MUTATION](state) {
// mutate state
}
}
})
Action 類似于mutation,不同在于:
- Action 提交的是 mutation,而不是直接變更狀態(tài);
- Action 可以包含任意異步操作;
actions: {
incrementAsync ({ commit }) {
setTimeout(() => {
commit('increment')
}, 1000)
}
}
Getter: 從store中的state中派生出一些狀態(tài)
getters: {
// ...
doneTodosCount: (state, getters) => {
return getters.doneTodos.length
}
}
State: 包含了全部的應用層級狀態(tài)。至此它便作為一個“唯一數(shù)據(jù)源 (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 的狀態(tài)
store.state.b // -> moduleB 的狀態(tài)
單向數(shù)據(jù)流
(官網(wǎng)來的)
Vuex
對比
Redux的Action可以是異步或者同步函數(shù)返回Javascript對象,Vuex分為同步函數(shù)不限制格式的mutation和可包含異步函數(shù)的action
React直接dispatch action觸發(fā)狀態(tài)更新,Vuex是dispatch action提交mutation再commmit觸發(fā)狀態(tài)更新
-
Redux指定了應用狀態(tài)的變化如何響應actions并發(fā)送到store的reducer,且必須為純函數(shù).Vuex實際上是使用mutation更新狀態(tài)
-- Redux: (dispatch)action -> (reducer)store
-- Vuex: (dispatch)action -> (commit)mutation -> (mutate)store Vuex可以用getter派生出一些狀態(tài),就像計算函數(shù)會被緩存起來只有依賴變化之后才會重新計算.
(更多內(nèi)容請自行查閱,本節(jié)到此為止了.)
官方腳手架
React 官方提供了 create-react-app,詬病的地方比較多.
- 它不允許在項目生成時進行任何配置,而 Vue 支持 Yeoman-like 定制。
- 它只提供一個構(gòu)建單頁面應用的單一模板,而 Vue 提供了各種用途的模板。
- 它不能用用戶自建的模板構(gòu)建項目,而自建模板對企業(yè)環(huán)境下預先建立協(xié)議是特別有用的。
更多人選擇自己搭建或者使用民間打包庫.
Vue 提供了 Vue-cli 腳手架,能讓你非常容易地構(gòu)建項目,包含了 Webpack,Browserify,甚至 no build system,但是有些設置例如Scss預處理器等自定義配置需要自己搞,總的來說相當實用.
入門難度
React正常來說需要搭配Jsx和Es6語法和構(gòu)建環(huán)境;
Vue可以直接引入js庫就能開發(fā),而且內(nèi)置的功能屬性比React多得多