我時常會聽到人們談起React函數元件,提到函數元件會不可避免的變得體積更大,邏輯更複雜。畢竟,我們把元件寫在了「一個函數」裡,因此你不得不接受元件會膨脹導致這個函數會不斷膨脹。 React的元件中也有提到:
既然函數元件能做的事情越來越多,那麼你的程式碼庫中的函式元件整體上看會越來越長。 【相關推薦:Redis影片教學、程式影片】
#其中也提到我們應該:
盡量避免過早加入抽象
如果你使用CodeScene,你可能會注意到它會在你的函數太長或太複雜的時候對你提示警告。如果按照我們之前所說的,我們可能會考慮是不是應該將CodeScene相關警告配置的更加廣泛一些。當然這是完全可以做到的,但是我覺得我們不應該這麼做,我們也不應該拒絕在程式碼中添加許多的抽象,我們可以從中獲取到很多的好處,並且大多數時候我們花費的成本並不高。我們可以繼續將我們的程式碼健康度保持的非常好!
我們應該要意識到,雖然函數元件被寫在「一個函數」裡,但是這個函數仍然可以像別的函數一樣,可以由許多其他函數組成的。像useState
,useEffect
,抑或是別的hooks,子元件它們本身也是個函數。因此我們自然可以利用相同的思路來處理函數元件的複雜性問題:透過建立一個新函數,來把即符合公共模式又複雜的程式碼封裝起來。
比較常見的處理複雜組件的方式是把它分解成多個子組件。但是這麼做可能會讓人覺得不自然或很難準確的去描述這些子組件。這時候我們就可以藉助梳理元件的鉤子函數的邏輯來發現新的抽象點。
每當我們在元件內看到由useState
、useEffect
或是其他內建鉤子函數組成的長長的清單時,我們就應該去考慮是否可以將它們提取到一個自訂hook中去。自訂hook函數是一種可以在其內部使用其他鉤子函數的函數,並且建立自訂鉤子函數也很簡單。
如下所示的元件相當於一個看板,用一個清單展示一個使用者倉庫的資料(想像成和github類似的)。這個元件並不算是個複雜元件,但是它是展示如何應用自訂hook的一個不錯的例子。
function Dashboard() { const [repos, setRepos] = useState<Repo[]>([]); const [isLoadingRepos, setIsLoadingRepos] = useState(true); const [repoError, setRepoError] = useState<string | null>(null); useEffect(() => { fetchRepos() .then((p) => setRepos(p)) .catch((err) => setRepoError(err)) .finally(() => setIsLoadingRepos(false)); }, []); return ( <div className="flex gap-2 mb-8"> {isLoadingRepos && <Spinner />} {repoError && <span>{repoError}</span>} {repos.map((r) => ( <RepoCard key={i.name} item={r} /> ))} </div> ); }
我們要把鉤子邏輯提取到一個自訂hook中,我們只需要把這些程式碼複製到一個以use
開頭的函數中(在這裡我們將其命名為useRepos
):
/** * 请求所有仓库用户列表的hook函数 */ export function useRepos() { const [repos, setRepos] = useState<Repo[]>([]); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState<string | null>(null); useEffect(() => { fetchRepos() .then((p) => setRepos(p)) .catch((err) => setError(err)) .finally(() => setIsLoading(false)); }, []); return [repos, isLoading, error] as const; }
必須用use
開頭的原因是linter
外掛可以偵測到你目前建立的是鉤子函數而不是普通函數,這樣插件就可以檢查你的鉤子函數是否符合正確的自訂鉤子的相關規則。
比較提煉之前,提煉後出現的新東西只有回傳語句和as const
。這裡的類型提示只是為了確保類型推論是正確的:一個包含3個元素的數組,類型分別是Repo[], boolean, string | null
。當然,你可以從鉤子函數返回任何你希望返回的東西。
譯者註:這裡加上
as const
在ts型別推論的差異主要體現在數字元素的個數。不加入as const
,推論的型別為(string | boolean | Repo[] | null)[]
,新增後的型別推論為readonly [Repo[], boolean, string | null]
。
將自訂鉤子useRepos
應用在我們的元件中,程式碼變成了:
function Dashboard() { const [repos, isLoadingRepos, repoError] = useRepos(); return ( <div className="flex gap-2 mb-8"> {isLoadingRepos && <Spinner />} {repoError && <span>{repoError}</span>} {repos.map((i) => ( <RepoCard key={i.name} item={i} /> ))} </div> ); }
可以發現,我們現在無法在元件內部呼叫任何的setter
函數,即無法改變狀態。在這個元件我們已經不需要包含修改狀態的邏輯,這些邏輯都包含在了useRepos
鉤子函數中。當然如果你確實需要它們,你也可以在鉤子函數的回傳語句中將其暴露出來。
這麼做有什麼好處呢? React的文檔中有提到:
透過提取自訂鉤子函數,可以實現元件邏輯的複用
我們可以簡單的想像一下,如果這個應用程式中的別的元件也需要展示倉庫中的使用者列表,那麼這個元件需要做的就只有導入useRepos
鉤子函式。如果鉤子更新了,可能使用某種形式的緩存,或者透過輪詢或更複雜的方法進行持續更新,那麼引用了這個鉤子的所有元件都將受益。
當然,提取自訂鉤子除了可以方便復用外,還有別的好處。在我們的例子中,所有的useState
和useEffect
都是為了實現同一個功能——就是獲取庫用戶列表,我們把這個看作一個原子功能,那麼在一個組件中,包含很多個這樣的原子功能也是很常見的。如果我們把這些原子功能的程式碼都分別提取到不同的自訂鉤子函數中,就更容易發現哪些狀態在我們修改程式碼邏輯時要保持同步更新,不容易出現遺漏的情況。除此之外,這麼做的好處還有:
我們已經了解到React的鉤子函數並沒有多麼神秘,也和其他函數一樣很容易就可以創建。我們可以創建自己的領域特定的鉤子,進而在整個應用程式中重複使用。也可以在各種部落格或「鉤子庫」中找到許多預先編寫好的通用鉤子。這些鉤子可以想useState
和useEffect
一樣很方便的在我們的專案中應用。 Dan Abramov的useInterval
鉤子就是一個例子,例如你有一個類似useRepos
的鉤子,但是你需要可以輪詢更新?那你就可以嘗試在你的鉤子中使用useInterval
。
英文原文網址:https://codescene.com/engineering-blog/refactoring-components-in-react-with-custom-hooks
#【推薦學習:javascript影片教學】
#以上是【翻譯】使用自訂hooks對React元件進行重構的詳細內容。更多資訊請關注PHP中文網其他相關文章!