React 哲學

轉載出自

https://zh-hans.reactjs.org/docs/thinking-in-react.html

React 哲學

我們認為,React 是用 JavaScript 構建快速響應的大型 Web 應用程序的首選方式。它在 Facebook 和 Instagram 上表現優秀。

React 最棒的部分之一是引導我們思考如何構建一個應用。在這篇文檔中,我們將會通過 React 構建一個可搜索的產品數據表格來更深刻地領會 React 哲學。

從設計稿開始

假設我們已經有了一個返回 JSON 的 API,以及設計師提供的組件設計稿。如下所示:

image

該 JSON API 會返回以下數據:

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

第一步:將設計好的 UI 劃分為組件層級

首先,你需要在設計稿上用方框圈出每一個組件(包括它們的子組件),并且以合適的名稱命名。如果你是和設計師一起完成此任務,那么他們可能已經做過類似的工作,所以請和他們進行交流!他們的 Photoshop 的圖層名稱可能最終就是你編寫的 React 組件的名稱!

但你如何確定應該將哪些部分劃分到一個組件中呢?你可以將組件當作一種函數或者是對象來考慮,根據單一功能原則來判定組件的范圍。也就是說,一個組件原則上只能負責一個功能。如果它需要負責更多的功能,這時候就應該考慮將它拆分成更小的組件。

在實踐中,因為你經常是在向用戶展示 JSON 數據模型,所以如果你的模型設計得恰當,UI(或者說組件結構)便會與數據模型一一對應,這是因為 UI 和數據模型都會傾向于遵守相同的信息結構。將 UI 分離為組件,其中每個組件需與數據模型的某部分匹配。

image

你會看到我們的應用中包含五個組件。我們已經將每個組件展示的數據標注為了斜體。

  1. FilterableProductTable (橙色): 是整個示例應用的整體
  2. SearchBar (藍色): 接受所有的用戶輸入
  3. ProductTable (綠色): 展示數據內容并根據用戶輸入篩選結果
  4. ProductCategoryRow (天藍色): 為每一個產品類別展示標題
  5. ProductRow (紅色): 每一行展示一個產品

你可能注意到,ProductTable 的表頭(包含 “Name” 和 “Price” 的那一部分)并未單獨成為一個組件。這僅僅是一種偏好選擇,如何處理這一問題也一直存在爭論。就這個示例而言,因為表頭只起到了渲染數據集合的作用——這與 ProductTable 是一致的,所以我們仍然將其保留為 ProductTable 的一部分。但是,如果表頭過于復雜(例如,我們需為其添加排序功能),那么將它作為一個獨立的 ProductTableHeader 組件就顯得很有必要了。

現在我們已經確定了設計稿中應該包含的組件,接下來我們將把它們描述為更加清晰的層級。設計稿中被其他組件包含的子組件,在層級上應該作為其子節點。

  • FilterableProductTable

    • SearchBar

    • ProductTable

      • ProductCategoryRow
      • ProductRow

第二步:用 React 創建一個靜態版本

參閱 CodePen 上的 React 哲學:第二步

現在我們已經確定了組件層級,可以編寫對應的應用了。最容易的方式,是先用已有的數據模型渲染一個不包含交互功能的 UI。最好將渲染 UI 和添加交互這兩個過程分開。這是因為,編寫一個應用的靜態版本時,往往要編寫大量代碼,而不需要考慮太多交互細節;添加交互功能時則要考慮大量細節,而不需要編寫太多代碼。所以,將這兩個過程分開進行更為合適。我們會在接下來的代碼中體會到其中的區別。

在構建應用的靜態版本時,我們需要創建一些會重用其他組件的組件,然后通過 props 傳入所需的數據。props 是父組件向子組件傳遞數據的方式。即使你已經熟悉了 state 的概念,也完全不應該使用 state 構建靜態版本。state 代表了隨時間會產生變化的數據,應當僅在實現交互時使用。所以構建應用的靜態版本時,你不會用到它。

你可以自上而下或者自下而上構建應用:自上而下意味著首先編寫層級較高的組件(比如 FilterableProductTable),自下而上意味著從最基本的組件開始編寫(比如 ProductRow)。當你的應用比較簡單時,使用自上而下的方式更方便;對于較為大型的項目來說,自下而上地構建,并同時為低層組件編寫測試是更加簡單的方式。

到此為止,你應該已經有了一個可重用的組件庫來渲染你的數據模型。由于我們構建的是靜態版本,所以這些組件目前只需提供 render() 方法用于渲染。最頂層的組件 FilterableProductTable 通過 props 接受你的數據模型。如果你的數據模型發生了改變,再次調用 ReactDOM.render(),UI 就會相應地被更新。數據模型變化、調用 render() 方法、UI 相應變化,這個過程并不復雜,因此很容易看清楚 UI 是如何被更新的,以及是在哪里被更新的。React 單向數據流(也叫單向綁定)的思想使得組件模塊化,易于快速開發。

如果你在完成這一步驟時遇到了困難,可以參閱 React 文檔

補充說明: 有關 props 和 state

在 React 中,有兩類“模型”數據:props 和 state。清楚地理解兩者的區別是十分重要的;如果你不太有把握,可以參閱 React 官方文檔。你也可以查看 FAQ: state 與 props 的區別是什么?

