背景
React是=起源于 Facebook 的內(nèi)部項(xiàng)目浇衬,因?yàn)樵摴緦κ袌錾纤?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)蚁阳,而不管你要的是什么铃绒,它都會按照你的命令實(shí)現(xiàn)。 -
Component-Based
任何一個(gè)功能獨(dú)立的模塊都定義成組件螺捐,一個(gè)個(gè)組件通過不斷復(fù)用颠悬,組合與嵌套,構(gòu)成一套完整的UI界面定血。 - Learn Once, Write Anywhere
核心思想
組件化思想
React 讓我們重新規(guī)劃界面赔癌,把任何一個(gè)功能獨(dú)立的模塊都定義成組件,即被獨(dú)立封裝的可復(fù)用 UI 部件澜沟。一個(gè)個(gè)的組件通過不斷復(fù)用灾票,組合與嵌套等,構(gòu)成一套完整的 UI 界面茫虽。
一個(gè)組件的寫法
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
會把這個(gè)組件顯示到頁面上的某個(gè)元素mountNode
里面刊苍,顯示的內(nèi)容就是<div>Hello world</div>
組件的劃分
-
把 UI 劃分出組件層級,可以想想什么情況下我們需要新建一個(gè)函數(shù)或?qū)ο?/strong>)
- 單一功能原則濒析, DRY
- 一個(gè)組件應(yīng)該只做一件事情班缰。如果這個(gè)組件功能不斷豐富,它應(yīng)該被分成更小的組件
JSX語法——有些人喜歡它悼枢,而其他人認(rèn)為這是一個(gè)很大的退步,
不得不說是一種非常聰明的做法脾拆,JSX代替?zhèn)鹘y(tǒng)的HTML Templates馒索,讓前端實(shí)現(xiàn)真正意義上的組件化成為了可能
HTML直接嵌入了JS代碼里面,前端被“表現(xiàn)和邏輯層分離”這種思想“洗腦”太久了名船,可能不好接受的設(shè)定之一绰上。
實(shí)際上組件的HTML是組成一個(gè)組件不可分割的一部分,能夠?qū)TML封裝起來才是組件的完全體渠驼,React發(fā)明了JSX讓JS支持嵌入HTML蜈块,但它具有無可爭議的優(yōu)點(diǎn):靜態(tài)分析,JSX標(biāo)記中發(fā)生錯(cuò)誤迷扇,編譯器會立即報(bào)錯(cuò)而不是留待運(yùn)行時(shí)出現(xiàn)莫名其妙的問題百揭。這有助于開發(fā)人員快速排查錯(cuò)誤以及避免其它愚蠢的錯(cuò)誤,比如拼寫錯(cuò)誤蜓席。
好處是你可以不一定使用這種語法器一,但是要使用包含JSX 的組件,是需要“編譯”輸出JS 代碼才能使用的
Virtual DOM
DOM是什么厨内?
DOM是Document Object Model祈秕,就是將XML(或者HTML)內(nèi)的節(jié)點(diǎn)定義成基本統(tǒng)一的對象數(shù)據(jù)可以供程序語言(如javaScript)控制的技術(shù)規(guī)范
通過 DOM 你可以改變網(wǎng)頁渺贤。
你可以使用 Javascript 語言來操作 DOM 以改變網(wǎng)頁。
為了改變網(wǎng)頁请毛,你必須告訴 Javascript 改變哪一個(gè)節(jié)點(diǎn)志鞍。這就是操作 DOM。
真正的 DOM 元素非常龐大方仿,這是因?yàn)闃?biāo)準(zhǔn)就是這么設(shè)計(jì)的固棚。而且操作它們的時(shí)候你要小心翼翼,輕微的觸碰可能就會導(dǎo)致頁面重排玻孟,這可是殺死性能的罪魁禍?zhǔn)住?/p>
React 最大的特色是當(dāng)View層在渲染的時(shí)候,它不會直接從模板里面去構(gòu)建一個(gè)DOM節(jié)點(diǎn). 首先, 它創(chuàng)建一些暫時(shí)的, 虛擬的 DOM, 然后和真實(shí)的DOM還有創(chuàng)建的Diffs一起做對比, 然后才決定需不需要渲染。
當(dāng)組件狀態(tài)state有更改的時(shí)候鳍征,React會自動調(diào)用組件的render方法重新渲染整個(gè)組件的UI黍翎,所以React實(shí)現(xiàn)了一個(gè)Virtual DOM,組件DOM結(jié)構(gòu)就是映射到這個(gè)Virtual DOM上艳丛,React在這個(gè)Virtual DOM上實(shí)現(xiàn)了一個(gè)diff算法匣掸,當(dāng)要重新渲染組件的時(shí)候,會通過diff尋找到要變更的DOM節(jié)點(diǎn)氮双,再把這個(gè)修改更新到瀏覽器實(shí)際的DOM節(jié)點(diǎn)上碰酝,所以實(shí)際上不是真的渲染整個(gè)DOM樹。這個(gè)Virtual DOM是一個(gè)純粹的JS數(shù)據(jù)結(jié)構(gòu)戴差,所以性能會比原生DOM快很多送爸。
Virtual DOM 本質(zhì)上就是在 JS 和 DOM 之間做了一個(gè)緩存∨停可以類比 CPU 和硬盤袭厂,既然硬盤這么慢,我們就在它們之間加個(gè)緩存:既然 DOM 這么慢球匕,我們就在它們 JS 和 DOM 之間加個(gè)緩存纹磺。CPU(JS)只操作內(nèi)存(Virtual DOM),最后的時(shí)候再把變更寫入硬盤(DOM)
Virtual DOM 算法:
觸發(fā)相應(yīng)組件render方法
重新構(gòu)建新的虛擬DOM樹
將當(dāng)前新的虛擬DOM樹和上一次的舊樹進(jìn)行對比
得到DOM結(jié)構(gòu)的區(qū)別亮曹,計(jì)算出最小變化集橄杨,進(jìn)行實(shí)際的瀏覽器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)一個(gè)組件的狀態(tài)數(shù)據(jù)的變化氏义。
React 組件之間交流的方式
組件免不了要與用戶互動锄列,React 的一大創(chuàng)新,就是將組件看成是一個(gè)狀態(tài)機(jī)惯悠,一開始有一個(gè)初始狀態(tài)邻邮,然后用戶互動,導(dǎo)致狀態(tài)變化克婶,從而觸發(fā)重新渲染 UI筒严。
- 【父組件】向【子組件】傳值;
父組件的數(shù)據(jù)可以通過設(shè)置子組件的props傳遞數(shù)據(jù)給子組件 - 【子組件】向【父組件】傳值情萤;
可以在父組件中傳一個(gè)callback(回調(diào)函數(shù))給子組件鸭蛙,子組件內(nèi)調(diào)用這個(gè)callback即可改變父組件的數(shù)據(jù) - 兄弟組件之間傳值
沒有任何嵌套關(guān)系的組件之間傳值(PS:比如:兄弟組件之間傳值)
當(dāng)兩個(gè)組件不是父子關(guān)系,但有相同的父組件時(shí)筋岛,將這兩個(gè)組件稱為兄弟組件娶视。兄弟組件不能直接相互傳送數(shù)據(jù),此時(shí)可以將數(shù)據(jù)掛載在父組件中睁宰,由兩個(gè)組件共享:如果組件需要數(shù)據(jù)渲染肪获,則由父組件通過props傳遞給該組件;如果組件需要改變數(shù)據(jù)柒傻,則父組件傳遞一個(gè)改變數(shù)據(jù)的回調(diào)函數(shù)給該組件孝赫,并在對應(yīng)事件中調(diào)用。
寫了個(gè)簡單的例子如下:
//孫子红符,將下拉選項(xiàng)的值傳給爺爺
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)備了兩個(gè)state刹前,username和sex用來接收子孫傳過來的值,對應(yīng)兩個(gè)函數(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)勢就不那么明顯了祖今。所以個(gè)人建議盡可能的減少組件的層次校坑,就像寫 HTML 一樣,簡單清晰的結(jié)構(gòu)更惹人愛)
- 難的地方
在創(chuàng)建應(yīng)用的每一個(gè) state之前要做的: - 確定每一個(gè)需要這個(gè) state 來渲染的組件千诬。
- 找到一個(gè)公共所有者組件(一個(gè)在層級上高于所有其他需要這個(gè) state 的組件的組件)
- 這個(gè)公共所有者組件或另一個(gè)層級更高的組件應(yīng)該擁有這個(gè) state耍目。
- 如果你沒有找到可以擁有這個(gè) state 的組件,創(chuàng)建一個(gè)僅用來保存狀態(tài)的組件并把它加入比這個(gè)公共所有者組件層級更高的地方
- 當(dāng)應(yīng)用足夠復(fù)雜時(shí)才能體會到它的好處徐绑,雖然在一般應(yīng)用場景下你可能不會意識到它的存在
兄弟組件的溝通的解決方案就是找到兩個(gè)組件共同的父組件邪驮,一層一層的調(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ù)雜的親戚圖問題
整個(gè)應(yīng)用只有唯一一個(gè)可信數(shù)據(jù)源,也就是只有一個(gè)Store守呜,如 Redux 限定一個(gè)應(yīng)用中只能有單一的 store型酥,這樣的限定能夠讓應(yīng)用中數(shù)據(jù)結(jié)果集中化,提高可控性
Action查乒、Reducer弥喉、及 Store
State 只能通過觸發(fā)Action 來更改
State 的更改必須寫成純函數(shù),也就是每次更改總是返回一個(gè)新的State玛迄,在*Redux 里這種函數(shù)稱為Reducer
Redux 對于組件間的解耦提供了很大的便利由境,如果你在考慮該不該使用 Redux 的時(shí)候,社區(qū)里有一句話說憔晒,“當(dāng)你不知道該不該使用 Redux 的時(shí)候藻肄,那就是不需要的”
Redux 用起來一時(shí)爽,重構(gòu)或者將項(xiàng)目留給后人的時(shí)候拒担,就是個(gè)大坑嘹屯,Redux 中的 dispatch 和 subscribe 方法遍布代碼的每一個(gè)角落。