第一個基于React應(yīng)用為一個投票的app,效果圖如下顯示:

點擊藍色的小三角對應(yīng)的票數(shù)會增加。
github源碼地址
使用create-react-app來構(gòu)建React應(yīng)用。具體的使用方法參考React官方教程create-react-app使用。安裝好create-react-app以后來構(gòu)建我們的第一個簡單的項目。
構(gòu)建方法如下:
create-react-app voting-app
cd voting-app
yarn start
當(dāng)網(wǎng)頁如下顯示以后表明應(yīng)用啟動成功:

項目結(jié)構(gòu)如下:

在public/index.html更改一下index.html的結(jié)構(gòu)
<div class="main ui text container">
<h1 class="ui dividing centered header">Popular Products</h1>
<div id="root"></div>
</div>
這里的<div id="root"></div>是React應(yīng)用的掛載點,后面會具體提到。樣式使用了semantic ui,在該文件中引入對應(yīng)的樣式文件即可。
什么是組件?
構(gòu)建一個React的核心就是組件。一個獨立的React組件可以被認(rèn)為是一個app中的UI組件。我們可以將voting-app劃分成兩個主要的組件:

從圖中我們可以看出有一個父組件包含了許多子組件,并且分別稱他們?yōu)?code>ProductList和Product。
-
ProductList:包含許多product組件 -
Product:顯示信息
每個組件可以包含標(biāo)簽,視圖邏輯,組件樣式,交互事件等。也就是說,一個組件中可以包含HTML結(jié)構(gòu),CSS樣式,JavaScript事件。這使得React組件有很好的復(fù)用性。(這和傳統(tǒng)的HTML,CSS,JavaScript分離的觀念不同)。
此外,React對于組件的數(shù)據(jù)流和交互性是嚴(yán)格限制的。在React中,當(dāng)一個組件的輸入發(fā)生了變化,那么組件也會重新渲染,所以這使得數(shù)據(jù)和UI的一致性得到了保障:只要給定的數(shù)據(jù)輸入不發(fā)生變化,那么其輸出(組件在頁面上的顯示)則始終保持一致。
根據(jù)上面的project-structure圖來建立兩個組件(都是js文件)。
ProductList.js:
import React, { Component } from 'react'
class ProductList extends Component {
render () {
return (
<div className="ui unstackable items">
</div>
)
}
}
export default ProductList
React組件是一個繼承與React.Component的 ES6 class。ProductList類中有一個方法名為render(),這個方法是一個組件中必須存在的一個方法,其返回值會決定將什么渲染到頁面上。
(關(guān)于ES6類的用法可以參考阮一峰老師的ES6入門。class基本語法,class的繼承)
如果你熟悉JavaScript,你會對render返回的值感到奇怪:
return (
<div className="ui unstackable items">
some text
</div>
)
這個返回值看起來并不像傳統(tǒng)的JavaScript,而看上去更像HTML。其實這是一種稱之為JSX(JavaScript eXtension syntax)的語法。這是由Facebook對JavaScript的一種語法拓展。使用JSX可以讓我們寫出HTML-like的語法來渲染頁面。其實JSX的本質(zhì)就是JavaScript對象,可以使用編譯器(如babel)將JSX翻譯成JavaScript對象。
JSX:
<div className="ui item">
some text
</div>
則會翻譯成:
React.createElement(
'div',
{className: "ui item"},
'some text'
)
總結(jié)一下JSX:
React組件最終都會被渲染成HTML顯示在頁面上。而render()方法則是描述view應(yīng)該如何被HTML表示。React使用一種偽DOM(fake DOM)來構(gòu)建app,這個偽DOM稱之為虛擬DOM(virtual DOM),之后會具體闡述什么是虛擬DOM(很重要的一個概念)。所以,React可以讓我們以JavaScript的方式來描述組件的HTML。
如何將組件顯示到頁面上?
在index.js中:
import React from 'react';
import ReactDOM from 'react-dom';
import registerServiceWorker from './registerServiceWorker';
import ProductList from './components/ProductList'
ReactDOM.render(<ProductList />, document.getElementById('root'));
registerServiceWorker();
其他的先不管,具體來看看ReactDOM.render()方法。
ReactDOM來自于react-dom庫,該方法接受兩個參數(shù),第一個參數(shù)是我們想要渲染什么(what),第二個參數(shù)是渲染到哪里(where):
ReactDOM.render(what, where)
這里,what就是我們剛剛創(chuàng)建的組件ProductList,它由import ProductList from './components/ProductList'導(dǎo)入。而where則是在index.html中的`<div id="root"></div>"中。相當(dāng)于將React的組件渲染到這個節(jié)點下。
注意:自定義組件的首字母必須大寫,否則React會將其看作HTML標(biāo)簽來渲染,從而導(dǎo)致錯誤。
保存以后可以看看頁面上的顯示效果(每次保存文件后,瀏覽器會自動刷新)。
創(chuàng)建Product
現(xiàn)在讓我們來創(chuàng)建子組件Product。同樣的,我們使用ES6的class來創(chuàng)建該組件:
import React, { Component } from 'react'
class Product extends Component {
render () {
return (
<div className="item">
{/* todo... 這是React中的注釋方法 */}
</div>
)
}
}
export default Product
組件可以以組合的方式來構(gòu)建,所以在父組件中可以嵌套子組件,由之前的組件劃分我們可以將Product組件放入ProductList組件中:
import React, { Component } from 'react'
import Product from './Product'
class ProductList extends Component {
render () {
return (
<div className="ui unstackable items">
<Product/>
</div>
)
}
}
export default ProductList
數(shù)據(jù)驅(qū)動的Product組件
創(chuàng)建一個products.json文件,里面的具體內(nèi)容可以參考代碼。
使用props
我們將修改Product組件,這樣就可以不使用靜態(tài)的,硬編碼的數(shù)據(jù)了。我們想要從父組件ProductList中將數(shù)據(jù)傳遞到子組件Product中。數(shù)據(jù)的流向如下:

數(shù)據(jù)是以props的方式從父組件傳遞到子組件。
現(xiàn)在讓我們修改ProductList,使用props來向Product傳遞數(shù)據(jù),然后渲染:
ProductList.js
import products from '../products.json'
class ProductList extends Component {
render () {
const product = products[0]
const productComponents = (
<Product
id={product.id}
title={product.title}
description={product.description}
url={product.url}
votes={product.votes}
submitterAvatarUrl={product.submitterAvatarUrl}
productImageUrl={product.productImageUrl}/>
)
})
return (
<div className="ui unstackable items">
{productComponents}
</div>
)
}
}
這里,變量product是用于藐視第一個products的JavaScript對象。我們以一種類似于HTML中的屬性的屬性方式在組件上寫屬性,而這個[propName]=[propValue]的語法方式就是props的用法。這樣子組件就可以通過一種方式來獲取這些“屬性”和“屬性值”。
當(dāng)然,這里還有兩個有趣的地方。第一個是在每個屬性值外包括的花括號{}。
id={product.id}
在JSX中,花括號是一種定界符,用來告訴JSX花括號中的內(nèi)容是JavaScript表達式。
另一個定界符是使用引號""來引用字符串,就像這樣:id="1"。所以,id={1}這里的id的內(nèi)容是數(shù)字1,而id="1"的id內(nèi)容是字符串1。
現(xiàn)在ProductList已經(jīng)通過props的方式將數(shù)據(jù)傳遞給了Product,那么在Product中如果得到這些數(shù)據(jù)呢?現(xiàn)在讓我們修改Product以獲取這些數(shù)據(jù)。
render () {
return (
<div className="item">
<div className="image">
<img src={this.props.productImageUrl} alt=""/>
</div>
<div className="middle aligned content">
<div className="header">
<a onClick={this.handleUpVote}>
<i className="large caret up icon"></i>
</a>
{this.props.votes}
</div>
<div className="description">
<a href={this.props.url}>{this.props.title}</a>
<p>{this.props.description}</p>
</div>
<div className="extra">
<span>Submitted by: </span>
<img src={this.props.submitterAvatarUrl} className="ui avatar image" alt=""/>
</div>
</div>
</div>
)
}
可以看出,這里是用this.props.xxx的方式來獲取props的值。
在HTML元素中使用props的方式可以讓我們創(chuàng)建動態(tài),數(shù)據(jù)驅(qū)動的React組件。
保存文件以后,可以看到瀏覽器已經(jīng)渲染出一個列表了。
渲染多個products
為了渲染多個products,我們需要讓ProductList產(chǎn)生一個Product組件數(shù)組,每個Product組件用于渲染特定的數(shù)據(jù)。我們可以使用JavaScript中的map()方法來產(chǎn)生一組特定的Product組件數(shù)組。map的解釋
ProductList.js:
render () {
const productComponents = products.map(product => {
return (
<Product
key={`product-${product.id}`}
id={product.id}
title={product.title}
description={product.description}
url={product.url}
votes={product.votes}
submitterAvatarUrl={product.submitterAvatarUrl}
productImageUrl={product.productImageUrl}
onVote={this.handleProductUpVote}/>
)
})
return (
<div className="ui unstackable items">
{productComponents}
</div>
)
這里新添加的key屬性很重要,必須添加,以后會詳細解釋為什么
保存文件,瀏覽器上就能顯示一個Product列表了。
現(xiàn)在再修改一下,讓頁面以票數(shù)從小到大來顯示。其實很簡單,只要將products數(shù)據(jù)做一個排序就可以了,可以使用sort()方法。sort的解釋
ProductList.js:
render () {
const sortedProducts = this.state.products.sort((a, b) => a.votes - b.votes)
const productComponents = sortedProducts.map(product => {
return (
<Product
key={`product-${product.id}`}
id={product.id}
title={product.title}
description={product.description}
url={product.url}
votes={product.votes}
submitterAvatarUrl={product.submitterAvatarUrl}
productImageUrl={product.productImageUrl}
onVote={this.handleProductUpVote}/>
)
})
return (
<div className="ui unstackable items">
{productComponents}
</div>
)
此時,瀏覽器的頁面渲染效果如下:
