React 的核心概念是【物件】,學了它好一陣子,一直感受到 React 的 Class Component 受制於 JavaScript 語法糖框框是一種半吊子的解決方案。
在 REACT 16.8.0 開始支援 HOOKS 後,Function Component 這種貼近 JavaScript 設計概念的正規作法終於獲得改善,HOOKS 也是源自 Class Component 的提取,也有更多的改善之處,除此之外,有沒有更加地好學、好上手,我想才是支援 HOOKS 後所需要關心的地方。
再次重溫 React 官方文件,在【用 React 思考】一節複習 React 所要傳達的概念,用 Function Component 重抄建的過程,發現元件的搭建法更加地有趣且實用。
使用 React 制作出元件僅需三個步驟:
UI 拆解後的元件架構圖
由小而大的建立元件
建立 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 的條件,官方說來自三個關鍵提問:
這個資料是從上層元件透過 props 傳下來的嗎?如果是的話,那它很可能不是 state。
這個資料是否一直保持不變呢?如果是的話,那它很可能不是 state。
你是否可以根據你的 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 再次重做此章節更覺得有趣且實用!
沒有留言:
張貼留言