篩選組件包含一個 button 和一個 form,button 能控制 form 的顯示與隱藏。設計里 button 和 form 在一行,實現時理所當然地把這一行封裝成了一個組件。但在另一個項目里,設計有區別,button 被放到了面板的右上角,之前寫的組件沒法復用了。 ...
一個篩選組件如下圖:
篩選組件包含一個 button
和一個 form
,button
能控制 form
的顯示與隱藏。設計里 button
和 form
在一行,實現時理所當然地把這一行封裝成了一個組件。
但在另一個項目里,設計有區別,button
被放到了面板的右上角,之前寫的組件沒法復用了。
看看之前封裝的組件:
function FilterBar() {
const [showForm, setShowForm] = useState(true);
return (
<div className="container">
<button
onClick={() => setShowForm(pre => !pre)}
>
{showForm ? "<<< Filter" : "Filter >>>"}
</button>
{showForm && (
<form>
<label>
Name <input />
</label>
<label>
IP <input />
</label>
</form>
)}
</div>
);
}
它做了以下 4 件事:
- 創建
button
, - 創建
form
, - 實現
button
和form
的聯動, - 創建容器
div.container
, 對button
和form
佈局。
「創建容器 div.container
, 對 button
和 form
佈局」這件事導致了組件沒法在新項目里復用。
能不能寫一個組件,不對子元素佈局,把佈局的工作交給組件使用者去做?
思路:把 div.container
去掉,把 button
和 form
的實例返回,組件變成了一個 hook。
function useFilterBar() {
const [showForm, setShowForm] = useState(true);
return [
<button
onClick={() => setShowForm(pre => !pre)}
>
{showForm ? "<<< Filter" : "Filter >>>"}
</button>,
showForm ? (
<form>
<label>
Name <input />
</label>
<label>
IP <input />
</label>
</form>
) : (
undefined
)
];
}
function App() {
const [filterBtn, filterForm] = useFilterBar();
return (
<div className="card">
<Header title="Users">{filterBtn}</Header>
{filterForm}
<table>
<thead>
<tr>
<th>Name</th>
<th>IP</th>
</tr>
</thead>
<tbody>...</tbody>
</table>
</div>
);
}
class 組件沒法用 hook 怎麼辦?
思路:使用高階組件,高階組件是函數組件,可以使用 hook,然後把 button
和 form
通過 props
傳遞給 class 組件。
const withFilterBar = Cmp => props => {
const [filterBtn, filterForm] = useFilterBar();
return (
<Cmp
{...props}
filterBtn={filterBtn}
filterForm={filterForm}
/>
);
};
我寫這篇文章的目的不是為了記錄技巧,而是希望對組件有新的思考:把「子元素關聯關係 - 邏輯」和「子元素佈局 - 視圖」兩件事拆開,讓組件更靈活。