鍍金池/ 教程/ HTML/ 深入理解 React
顯示數(shù)據(jù)
組件的引用
Controlled Input 值為 null 的情況
Reconciliation
子 props 的類型
組件的詳細(xì)說明和生命周期
傳遞 Props
特殊的非 DOM 屬性
組件 API
PureRenderMixin
雙向綁定輔助工具
瀏覽器中的工作原理
深入 JSX
表單組件
Dangerously Set innerHTML
入門
JSX 中的 If-Else
克隆組件
教程
更多的關(guān)于Refs
JSX 的 false 處理
高級(jí)性能
Mounting 后 componentWillReceiveProps 未被觸發(fā)
簡介
測(cè)試工具集
JSX 陷阱
工具集成(ToolingIntegration)
公開組件功能
通過 AJAX 加載初始數(shù)據(jù)
事件系統(tǒng)
可復(fù)用組件
this.props.children undefined
不可變數(shù)據(jù)的輔助工具(Immutability Helpers)
動(dòng)態(tài)交互式用戶界面
組件的 DOM 事件監(jiān)聽
復(fù)合組件
動(dòng)畫
插件
JSX 展開屬性
行內(nèi)樣式
性能分析工具
類名操作
與其他類庫并行使用 React
鍵控的片段
標(biāo)簽和屬性支持
組件間的通信
React (虛擬)DOM 術(shù)語
JSX 根節(jié)點(diǎn)的最大數(shù)量
在樣式props中快速制定像素值
頂層 API
深入理解 React
自閉合標(biāo)簽
為什么使用 React?
getInitialState 里的 Props 是一個(gè)反模式
與 DOM 的差異

深入理解 React

這是一篇源自官方博客文章

在我看來, React 是較早使用 JavaScript 構(gòu)建大型、快速的 Web 應(yīng)用程序的技術(shù)方案。它已經(jīng)被我們廣泛應(yīng)用于 Facebook 和 Instagram 。

React 眾多優(yōu)秀特征中的其中一部分就是,教會(huì)你去重新思考如何構(gòu)建應(yīng)用程序。

本文中,我將跟你一起使用 React 構(gòu)建一個(gè)具備搜索功能的產(chǎn)品列表。

注意:

如果你無法看到本頁內(nèi)嵌的代碼片段,請(qǐng)確認(rèn)你不是用 https 協(xié)議加載本頁的。

從原型( mock )開始

假設(shè)我們已經(jīng)擁有了一個(gè) JSON API 和設(shè)計(jì)師設(shè)計(jì)的原型。我們的設(shè)計(jì)師顯然不夠好,因?yàn)樵涂雌饋砣缦拢?/p>

http://wiki.jikexueyuan.com/project/react/images/thinking-in-react-mock.png" alt="Mockup" />

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"}
];

第一步:拆分用戶界面為一個(gè)組件樹

你要做的第一件事是,為所有組件(及子組件)命名并畫上線框圖。假如你和設(shè)計(jì)師一起工作,也許他們已經(jīng)完成了這項(xiàng)工作,所以趕緊去跟他們溝通!他們的 Photoshop 圖層名也許最終可以直接用于你的 React 組件名。

然而你如何知道哪些才能成為組件?想象一下,當(dāng)你創(chuàng)建一些函數(shù)或?qū)ο髸r(shí),用到一些類似的技術(shù)。其中一項(xiàng)技術(shù)就是單一功能原則,指的是,理想狀態(tài)下一個(gè)組件應(yīng)該只做一件事,假如它功能逐漸變大就需要被拆分成更小的子組件。

由于你經(jīng)常需要將一個(gè)JSON數(shù)據(jù)模型展示給用戶,因此你需要檢查這個(gè)模型結(jié)構(gòu)是否正確以便你的 UI (在這里指組件結(jié)構(gòu))是否能夠正確的映射到這個(gè)模型上。這是因?yàn)橛脩艚缑婧蛿?shù)據(jù)模型在 信息構(gòu)造 方面都要一致,這意味著將你可以省下很多將 UI 分割成組件的麻煩事。你需要做的僅僅只是將數(shù)據(jù)模型分隔成一小塊一小塊的組件,以便它們都能夠表示成組件。

