v19.2Latest

React 思維模式

React 可以改變您看待設計與建構應用程式的方式。當您使用 React 建構使用者介面時,首先會將其分解為稱為元件的片段。接著,您會描述每個元件的不同視覺狀態。最後,您會將元件連接起來,讓資料在其中流動。在本教學中,我們將引導您完成使用 React 建構可搜尋產品資料表的思考過程。

從設計稿開始

假設您已經有一個 JSON API 和設計師提供的設計稿。

JSON API 回傳的資料看起來像這樣:

設計稿看起來像這樣:

要在 React 中實作 UI,您通常會遵循相同的五個步驟。

步驟 1:將 UI 分解為元件階層

在設計稿中為每個元件和子元件畫上方框並為它們命名。如果您與設計師合作,他們可能已經在設計工具中為這些元件命名了。問問他們!

根據您的背景,您可以透過不同的方式思考如何將設計拆分為元件:

  • 程式設計—使用與決定是否應該建立新函式或物件相同的技巧。其中一種技巧是關注點分離,也就是說,一個元件理想上應該只關注一件事。如果它最終變得龐大,則應分解為更小的子元件。
  • CSS—考慮您會為哪些元素建立類別選擇器。(然而,元件的粒度通常稍大一些。)
  • 設計—考慮您會如何組織設計的圖層。

如果您的 JSON 結構良好,您通常會發現它自然地映射到 UI 的元件結構。這是因為 UI 和資料模型通常具有相同的資訊架構—也就是相同的形狀。將您的 UI 拆分為元件,每個元件對應資料模型的一個部分。

這個畫面上有五個元件:

  1. FilterableProductTable(灰色)包含整個應用程式。
  2. SearchBar(藍色)接收使用者輸入。
  3. ProductTable(淡紫色)根據使用者輸入顯示和篩選清單。
  4. ProductCategoryRow(綠色)為每個類別顯示標題。
  5. ProductRow(黃色)為每個產品顯示一行。

如果您查看ProductTable(淡紫色),您會看到表格標頭(包含「名稱」和「價格」標籤)並非獨立的元件。這是個人偏好問題,兩種方式都可以。在此範例中,它是ProductTable的一部分,因為它出現在ProductTable的清單內部。然而,如果這個標頭變得複雜(例如,如果您新增排序功能),您可以將其移入自己的ProductTableHeader元件。

現在您已經識別出設計稿中的元件,將它們排列成階層。在設計稿中出現在另一個元件內部的元件,在階層中應作為子元件出現:

  • FilterableProductTable
    • SearchBar
    • ProductTable
      • ProductCategoryRow
      • ProductRow

步驟 2:在 React 中建構靜態版本

現在您有了元件階層,是時候實作您的應用程式了。最直接的方法是建構一個從資料模型渲染 UI 的版本,但先不加入任何互動性… 之後再加!通常先建構靜態版本會比較容易,之後再加入互動性。建構靜態版本需要大量打字而不需要太多思考,但加入互動性則需要大量思考而不需要太多打字。

要建構一個渲染您資料模型的應用程式靜態版本,您需要建構元件,這些元件會重複使用其他元件並透過props傳遞資料。Props 是從父元件向子元件傳遞資料的一種方式。(如果您熟悉狀態的概念,在建構這個靜態版本時完全不要使用狀態。狀態僅保留給互動性使用,也就是隨時間變化的資料。由於這是應用程式的靜態版本,您不需要它。)

您可以採用「自上而下」的方式,從階層中較高的元件(如FilterableProductTable)開始建構;或者採用「自下而上」的方式,從較低的元件(如ProductRow)開始著手。在較簡單的範例中,通常自上而下比較容易;而在較大型的專案中,自下而上則較為容易。

(如果這段程式碼看起來很嚇人,請先閱讀快速入門!)

建構完你的元件後,你將擁有一個可重複使用的元件庫,用於渲染你的資料模型。由於這是一個靜態應用程式,元件只會回傳 JSX。位於層級結構頂部的元件(FilterableProductTable)會將你的資料模型作為 prop 接收。這稱為單向資料流,因為資料從頂層元件向下流動到樹狀結構底部的元件。

陷阱

此時,你不應該使用任何狀態值。那是下一步的工作!

步驟 3:找出 UI 狀態的最小但完整表示

為了讓 UI 具有互動性,你需要讓使用者能夠更改底層資料模型。你將使用狀態來實現這一點。

將狀態視為你的應用程式需要記住的最小變動資料集。建構狀態最重要的原則是保持其DRY(不要重複自己)。找出你的應用程式所需的絕對最小狀態表示,並按需計算其他所有內容。例如,如果你正在建構一個購物清單,可以將項目作為陣列儲存在狀態中。如果你還想顯示清單中的項目數量,不要將項目數量儲存為另一個狀態值——而是讀取陣列的長度。

現在思考這個範例應用程式中的所有資料片段:

  1. 原始的產品清單
  2. 使用者輸入的搜尋文字
  3. 核取方塊的值
  4. 篩選後的產品清單

