在我看來, React 是較早使用 JavaScript 構(gòu)建大型、快速的 Web 應(yīng)用程序的技術(shù)方案。它已經(jīng)被我們廣泛應(yīng)用于 Facebook 和 Instagram 。
React 眾多優(yōu)秀特征中的其中一部分就是,教會你去重新思考如何構(gòu)建應(yīng)用程序。
本文中,我將跟你一起使用 React 構(gòu)建一個具備搜索功能的產(chǎn)品列表。
注意:
如果你無法看到本頁內(nèi)嵌的代碼片段,請確認(rèn)你不是用
https
協(xié)議加載本頁的。
假設(shè)我們已經(jīng)擁有了一個 JSON API 和設(shè)計(jì)師設(shè)計(jì)的原型。我們的設(shè)計(jì)師顯然不夠好,因?yàn)樵涂雌饋砣缦拢?/p>
JSON接口返回?cái)?shù)據(jù)如下:
[ {category: "Sporting Goods", price: "$49.99", stocked: true, name: "Football"}, {category: "Sporting Goods", price: "$9.99", stocked: true, name: "Baseball"}, {category: "Sporting Goods", price: "$29.99", stocked: false, name: "Basketball"}, {category: "Electronics", price: "$99.99", stocked: true, name: "iPod Touch"}, {category: "Electronics", price: "$399.99", stocked: false, name: "iPhone 5"}, {category: "Electronics", price: "$199.99", stocked: true, name: "Nexus 7"} ];
你要做的第一件事是,為所有組件(及子組件)命名并畫上線框圖。假如你和設(shè)計(jì)師一起工作,也許他們已經(jīng)完成了這項(xiàng)工作,所以趕緊去跟他們溝通!他們的 Photoshop 圖層名也許最終可以直接用于你的 React 組件名。
然而你如何知道哪些才能成為組件?想象一下,當(dāng)你創(chuàng)建一些函數(shù)或?qū)ο髸r(shí),用到一些類似的技術(shù)。其中一項(xiàng)技術(shù)就是單一功能原則,指的是,理想狀態(tài)下一個組件應(yīng)該只做一件事,假如它功能逐漸變大就需要被拆分成更小的子組件。
由于你經(jīng)常需要將一個JSON數(shù)據(jù)模型展示給用戶,因此你需要檢查這個模型結(jié)構(gòu)是否正確以便你的 UI (在這里指組件結(jié)構(gòu))是否能夠正確的映射到這個模型上。這是因?yàn)橛脩艚缑婧蛿?shù)據(jù)模型在 信息構(gòu)造 方面都要一致,這意味著將你可以省下很多將 UI 分割成組件的麻煩事。你需要做的僅僅只是將數(shù)據(jù)模型分隔成一小塊一小塊的組件,以便它們都能夠表示成組件。
由此可見,我們的 app 中包含五個組件。下面我已經(jīng)用斜體標(biāo)示出每個組件對應(yīng)的數(shù)據(jù)。
FilterableProductTable
(橘色): 包含整個例子的容器
SearchBar
(藍(lán)色): 接受所有 用戶輸入( user input )
ProductTable
(綠色): 根據(jù) 用戶輸入( user input ) 過濾和展示 數(shù)據(jù)集合( data collection )
ProductCategoryRow
(青色): 為每個 分類( category ) 展示一列表頭
ProductRow
(紅色): 為每個 產(chǎn)品( product ) 展示一列
如果你仔細(xì)觀察 ProductTable
,你會發(fā)現(xiàn)表頭(包含“ Name ”和“ Price ”標(biāo)簽)并不是單獨(dú)的組件。這只是一種個人偏好,也有一定的爭論。在這個例子當(dāng)中,我把表頭當(dāng)做 ProductTable
的一部分,因?yàn)樗卿秩尽皵?shù)據(jù)集合”的一份子,這也是 ProductTable
的職責(zé)。但是,當(dāng)這個表頭變得復(fù)雜起來的時(shí)候(例如,添加排序功能),就應(yīng)該單獨(dú)地寫一個 ProductTableHeader
組件。
既然我們在原型當(dāng)中定義了這個組件,讓我們把這些元素組成一棵樹形結(jié)構(gòu)。這很簡單。被包含在其它組件中的組件在屬性機(jī)構(gòu)中應(yīng)該是子級:
FilterableProductTable
ProductCategoryRow
ProductRow
SearchBar
-ProductTable
既然已經(jīng)擁有了組件樹,是時(shí)候開始實(shí)現(xiàn)應(yīng)用了。最簡單的方式就是創(chuàng)建一個應(yīng)用,這個應(yīng)用將數(shù)據(jù)模型渲染到 UI 上,但是沒有交互功能。拆分這兩個過程是最簡單的,因?yàn)闃?gòu)建一個靜態(tài)的版本僅需要大量的輸入,而不需要思考;但是添加交互功能卻需要大量的思考和少量的輸入。我們將會知道這是為什么。
為了創(chuàng)建一個渲染數(shù)據(jù)模型的應(yīng)用的靜態(tài)版本,你將會構(gòu)造一些組件,這些組件重用其它組件,并且通過 props 傳遞數(shù)據(jù)。 props 是一種從父級向子級傳遞數(shù)據(jù)的方式。如果你對 state 概念熟悉,那么不要使用 state 來構(gòu)建這個靜態(tài)版本。 state 僅用于實(shí)現(xiàn)交互功能,也就是說,數(shù)據(jù)隨著時(shí)間變化。因?yàn)檫@是一個靜態(tài)的應(yīng)用版本,所以你并不需要 state 。
你可以從上至下或者從下至上來構(gòu)建應(yīng)用。也就是說,你可以從屬性結(jié)構(gòu)的頂部開始構(gòu)建這些組件(例如,從 FilterableProductTable
開始),或者從底部開始( ProductRow
)。在簡單的應(yīng)用中,通常情況下從上至下的方式更加簡單;在大型的項(xiàng)目中,從下至上的方式更加簡單,這樣也可以在構(gòu)建的同時(shí)寫測試代碼。
在這步結(jié)束的時(shí)候,將會有一個可重用的組件庫來渲染數(shù)據(jù)模型。這些組件將會僅有 render()
方法,因?yàn)檫@是應(yīng)用的一個靜態(tài)版本。位于樹形結(jié)構(gòu)頂部的組件( FilterableProductTable
)將會使用數(shù)據(jù)模型作為 prop 。如果你改變底層數(shù)據(jù)模型,然后再次調(diào)用 React.render()
, UI 將會更新。查看 UI 如何被更新和什么地方改變都是很容易的,因?yàn)?React 的單向數(shù)據(jù)流(也被稱作“單向綁定”)保持了一切東西模塊化,很容易查錯,并且速度很快,沒有什么復(fù)雜的。
如果你在這步中需要幫助,請查看 React 文檔。
在 React 中有兩種類型的數(shù)據(jù)“模型”: props 和 state 。理解兩者的區(qū)別是很重要的;如果你不太確定兩者有什么區(qū)別,請大致瀏覽一下官方的 React 文檔。
為了使 UI 可交互,需要能夠觸發(fā)底層數(shù)據(jù)模型的變化。 React 通過 state 使這變得簡單。
為了正確構(gòu)建應(yīng)用,首先需要考慮應(yīng)用需要的最小的可變 state 數(shù)據(jù)模型集合。此處關(guān)鍵點(diǎn)在于精簡:不要存儲重復(fù)的數(shù)據(jù)。構(gòu)造出絕對最小的滿足應(yīng)用需要的最小 state 是有必要的,并且計(jì)算出其它強(qiáng)烈需要的東西。例如,如果構(gòu)建一個 TODO 列表,僅保存一個 TODO 列表項(xiàng)的數(shù)組,而不要保存另外一個指代數(shù)組長度的 state 變量。當(dāng)想要渲染 TODO 列表項(xiàng)總數(shù)的時(shí)候,簡單地取出 TODO 列表項(xiàng)數(shù)組的長度就可以了。
思考示例應(yīng)用中的所有數(shù)據(jù)片段,有:
最初的 products 列表
用戶輸入的搜索文本
復(fù)選框的值
過濾后的 products 列表
讓我們分析每一項(xiàng),指出哪一個是 state 。簡單地對每一項(xiàng)數(shù)據(jù)提出三個問題:
是否是從父級通過 props 傳入的?如果是,可能不是 state 。
是否會隨著時(shí)間改變?如果不是,可能不是 state 。
能根據(jù)組件中其它 state 數(shù)據(jù)或者 props 計(jì)算出來嗎?如果是,就不是 state 。
初始的 products 列表通過 props 傳入,所以不是 state 。搜索文本和復(fù)選框看起來像是 state ,因?yàn)樗鼈冸S著時(shí)間改變,也不能根據(jù)其它數(shù)據(jù)計(jì)算出來。最后,過濾的 products 列表不是 state ,因?yàn)榭梢酝ㄟ^搜索文本和復(fù)選框的值從初始的 products 列表計(jì)算出來。
所以最終, state 是:
用戶輸入的搜索文本
復(fù)選框的值
OK,我們辨別出了應(yīng)用的 state 數(shù)據(jù)模型的最小集合。接下來,需要指出哪個組件會改變或者說擁有這個 state 數(shù)據(jù)模型。
記住: React 中數(shù)據(jù)是沿著組件樹從上到下單向流動的??赡懿粫⒖堂靼啄膫€組件應(yīng)該擁有哪些 state 數(shù)據(jù)模型。這對新手通常是最難理解和最具挑戰(zhàn)的,因此跟隨以下步驟來弄清楚這點(diǎn):
對于應(yīng)用中的每一個 state 數(shù)據(jù):
找出每一個基于那個 state 渲染界面的組件。
找出共同的祖先組件(某個單個的組件,在組件樹中位于需要這個 state 的所有組件的上面)。
要么是共同的祖先組件,要么是另外一個在組件樹中位于更高層級的組件應(yīng)該擁有這個 state 。
如果找不出擁有這個 state 數(shù)據(jù)模型的合適的組件,創(chuàng)建一個新的組件來維護(hù)這個 state ,然后添加到組件樹中,層級位于所有共同擁有者組件的上面。
讓我們在應(yīng)用中應(yīng)用這個策略:
ProductTable
需要基于 state 過濾產(chǎn)品列表,SearchBar
需要顯示搜索文本和復(fù)選框狀態(tài)。
共同擁有者組件是 FilterableProductTable
。
理論上,過濾文本和復(fù)選框值位于 FilterableProductTable
中是合適的。
太酷了,我們決定了 state 數(shù)據(jù)模型位于 FilterableProductTable
之中。首先,給 FilterableProductTable
添加 getInitialState()
方法,該方法返回 {filterText: '', inStockOnly: false}
來反映應(yīng)用的初始化狀態(tài)。然后傳遞 filterText
和 inStockOnly
給 ProductTable
和 SearchBar
作為 prop 。最后,使用這些 props 來過濾 ProductTable
中的行,設(shè)置在 SearchBar
中表單字段的值。
你可以開始觀察應(yīng)用將會如何運(yùn)行:設(shè)置 filterText
為 "ball"
,然后刷新應(yīng)用。將會看到數(shù)據(jù)表格被正確更新了。
到目前為止,已經(jīng)構(gòu)建了渲染正確的基于 props 和 state 的沿著組件樹從上至下單向數(shù)據(jù)流動的應(yīng)用?,F(xiàn)在,是時(shí)候支持另外一種數(shù)據(jù)流動方式了:組件樹中層級很深的表單組件需要更新 FilterableProductTable
中的 state 。
React 讓這種數(shù)據(jù)流動非常明確,從而很容易理解應(yīng)用是如何工作的,但是相對于傳統(tǒng)的雙向數(shù)據(jù)綁定,確實(shí)需要輸入更多的東西。 React 提供了一個叫做 ReactLink
的插件來使其和雙向數(shù)據(jù)綁定一樣方便,但是考慮到這篇文章的目的,我們將會保持所有東西都直截了當(dāng)。
如果你嘗試在示例的當(dāng)前版本中輸入或者選中復(fù)選框,將會發(fā)現(xiàn) React 會忽略你的輸入。這是有意的,因?yàn)橐呀?jīng)設(shè)置了 input
的 value
屬性,使其總是與從 FilterableProductTable
傳遞過來的 state
一致。
讓我們思考下我們希望發(fā)生什么。我們想確保無論何時(shí)用戶改變了表單,都要更新 state 來反映用戶的輸入。由于組件只能更新自己的 state ,FilterableProductTable
將會傳遞一個回調(diào)函數(shù)給 SearchBar
,此函數(shù)將會在 state 應(yīng)該被改變的時(shí)候觸發(fā)。我們可以使用 input 的 onChange
事件來監(jiān)聽用戶輸入,從而確定何時(shí)觸發(fā)回調(diào)函數(shù)。 FilterableProductTable
傳遞的回調(diào)函數(shù)將會調(diào)用 setState()
,然后應(yīng)用將會被更新。
雖然這聽起來有很多內(nèi)容,但是實(shí)際上僅僅需要幾行代碼。并且關(guān)于數(shù)據(jù)在應(yīng)用中如何流動真的非常清晰明確。
希望以上內(nèi)容讓你明白了如何思考用 React 去構(gòu)造組件和應(yīng)用。雖然可能比你之前要輸入更多的代碼,記住,讀代碼的時(shí)間遠(yuǎn)比寫代碼的時(shí)間多,并且閱讀這種模塊化的清晰的代碼是相當(dāng)容易的。當(dāng)你開始構(gòu)建大型的組件庫的時(shí)候,你將會非常感激這種清晰性和模塊化,并且隨著代碼的復(fù)用,整個項(xiàng)目代碼量就開始變少了 :)。
更多建議: