背景
React是=起源于 Facebook 的內(nèi)部項目,因為該公司對市場上所有 JavaScript MVC 都不滿意,就決定自己寫一套,用來架設(shè) Instagram 的網(wǎng)站,2013年5月開源
WHAT
官網(wǎng)是這樣解釋的 —— A JavaScript library for building user interfaces,三大特性如下:
-
Declarative
聲明式編程,告訴機(jī)器你想要什么(What),讓機(jī)器想出如何做(How),
對應(yīng)的是命令式編程,命令機(jī)器如何去做某件事(How),而不管你要的是什么,它都會按照你的命令實現(xiàn)。 -
Component-Based
任何一個功能獨立的模塊都定義成組件,一個個組件通過不斷復(fù)用,組合與嵌套,構(gòu)成一套完整的UI界面。 - Learn Once, Write Anywhere
核心思想
組件化思想
React 讓我們重新規(guī)劃界面,把任何一個功能獨立的模塊都定義成組件,即被獨立封裝的可復(fù)用 UI 部件。一個個的組件通過不斷復(fù)用,組合與嵌套等,構(gòu)成一套完整的 UI 界面。
一個組件的寫法
import React, { Component } from 'react';
import { render } from 'react-dom';
class HelloMessage extends Component {
render() {
return <div>Hello {this.props.name}</div>;
}
}
render(<HelloMessage name="world" />, mountNode);
render
會把這個組件顯示到頁面上的某個元素mountNode
里面,顯示的內(nèi)容就是<div>Hello world</div>
組件的劃分
-
把 UI 劃分出組件層級,可以想想什么情況下我們需要新建一個函數(shù)或?qū)ο?/strong>)
- 單一功能原則, DRY
- 一個組件應(yīng)該只做一件事情。如果這個組件功能不斷豐富,它應(yīng)該被分成更小的組件
JSX語法——有些人喜歡它,而其他人認(rèn)為這是一個很大的退步,
不得不說是一種非常聰明的做法,JSX代替?zhèn)鹘y(tǒng)的HTML Templates,讓前端實現(xiàn)真正意義上的組件化成為了可能
HTML直接嵌入了JS代碼里面,前端被“表現(xiàn)和邏輯層分離”這種思想“洗腦”太久了,可能不好接受的設(shè)定之一。
實際上組件的HTML是組成一個組件不可分割的一部分,能夠?qū)TML封裝起來才是組件的完全體,React發(fā)明了JSX讓JS支持嵌入HTML,但它具有無可爭議的優(yōu)點:靜態(tài)分析,JSX標(biāo)記中發(fā)生錯誤,編譯器會立即報錯而不是留待運行時出現(xiàn)莫名其妙的問題。這有助于開發(fā)人員快速排查錯誤以及避免其它愚蠢的錯誤,比如拼寫錯誤。
好處是你可以不一定使用這種語法,但是要使用包含JSX 的組件,是需要“編譯”輸出JS 代碼才能使用的
Virtual DOM
DOM是什么?
DOM是Document Object Model,就是將XML(或者HTML)內(nèi)的節(jié)點定義成基本統(tǒng)一的對象數(shù)據(jù)可以供程序語言(如javaScript)控制的技術(shù)規(guī)范
通過 DOM 你可以改變網(wǎng)頁。
你可以使用 Javascript 語言來操作 DOM 以改變網(wǎng)頁。
為了改變網(wǎng)頁,你必須告訴 Javascript 改變哪一個節(jié)點。這就是操作 DOM。
真正的 DOM 元素非常龐大,這是因為標(biāo)準(zhǔn)就是這么設(shè)計的。而且操作它們的時候你要小心翼翼,輕微的觸碰可能就會導(dǎo)致頁面重排,這可是殺死性能的罪魁禍?zhǔn)住?/p>
React 最大的特色是當(dāng)View層在渲染的時候,它不會直接從模板里面去構(gòu)建一個DOM節(jié)點. 首先, 它創(chuàng)建一些暫時的, 虛擬的 DOM, 然后和真實的DOM還有創(chuàng)建的Diffs一起做對比, 然后才決定需不需要渲染。
當(dāng)組件狀態(tài)state有更改的時候,React會自動調(diào)用組件的render方法重新渲染整個組件的UI,所以React實現(xiàn)了一個Virtual DOM,組件DOM結(jié)構(gòu)就是映射到這個Virtual DOM上,React在這個Virtual DOM上實現(xiàn)了一個diff算法,當(dāng)要重新渲染組件的時候,會通過diff尋找到要變更的DOM節(jié)點,再把這個修改更新到瀏覽器實際的DOM節(jié)點上,所以實際上不是真的渲染整個DOM樹。這個Virtual DOM是一個純粹的JS數(shù)據(jù)結(jié)構(gòu),所以性能會比原生DOM快很多。
Virtual DOM 本質(zhì)上就是在 JS 和 DOM 之間做了一個緩存。可以類比 CPU 和硬盤,既然硬盤這么慢,我們就在它們之間加個緩存:既然 DOM 這么慢,我們就在它們 JS 和 DOM 之間加個緩存。CPU(JS)只操作內(nèi)存(Virtual DOM),最后的時候再把變更寫入硬盤(DOM)
Virtual DOM 算法:
觸發(fā)相應(yīng)組件render方法
重新構(gòu)建新的虛擬DOM樹
將當(dāng)前新的虛擬DOM樹和上一次的舊樹進(jìn)行對比
得到DOM結(jié)構(gòu)的區(qū)別,計算出最小變化集,進(jìn)行實際的瀏覽器DOM更新(批量更新)。
Data Flow
數(shù)據(jù)如何存放,如何更改數(shù)據(jù),如何通知數(shù)據(jù)更改等等,單向響應(yīng)的數(shù)據(jù)流,從而減少了重復(fù)代碼,這也是它為什么比傳統(tǒng)數(shù)據(jù)綁定更簡單。
接受輸入數(shù)據(jù)(通過 this.props ),組件還可以保持內(nèi)部狀態(tài)數(shù)據(jù)(通過 this.state )當(dāng)一個組件的狀態(tài)數(shù)據(jù)的變化。
React 組件之間交流的方式
組件免不了要與用戶互動,React 的一大創(chuàng)新,就是將組件看成是一個狀態(tài)機(jī),一開始有一個初始狀態(tài),然后用戶互動,導(dǎo)致狀態(tài)變化,從而觸發(fā)重新渲染 UI。
- 【父組件】向【子組件】傳值;
父組件的數(shù)據(jù)可以通過設(shè)置子組件的props傳遞數(shù)據(jù)給子組件 - 【子組件】向【父組件】傳值;
可以在父組件中傳一個callback(回調(diào)函數(shù))給子組件,子組件內(nèi)調(diào)用這個callback即可改變父組件的數(shù)據(jù) - 兄弟組件之間傳值
沒有任何嵌套關(guān)系的組件之間傳值(PS:比如:兄弟組件之間傳值)
當(dāng)兩個組件不是父子關(guān)系,但有相同的父組件時,將這兩個組件稱為兄弟組件。兄弟組件不能直接相互傳送數(shù)據(jù),此時可以將數(shù)據(jù)掛載在父組件中,由兩個組件共享:如果組件需要數(shù)據(jù)渲染,則由父組件通過props傳遞給該組件;如果組件需要改變數(shù)據(jù),則父組件傳遞一個改變數(shù)據(jù)的回調(diào)函數(shù)給該組件,并在對應(yīng)事件中調(diào)用。
寫了個簡單的例子如下:
//孫子,將下拉選項的值傳給爺爺
var Grandson = React.createClass({
render: function(){
return (
<div>性別:
<select onChange={this.props.handleSelect}>
<option value="男">男</option>
<option value="女">女</option>
</select>
</div>
)
}
});
//子,將用戶輸入的姓名傳給爹
//對于孫子的處理函數(shù),父只需用props傳下去即可
var Child = React.createClass({
render: function(){
return (
<div>
姓名:<input onChange={this.props.handleVal}/>
<Grandson handleSelect={this.props.handleSelect}/>
</div>
)
}
});
//父組件,準(zhǔn)備了兩個state,username和sex用來接收子孫傳過來的值,對應(yīng)兩個函數(shù)handleVal和handleSelect
var Parent = React.createClass({
getInitialState: function(){
return {
username: '',
sex: ''
}
},
handleVal: function(event){
this.setState({username: event.target.value});
},
handleSelect: function(event) {
this.setState({sex: event.target.value});
},
render: function(){
return (
<div>
<div>用戶姓名:{this.state.username}</div>
<div>用戶性別:{this.state.sex}</div>
<Child handleVal={this.handleVal.bind(this)} handleSelect={this.handleSelect.bind(this)}/>
</div>
)
}
});
React.render(
<Parent />,
document.getElementById('test')
);
- Props 傳遞數(shù)據(jù)
如果組件嵌套層次太深,那么從外到內(nèi)組件的交流成本就變得很高,通過 props 傳遞值的優(yōu)勢就不那么明顯了。所以個人建議盡可能的減少組件的層次,就像寫 HTML 一樣,簡單清晰的結(jié)構(gòu)更惹人愛)
- 難的地方
在創(chuàng)建應(yīng)用的每一個 state之前要做的: - 確定每一個需要這個 state 來渲染的組件。
- 找到一個公共所有者組件(一個在層級上高于所有其他需要這個 state 的組件的組件)
- 這個公共所有者組件或另一個層級更高的組件應(yīng)該擁有這個 state。
- 如果你沒有找到可以擁有這個 state 的組件,創(chuàng)建一個僅用來保存狀態(tài)的組件并把它加入比這個公共所有者組件層級更高的地方
- 當(dāng)應(yīng)用足夠復(fù)雜時才能體會到它的好處,雖然在一般應(yīng)用場景下你可能不會意識到它的存在
兄弟組件的溝通的解決方案就是找到兩個組件共同的父組件,一層一層的調(diào)用上一層的回調(diào),再一層一層地傳遞props。如果組件樹嵌套太深,就會出現(xiàn)如下慘不忍睹的組件親戚調(diào)用圖
- 全局事件
Context 使用上下文可以讓子組件直接訪問祖先的數(shù)據(jù)或函數(shù),無需從祖先組件一層層地傳遞數(shù)據(jù)到子組件中
第三方的類庫
React讓我們有很大的自由度去挑選第三方的類庫,比如
路由機(jī)制
各種CSS封裝(詳見:github.com/MicheleBert…)
更強(qiáng)大的單元測試(Enzyme)
Remarkable 渲染markdown語法
Redux
Redux是一種組織代碼的推薦思想,就像 “引導(dǎo)數(shù)據(jù)流流向的導(dǎo)流管”,解決上面那幅關(guān)系復(fù)雜的親戚圖問題
整個應(yīng)用只有唯一一個可信數(shù)據(jù)源,也就是只有一個Store,如 Redux 限定一個應(yīng)用中只能有單一的 store,這樣的限定能夠讓應(yīng)用中數(shù)據(jù)結(jié)果集中化,提高可控性
Action、Reducer、及 Store
State 只能通過觸發(fā)Action 來更改
State 的更改必須寫成純函數(shù),也就是每次更改總是返回一個新的State,在*Redux 里這種函數(shù)稱為Reducer
Redux 對于組件間的解耦提供了很大的便利,如果你在考慮該不該使用 Redux 的時候,社區(qū)里有一句話說,“當(dāng)你不知道該不該使用 Redux 的時候,那就是不需要的”
Redux 用起來一時爽,重構(gòu)或者將項目留給后人的時候,就是個大坑,Redux 中的 dispatch 和 subscribe 方法遍布代碼的每一個角落。