哪些是狀態?識別那些不是的:

  • 它是否隨時間保持不變?如果是,則不是狀態。
  • 它是否透過 props 從父元件傳入?如果是,則不是狀態。
  • 你能根據元件中現有的狀態或 props 計算出它嗎?如果是,它絕對不是狀態!

剩下的可能就是狀態。

讓我們再逐一檢視它們:

  1. 原始的產品清單是透過 props 傳入的,所以它不是狀態。
  2. 搜尋文字似乎是狀態,因為它隨時間變化且無法從任何東西計算出來。
  3. 核取方塊的值似乎是狀態,因為它隨時間變化且無法從任何東西計算出來。
  4. 篩選後的產品清單不是狀態,因為它可以被計算出來,方法是取得原始產品清單並根據搜尋文字和核取方塊的值進行篩選。

這意味著只有搜尋文字和核取方塊的值是狀態!做得好!

Deep Dive
Props 與 State

步驟 4:識別你的 state 應該存放在哪裡

在識別出應用程式的最小狀態資料後,你需要識別哪個元件負責改變這個狀態,或者說 擁有這個狀態。記住:React 使用單向資料流,將資料沿著元件層級從父元件向下傳遞給子元件。可能不會立即清楚哪個元件應該擁有什麼狀態。如果你對這個概念還不熟悉,這可能會有些挑戰,但你可以按照以下步驟來找出答案!

對於應用程式中的每個狀態片段:

  1. 識別 所有 根據該狀態渲染內容的元件。
  2. 找到它們最近的共同父元件——在層級結構中位於它們所有元件之上的元件。
  3. 決定 state 應該存放在哪裡:
    1. 通常,你可以直接將 state 放入它們的共同父元件中。
    2. 你也可以將 state 放入它們共同父元件之上的某個元件中。
    3. 如果你找不到一個有意義擁有該狀態的元件,可以建立一個專門用於存放狀態的新元件,並將其添加到共同父元件之上的層級結構中的某個位置。

在上一步中,你在這個應用程式中找到了兩個狀態片段:搜尋輸入文字和核取方塊的值。在這個例子中,它們總是同時出現,因此將它們放在同一個地方是有意義的。

現在讓我們針對它們執行我們的策略:

  1. 識別使用狀態的元件:
    • ProductTable 需要根據該狀態(搜尋文字和核取方塊值)過濾產品清單。
    • SearchBar需要顯示該狀態(搜尋文字和核取方塊值)。
  2. 找到它們的共同父元件: 這兩個元件共享的第一個父元件是 FilterableProductTable
  3. 決定 state 存放的位置:我們將把過濾文字和核取狀態值保存在FilterableProductTable中。

因此,狀態值將存放在FilterableProductTable中。

使用 useState() Hook將 state 添加到元件中。Hooks 是特殊的函數,可以讓你「掛入」React。在FilterableProductTable的頂部添加兩個狀態變數並指定它們的初始狀態:

然後,將filterTextinStockOnly作為 props 傳遞給ProductTableSearchBar

你可以開始看到你的應用程式將如何運作。在下面的沙箱程式碼中,將filterText 的初始值從 useState('')編輯為useState('fruit')。你將看到搜尋輸入文字和表格都會更新:

請注意,編輯表單目前還無法運作。上面的沙盒中有一個控制台錯誤訊息解釋了原因:

控制台

您為表單欄位提供了 `value` 屬性,但沒有提供 `onChange` 處理函式。這將渲染一個唯讀欄位。

在上面的沙盒中,ProductTableSearchBar 讀取 filterTextinStockOnly 屬性來渲染表格、輸入框和核取方塊。例如,以下是 SearchBar如何填入輸入框的值:

然而,您尚未新增任何程式碼來響應使用者操作(例如打字)。這將是您的最後一步。

步驟 5:新增反向資料流

目前您的應用程式能正確地透過屬性和狀態沿著階層向下流動來渲染。但為了根據使用者輸入來改變狀態,您需要支援資料反向流動:位於階層深處的表單元件需要更新 FilterableProductTable中的狀態。

React 讓這種資料流變得明確,但比起雙向資料綁定需要多打一些字。如果您嘗試在上面的範例中打字或勾選核取方塊,您會發現 React 忽略了您的輸入。這是刻意的。透過撰寫<input value={filterText} />,您已將 inputvalue屬性設定為始終等於從FilterableProductTable 傳入的 filterText狀態。由於filterText狀態從未被設定,輸入框永遠不會改變。

您希望做到的是,每當使用者改變表單輸入時,狀態會更新以反映這些變化。狀態由 FilterableProductTable擁有,因此只有它能呼叫setFilterTextsetInStockOnly。為了讓SearchBar 更新 FilterableProductTable的狀態,您需要將這些函式向下傳遞給SearchBar

SearchBar內部,您將新增onChange事件處理器,並從中設定父元件的狀態:

現在應用程式完全正常運作了!

您可以在新增互動性章節中學習所有關於處理事件與更新狀態的知識。

接下來該往哪裡去

這是一個非常簡短的介紹,說明如何思考使用 React 建構元件與應用程式。您可以立即開始一個 React 專案,或是深入探討本教學中使用的所有語法