<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>

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

          共 13191字,需瀏覽 27分鐘

           ·

          2022-10-15 15:20

          今天小編為大家?guī)淼氖巧鐓^(qū)作者 夕水 的文章。看看他如何手寫一個(gè)mini版本的React狀態(tài)管理工具。



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

          • React Redux
          • Mobx
          • Hox

          每一個(gè)狀態(tài)管理工具都有著不盡相同的API和使用方式,而且都有一定的學(xué)習(xí)成本,而且這些狀態(tài)管理工具也有一定的復(fù)雜度,并沒有做到極致的簡單。在開發(fā)者的眼中,只有用起來比較簡單,那么才會有更多的人去使用它,Vue不就是因?yàn)槭褂煤唵危鲜挚欤餍械膯幔?/span>

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

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

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

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

          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è)組件來使用這個(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)該是一致的,也就是說我們的計(jì)數(shù)器組件應(yīng)該是共享同一個(gè)狀態(tài),那么如何共享同一個(gè)狀態(tài)?這時(shí)候就需要Context出場了。將以上的組件改造一下,我們將狀態(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>
              )
          }

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

          雖然這樣使用解決了共享狀態(tài)的問題,可是我們發(fā)現(xiàn),我們在使用的時(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>
              )
          }

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

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

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

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

          const EMPTY:unique symbol = Symbol();


          接下來我們要定義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è)的類型就夠了。

          接下來就是我們的核心函數(shù)createModel函數(shù)的實(shí)現(xiàn),我們一步一步來,首先當(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ù)的泛型。

          接下來,我們要做的自然是創(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í)際上就是通過父組件拿到初始狀態(tài)和子節(jié)點(diǎn),從context中拿到Provider組件,然后返回即可,注意我們的value是通過傳入的自定義hook函數(shù)包裝后的值。

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

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

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

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

          return { Provider,useModel }

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

          最后,我們把所有代碼合并起來,這個(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){
                      //拋出異常,使用者并沒有用Provider包裹組件
                      throw new Error('Component must be wrapped with <Container.Provider>');
                  }
                  // 返回context
                  return value;
              }
              return { Provider,useModel };
          }

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

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

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

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

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

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



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

          - END -


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

          手機(jī)掃一掃分享

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

          手機(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>
                  翔田千里与黑人未删减avXX | 亚洲电影天堂在线 | 97精品网| 欧美成人网站视频 | 国产激情自拍视频 |