<p id="m2nkj"><option id="m2nkj"><big id="m2nkj"></big></option></p>
    <strong id="m2nkj"></strong>
    <ruby id="m2nkj"></ruby>

    <var id="m2nkj"></var>
  • 原子化狀態(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)
    <p id="m2nkj"><option id="m2nkj"><big id="m2nkj"></big></option></p>
    <strong id="m2nkj"></strong>
    <ruby id="m2nkj"></ruby>

    <var id="m2nkj"></var>
  • 精品国产v在线观看 | 免费艹逼视频 | 北条麻妃成人网站 | 美女喷水网站 | 蜜乳一区二区 | 91在线无码精品秘 蜜桃 | 国产精品福利免费在线观看 | 国产又爽 又黄 免费网站免费观看 | 天堂中文在线观看 | 一级做a爰片久久毛片A片 9 1? |