第三步:確定 UI state 的最小(且完整)表示

想要使你的 UI 具備交互功能,需要有觸發基礎數據模型改變的能力。React 通過實現 state 來完成這個任務。

為了正確地構建應用,你首先需要找出應用所需的 state 的最小表示,并根據需要計算出其他所有數據。其中的關鍵正是 DRY: Don’t Repeat Yourself。只保留應用所需的可變 state 的最小集合,其他數據均由它們計算產生。比如,你要編寫一個任務清單應用,你只需要保存一個包含所有事項的數組,而無需額外保存一個單獨的 state 變量(用于存儲任務個數)。當你需要展示任務個數時,只需要利用該數組的 length 屬性即可。

我們的示例應用擁有如下數據:

  • 包含所有產品的原始列表
  • 用戶輸入的搜索詞
  • 復選框是否選中的值
  • 經過搜索篩選的產品列表

通過問自己以下三個問題,你可以逐個檢查相應數據是否屬于 state:

  1. 該數據是否是由父組件通過 props 傳遞而來的?如果是,那它應該不是 state。
  2. 該數據是否隨時間的推移而保持不變?如果是,那它應該也不是 state。
  3. 你能否根據其他 state 或 props 計算出該數據的值?如果是,那它也不是 state。

包含所有產品的原始列表是經由 props 傳入的,所以它不是 state;搜索詞和復選框的值應該是 state,因為它們隨時間會發生改變且無法由其他數據計算而來;經過搜索篩選的產品列表不是 state,因為它的結果可以由產品的原始列表根據搜索詞和復選框的選擇計算出來。

綜上所述,屬于 state 的有:

  • 用戶輸入的搜索詞
  • 復選框是否選中的值

第四步:確定 state 放置的位置

參閱 CodePen 上的 React 哲學:第四步

我們已經確定了應用所需的 state 的最小集合。接下來,我們需要確定哪個組件能夠改變這些 state,或者說擁有這些 state。

注意:React 中的數據流是單向的,并順著組件層級從上往下傳遞。哪個組件應該擁有某個 state 這件事,對初學者來說往往是最難理解的部分。盡管這可能在一開始不是那么清晰,但你可以嘗試通過以下步驟來判斷:

對于應用中的每一個 state:

  • 找到根據這個 state 進行渲染的所有組件。
  • 找到他們的共同所有者(common owner)組件(在組件層級上高于所有需要該 state 的組件)。
  • 該共同所有者組件或者比它層級更高的組件應該擁有該 state。
  • 如果你找不到一個合適的位置來存放該 state,就可以直接創建一個新的組件來存放該 state,并將這一新組件置于高于共同所有者組件層級的位置。

根據以上策略重新考慮我們的示例應用:

  • ProductTable 需要根據 state 篩選產品列表。SearchBar 需要展示搜索詞和復選框的狀態。
  • 他們的共同所有者是 FilterableProductTable
  • 因此,搜索詞和復選框的值應該很自然地存放在 FilterableProductTable 組件中。

很好,我們已經決定把這些 state 存放在 FilterableProductTable 組件中。首先,將實例屬性 this.state = {filterText: '', inStockOnly: false} 添加到 FilterableProductTableconstructor 中,設置應用的初始 state;接著,將 filterTextinStockOnly 作為 props 傳入 ProductTableSearchBar;最后,用這些 props 篩選 ProductTable 中的產品信息,并設置 SearchBar 的表單值。

你現在可以看到應用的變化了:將 filterText 設置為 "ball" 并刷新應用,你能發現表格中的數據已經更新了。

第五步:添加反向數據流

參閱 CodePen 上的 React 哲學:第五步

到目前為止,我們已經借助自上而下傳遞的 props 和 state 渲染了一個應用。現在,我們將嘗試讓數據反向傳遞:處于較低層級的表單組件更新較高層級的 FilterableProductTable 中的 state。

React 通過一種比傳統的雙向綁定略微繁瑣的方法來實現反向數據傳遞。盡管如此,但這種需要顯式聲明的方法更有助于人們理解程序的運作方式。

如果你在這時嘗試在搜索框輸入或勾選復選框,React 不會產生任何響應。這是正常的,因為我們之前已經將 input 的值設置為了從 FilterableProductTablestate 傳遞而來的固定值。

讓我們重新梳理一下需要實現的功能:每當用戶改變表單的值,我們需要改變 state 來反映用戶的當前輸入。由于 state 只能由擁有它們的組件進行更改,FilterableProductTable 必須將一個能夠觸發 state 改變的回調函數(callback)傳遞給 SearchBar。我們可以使用輸入框的 onChange 事件來監視用戶輸入的變化,并通知 FilterableProductTable 傳遞給 SearchBar 的回調函數。然后該回調函數將調用 setState(),從而更新應用。

這就是全部了

希望這篇文檔能夠幫助你建立起構建 React 組件和應用的一般概念。盡管你可能需要編寫更多的代碼,但是別忘了:比起寫,代碼更多地是給人看的。我們一起構建的這個模塊化示例應用的代碼就很易于閱讀。當你開始構建更大的組件庫時,你會意識到這種代碼模塊化和清晰度的重要性。并且隨著代碼重用程度的加深,你的代碼行數也會顯著地減少。:)

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容