2020/08/19

用 REACT 思考 -- 使用 Function Component

 

React 的核心概念是【物件】,學了它好一陣子,一直感受到 React 的 Class Component 受制於 JavaScript 語法糖框框是一種半吊子的解決方案。

在 REACT 16.8.0 開始支援 HOOKS 後,Function Component 這種貼近 JavaScript 設計概念的正規作法終於獲得改善,HOOKS 也是源自 Class Component 的提取,也有更多的改善之處,除此之外,有沒有更加地好學、好上手,我想才是支援 HOOKS 後所需要關心的地方。


再次重溫 React 官方文件,在【用 React 思考】一節複習 React 所要傳達的概念,用 Function Component 重建的過程,發現元件的搭建法更加地有趣且實用。

使用 React 制作出元件僅需三個步驟:

  1. UI 拆解後的元件架構圖

  2. 由小而大的建立元件

  3. 建立 State Hook 把各項元件掛勾起來

這三項流程做完,就等於完成一個專案,484 很簡單! 

UI 拆解後的元件架構圖

React 專注在 VIEW 上,View 就是要給使用者看的畫面,文中所要呈現的示意圖如下:

以【單一職責原則】進行圈選,就能很快知道自己要寫幾個元件,臨摩作者的思路並重製後如下圖如示:


名字很難想,就直上官方的名字了:

FilterableProductTable(亮橘): 包含整個範例

SearchBar(藍色): 接收所有使用者的輸入

ProductTable(咖啡色): 展示並過濾根據使用者輸入的資料集

ProductCategoryRow(綠色): 為每個列別展示標題

ProductRow(紅色): 為每個產品展示一列


由大到小的排出樹狀圖:

  • FilterableProductTable

    • SearchBar

    • ProductTable

      • ProductCategoryRow

      • ProductRow

示意圖和元件拼圖都完成後,接下來就是實作各部元件。

由小而大的建立元件

示意圖從大到小的排出來後,實作便要從小到大的組裝上去,分成三個階層設計,注意的是,這個階段全部只用 props 硬編碼上去,避免被 state 模糊焦點。

ProductCategoryRow、ProductRow

function ProductCategoryRow(props) {
  const category = props.category
  return (
    <tr>
      <th colSpan="2">{category}</th>
    </tr>
  )
}

function ProductRow(props) {
  const product = props.product
  const name =
    product.stocked ?
       product.name :
       <span style={{color:"red"}}>{product.name}</span>
  return (
    <tr>
      <td>{name}</td>
      <td>{product.price}</td>
    </tr>
  )
}
收到資料後要畫出『種類』和『內容』,這裡看不到外層的 <table> 標籤,僅有 <tr>、<th>、<td> 三個 table 子標籤使用。

ProductTable

function ProductTable(props) {
  const filterText = props.filterText
  const inStockOnly = props.inStockOnly

  const rows = []

  let lastCategory = null
  props.products.forEach( product => {
    if (product.name.indexOf(filterText) === -1) {
      return
    }
    // 限有庫存 且 沒庫存
    if (inStockOnly && !product.stocked) {
      return
    }
    if (product.category !== lastCategory) {
      rows.push(
        <ProductCategoryRow category={product.category} key={product.category} />
      )
    }
    rows.push(
      <ProductRow product={product} key={product.name}/>
    )
    lastCategory = product.category
  })

  return (
    <table>
      <thead><tr><td>Name</td><td>Price</td></tr></thead>
      {rows}
    </table>
  )
}

這裡說明拿到資料後要建立產品清單的處理流程,隨時都可以嘗試繪制出來除錯。

SearchBar

function SearchBar(props) {
  const filterText = props.filterText
  const inStockOnly = props.inStockOnly

  return (
    <form>
      <input type="text" placeholder="Search..." />
      <p>
        <input type="checkbox" checked={inStockOnly} />
        {" "}Only show products in stock
      </p>
    </form>
  )
}

FilterableProductTable

function FilterableProductTable(props){
  const filterText = ""
  const inStockOnly = false

  return (
    <>
      <SearchBar
        filterText={filterText}
        inStockOnly={inStockOnly}
      />
      <ProductTable filterText={filterText} inStockOnly={inStockOnly} products={props.products} />
    </>
  )
}

FilterableProductTable 最大的容器元件包裝 SearchBar 和 ProductTable 元件,到此可以順利繪制 UI 全貌以及修改 FilterableProductTable 元件的 filterText、inStockOnly 屬性來檢視 UI 效果。

建立 State Hook 把各項元件掛勾起來

React 強調的是單向資料流,而且是由大到小的傳遞,是否設置為 state 的條件,官方說來自三個關鍵提問:

  1. 這個資料是從上層元件透過 props 傳下來的嗎?如果是的話,那它很可能不是 state。

  2. 這個資料是否一直保持不變呢?如果是的話,那它很可能不是 state。

  3. 你是否可以根據你的 component 中其他的 state 或 prop 來計算這個資料呢?如果是的話,那它一定不是 state。

經過這三個問題梳理後,可以得出 state 設定在 FilterableProductTable 最外圈的容器裡是合理的選項。

state 是變動的,而且元件應該要自己更新自己的 state,更新方法會在 FilterableProductTable 元件裡寫好,並提交 Callback 給其下的子元件呼叫,透過子元件事件觸發來達成反向資料流的傳遞。

ProductTable 是被動地等待 props 被更新才重繪,只有 SearchBar 會有輸入篩選文字及勾選顯示有庫存的事件,增加 state 及反向資料流事件後如下所示:

SearchBar、FilterableProductTable

function SearchBar(props) {
  const filterText = props.filterText
  const inStockOnly = props.inStockOnly
  const onFilterTextChange = props.onFilterTextChange
  const onInStockOnlyChange = props.onInStockOnlyChange

  return (
    <form>
      <input type="text" placeholder="Search..." value={filterText} onChange={onFilterTextChange} />
      <p>
        <input type="checkbox" checked={inStockOnly} onChange={onInStockOnlyChange} />
        {" "}Only show products in stock
      </p>
    </form>
  )
}

function FilterableProductTable(props){
  const [filterText, setFilterText] = React.useState("")
  const [inStockOnly, setInStockOnly] = React.useState(false)

  const onFilterTextChange = event => setFilterText(event.target.value)
  const onInStockOnlyChange = event => setInStockOnly(event.target.checked)

  return (
    <>
      <SearchBar
        filterText={filterText}
        inStockOnly={inStockOnly}
        onFilterTextChange={onFilterTextChange}
        onInStockOnlyChange={onInStockOnlyChange}
      />
      <ProductTable filterText={filterText} inStockOnly={inStockOnly} products={props.products} />
    </>
  )
}

元件屬性集中在開頭區塊,不讓 props 在元件內肆意奔跑,是一種維持良好程式碼的習慣。

總結

React 核心概念是物件,其設計目標是程式的【明確性】和【模組性】,一旦開始重複使用這些元件,就是減少程式碼的開始。

【用 React 思考】所帶來的哲學內容值得一再咀嚼,每當有新東西時再回頭看,都會有新發現,使用 Function Component 再次重做此章節更覺得有趣且實用!

See Also

沒有留言:

張貼留言