1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        社區(qū)精選 | 手寫(xiě)一個(gè)mini版本的React狀態(tài)管理工具

        共 13191字,需瀏覽 27分鐘

         ·

        2022-10-15 15:20

        今天小編為大家?guī)?lái)的是社區(qū)作者 夕水 的文章。看看他如何手寫(xiě)一個(gè)mini版本的React狀態(tài)管理工具。



        目前在React中,有很多各式各樣的狀態(tài)管理工具,如:

        • React Redux
        • Mobx
        • Hox

        每一個(gè)狀態(tài)管理工具都有著不盡相同的API和使用方式,而且都有一定的學(xué)習(xí)成本,而且這些狀態(tài)管理工具也有一定的復(fù)雜度,并沒(méi)有做到極致的簡(jiǎn)單。在開(kāi)發(fā)者的眼中,只有用起來(lái)比較簡(jiǎn)單,那么才會(huì)有更多的人去使用它,Vue不就是因?yàn)槭褂煤?jiǎn)單,上手快,而流行的嗎?

        有時(shí)候我們只需要一個(gè)全局的狀態(tài),放置一些狀態(tài)和更改狀態(tài)的函數(shù)就足夠了,這樣也達(dá)到了最簡(jiǎn)化原則。

        下面讓我們一起來(lái)實(shí)現(xiàn)一個(gè)最簡(jiǎn)單的狀態(tài)管理工具吧。

        這個(gè)狀態(tài)管理工具的核心就使用到了Context API(https://reactjs.org/docs/context.html),在了解本文之前務(wù)必先了解并熟悉使用這個(gè)API的用法。

        首先我們來(lái)看這個(gè)狀態(tài)管理工具是如何使用的。假設(shè)有一個(gè)計(jì)數(shù)器狀態(tài),然后我們通過(guò)二個(gè)方法分別去修改計(jì)數(shù)器,也就是做加法和減法,換句話說(shuō)我們需要用到一個(gè)計(jì)數(shù)器狀態(tài),二個(gè)方法來(lái)修改這個(gè)狀態(tài)。在React函數(shù)組件中,我們使用useState方法來(lái)初始化一個(gè)狀態(tài),因此,我們可以很容易的寫(xiě)出如下代碼:

        import { useState } from 'react'
        const useCounter = (initialCount = 0) => {
            const [count,setCount] = useState(initialCount);
            const increment = () => setCount(count + 1);
            const decrement = () => setCount(count - 1);
            return {
                count,
                increment,
                decrement
            }
        }
        export default useCounter;

        現(xiàn)在,讓我們創(chuàng)建一個(gè)組件來(lái)使用這個(gè)useCounter鉤子函數(shù),如下:

        import React from 'react'
        import useCounter from './useCounter'

        const Counter = () => {
            const { count,increment,decrement } = useCounter();
            return (
                <div className="counter">
                    { count }
                    <button type="button" onClick={increment}>add</button>
                    <button type="button" onClick={decrement}>plus</button>
                </div>
            )
        }

        然后在根組件App當(dāng)中使用,如下:

        import React from 'react'
        const App = () => {
            return (
                <div className="App">
                    <Counter />
                    <Counter />
                </div>
            )
        }

        這樣,一個(gè)計(jì)數(shù)器組件就大功告成了,可是真的只是這樣嗎?

        首先,我們應(yīng)該知道計(jì)數(shù)器組件的狀態(tài)應(yīng)該是一致的,也就是說(shuō)我們的計(jì)數(shù)器組件應(yīng)該是共享同一個(gè)狀態(tài),那么如何共享同一個(gè)狀態(tài)?這時(shí)候就需要Context出場(chǎng)了。將以上的組件改造一下,我們將狀態(tài)放在根組件App當(dāng)中初始化,并且傳到子組件中去。先修改App根組件的代碼如下:

        新建一個(gè)CounterContext.ts文件,代碼如下:


        const CounterContext = createContext();
        export default CounterContext;
        import React,{ createContext } from 'react'
        import CounterContext from './CounterContext'

        const App = () => {
            const { count,increment,decrement } = useCounter();
            return (
                <div className="App">
                    <CounterContext.Provider value={{count,increment,decrement}}>
                        <Counter />
                        <Counter />
                    </CounterContext.Provider>
                </div>
            )
        }

        然后在Counter組件代碼我們也修改如下:

        import React,{ useContext } from 'react'
        import CounterContext from './CounterContext'

        const Counter = () => {
            const { count,increment,decrement } = useContext(CounterContext);
            return (
                <div className="counter">
                    { count }
                    <button type="button" onClick={increment}>add</button>
                    <button type="button" onClick={decrement}>plus</button>
                </div>
            )
        }

        如此一來(lái),我們就可以共享count狀態(tài),無(wú)論是在多深的子組件當(dāng)中使用都沒(méi)有問(wèn)題,但是這并沒(méi)有結(jié)束,讓我們繼續(xù)。

        雖然這樣使用解決了共享狀態(tài)的問(wèn)題,可是我們發(fā)現(xiàn),我們?cè)谑褂玫臅r(shí)候還要額外的傳入一個(gè)context名,所以我們需要包裝一下,到最后,我們只需要像如下這樣使用:


        const Counter = createModel(useCounter);
        export default Counter;

        const { Provider,useModel } = Counter;


        然后我們的App組件就應(yīng)該是這樣:

        import React,{ createContext } from 'react'
        import counter from './Counter'

        const App = () => {
            const { Provider } = counter;
            return (
                <div className="App">
                    <Provider>
                        <Counter />
                        <Counter />
                    </Provider>
                </div>
            )
        }

        繼續(xù)修改我們的Counter組件,如下:
        import React,{ useContext } from 'react'
        import counter from './Counter'

        const Counter = () => {
            const { count,increment,decrement } = counter.useModel();
            return (
                <div className="counter">
                    { count }
                    <button type="button" onClick={increment}>add</button>
                    <button type="button" onClick={decrement}>plus</button>
                </div>
            )
        }

        通過(guò)以上代碼的展示,其實(shí)我們也就明白了,我們無(wú)非是將useContext和createContext內(nèi)置到我們封裝的Model里面去了。

        接下來(lái)我們就來(lái)揭開(kāi)這個(gè)狀態(tài)管理工具的神秘面紗,首先要用到React相關(guān)的API,所以我們需要導(dǎo)入進(jìn)來(lái)。如下:

        // 導(dǎo)入類型
        import type { ReactNode,ComponentType } from 'react';
        import { createContext,useContext } from 'react';

        接下來(lái)定義一個(gè)唯一標(biāo)識(shí),用于確定傳入的Context,
        并且這個(gè)用來(lái)確定使用者使用Context時(shí)是正確使用的。

        const EMPTY:unique symbol = Symbol();


        接下來(lái)我們要定義Provider的類型。如下:

        export interface ModelProviderProps<State = void> {
            initialState?: State
            children: ReactNode
        }

        以上我們定義了context的狀態(tài)類型,是一個(gè)泛型,參數(shù)就是狀態(tài)的類型,默認(rèn)初始化為undefined類型,并且定義了一個(gè)children的類型,因?yàn)镻rovider的子節(jié)點(diǎn)是一個(gè)React節(jié)點(diǎn),所以也就定義成ReactNode類型。

        然后就是我們的Model類型,如下:

        export interface Model<Value,State = void> {
            Provider: ComponentType<ModelProviderProps<State>>
            useModel: () => Value
        }

        這個(gè)也很好理解,因?yàn)镸odel暴露了兩個(gè)東西,第一個(gè)是Provider,第二個(gè)就是useContext,只是換了一個(gè)名字而已,定義這兩個(gè)的類型就夠了。

        接下來(lái)就是我們的核心函數(shù)createModel函數(shù)的實(shí)現(xiàn),我們一步一步來(lái),首先當(dāng)然是定義這個(gè)函數(shù),注意類型,如下:

        export const createModel = <Value,State = void>(useHook:(initialState?:State) => Value): Model<Value,State> => {
            //核心代碼
        }

        以上函數(shù)難以理解的應(yīng)該是類型的定義,我們createModel函數(shù)傳入一個(gè)hook函數(shù),hook函數(shù)傳入一個(gè)狀態(tài)作為參數(shù),然后返回值就是我們定義好的Model泛型,參數(shù)為類型就是我們定義好的這個(gè)函數(shù)的泛型。

        接下來(lái),我們要做的自然是創(chuàng)建一個(gè)context,如下:


        //創(chuàng)建一個(gè)context
        const context = createContext<Value | typeof EMPTY>(EMPTY);

        然后我們要?jiǎng)?chuàng)建一個(gè)Provider函數(shù),本質(zhì)上也是一個(gè)React組件,如下:

        const Provider = (props:ModelProviderProps<State>) => {
            // 這里使用ModelProvider主要是不能和定義的函數(shù)名起沖突
            const { Provider:ModelProvider } = context;
            const { initialState,children } = props;
            const value = useHook(initialState);
            return (
                <ModelProvider value={value}>{children}</ModelProvider>
            )
        }

        這里也很好理解,實(shí)際上就是通過(guò)父組件拿到初始狀態(tài)和子節(jié)點(diǎn),從context中拿到Provider組件,然后返回即可,注意我們的value是通過(guò)傳入的自定義hook函數(shù)包裝后的值。

        第三步,我們就需要定義一個(gè)hook函數(shù)拿到這個(gè)自定義的Context,如下:

        const useModel = ():Value => {
            const value = useContext(context);
            // 這里確定一下用戶是否正確使用Provider
            if(value === EMPTY){
                //拋出異常,使用者并沒(méi)有用Provider包裹組件
                throw new Error('Component must be wrapped with <Container.Provider>');
            }
            // 返回context
            return value;
        }

        這個(gè)函數(shù)的實(shí)現(xiàn)也很好理解,就是獲取context,判斷context是否正確使用,然后返回。

        最后我們?cè)谶@個(gè)函數(shù)內(nèi)部返回這2個(gè)東西,即返回Provider和useModel兩個(gè)函數(shù)。如下:

        return { Provider,useModel }

        把以上代碼全部合并起來(lái),createModel函數(shù)就大功告成啦。

        最后,我們把所有代碼合并起來(lái),這個(gè)狀態(tài)管理工具也就完成了。

        // 導(dǎo)入類型
        import type { ReactNode,ComponentType } from 'react';
        import { createContext,useContext } from 'react';
        const EMPTY:unique symbol = Symbol();
        export interface ModelProviderProps<State = void> {
            initialState?: State
            children: ReactNode
        }
        export interface Model<Value,State = void> {
            Provider: ComponentType<ModelProviderProps<State>>
            useModel: () => Value
        }
        export const createModel = <Value,State = void>(useHook:(initialState?:State) => Value): Model<Value,State> => {
            //創(chuàng)建一個(gè)context
            const context = createContext<Value | typeof EMPTY>(EMPTY);
            // 定義Provider函數(shù)
            const Provider = (props:ModelProviderProps<State>) => {
                const { Provider:ModelProvider } = context;
                const { initialState,children } = props;
                const value = useHook(initialState);
                return (
                    <ModelProvider value={value}>{children}</ModelProvider>
                )
            }
            // 定義useModel函數(shù)
            const useModel = ():Value => {
                const value = useContext(context);
                // 這里確定一下用戶是否正確使用Provider
                if(value === EMPTY){
                    //拋出異常,使用者并沒(méi)有用Provider包裹組件
                    throw new Error('Component must be wrapped with <Container.Provider>');
                }
                // 返回context
                return value;
            }
            return { Provider,useModel };
        }

        更近一步,我們?cè)賹?dǎo)出一個(gè)useModel函數(shù),如下:

        export const useModel = <Value,State = void>(model:Model<Value,State>):Value => {
            return model.useModel();
        }

        到目前為止,我們的整個(gè)狀態(tài)管理工具就完成啦,使用起來(lái)也很簡(jiǎn)單,很多輕量的共享狀態(tài)項(xiàng)目當(dāng)中我們也就再也不需要使用像Redux這樣的比較復(fù)雜的狀態(tài)管理工具了。

        當(dāng)然這個(gè)想法也并不是我本人想的,文末已注明來(lái)源,本文對(duì)源碼做了一遍分析。

        源碼地址:https://github.com/eveningwater/code-segment-react/blob/main/docs/model/model.zh-CN.md

        PS: 本文源碼來(lái)自:https://github.com/jamiebuilds/unstated-next/blob/master/src/unstated-next.tsx



        點(diǎn)擊左下角閱讀原文,到 SegmentFault 思否社區(qū) 和文章作者展開(kāi)更多互動(dòng)和交流,公眾號(hào)后臺(tái)回復(fù)“ 入群 ”即可加入我們的技術(shù)交流群,收獲更多的技術(shù)文章~

        - END -


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

        手機(jī)掃一掃分享

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

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            粉嫩精品国产色综合久久不8 | 免费看日本一级王色直播 | 亚洲天堂网站在线 | 少妇高潮婬片免费观看 | 国产激情视频网 | 新激情五月天 | 天天鲁天天躁天在线观看 | 99精品视频16在线免费观看 | 国产香蕉网 | 大香蕉啪啪网 |