現(xiàn)在最熱門的前端框架垛孔,毫無疑問是React激挪。在基于React的React Native發(fā)布一天之內(nèi),就獲得了 5000 顆星番刊,受矚目程度可見一斑。
React 起源于 Facebook 的內(nèi)部項目影锈,因為該公司對市場上所有JavaScript MVC 框架芹务,都不滿意,就決定自己寫一套鸭廷,用來架設(shè) Instagram 的網(wǎng)站枣抱。做出來以后,發(fā)現(xiàn)這套東西很好用辆床,就在2013年5月開源了佳晶。
由于 React 的設(shè)計思想極其獨特,屬于革命性創(chuàng)新讼载,性能出眾轿秧,代碼邏輯卻非常簡單。所以咨堤,越來越多的人開始關(guān)注和使用菇篡,認為它可能是將來 Web 開發(fā)的主流工具。
這個項目本身也越滾越大一喘,從最早的UI引擎變成了一整套前后端通吃的 Web App 解決方案驱还。衍生的 React Native 項目,目標更是宏偉凸克,希望用寫 Web App 的方式去寫 Native App铝侵。如果能夠?qū)崿F(xiàn),整個互聯(lián)網(wǎng)行業(yè)都會被顛覆触徐,因為同一組人只需要寫一次 UI 咪鲜,就能同時運行在服務(wù)器、瀏覽器和手機(參見《也許撞鹉,DOM 不是答案》)疟丙。
既然 React 這么熱門,看上去充滿希望鸟雏,當然應(yīng)該好好學(xué)一下享郊。從技術(shù)角度,可以滿足好奇心孝鹊,提高技術(shù)水平炊琉;從職業(yè)角度,有利于求職和晉升,有利于參與潛力大的項目苔咪。但是锰悼,好的 React 教程卻不容易找到,這一方面因為這項技術(shù)太新团赏,剛剛開始走紅箕般,大家都沒有經(jīng)驗,還在摸索之中舔清;另一方面因為 React 本身還在不斷變動丝里,API 一直在調(diào)整,至今沒發(fā)布1.0版体谒。
我學(xué)習(xí) React 時杯聚,就很苦惱。有的教程討論一些細節(jié)問題抒痒,對入門沒幫助械媒;有的教程寫得不錯,但比較短评汰,無助于看清全貌纷捞。我斷斷續(xù)續(xù)學(xué)了幾個月,看過二十幾篇教程被去,在這個過程中主儡,將對自己有幫助的 Demo 都收集下來,做成了一個庫React Demos惨缆。
下面糜值,我就根據(jù)這個庫,寫一篇全面又易懂的 React 入門教程坯墨。你只需要跟著每一個Demo做一遍寂汇,就能初步掌握React 。當然捣染,前提是你必須擁有基本JavaScript和DOM知識骄瓣,但是你讀完就會發(fā)現(xiàn),React所要求的預(yù)備知識真的很少耍攘。
零榕栏、安裝
React 的安裝包,可以到官網(wǎng)下載蕾各。不過扒磁,React Demos已經(jīng)自帶 React 源碼,不用另外安裝式曲,只需把這個庫拷貝到你的硬盤就行了妨托。
1
$ git clone git@github.com:ruanyf/react-demos.git
如果你沒安裝 git, 那就直接下載zip壓縮包。
下面要講解的10個例子在各個 Demo 子目錄兰伤,每個目錄都有一個 index.html 文件内颗,在瀏覽器打開這個文件(大多數(shù)情況下雙擊即可),就能立刻看到效果医清。
需要說明的是,React 可以在瀏覽器運行卖氨,也可以在服務(wù)器運行会烙,但是本教程只涉及瀏覽器。一方面是為了盡量保持簡單筒捺,另一方面 React 的語法是一致的柏腻,服務(wù)器的用法與瀏覽器差別不大。Demo11是服務(wù)器首屏渲染的例子系吭,有興趣的朋友可以自己去看源碼五嫂。
一、HTML 模板
使用 React 的網(wǎng)頁源碼肯尺,結(jié)構(gòu)大致如下沃缘。
上面代碼有兩個地方需要注意。首先则吟,最后一個 script 標簽的 type 屬性為 text/jsx 槐臀。這是因為 React 獨有的 JSX 語法,跟 JavaScript 不兼容氓仲。凡是使用 JSX 的地方水慨,都要加上 type="text/jsx" 。
其次敬扛,React 提供兩個庫:react.js和JSXTransformer.js晰洒,它們必須首先加載。其中啥箭,JSXTransformer.js 的作用是將 JSX 語法轉(zhuǎn)為 JavaScript 語法谍珊。這一步很消耗時間,實際上線的時候急侥,應(yīng)該將它放到服務(wù)器完成抬驴。
1
$ jsx src/ build/
上面命令可以將 src 子目錄的 js 文件進行語法轉(zhuǎn)換,轉(zhuǎn)碼后的文件全部放在 build 子目錄缆巧。
二布持、React.render()
React.render 是 React 的最基本方法,用于將模板轉(zhuǎn)為 HTML 語言陕悬,并插入指定的 DOM 節(jié)點题暖。
1
2
3
4React.render(
Hello, world!,
document.getElementById('example')
);
上面代碼將一個 h1 標題,插入 example 節(jié)點(查看demo01),運行結(jié)果如下胧卤。
三唯绍、JSX 語法
上一節(jié)的代碼, HTML 語言直接寫在 JavaScript 語言之中枝誊,不加任何引號况芒,這就是 JSX 的語法,它允許 HTML 與 JavaScript 的混寫(查看Demo02)叶撒。
1
2
3
4
5
6
7
8
9varnames = ['Alice','Emily','Kate'];
React.render(
{
names.map(function(name) {
returnHello, {name}! ? ?})
}
,
document.getElementById('example')
);
上面代碼體現(xiàn)了 JSX 的基本語法規(guī)則:遇到 HTML 標簽(以 < 開頭)绝骚,就用 HTML 規(guī)則解析;遇到代碼塊(以 { 開頭)祠够,就用 JavaScript 規(guī)則解析压汪。上面代碼的運行結(jié)果如下。
JSX 允許直接在模板插入 JavaScript 變量古瓤。如果這個變量是一個數(shù)組止剖,則會展開這個數(shù)組的所有成員(查看demo03)。
1
2
3
4
5
6
7
8vararr = [
Hello world!,
React is awesome,
];
React.render(
{arr},
document.getElementById('example')
);
上面代碼的arr變量是一個數(shù)組落君,結(jié)果 JSX 會把它的所有成員穿香,添加到模板,運行結(jié)果如下绎速。
四扔水、組件
React 允許將代碼封裝成組件(component),然后像插入普通 HTML 標簽一樣朝氓,在網(wǎng)頁中插入這個組件魔市。React.createClass 方法就用于生成一個組件類(查看demo04)。
1
2
3
4
5
6
7
8
9varHelloMessage = React.createClass({
render:function() {
returnHello {this.props.name};
}
});
React.render(
,
document.getElementById('example')
);
上面代碼中赵哲,變量 HelloMessage 就是一個組件類待德。模板插入時,會自動生成 HelloMessage 的一個實例(下文的"組件"都指組件類的實例)枫夺。所有組件類都必須有自己的 render 方法将宪,用于輸出組件。
組件的用法與原生的 HTML 標簽完全一致橡庞,可以任意加入屬性较坛,比如,就是 HelloMessage 組件加入一個 name 屬性扒最,值為 John丑勤。組件的屬性可以在組件類的 this.props 對象上獲取,比如 name 屬性就可以通過 this.props.name 讀取吧趣。上面代碼的運行結(jié)果如下法竞。
添加組件屬性耙厚,有一個地方需要注意,就是 class 屬性需要寫成 className 岔霸,for 屬性需要寫成 htmlFor 薛躬,這是因為 class 和 for 是 JavaScript 的保留字。
五呆细、this.props.children
this.props 對象的屬性與組件的屬性一一對應(yīng)型宝,但是有一個例外,就是 this.props.children 屬性絮爷。它表示組件的所有子節(jié)點(查看demo05)趴酣。
1
2
3
4
5
6
7
8
9
10
11
12
13
14varNotesList = React.createClass({
render:function() {
return(
{
this.props.children.map(function(child) {
return{child} ? ? ? ?})
}
);
}
});
React.render(
hello ? ?world ?,
document.body
);
上面代碼的 NoteList 組件有兩個 span 子節(jié)點,它們都可以通過 this.props.children 讀取略水,運行結(jié)果如下价卤。
這里需要注意劝萤,只有當子節(jié)點多余1個時渊涝,this.props.children 才是一個數(shù)組,否則是不能用 map 方法的床嫌, 會報錯跨释。
六、React.findDOMNode()
組件并不是真實的 DOM 節(jié)點厌处,而是存在于內(nèi)存之中的一種數(shù)據(jù)結(jié)構(gòu)沮协,叫做虛擬 DOM (virtual DOM)南片。只有當它插入文檔以后,才會變成真實的 DOM 。根據(jù) React 的設(shè)計突照,所有的 DOM 變動,都先在虛擬 DOM 上發(fā)生殿托,然后再將實際發(fā)生變動的部分支鸡,反映在真實 DOM上,這種算法叫做 DOM diff 椭住,它可以極大提高網(wǎng)頁的性能表現(xiàn)崇渗。
但是,有時需要從組件獲取真實 DOM 的節(jié)點京郑,這時就要用到 React.findDOMNode 方法(查看demo06)宅广。
1
2
3
4
5
6
7
8
9
10
11
12
13varMyComponent = React.createClass({
handleClick:function() {
React.findDOMNode(this.refs.myTextInput).focus();
},
render:function() {
return(
);
}
});
React.render(
,
document.getElementById('example')
);
上面代碼中,組件 MyComponent 的子節(jié)點有一個文本輸入框些举,用于獲取用戶的輸入跟狱。這時就必須獲取真實的 DOM 節(jié)點,虛擬 DOM 是拿不到用戶輸入的户魏。為了做到這一點兽肤,文本輸入框必須有一個 ref 屬性套腹,然后 this.refs.[refName] 就指向這個虛擬 DOM 的子節(jié)點,最后通過 React.findDOMNode 方法獲取真實 DOM 的節(jié)點资铡。
需要注意的是电禀,由于 React.findDOMNode 方法獲取的是真實 DOM ,所以必須等到虛擬 DOM 插入文檔以后笤休,才能使用這個方法尖飞,否則會返回 null 。上面代碼中店雅,通過為組件指定 Click 事件的回調(diào)函數(shù)政基,確保了只有等到真實 DOM 發(fā)生 Click 事件之后,才會調(diào)用 React.findDOMNode 方法闹啦。
React 組件支持很多事件沮明,除了 Click 事件以外,還有 KeyDown 窍奋、Copy荐健、Scroll 等,完整的事件清單請查看官方文檔琳袄。
七江场、this.state
組件免不了要與用戶互動,React 的一大創(chuàng)新窖逗,就是將組件看成是一個狀態(tài)機址否,一開始有一個初始狀態(tài),然后用戶互動碎紊,導(dǎo)致狀態(tài)變化佑附,從而觸發(fā)重新渲染 UI(查看demo07)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18varLikeButton = React.createClass({
getInitialState:function() {
return{liked:false};
},
handleClick:function(event) {
this.setState({liked: !this.state.liked});
},
render:function() {
vartext =this.state.liked ?'like':'haven\\'t liked';
return(
You {text}this. Click to toggle.
);
}
});
React.render(
,
document.getElementById('example')
);
上面代碼是一個 LikeButton 組件仗考,它的 getInitialState 方法用于定義初始狀態(tài)音同,也就是一個對象,這個對象可以通過 this.state 屬性讀取痴鳄。當用戶點擊組件瘟斜,導(dǎo)致狀態(tài)變化,this.setState 方法就修改狀態(tài)值痪寻,每次修改以后螺句,自動調(diào)用 this.render 方法,再次渲染組件橡类。
由于 this.props 和 this.state 都用于描述組件的特性蛇尚,可能會產(chǎn)生混淆。一個簡單的區(qū)分方法是顾画,this.props 表示那些一旦定義取劫,就不再改變的特性匆笤,而 this.state 是會隨著用戶互動而產(chǎn)生變化的特性。
八谱邪、表單
用戶在表單填入的內(nèi)容炮捧,屬于用戶跟組件的互動,所以不能用 this.props 讀鹊胍(查看demo08)咆课。
1
2
3
4
5
6
7
8
9
10
11
12
13
14varInput = React.createClass({
getInitialState:function() {
return{value:'Hello!'};
},
handleChange:function(event) {
this.setState({value: event.target.value});
},
render:function() {
varvalue =this.state.value;
return(
{value} ? ? ? ? ?);
}
});
React.render(, document.body);
上面代碼中,文本輸入框的值扯俱,不能用 this.props.value 讀取书蚪,而要定義一個 onChange 事件的回調(diào)函數(shù),通過 event.target.value 讀取用戶輸入的值迅栅。textarea 元素殊校、select元素、radio元素都屬于這種情況读存,更多介紹請參考官方文檔为流。
九、組件的生命周期
組件的生命周期分成三個狀態(tài):
Mounting:已插入真實 DOM
Updating:正在被重新渲染
Unmounting:已移出真實 DOM
React 為每個狀態(tài)都提供了兩種處理函數(shù)宪萄,will 函數(shù)在進入狀態(tài)之前調(diào)用艺谆,did 函數(shù)在進入狀態(tài)之后調(diào)用榨惰,三種狀態(tài)共計五種處理函數(shù)拜英。
componentWillMount()
componentDidMount()
componentWillUpdate(object nextProps, object nextState)
componentDidUpdate(object prevProps, object prevState)
componentWillUnmount()
此外,React 還提供兩種特殊狀態(tài)的處理函數(shù)琅催。
componentWillReceiveProps(object nextProps):已加載組件收到新的參數(shù)時調(diào)用
shouldComponentUpdate(object nextProps, object nextState):組件判斷是否重新渲染時調(diào)用
這些方法的詳細說明居凶,可以參考官方文檔。下面是一個例子(查看demo09)藤抡。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28varHello = React.createClass({
getInitialState:function() {
return{
opacity: 1.0
};
},
componentDidMount:function() {
this.timer = setInterval(function() {
varopacity =this.state.opacity;
opacity -= .05;
if(opacity < 0.1) {
opacity = 1.0;
}
this.setState({
opacity: opacity
});
}.bind(this), 100);
},
render:function() {
return(
Hello {this.props.name}
);
}
});
React.render(
,
document.body
);
上面代碼在hello組件加載以后侠碧,通過 componentDidMount 方法設(shè)置一個定時器,每隔100毫秒缠黍,就重新設(shè)置組件的透明度弄兜,從而引發(fā)重新渲染。
另外瓷式,組件的style屬性的設(shè)置方式也值得注意替饿,不能寫成
1
style="opacity:{this.state.opacity};"
而要寫成
1
style={{opacity:this.state.opacity}}
這是因為React 組件樣式是一個對象,所以第一重大括號表示這是 JavaScript 語法贸典,第二重大括號表示樣式對象视卢。
十、Ajax
組件的數(shù)據(jù)來源廊驼,通常是通過 Ajax 請求從服務(wù)器獲取据过,可以使用 componentDidMount 方法設(shè)置 Ajax 請求惋砂,等到請求成功,再用 this.setState 方法重新渲染 UI(查看demo10)绳锅。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29varUserGist = React.createClass({
getInitialState:function() {
return{
username:'',
lastGistUrl:''
};
},
componentDidMount:function() {
$.get(this.props.source,function(result) {
varlastGist = result[0];
if(this.isMounted()) {
this.setState({
username: lastGist.owner.login,
lastGistUrl: lastGist.html_url
});
}
}.bind(this));
},
render:function() {
return(
{this.state.username}'s last gist is
here.
);
}
});
React.render(
,
document.body
);
上面代碼使用 jQuery 完成 Ajax 請求西饵,這是為了便于說明。React 沒有任何依賴鳞芙,完全可以使用其他庫罗标。