<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          原子化狀態(tài)管理庫(kù) Jotai,它和 Zustand 有啥區(qū)別?

          共 22850字,需瀏覽 46分鐘

           ·

          2024-04-19 01:50

          Jotai 是一個(gè) react 的狀態(tài)管理庫(kù),主打原子化。

          提到原子化,你可能會(huì)想到原子化 CSS 框架 tailwind。

          比如這樣的 css:

          <div class="aaa"></div>
          .aaa {
              font-size16px;
              border1px solid #000;
              padding4px;
          }

          用 tailwind 這樣寫:

          <div class="text-base p-1 border border-black border-solid"></div>
          .text-base {
              font-size16px;
          }
          .p-1 {
              padding4px;
          }
          .border {
              border-width1px;
          }
          .border-black {
              border-color: black;
          }
          .border-solid {
              border-style: solid;
          }

          定義一系列原子 class,用到的時(shí)候組合這些 class。

          jotai 也是這個(gè)思想:

          通過(guò) atom 定義一個(gè)原子狀態(tài),可以把它組合起來(lái)成為新的狀態(tài)。

          那狀態(tài)為什么要原子化呢?

          來(lái)看個(gè)例子:

          import { FC, PropsWithChildren, createContext, useContext, useState } from "react";

          interface ContextType {
            aaa: number;
            bbb: number;
            setAaa: (aaa: number) => void;
            setBbb: (bbb: number) => void;
          }

          const context = createContext<ContextType>({
            aaa0,
            bbb0,
            setAaa() => {},
            setBbb() => {}
          });

          const Provider: FC<PropsWithChildren> = ({ children }) => {
            const [aaa, setAaa] = useState(0);
            const [bbb, setBbb] = useState(0);

            return (
              <context.Provider
                value={{
                  aaa,
                  bbb,
                  setAaa,
                  setBbb
                }}
              >

                {children}
              </context.Provider>

            );
          };

          const App = () => (
            <Provider>
              <Aaa />
              <Bbb />
            </Provider>

          );

          const Aaa = () => {
            const { aaa, setAaa } = useContext(context);
            
            console.log('Aaa render...')

            return <div>
              aaa: {aaa}
              <button onClick={() => setAaa(aaa + 1)}>加一</button>
            </div>
          ;
          };

          const Bbb = () => {
            const { bbb, setBbb } = useContext(context);
            
            console.log("Bbb render...");
            
            return <div>
              bbb: {bbb}
              <button onClick={() => setBbb(bbb + 1)}>加一</button>
            </div>
          ;
          };

          export default App;

          用 createContext 創(chuàng)建了 context,其中保存了 2 個(gè)useState 的 state 和 setState 方法。

          用 Provider 向其中設(shè)置值,在 Aaa、Bbb 組件里用 useContext 取出來(lái)渲染。

          瀏覽器訪問(wèn)下:

          可以看到,修改 aaa 的時(shí)候,會(huì)同時(shí)觸發(fā) bbb 組件的渲染,修改 bbb 的時(shí)候,也會(huì)觸發(fā) aaa 組件的渲染。

          因?yàn)椴还苄薷?aaa 還是 bbb,都是修改 context 的值,會(huì)導(dǎo)致所有用到這個(gè) context 的組件重新渲染。

          這就是 Context 的問(wèn)題。

          解決方案也很容易想到:拆分成兩個(gè) context 不就不會(huì)互相影響了?

          import { FC, PropsWithChildren, createContext, useContext, useState } from "react";

          interface AaaContextType {
            aaa: number;
            setAaa: (aaa: number) => void;
          }

          const aaaContext = createContext<AaaContextType>({
            aaa0,
            setAaa() => {}
          });

          interface BbbContextType {
            bbb: number;
            setBbb: (bbb: number) => void;
          }

          const bbbContext = createContext<BbbContextType>({
            bbb0,
            setBbb() => {}
          });

          const AaaProvider: FC<PropsWithChildren> = ({ children }) => {
            const [aaa, setAaa] = useState(0);

            return (
              <aaaContext.Provider
                value={{
                  aaa,
                  setAaa
                }}
              >

                {children}
              </aaaContext.Provider>

            );
          };

          const BbbProvider: FC<PropsWithChildren> = ({ children }) => {
            const [bbb, setBbb] = useState(0);

            return (
              <bbbContext.Provider
                value={{
                  bbb,
                  setBbb
                }}
              >

                {children}
              </bbbContext.Provider>

            );
          };

          const App = () => (
            <AaaProvider>
              <BbbProvider>
                <Aaa />
                <Bbb />
              </BbbProvider>
            </AaaProvider>

          );

          const Aaa = () => {
            const { aaa, setAaa } = useContext(aaaContext);
            
            console.log('Aaa render...')

            return <div>
              aaa: {aaa}
              <button onClick={() => setAaa(aaa + 1)}>加一</button>
            </div>
          ;
          };

          const Bbb = () => {
            const { bbb, setBbb } = useContext(bbbContext);
            
            console.log("Bbb render...");
            
            return <div>
              bbb: {bbb}
              <button onClick={() => setBbb(bbb + 1)}>加一</button>
            </div>
          ;
          };

          export default App;

          這樣就好了。

          這種把狀態(tài)放到不同的 context 中管理,也是一種原子化的思想。

          雖然說(shuō)這個(gè)與 jotai 沒(méi)啥關(guān)系,因?yàn)闋顟B(tài)管理庫(kù)不依賴于 context 實(shí)現(xiàn),自然也沒(méi)那些問(wèn)題。

          但是 jotai 在介紹原子化思想時(shí)提到了這個(gè):

          可能你用過(guò) redux、zustand 這些狀態(tài)管理庫(kù),jotai 和它們是完全兩種思路。

          用 zustand 是這樣寫:

          import { create } from 'zustand'

          const useStore = create((set) => ({
            aaa0,
            bbb0,
            setAaa(value) => set({ aaa: value}),
            setBbb(value) => set({ bbb: value})
          }))

          function Aaa({
              const aaa = useStore(state => state.aaa);
              const setAaa = useStore((state) => state.setAaa);
              
              console.log('Aaa render...')
              return <div>
                  aaa: {aaa}
                  <button onClick={() => setAaa(aaa + 1)}>加一</button>
              </div>

          }

          function Bbb({
              const bbb = useStore(state => state.bbb);
              const setBbb = useStore((state) => state.setBbb);

              console.log('Bbb render...')

              return <div>
                  bbb: {bbb}
                  <button onClick={() => setBbb(bbb + 1)}>加一</button>
              </div>

          }

          export default function App({
              return <div>
                  <Aaa></Aaa>
                  <Bbb></Bbb>
              </div>

          }

          store 里定義全部的 state,然后在組件里選出一部分來(lái)用。

          這個(gè)叫做 selector:

          狀態(tài)變了之后,zustand 會(huì)對(duì)比 selector 出的狀態(tài)的新舊值,變了才會(huì)觸發(fā)組件重新渲染。

          此外,這個(gè) selector 還可以起到派生狀態(tài)的作用,對(duì)原始狀態(tài)做一些修改:

          而在 jotai 里,每個(gè)狀態(tài)都是獨(dú)立的原子:

          import { atom, useAtom } from 'jotai'

          const aaaAtom = atom (0);

          const bbbAtom = atom(0);

          function Aaa({
              const [aaa, setAaa]= useAtom(aaaAtom);
              
              console.log('Aaa render...')
              return <div>
                  aaa: {aaa}
                  <button onClick={() => setAaa(aaa + 1)}>加一</button>
              </div>

          }

          function Bbb({
              const [bbb, setBbb]= useAtom(bbbAtom);

              console.log('Bbb render...')

              return <div>
                  bbb: {bbb}
                  <button onClick={() => setBbb(bbb + 1)}>加一</button>
              </div>

          }

          export default function App({
              return <div>
                  <Aaa></Aaa>
                  <Bbb></Bbb>
              </div>

          }

          狀態(tài)可以組合,產(chǎn)生派生狀態(tài):

          而在 zustand 里是通過(guò) selector 來(lái)做:

          不知道大家有沒(méi)有感受到這兩種方式的區(qū)別:

          zustand 是所有 state 放在全局 store 里,然后用到的時(shí)候 selector 取需要的部分。

          jotai 是每個(gè) state 單獨(dú)聲明原子狀態(tài),用到的時(shí)候單獨(dú)用或者組合用。

          一個(gè)自上而下,一個(gè)自下而上,算是兩種思路。

          此外,異步邏輯,比如請(qǐng)求服務(wù)端接口來(lái)拿到數(shù)據(jù),這種也是一個(gè)放在全局 store,一個(gè)單獨(dú)放在原子狀態(tài)里:

          在 zustand 里是這樣:

          import { create } from 'zustand'

          async function getListById(id{
              const data = {
                  1: ['a1''a2''a3'],
                  2: ['b1''b2''b3''b4']
              }
              return new Promise((resolve) => {
                  setTimeout(() => {
                      resolve(data[id]);
                  }, 2000);
              });
          }

          const useStore = create((set) => ({
            list: [],
            fetchDataasync (param) => {
              const data = await getListById(param);
              set({ list: data });
            },
          }))

          export default function App({
              const list = useStore(state => state.list);
              const fetchListData = useStore((state) => state.fetchData);

              return <div>
                  <button onClick={() => fetchListData(1)}>列表111</button>
                  <ul>
                      {
                          list.map(item => {
                              return <li key={item}>{item}</li>
                          })
                      }
                  </ul>
              </div>

          }

          在 store 里添加一個(gè) fetchData 的 async 方法,組件里取出來(lái)用就行。

          可以看到,2s 后拿到了數(shù)據(jù)設(shè)置到 list,并且觸發(fā)了組件渲染。

          而在 jotai 里,也是單獨(dú)放在 atom 里的:

          import { atom, useAtom } from 'jotai'

          async function getListById(id{
              const data = {
                  1: ['a1''a2''a3'],
                  2: ['b1''b2''b3''b4']
              }
              return new Promise((resolve) => {
                  setTimeout(() => {
                      resolve(data[id]);
                  }, 2000);
              });
          }

          const listAtom = atom([]);

          const fetchDataAtom = atom(nullasync (getset, param) => {
              const data = await getListById(param);
              set(listAtom, data);
          });

          export default function App() {
              const [,fetchListData] = useAtom(fetchDataAtom);
              const [list] = useAtom(listAtom);

              return <div>
                  <button onClick={() => fetchListData(2)}>列表222</button>
                  <ul>
                      {
                          list.map(item => {
                              return <li key={item}>{item}</li>
                          })
                      }
                  </ul>
              </div>

          }

          atom 除了可以直接傳值外,也可以分別傳入 get、set 函數(shù)。

          之前的派生狀態(tài)就是只傳入了 get 函數(shù):

          這樣,狀態(tài)是只讀的。

          這里我們只傳入了 set 函數(shù):

          所以狀態(tài)是只能寫。

          用的時(shí)候要取第二個(gè)參數(shù):

          當(dāng)然,這么寫有點(diǎn)費(fèi)勁,所以 atom 對(duì)于只讀只寫的狀態(tài)多了兩個(gè) hook:

          useAtomValue 是讀取值,useSetAtom 是拿到寫入函數(shù)。

          而常用的 useAtom 就是拿到這兩者返回值的數(shù)組。

          效果一樣:

          當(dāng)然,這里沒(méi)必要用兩個(gè) atom,合并成一個(gè)就行:

          import { atom, useAtom, useAtomValue, useSetAtom } from 'jotai'

          async function getListById(id{
              const data = {
                  1: ['a1''a2''a3'],
                  2: ['b1''b2''b3''b4']
              }
              return new Promise((resolve) => {
                  setTimeout(() => {
                      resolve(data[id]);
                  }, 2000);
              });
          }

          const listAtom = atom([]);

          const dataAtom = atom((get) => {
              return get(listAtom);
          }, async (getset, param) => {
              const data = await getListById(param);
              set(listAtom, data);
          });

          export default function App() {
              const [list, fetchListData] = useAtom(dataAtom);
              
              return <div>
                  <button onClick={() => fetchListData(2)}>列表222</button>
                  <ul>
                      {
                          list.map(item => {
                              return <li key={item}>{item}</li>
                          })
                      }
                  </ul>
              </div>

          }

          此外,用 useSetAtom 有時(shí)候可以起到性能優(yōu)化的作用。

          比如這段代碼:

          import { atom, useAtom, useAtomValue, useSetAtom } from 'jotai'

          const aaaAtom = atom(0);

          function Aaa({
              const [aaa] = useAtom(aaaAtom);

              console.log('Aaa render...');

              return <div>
                  {aaa}
              </div>

          }

          function Bbb({
              const [, setAaa] = useAtom(aaaAtom);

              console.log('Bbb render...');

              return <div>
                  <button onClick={() => setAaa(Math.random())}>按鈕</button>
              </div>

          }

          export default function App({
              return <div>
                  <Aaa></Aaa>
                  <Bbb></Bbb>
              </div>

          }

          在 Aaa 組件里讀取狀態(tài),在 Bbb 組件里修改狀態(tài)。

          可以看到,點(diǎn)擊按鈕 Aaa、Bbb 組件都重新渲染了。

          而其實(shí) Bbb 組件不需要重新渲染。

          這時(shí)候可以改一下:

          換成 useSetAtom,也就是不需要讀取狀態(tài)值。

          這樣狀態(tài)變了就不如觸發(fā)這個(gè)組件的重新渲染了:

          上面 Aaa 組件里也可以簡(jiǎn)化成 useAtomValue:

          import { atom, useAtom, useAtomValue, useSetAtom } from 'jotai'

          const aaaAtom = atom(0);

          function Aaa({
              const aaa = useAtomValue(aaaAtom);

              console.log('Aaa render...');

              return <div>
                  {aaa}
              </div>

          }

          function Bbb({
              const setAaa = useSetAtom(aaaAtom);

              console.log('Bbb render...');

              return <div>
                  <button onClick={() => setAaa(Math.random())}>按鈕</button>
              </div>

          }

          export default function App({
              return <div>
                  <Aaa></Aaa>
                  <Bbb></Bbb>
              </div>

          }

          至此,jotai 的核心功能就講完了:

          通過(guò) atom 創(chuàng)建原子狀態(tài),定義的時(shí)候還可以單獨(dú)指定 get、set 函數(shù)(或者叫 read、write 函數(shù)),用來(lái)實(shí)現(xiàn)狀態(tài)派生、異步狀態(tài)修改。

          組件里可以用 useAtom 來(lái)拿到 get、set 函數(shù),也可以通過(guò) useAtomValue、useSetAtom 分別拿。

          不需要讀取狀態(tài)的,用 useSetAtom 還可以避免不必要的渲染。

          那 zustand 支持的中間件機(jī)制在 jotai 里怎么實(shí)現(xiàn)呢?

          zustand 支持通過(guò)中間件來(lái)修改 get、set 函數(shù):

          比如在 set 的時(shí)候打印日志。

          或者用 persist 中間件把狀態(tài)存儲(chǔ)到 localStorage 中:

          zustand 中間件的原理很簡(jiǎn)單,就是修改了 get、set 函數(shù),做一些額外的事情。

          試一下:

          import { create } from 'zustand'
          import { persist } from 'zustand/middleware'

          const useStore = create(persist((set) => ({
              count0,
              setCount(value) => set({ count: value})
          }), {
              name'count-key'
          }))

          export default function App({
              const count = useStore(state => state.count);
              const setCount = useStore((state) => state.setCount);
              
              return <div>
                  count: {count}
                  <button onClick={() => setCount(count + 1)}>加一</button>
              </div>

          }

          jotai 里是用 utils 包的 atomWithStorage:

          試一下:

          import { useAtom } from 'jotai'
          import { atomWithStorage } from 'jotai/utils'

          const countAtom = atomWithStorage('count-key2'0)

          export default function App({
              const [count, setCount] = useAtom(countAtom);
              
              return <div>
                  count: {count}
                  <button onClick={() => setCount(count + 1)}>加一</button>
              </div>

          }

          它是怎么實(shí)現(xiàn)的呢?和 zustand 的中間件有啥區(qū)別么?

          看下源碼:

          聲明一個(gè) atom 來(lái)存儲(chǔ)狀態(tài)值,然后又聲明了一個(gè) atom 來(lái) get、set 它。

          其實(shí)和 zustand 中間件修改 get、set 方法的原理是一樣的,只不過(guò) atom 本來(lái)就支持自定義 get、set 方法。

          總結(jié)

          今天我們學(xué)了狀態(tài)管理庫(kù) jotai,以及它的原子化的思路。

          聲明原子狀態(tài),然后組合成新的狀態(tài),和 tailwind 的思路類似。

          提到原子化狀態(tài)管理,都會(huì)提到 context 的性能問(wèn)題,也就是 context 里通過(guò)對(duì)象存儲(chǔ)了多個(gè)值的時(shí)候,修改一個(gè)值,會(huì)導(dǎo)致依賴其他值的組件也跟著重新渲染。

          所以要拆分 context,這也是原子化狀態(tài)管理的思想。

          zustand 是所有 state 放在全局 store 里,然后用到的時(shí)候 selector 取需要的部分。

          jotai 是每個(gè) state 單獨(dú)聲明原子狀態(tài),用到的時(shí)候單獨(dú)用或者組合用。

          一個(gè)自上而下,一個(gè)自下而上,這是兩種思路。

          jotai 通過(guò) atom 創(chuàng)建原子狀態(tài),定義的時(shí)候還可以單獨(dú)指定 get、set 函數(shù)(或者叫 read、write 函數(shù)),用來(lái)實(shí)現(xiàn)狀態(tài)派生、異步狀態(tài)修改。

          組件里可以用 useAtom 來(lái)拿到 get、set 函數(shù),也可以通過(guò) useAtomValue、useSetAtom 分別拿。

          不需要讀取狀態(tài)的,用 useSetAtom 還可以避免不必要的渲染。

          不管是狀態(tài)、派生狀態(tài)、異步修改狀態(tài)、中間件等方面,zustand 和 jotai 都是一樣的。

          區(qū)別只是一個(gè)是全局 store 里存儲(chǔ)所有 state,一個(gè)是聲明原子 state,然后組合。

          這只是兩種思路,沒(méi)有好壞之分,看你業(yè)務(wù)需求,適合哪個(gè)就用那個(gè),或者你習(xí)慣哪種思路就用哪個(gè)。

          瀏覽 39
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  精品人妻一区二区乱码 | 俺去啦综合 | 精品无码久久久久久久久不卡 | 免费啪啪网 | 国产成人肏逼视频 |