TL;DR: 如果要找取代 Context 的就用 Jotai,跟 Context 一樣要使用必須有 Provider,也有跟 useState 幾乎一樣的 API,資料存在 Context 層;正在找全域 state 的就用 Zustand,他是存在 Module 層
Jotai & Zustand 背景介紹
真可愛,這兩個字的意思分別是日文跟德文的「狀態」,都是在 Poimandres 底下的專案,Jotai 作者 Daishi Kato (@dai_shi) / X 在加入 Poimandres 後開始接手 Zustand 維護,後來又做了 Jotai!
簡潔的 Jotai API vs. 全能的 Zustand
在 Jotai docs 就有簡單說明他跟 useContext / Zustand / Recoli 的差別,詳細可以看 Large objects — Jotai。我看下來,簡單來說差異主要是在儲存的地方,Jotai 主要把狀態存在「context level」而 Zustand 則是存在「module level」(在 app 外全域儲存),所以 Zustand 用 createStore()
隨時隨地都能使用,不需要包 <Provider />
;而 Jotai 是用 atom
,必須要在 <Provider />
中才能使用。
以下簡單說明 Jotai 的用法:
Jotai focusAtom
訂閱大 Object 中特定項目
如果是用 state 儲存,例如說一篇文章,結構如下:
const [postRelated, setPostRelated] = useState<PostRelated>({
author: {},
post: {},
seo: {},
tags: {},
categories: {},
})
壞處顯而易見,即使我只是改個跟內文不相關的 tag / category / author / seo,整個使用到這些東西的 component 都會 rerender,這時候就會需要將整個 PostRelated 拆分成超多項,分別用 useState
/ useContext
存並傳到 child components 中,而且他們兩個裡面的東西並不會自動拆分狀態,牽一髮動全身。
這時候 Jotai 的好處就來了!使用 focusAtom()
只訂閱特定的 object key,只有當該 atom 內容有改變時才會 rerender:
// data comes from server
const postRelatedAtom = atom({
author: {},
post: {},
seo: {},
tags: [],
categories: [],
})
// 這樣就直接可以作為新的 atom 使用
const authorAtom = focusAtom(postRelatedAtom, (optic) => optic.prop('author'))
const postAtom = focusAtom(postRelatedAtom, (optic) => optic.prop('post'))
// Parent
function PostLayout() {
return (
<Provider>
<PostHeader />
<Post />
</ Provider>
)
}
// Children
function PostHeader() {
const [authorState, setAuthorState] = useAtom(authorAtom)
return <AuthorComponent author={authorState} />
}
function PostContent() {
const [postState, setPostState] = useAtom(postAtom)
// ... change post to html
return <Post />
}
現在知道 atom 的概念其實就是一個 syntax 非常簡單,卻可以達到跟
useContext
一樣結果的東東,可以想像成在<Provider />
內,可以隨意取用的 state。