在閱讀了大量 React 的資料(主要是官網(wǎng))之后,發(fā)現(xiàn)有兩個(gè)概念非常重要
Sample Mobile Application with React and Cordova 頁面有一張關(guān)于 React 組件的圖,說明了這個(gè) React Demo 應(yīng)用中各組件的關(guān)系(注意觀察線框和箭頭的顏色)。
Amaze UI React 是在 Amaze UI (jQuery版) 的基礎(chǔ)上,拋棄 jQuery,使用 React 開發(fā)的組件庫。Amaze UI React 和 Amaze UI (jQuery版) 共用一套 CSS。
就從 Amaze 官網(wǎng)的菜單組織也能看到二者的區(qū)別。jQuery 版的菜單包含三項(xiàng):CSS、JS插件、Web組件;而 React 版就只有一項(xiàng):組件。因此 Amaze UI 的 React 版不再需要用戶去了解 CSS 類和過多的腳本操作,只需要關(guān)注組件,及其數(shù)據(jù)(屬性和狀態(tài))即可。
參考文章的通訊錄包含列表頁和詳情頁。學(xué)習(xí)得一步步進(jìn)行,所以先實(shí)現(xiàn)列表頁。
學(xué)習(xí)的目標(biāo)是做一個(gè)通訊錄,功能和而已都與上圖類似,只是界面改用 Amaze,所以得先去 Amanze UI React 組件文檔頁面 了解需要使用的組件,并畫出自己的草圖。
這個(gè)草圖就是最初需要實(shí)現(xiàn)的東西:一個(gè)頁頭、一個(gè)列表、列表項(xiàng)分圖標(biāo)、文本、圖標(biāo)按鈕三個(gè)部分。而圖標(biāo)按鈕點(diǎn)擊可以轉(zhuǎn)到撥號(hào)頁面準(zhǔn)備撥號(hào)。
目標(biāo)已經(jīng)清楚了,下面就要開始搭建程序。第一步,先實(shí)現(xiàn)在 Nginx 中能正常顯示;第二步再實(shí)現(xiàn)構(gòu)建成 Android 程序在手機(jī)上使用(包括顯示和撥號(hào)操作)。
之前已經(jīng)做了很充分的準(zhǔn)備,但那主要是對(duì)環(huán)境的準(zhǔn)備?,F(xiàn)在馬上要開始寫代碼,不得不做一些細(xì)致的準(zhǔn)備,比如,把需要用到的庫安放在代碼中適當(dāng)?shù)奈恢谩?/p>
之前已經(jīng)確定了要使用 jQuery,React 和 Amaze UI React。按我個(gè)人的習(xí)慣,會(huì)把它放在 Web 應(yīng)用的 /libs
目錄下。沒有必要去篩選哪些文件用得上哪些用不上,都拷貝過來,結(jié)果就有了這樣的目錄結(jié)構(gòu)
首先就是修改 index.html 文件,在 index.html 中引入需要的 AmazeUI 的 CSS,以及各依賴庫的腳本文件。直接從默認(rèn)生成的 index.html 改過來就好。
<!doctype html>
<html>
<head>
<meta name="format-detection" content="telephone=no">
<meta name="msapplication-tap-highlight" content="no">
<meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width">
<title>Concats</title>
<link rel="stylesheet" type="text/css" href="libs/amazeui/css/amazeui.min.css" />
<link rel="stylesheet" type="text/css" href="css/index.css">
<script src="libs/jquery/jquery-2.1.4.min.js"></script>
<script src="libs/react/react.min.js"></script>
<script src="libs/react/JSXTransformer.js"></script>
<script src="libs/amazeui/js/amazeui.react.min.js"></script>
<script type="text/jsx" src="js/index.jsx"></script>
</head>
<body>
</body>
</html>
<body>
標(biāo)簽中留空,因?yàn)橹?React 會(huì)將組件渲染在 <body>
中。
<head>
最后引入的 js/index.jsx
就是頁面對(duì)應(yīng)的腳本,使用 JSX 語法以 React 組件的形式實(shí)現(xiàn)。一般這個(gè)文件是以 .js
作為擴(kuò)展名,但是我認(rèn)為用 .jsx
作為擴(kuò)展名可以清楚表明該文件中使用了 JSX 語法。
這已經(jīng)是第2次提到渲染了,為什么說 React 渲染,而不說“執(zhí)行”、“搭建”、“構(gòu)造”諸如此類的詞呢?先來看看 React 官方是怎么描述 React 的:
React is a JavaScript library for creating user interfaces by Facebook and Instagram. Many people choose to think of React as the V in MVC.
We built React to solve one problem: building large applications with data that changes over time.
簡單的說,React 是一個(gè)構(gòu)建用戶界面的 JavaScript 庫,它為構(gòu)建大型應(yīng)用而生,這些應(yīng)用可能隨時(shí)產(chǎn)生數(shù)據(jù)變動(dòng)。很多人把 React 作為 MVC 中的 V 來使用。
而在其它文章資料中也提到,React 的處理過程是從數(shù)據(jù)到視圖的一個(gè)單向過程。綜合起來就可以這樣理解:React 對(duì)數(shù)據(jù)進(jìn)行渲染,以 UI 的形式呈現(xiàn)。如果數(shù)據(jù)發(fā)生變動(dòng),React 會(huì)重新進(jìn)行渲染(實(shí)際上 React 會(huì)判斷數(shù)據(jù)變動(dòng)造成的形式,智能選擇最小渲染范圍以提高效率)。
上面提到將 <body>
標(biāo)簽內(nèi)容留空,以便 React 在其中進(jìn)行渲染。也就是下面這句,React 的渲染入口:
React.render(<Page />, document.body);
上述表達(dá)式用了 JSX 語法,而 JSX 中的 X 部分,我理解為是以 XML 方式描寫的表達(dá)式。為什么是表達(dá)式,這會(huì)在后面的循環(huán)中提及。
正如 React 文檔所述,JSX 語法只是一個(gè)語法糖,它完全可以用純粹的腳本來寫,而且 React 本身也是需要將 JSX 翻譯成 JS 來執(zhí)行的。上面那句話的純 JS 寫法會(huì)是這樣
React.readner(React.createElement(Page), document.body);
那么 Page
是什么,這里看起來它應(yīng)該是一個(gè)合法的 JS 變量——是的,這就是馬上需要定義的 React 組件。
定義組件會(huì)使用 React.createClass()
方法。根據(jù)對(duì) React 的初步了解,我想當(dāng)然的寫下了一個(gè)錯(cuò)誤的定義
var Header = AMUIReact.Header;
var List = AMUIReact.List;
// 錯(cuò)誤的定義
var Page = React.createClass({
render: function() {
return (
<Header title="通訊錄" />
<List />
);
}
});
React.render(React.createElement(Page), document.body);
我的原意是希望能定義 <Page />
組件,并將其渲染在 <body>
中,只要能顯示頁頭就行,列表部分暫時(shí)留空。
然而通過 Nginx 跑出來之后,從瀏覽器的控制臺(tái)得到了一個(gè)錯(cuò)誤消息
Uncaught Error: Parse Error: Line 13: Adjacent JSX elements must be wrapped in an enclosing tag
大概意思是說,JSX 的元素必須是1個(gè)封閉的標(biāo)簽。這里提供了兩個(gè)要素:“1個(gè)”、“封閉”。所以我根據(jù)這個(gè)意思,修改了一下,然后運(yùn)行出了預(yù)期的效果。
render: function(){
return (
<div>
<Header title="通訊錄" />
<List />
</div>
);
}
上面的代碼中,var Header = AMUIReact.Header
和 var List = AMUIReact.List
是 Amaze UI React 教程示例中演示的用法——為帶命名空間的組件創(chuàng)建簡短的名稱。其實(shí)我更傾向于使用帶命名空間的名稱,就像 <AMUIReact.Header title="通訊錄" />
。這可以避免自定義組件和 Amaze UI React 組件的名稱沖突。不過 Amaze UI React 定義的這個(gè)命名空間太長,可以自己縮短一下,比如
var A = AMUIReact;
<A.Header title="通訊錄" />
后面的示例中就采用這種縮短命名空間的寫法。
ReactClass createClass(object specification)
根據(jù)參數(shù)提供的規(guī)格說明,創(chuàng)建組件類。規(guī)則說明是一個(gè) JavaScript 對(duì)象,其提供的 ReactElement render()
函數(shù)會(huì)返回一個(gè) ReactElement
(對(duì)象)用于渲染。render()
中返回的 ReactElement 可以是 JSX 描述,也可以是純 JS 腳本。上面的例子是用的 JSX 描述,如果改成 JS 腳本,應(yīng)該像這樣
render: function() {
return React.createElement("div", {}, [
React.createElement(Header, {
title: "通訊錄"
}),
React.createElement(List)
]);
}
很明顯,JSX 描述更清晰易讀也更容易寫出來。
()
包起來的,其實(shí)如果不包起來也不會(huì)出錯(cuò)。但很顯然,在寫 JavaScript 程序的時(shí)候,如果 return 的內(nèi)容是多行,用括號(hào)包起來是個(gè)好習(xí)慣。現(xiàn)在繼續(xù)下一步,添加列表項(xiàng)。在沒搞清楚如何使用循環(huán)之前,還是先用重復(fù)的代碼把列表項(xiàng)顯示出來再說——當(dāng)然,在目前沒有定義數(shù)據(jù)結(jié)構(gòu)的情況下,也不會(huì)用到循環(huán)??紤]到對(duì)組件還不夠熟悉,列表項(xiàng)的內(nèi)容,暫時(shí)僅展示姓名。
// js/index.jsx
var A = AMUIReact;
var Page = React.createClass({
render: function() {
return (<div>
<A.Header title="通訊錄" />
<A.List>
<A.ListItem>
張三
</A.ListItem>
<A.ListItem>
李四
</A.ListItem>
<A.ListItem>
王麻子
</A.ListItem>
</A.List>
</div>);
}
});
React.render(React.createElement(Page), document.body);
這個(gè)結(jié)果并不好看,但至少已經(jīng)實(shí)現(xiàn)了列表的顯示。待功能完善之后還不能達(dá)到滿意的效果,可以自定義 CSS 來調(diào)整,所以不急。
數(shù)據(jù)當(dāng)然不會(huì)是固定不變的,直接將列表項(xiàng)寫死非常不切合實(shí)際。在學(xué)習(xí)初期,我暫時(shí)還不想去和數(shù)據(jù)庫打交道,所以暫時(shí)用 JSON 來保存數(shù)據(jù),而且為了保持獲取數(shù)據(jù)不節(jié)外生枝,先把數(shù)據(jù)定義在 index.jsx 的最前面。
// js/index.jsx
var data = [
{
"name": "張三",
"tel": "13801234567"
},
{
"name": "李四",
"tel": "18018001800"
},
{
"name": "王麻子",
"tel": "17098765432"
}
];
var A = AMUIReact;
// ... 后面的代碼略
然后改 render()
,想當(dāng)然的又寫了個(gè)段錯(cuò)誤代碼
// 錯(cuò)誤的代碼
render: function() {
return (<div>
<A.Header title="通訊錄" />
<A.List>
for (var i = 0; i < data.length; i++) {
<A.ListItem>{data[i].name}</A.ListItem>
}
</A.List>
</div>);
}
這回從錯(cuò)誤消息中可以發(fā)現(xiàn)是 for
循環(huán)惹的禍。一開始不明白為啥,仔細(xì)思考之后明白了——return
后面的應(yīng)該是一個(gè)表達(dá)式,而 for
循環(huán)不是表示式。于是嘗試把 for
語句改成 Array.prototype.map()
方法
render: function() {
return (<div>
<A.Header title="通訊錄" />
<A.List>
{data.map(function(t) {
return (
<A.ListItem>{t.name}</A.ListItem>
);
})}
</A.List>
</div>);
}
這回是對(duì)了,但是這寫法看起來不夠簡潔,容易把人搞暈。參考網(wǎng)上的資料,發(fā)現(xiàn)可以將 map()
的結(jié)果保存在一個(gè)變量中,再在 <A.List>
中引用
render: function() {
var items = data.map(function(t) {
return <A.ListItem>{t.name}</A.ListItem>;
});
return (<div>
<A.Header title="通訊錄" />
<A.List>
{items}
</A.List>
</div>);
}
小結(jié) JSX 中如果在代碼中嵌入成對(duì)的
<XML標(biāo)簽&
,會(huì)被翻譯成組件代碼(React.createElement(...)
等),而在 xml 標(biāo)簽中使用一對(duì)大括號(hào){}
可以嵌入 JS 代碼。如果對(duì) ASP.NET MVC 的 Razor 模塊有所了解,就會(huì)有似曾相識(shí)的感覺。
接下來還要為列表項(xiàng)添加圖標(biāo),以 React 的組件化思維來思考,比較好的作法是定義一個(gè)組件,不妨叫 Person
來封裝圖標(biāo)、姓名和電話按鈕。那么,如何將每個(gè)人的數(shù)據(jù)傳入 Person
對(duì)象?
另外,把數(shù)據(jù)放在 index.jsx 中也不是個(gè)辦法,遲早還是得用異步(比如 Ajax 或從數(shù)據(jù)庫獲?。┘虞d的,又該如何將數(shù)據(jù)更新到頁面中?
欲知后事如何,且看下回分解!
更多建議: