前兩天,用React把 之前的 基于樹莓派3B,DHT11/DHT22,LCD1602的一個實時溫度濕度檢測系統(tǒng) 的Web部分重構(gòu)了一下。
這個項目之前是用Semantic UI + eCharts + JQuery Datapicker寫的,由于這個項目的實現(xiàn)過程比較經(jīng)典,幾乎把所有的感覺React的基礎(chǔ)思想都實現(xiàn)了一遍。
無疑,React給前端帶來了各種新的想法與思想,聲明式、虛擬Dom、單向數(shù)據(jù)流、JSX、組件化、Flux、以JavaScript為中心、React Native。
虛擬Dom
React是以JS為中心的,而非HTML,虛擬Dom是一個至關(guān)重要的概念。
在jQuery出現(xiàn)之前,我們一般都是直接操作Dom,方法“暴力、簡單”,也存在兼容性的問題。在jQuery出現(xiàn)之后,jQuery的選擇器幫助我們簡單、高效的操作Dom,也就是在jQuery出現(xiàn)之后,前端就開始大爆發(fā)了。
但是隨著發(fā)展,這種簡單暴力的操作Dom的方法對于很多大型系統(tǒng)性工程來說,從軟件工程的角度上講,是雜亂、無序的,一旦時間久遠,項目越來越大,維護成本就變大。于是,我們有了各種前端MVC、MVVM框架,典型的有Angular、React、vue.js,MVVM給我們帶來一種全新的思想,數(shù)據(jù)綁定。
不過,與此同時,React給我們帶來了一種全新的思想,虛擬Dom,從此,我們再也不用直接操作Dom了,而是用虛擬Dom更新Dom。
你可能會問,這樣做,效率豈不是更低了?
是的,也不是。其實沒有那么明顯,一般來講,操作Dom樹的Diff算法應(yīng)該是O(n^3),而React把效率降低到了O(n)。
簡單來講,如圖所示,React只會比較相同層級的Dom,從而簡化了Dom Diff的復(fù)雜度,雖然有所舍棄,但是全局比較是沒有必要的。
通過在JSX文件中寫虛擬Dom,React在內(nèi)存中會自動拿現(xiàn)在的虛擬Dom和舊的虛擬Dom進行Diff操作,如果發(fā)現(xiàn)有Diff的部分,就拿出來形成新的Patch部分,加入到現(xiàn)在的Dom樹上。
也就是因為這個原因,虛擬Dom使得在操作Dom上更加高效,每次更新數(shù)據(jù)不會進行O(n^3)復(fù)雜的操作。
你可能會問,什么是JSX?
JSX
其實JSX只是一種未編譯的JavaScript,可以方便的對HTML進行書寫,先上代碼:
import React from 'react';
import { Button } from 'semantic-ui-react';
class ButtonComponent extends React.Component {
render() {
return(
<span>
<Button color='blue' >分鐘</Button>
<Button color='violet'>小時</Button>
</span>
);
}
}
export default ButtonComponent;
代碼部分用ES6書寫,你可能會問什么是ES6?其實還有ES7(ECMAScript 2016)呢~
那么,你可能看到了很變態(tài)的寫法,就是HTML和JS混寫,沒錯,這就是EJS,但請注意,這不是HTML,寫著寫著很有可能會掉入JSX陷阱。
經(jīng)過babel等工具的編譯后,JSX在運行的時候會被編譯為真實的JavaScript語言,之所以這樣寫,只是為了方便程序員更方便的寫代碼。
比如上面的代碼“HTML部分”會被編譯為:
React.createElement('button', {class: 'ui blue button'}, '分鐘');
React.createElement('button', {class: 'ui violet button'}, '小時');
當(dāng)然,JSX也有很多坑,比如:
Module build failed: SyntaxError: Adjacent JSX elements must be wrapped in an enclosing tag
作為新手,這個問題,我經(jīng)常遇到,因為你在寫每一個JSX模塊的時候,你需要用類似于<div></div>作為開始與結(jié)尾,不規(guī)范的代碼不會被編譯成功。
ECMAScript(ES)
ES無疑是這幾年最火的前端語言,沒錯,他是JavaScript的改進版,我知道,其實還有很多其他類似的比如CoffeeScript,但是ES會被各大瀏覽器所兼容支持,但是你可能會問到兼容性問題,沒事,我們有babel工具。
- ECMAScript:一個由 ECMA International 進行標(biāo)準(zhǔn)化,TC39 委員會進行監(jiān)督的語言。通常用于指代標(biāo)準(zhǔn)本身。
- JavaScript:ECMAScript 標(biāo)準(zhǔn)的各種實現(xiàn)的最常用稱呼。這個術(shù)語并不局限于某個特定版本的 ECMAScript 規(guī)范,并且可能被用于任何不同程度的任意版本的 ECMAScript 的實現(xiàn)。
- ECMAScript 5 (ES5):ECMAScript 的第五版修訂,于 2009 年完成標(biāo)準(zhǔn)化。這個規(guī)范在所有現(xiàn)代瀏覽器中都相當(dāng)完全的實現(xiàn)了。
- ECMAScript 6 (ES6) / ECMAScript 2015 (ES2015):ECMAScript 的第六版修訂,于 2015 年完成標(biāo)準(zhǔn)化。這個標(biāo)準(zhǔn)被部分實現(xiàn)于大部分現(xiàn)代瀏覽器??梢圆殚嗊@張兼容性表來查看不同瀏覽器和工具的實現(xiàn)情況。
- ECMAScript Proposals:被考慮加入未來版本 ECMAScript 標(biāo)準(zhǔn)的特性與語法提案,他們需要經(jīng)歷五個階段:Strawman(稻草人),Proposal(提議),Draft(草案),Candidate(候選)以及 Finished (完成)。
- ECMAScript 2016:第七版 ECMAScript 修訂,增加了兩個新特性。
以上,都是我們常說的ES,在網(wǎng)上看到的代碼目前以ES5和ES6居多,寫法各異,我建議用最新版的寫法寫代碼。
說白了,就是Javascript并不是一個優(yōu)秀的語言,但是經(jīng)過不斷的填補,修正,已經(jīng)讓Javascript支持了很多新特性,新的語法,更多的書寫方式,以及擁有了更嚴(yán)謹?shù)木幊趟枷?。比?code>let,arrow function(就是這個:(i) => i + 1 )在ES中的運用等。
所以,在認識React之前,最好學(xué)習(xí)ES的新特性以及常用的新特性。
組件化
我覺得這是React的重量級思想,組件化前端,從此,前端再也不是東拼西湊,而是像積木一樣,拼起來。
React把單一的用戶界面,拆成了各種各樣的組件,形成“組件樹”,采用分而治之的思想,有一個好處,就是維護起來極其方便,對于組件的修改也顯得很容易。
數(shù)據(jù)綁定
眾所周知,Angular是雙向數(shù)據(jù)綁定的,并且引以為豪,因為更容易維護與理解。
但是雙向數(shù)據(jù)綁定也帶來了許多問題,會在些其他功能的時候莫名其妙出現(xiàn)很多bug。
React是單向數(shù)據(jù)綁定的,是非常穩(wěn)定的做法,但是也存在一些問題,就是在操作其他組件的時候,對于數(shù)據(jù)流的處理,顯得異常困難。
在React中,數(shù)據(jù)綁定僅僅需要在render中寫{數(shù)據(jù)綁定},而非像Angular一樣,兩邊都要設(shè)置,一定情況下講,這是比較方便的。
數(shù)據(jù)模型
state
話說回來,React的每一個組件的實質(zhì)是什么?狀態(tài)機(State Machines),在React的每一個組件里,通過更新this.state,再通過render()進行渲染,React會自動把最新的狀態(tài)渲染到網(wǎng)頁上。
舉個栗子??
class DateSelectorComponent extends Component {
constructor(props) {
super(props);
let todaydate = this.showLocale(new Date());
this.state = {
timenow: todaydate,
showCalendar: false
}
return(
<div>
<Input icon='calendar' iconPosition='left' readOnly onClick={this.showCalendar} value={this.state.timenow} ref="inputbox"/>
<ButtonComponent />
{CalendarComponent}
</div>
)
}
通過這樣的方式,我就設(shè)置了state的默認初始值,默認日歷選擇器是不出現(xiàn)的,而今天的日期是今天日期,這很正常。然后在將所謂的值渲染到Dom上。
props
React的數(shù)據(jù)流是單向的,是自上向下的層級傳遞的,props可以對固定的數(shù)據(jù)進行傳遞。
class HelloMessage extends Component {
render(
return <h1>Hello {this.props.name}</h1>;
)
}
class MainPage extends Component {
render(){
return(
<HelloMessage name="John" />
)
}
}
在這種情況下,子組件會自動讀取出父組件傳遞過來的props值John,并快速的渲染在頁面上。
到底誰是誰
state和props看起來很相似,其實是完全不同的東西。
| 問題 | props | state |
|---|---|---|
| 可以從父組件得到初始值嗎? | 可以 | 可以 |
| 可以被父組件改變值嗎? | 可以 | 不可以 |
| 可以設(shè)置組件內(nèi)部的初始值嗎? | 可以 | 可以 |
| 可以改變組件內(nèi)部的初始值嗎? | 不可以 | 可以 |
| 可以設(shè)置子組件的初始值嗎? | 可以 | 可以 |
| 可以改變子組件的初始值嗎? | 可以 | 不可以 |
按照我的個人理解,props是靜態(tài)的,存入不變量,比如 購物車?yán)锏?商品名稱、價格;state是動態(tài)的,存入隨時變化的量,比如 用戶購買商品的總價,購買數(shù)目。
從操作的角度上講,props是單向傳遞的,會一直被傳遞到子組件,而state更傾向于自身,只能改變自身的值。
獲取Dom
this.refs是react的重要組成之一,通過該方法可以快速高效的獲取Dom。
例如,在JSX代碼中這樣寫道
<input ref="myInput" />
便可以通過
this.refs.myInput.value
命令獲取該Input下的dom屬性的value值,非常方便。
生命周期
組件都是有生命周期的,生命周期內(nèi),props和state改變會導(dǎo)致React自動用Diff算法重新渲染頁面。那么生命周期到底都有哪些呢?
大體上分為三類:
- 掛載: 組件被插入到DOM中。
- 更新: 組件被重新渲染,查明DOM是否應(yīng)該刷新。
- 移除: 組件從DOM中移除。
從流程上講,是這樣的:
掛載期:
getInitialState() -->> componentWillMount() -->> render() -->> componentDidMount()
更新期:
componentWillReceiveProps() -->> shouldComponentUpdate() -->> componentWillUpdate render() -->> componentDidUpdate()
移除期:
componentWillUnmount()
掛載
componentWillMount(): 在初次渲染之前執(zhí)行一次,最早的執(zhí)行點
componentDidMount(): 在初次渲染之后執(zhí)行,比較常用,比如持續(xù)執(zhí)行某事件:
componentDidMount(){
setInterval(this.loadData(this.state.date),1000);
}
更新
componentWillReceiveProps(): 在組件接收到新的 props 的時候調(diào)用。在初始化渲染的時候,該方法不會調(diào)用。
shouldComponentUpdate(): 在接收到新的 props 或者 state,將要渲染之前調(diào)用。
componentWillUpdate(): 在接收到新的 props 或者 state 之前立刻調(diào)用。
componentDidUpdate(): 在組件的更新已經(jīng)同步到 DOM 中之后立刻被調(diào)用。
移除
componentWillUnmount(): 在組件從 DOM 中移除的時候立刻被調(diào)用。
Flux
React是MVC中V的一部分,而Flux則是M和C的部分,F(xiàn)lux是單向數(shù)據(jù)流,符合React的核心思想,不過,F(xiàn)lux并不完善,是一個很松散的架構(gòu)。
- Dispatcher: 處理動作分發(fā),維護 Store 之間的依賴關(guān)系
- Store: 數(shù)據(jù)和邏輯部分
- View: React 組件,這一層可以看作 controller-views,作為視圖同時響應(yīng)用戶交互
- Action: 提供給 Dispatcher 傳遞數(shù)據(jù)給 Store
Redux
Flux把總架構(gòu)都搭好了,可是實現(xiàn)起來并不容易,因為Flux并沒有強大的API,只是一種純粹的思想實現(xiàn),而Redux是Flux的“升級版”,把各個部分更加方便的實現(xiàn)起來。
此部分未完待續(xù)
總結(jié)
我覺得React給前端開發(fā)帶來了一種全新的思想,那就是以JS為中心,一種全新的世界。
網(wǎng)頁不再是網(wǎng)頁,而是像工程一樣,一塊塊搭建起來的,拆下來,搬上去都很容易,似的前端再也不是那個凌亂拼湊的年代。
我想任何一種編程語言,無論是解釋性、腳本、編譯語言都逃離不了這個全新的年代,就是組件化,大家似乎都在不同方向發(fā)展,但實際上,確實一樣的。