http://wiki.jikexueyuan.com/project/react/images/thinking-in-react-components.png" alt="Component diagram" />

由此可見,我們的 app 中包含五個(gè)組件。下面我已經(jīng)用斜體標(biāo)示出每個(gè)組件對(duì)應(yīng)的數(shù)據(jù)。

  1. FilterableProductTable (橘色): 包含整個(gè)例子的容器
  2. SearchBar (藍(lán)色): 接受所有 用戶輸入( user input )
  3. ProductTable (綠色): 根據(jù) 用戶輸入( user input ) 過濾和展示 數(shù)據(jù)集合( data collection )
  4. ProductCategoryRow (青色): 為每個(gè) 分類( category ) 展示一列表頭
  5. ProductRow (紅色): 為每個(gè) 產(chǎn)品( product ) 展示一列

如果你仔細(xì)觀察 ProductTable ,你會(huì)發(fā)現(xiàn)表頭(包含“ Name ”和“ Price ”標(biāo)簽)并不是單獨(dú)的組件。這只是一種個(gè)人偏好,也有一定的爭論。在這個(gè)例子當(dāng)中,我把表頭當(dāng)做 ProductTable 的一部分,因?yàn)樗卿秩尽皵?shù)據(jù)集合”的一份子,這也是 ProductTable 的職責(zé)。但是,當(dāng)這個(gè)表頭變得復(fù)雜起來的時(shí)候(例如,添加排序功能),就應(yīng)該單獨(dú)地寫一個(gè) ProductTableHeader 組件。

既然我們?cè)谠彤?dāng)中定義了這個(gè)組件,讓我們把這些元素組成一棵樹形結(jié)構(gòu)。這很簡單。被包含在其它組件中的組件在屬性機(jī)構(gòu)中應(yīng)該是子級(jí):

  • FilterableProductTable
    • SearchBar -ProductTable
      • ProductCategoryRow
      • ProductRow

第二步: 利用 React ,創(chuàng)建應(yīng)用的一個(gè)靜態(tài)版本

既然已經(jīng)擁有了組件樹,是時(shí)候開始實(shí)現(xiàn)應(yīng)用了。最簡單的方式就是創(chuàng)建一個(gè)應(yīng)用,這個(gè)應(yīng)用將數(shù)據(jù)模型渲染到 UI 上,但是沒有交互功能。拆分這兩個(gè)過程是最簡單的,因?yàn)闃?gòu)建一個(gè)靜態(tài)的版本僅需要大量的輸入,而不需要思考;但是添加交互功能卻需要大量的思考和少量的輸入。我們將會(huì)知道這是為什么。

為了創(chuàng)建一個(gè)渲染數(shù)據(jù)模型的應(yīng)用的靜態(tài)版本,你將會(huì)構(gòu)造一些組件,這些組件重用其它組件,并且通過 props 傳遞數(shù)據(jù)。 props 是一種從父級(jí)向子級(jí)傳遞數(shù)據(jù)的方式。如果你對(duì) state 概念熟悉,那么不要使用 state 來構(gòu)建這個(gè)靜態(tài)版本。 state 僅用于實(shí)現(xiàn)交互功能,也就是說,數(shù)據(jù)隨著時(shí)間變化。因?yàn)檫@是一個(gè)靜態(tài)的應(yīng)用版本,所以你并不需要 state 。

你可以從上至下或者從下至上來構(gòu)建應(yīng)用。也就是說,你可以從屬性結(jié)構(gòu)的頂部開始構(gòu)建這些組件(例如,從 FilterableProductTable 開始),或者從底部開始( ProductRow )。在簡單的應(yīng)用中,通常情況下從上至下的方式更加簡單;在大型的項(xiàng)目中,從下至上的方式更加簡單,這樣也可以在構(gòu)建的同時(shí)寫測(cè)試代碼。

