現代前端開發 - 那些我使用過的 Pattern 和 工具

以 React Hook Form + TS 完成一個 Nested Todo List 來舉例

不多說先看結果 Nested Todo List - GitHub Pages
如果說你比較習慣看 Code,可以看這裡 Source Code

# 誰可能比較適合閱讀這篇文章?

如果你不是上述對象,也沒有上述問題,你可以考慮改去讀讀其他夥伴們的 優秀作品

# 契機

最近工作需要將專案中的表單 Migrate 到 React Hook Form,原來是想要看懂它的 Source Code 是怎麼實作,來寫一篇分享文,但因為看不懂(明明都是最熟悉的 React + TS 但我就是看不懂),加上拖延症爆發,於是去看了隔壁的 Xiang 寫的這篇《進階版 To do list》,警覺自己還沒學會走就想飛,連 React Hook Form 都還沒學會使用,竟然就妄想看懂 Source Code(?,於是便想用 React Hook Form 加上原來就已經在使用的 React + TS,做做看這個 Side Project,順便熟悉一下 React Hook Form 的使用,於是就有了這篇文章。

# 從 User Story 開始

成品 Nested Todo List

# 我是如何開始學習 React Hook Form 的?

雖然已經先用 React + TS 試著實作過 Nested Todo List
但用的都是 React 內建的 useState,並不熟悉 React Hook Form 的 API,於是我先掃描了 官方文件的 API,然後看了一下 官方的 Example,知道 Nested List 是可以做到的(知道一個東西已經做的到,比不確定做不做的到,在實作上的把握和信心程度會差很多),再知道可以用 useForm 和 useFieldArray 這 2 個 Custom Hook 來做之後,就開始邊看 Example 和 API 文件邊實作邊整理自己的程式碼。

大致的步驟是

  1. 完成功能
  2. 反覆優化(抽象、樣式)
  3. 改完收工

有興趣可以看看我那些笨拙可愛的過程[1]

# 從這個 Over Engineering 的專案,看常見的 React Pattern 和工具使用

以我對前端工程認知,其實就是在開發的過程,不斷的將重複的部分不斷抽象、再組合.不同組合和抽象方式,解決的是不同場景下的問題,值得注意的是,每一次的抽象都會帶來認知上的負擔,良好的架構、Pattern 和命名會讓我們對功能產生正確的預期,這也是他們之所以重要的原因。

從一個專案中,會持續重組大致就是這幾大類

對應到整個專案的結構就會是

# Type 的組合

# 自定義 Type

type FormValues = {
nestedList: {
value: string;
isDone: boolean;
list?: {
value: string;
isDone: boolean;
}[];
};
};
type FormValues = {
nestedList: NestedList;
};

type NestedList = (Todo & {
list?: List;
})[];

type List = Todo[];

type Todo = {
value: string;
isDone: boolean;
};
const initialList: NestedList = [
{
value: "todo group 2",
isDone: true,
},
{
value: "todo group 1",
isDone: false,
list: [
{
value: "group todo 2",
isDone: false,
},
{
value: "group todo 1",
isDone: true,
},
],
},
];

# Library 定義的 Type

import React, { ReactElement, FC } from "react"; // from library type definition

const TodoForm: (props: {children: ReactElement}) => ReactElement = () => <form />
const TodoList: React.FC = ({children}) => <ol>{children}</ol>
const Todo: FC = ({children}) => <li>{children}</li>

<TodoForm>
<TodoList>
<Todo/>
<Todo/>
<Todo/>
<Todo/>
</TodoList>
</TodoForm>

# 綜合使用

// library type definition
import { FC, ReactElement, ChangeEventHandler } from "react";
import { UseFormRegisterReturn } from "react-hook-form/dist/types/form";

// self type definition
import { NestedList } from "../types/todo";

const TodoForm: FC = function () {
const {} = useForm<{ nestedList: NestedList }>();

return <form />;
};

const Todo: (props: {
onRegister: (name: "isDone" | "value") => UseFormRegisterReturn;
children?: ReactElement;
}) => ReactElement = function ({ onRegister, children }) {
const handleChange: ChangeEventHandler<HTMLInputElement> = (e) => {
onRegister?.("isDone").onChange(e);
onCheck?.();
};

return (
<li>
<StyledCheckbox onChange={handleChange} />
<StyledTextInput />
{children}
</li>
);
};

# Component 的組合與複用

# Composition Component

const TodoList: FC = function ({ children }) {
return <ol>{children}</ol>;
};

const Todo: FC & { TodoList: typeof TodoList } = function () {
return <li>{children}</li>;
};

Todo.TodoList = TodoList;
<TodoList>
<Todo>
<Todo.TodoList>
<Todo>
<Todo.TodoList>
</Todo>
</TodoList>
<ol>
<li>
<ol>
<li></li>
</ol>
</li>
</ol>

# Props

# Style 的組合與複用

# Utility First CSS (for layout)

Tailwind, Bootstrap, etc

@tailwind base;
@tailwind utilities;
<div className="flex items-center gap-3 mb-3">
<input type="checkbox" />
<input type="text" />
<button />
</div>

# CSS in JS (for customize)

Styled Component, Emotion, Linaria, etc.

import { Button } from "@geist-ui/react"; // any ui library
import styled from "styled-components"; // any css in js solution

const StyledAddButton = styled(Button)`
&&& {
display: flex;
align-items: center;
justify-content: center;
box-sizing: border-box;
padding: 0;
width: 33px;
height: 33px;
}
`
;
import styled, { css } from "styled-components";

const ContainerMixin = css`
margin: 0 auto;
width: 500px;
`
;

const StyledSection = styled.section`
${ContainerMixin}
`
;

# Logic 的複用(React Hooks)

在這個專案中沒有直接用到任何 React 的 Hook(useState, useEffect, useRef...),因為這些 Hook 都藏在 React Hook Form 提供給我們 Custom Hook 裡面,我們可以直接使用,這些 Hook 提供給我們重複出現於表單處理的邏輯做開發。

import { useForm, useFieldArray } from "react-hook-form"; // any react hook library or custom hook

const { control, register, getValues, setValue, handleSubmit } = useForm(); // 經常用於表單資料獲取和提交
const { fields, prepend, remove } = useFieldArray({ name, control }); // 經常用於動態新增表單項目

# MISC

# Module

# 未來展望

那些我還做不到、還沒做、也許未來也不會做的事

# 感謝

天下文章一大抄,感謝巨人們的肩膀。

# 參考資料

# 推薦閱讀

《我是如何開始做不會的事》

# 備註

[1]紀錄那些我笨拙的時刻
我第一個用 React 做的 Todo List(轉職前)https://github.com/futianshen/js-react-class-component-todo-list
完成功能 https://github.com/futianshen/react-hook-form
不斷優化 https://github.com/futianshen/ts-react-hook-form-nested-todo-list
改完收工 https://github.com/futianshen/nested-todo-list

你有任何處理複雜表單,或學習新工具、新知識的經驗嗎?希望你能留言與我分享~


關於作者

紀錄那些我笨拙可愛的時刻

分享文章