在這步結(jié)束的時(shí)候,將會(huì)有一個(gè)可重用的組件庫來渲染數(shù)據(jù)模型。這些組件將會(huì)僅有 render() 方法,因?yàn)檫@是應(yīng)用的一個(gè)靜態(tài)版本。位于樹形結(jié)構(gòu)頂部的組件( FilterableProductTable )將會(huì)使用數(shù)據(jù)模型作為 prop 。如果你改變底層數(shù)據(jù)模型,然后再次調(diào)用 React.render() , UI 將會(huì)更新。查看 UI 如何被更新和什么地方改變都是很容易的,因?yàn)?React 的單向數(shù)據(jù)流(也被稱作“單向綁定”)保持了一切東西模塊化,很容易查錯(cuò),并且速度很快,沒有什么復(fù)雜的。

如果你在這步中需要幫助,請(qǐng)查看 React 文檔。

穿插一小段內(nèi)容: props 與 state 比較

在 React 中有兩種類型的數(shù)據(jù)“模型”: props 和 state 。理解兩者的區(qū)別是很重要的;如果你不太確定兩者有什么區(qū)別,請(qǐng)大致瀏覽一下官方的 React 文檔。

第三步:識(shí)別出最小的(但是完整的)代表 UI 的 state

為了使 UI 可交互,需要能夠觸發(fā)底層數(shù)據(jù)模型的變化。 React 通過 state 使這變得簡單。

為了正確構(gòu)建應(yīng)用,首先需要考慮應(yīng)用需要的最小的可變 state 數(shù)據(jù)模型集合。此處關(guān)鍵點(diǎn)在于精簡:不要存儲(chǔ)重復(fù)的數(shù)據(jù)。構(gòu)造出絕對(duì)最小的滿足應(yīng)用需要的最小 state 是有必要的,并且計(jì)算出其它強(qiáng)烈需要的東西。例如,如果構(gòu)建一個(gè) TODO 列表,僅保存一個(gè) TODO 列表項(xiàng)的數(shù)組,而不要保存另外一個(gè)指代數(shù)組長度的 state 變量。當(dāng)想要渲染 TODO 列表項(xiàng)總數(shù)的時(shí)候,簡單地取出 TODO 列表項(xiàng)數(shù)組的長度就可以了。

思考示例應(yīng)用中的所有數(shù)據(jù)片段,有:

  • 最初的 products 列表
  • 用戶輸入的搜索文本
  • 復(fù)選框的值
  • 過濾后的 products 列表

讓我們分析每一項(xiàng),指出哪一個(gè)是 state 。簡單地對(duì)每一項(xiàng)數(shù)據(jù)提出三個(gè)問題:

  1. 是否是從父級(jí)通過 props 傳入的?如果是,可能不是 state 。
  2. 是否會(huì)隨著時(shí)間改變?如果不是,可能不是 state 。
  3. 能根據(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ù)選框的值

第四步:確認(rèn) state 的生命周期

OK,我們辨別出了應(yīng)用的 state 數(shù)據(jù)模型的最小集合。接下來,需要指出哪個(gè)組件會(huì)改變或者說擁有這個(gè) state 數(shù)據(jù)模型。

記?。?React 中數(shù)據(jù)是沿著組件樹從上到下單向流動(dòng)的??赡懿粫?huì)立刻明白哪個(gè)組件應(yīng)該擁有哪些 state 數(shù)據(jù)模型。這對(duì)新手通常是最難理解和最具挑戰(zhàn)的,因此跟隨以下步驟來弄清楚這點(diǎn):

對(duì)于應(yīng)用中的每一個(gè) state 數(shù)據(jù):

  • 找出每一個(gè)基于那個(gè) state 渲染界面的組件。
  • 找出共同的祖先組件(某個(gè)單個(gè)的組件,在組件樹中位于需要這個(gè) state 的所有組件的上面)。
  • 要么是共同的祖先組件,要么是另外一個(gè)在組件樹中位于更高層級(jí)的組件應(yīng)該擁有這個(gè) state 。
  • 如果找不出擁有這個(gè) state 數(shù)據(jù)模型的合適的組件,創(chuàng)建一個(gè)新的組件來維護(hù)這個(gè) state ,然后添加到組件樹中,層級(jí)位于所有共同擁有者組件的上面。

讓我們?cè)趹?yīng)用中應(yīng)用這個(gè)策略:

  • ProductTable 需要基于 state 過濾產(chǎn)品列表,SearchBar 需要顯示搜索文本和復(fù)選框狀態(tài)。
  • 共同擁有者組件是 FilterableProductTable 。
  • 理論上,過濾文本和復(fù)選框值位于 FilterableProductTable 中是合適的。

太酷了,我們決定了 state 數(shù)據(jù)模型位于 FilterableProductTable 之中。首先,給 FilterableProductTable 添加 getInitialState() 方法,該方法返回 {filterText: '', inStockOnly: false} 來反映應(yīng)用的初始化狀態(tài)。然后傳遞 filterTextinStockOnlyProductTableSearchBar 作為 prop 。最后,使用這些 props 來過濾 ProductTable 中的行,設(shè)置在 SearchBar 中表單字段的值。

你可以開始觀察應(yīng)用將會(huì)如何運(yùn)行:設(shè)置 filterText"ball" ,然后刷新應(yīng)用。將會(huì)看到數(shù)據(jù)表格被正確更新了。

第五步:添加反向數(shù)據(jù)流

到目前為止,已經(jīng)構(gòu)建了渲染正確的基于 props 和 state 的沿著組件樹從上至下單向數(shù)據(jù)流動(dòng)的應(yīng)用。現(xiàn)在,是時(shí)候支持另外一種數(shù)據(jù)流動(dòng)方式了:組件樹中層級(jí)很深的表單組件需要更新 FilterableProductTable 中的 state 。

React 讓這種數(shù)據(jù)流動(dòng)非常明確,從而很容易理解應(yīng)用是如何工作的,但是相對(duì)于傳統(tǒng)的雙向數(shù)據(jù)綁定,確實(shí)需要輸入更多的東西。 React 提供了一個(gè)叫做 ReactLink 的插件來使其和雙向數(shù)據(jù)綁定一樣方便,但是考慮到這篇文章的目的,我們將會(huì)保持所有東西都直截了當(dāng)。

如果你嘗試在示例的當(dāng)前版本中輸入或者選中復(fù)選框,將會(huì)發(fā)現(xiàn) React 會(huì)忽略你的輸入。這是有意的,因?yàn)橐呀?jīng)設(shè)置了 inputvalue 屬性,使其總是與從 FilterableProductTable 傳遞過來的 state 一致。

讓我們思考下我們希望發(fā)生什么。我們想確保無論何時(shí)用戶改變了表單,都要更新 state 來反映用戶的輸入。由于組件只能更新自己的 state , FilterableProductTable 將會(huì)傳遞一個(gè)回調(diào)函數(shù)給 SearchBar ,此函數(shù)將會(huì)在 state 應(yīng)該被改變的時(shí)候觸發(fā)。我們可以使用 input 的 onChange 事件來監(jiān)聽用戶輸入,從而確定何時(shí)觸發(fā)回調(diào)函數(shù)。 FilterableProductTable 傳遞的回調(diào)函數(shù)將會(huì)調(diào)用 setState() ,然后應(yīng)用將會(huì)被更新。

雖然這聽起來有很多內(nèi)容,但是實(shí)際上僅僅需要幾行代碼。并且關(guān)于數(shù)據(jù)在應(yīng)用中如何流動(dòng)真的非常清晰明確。

就這么簡單

希望以上內(nèi)容讓你明白了如何思考用 React 去構(gòu)造組件和應(yīng)用。雖然可能比你之前要輸入更多的代碼,記住,讀代碼的時(shí)間遠(yuǎn)比寫代碼的時(shí)間多,并且閱讀這種模塊化的清晰的代碼是相當(dāng)容易的。當(dāng)你開始構(gòu)建大型的組件庫的時(shí)候,你將會(huì)非常感激這種清晰性和模塊化,并且隨著代碼的復(fù)用,整個(gè)項(xiàng)目代碼量就開始變少了 :)。