欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

基于React?Hooks的小型狀態(tài)管理詳解

 更新時間:2021年12月28日 11:35:43   作者:酷家樂前端團隊博客  
本文主要介紹一種基于?React?Hooks?的狀態(tài)共享方案,介紹其實現(xiàn),并總結(jié)一下使用感受,目的是在狀態(tài)管理方面提供多一種選擇方式。感興趣的小伙伴可以了解一下

本文主要介紹一種基于 React Hooks 的狀態(tài)共享方案,介紹其實現(xiàn),并總結(jié)一下使用感受,目的是在狀態(tài)管理方面提供多一種選擇方式。

實現(xiàn)基于 React Hooks 的狀態(tài)共享

React 組件間的狀態(tài)共享,是一個老生常談的問題,也有很多解決方案,例如 Redux、MobX 等。這些方案很專業(yè),也經(jīng)歷了時間的考驗,但私以為他們不太適合一些不算復(fù)雜的項目,反而會引入一些額外的復(fù)雜度。

實際上很多時候,我不想定義 mutation 和 action、我不想套一層 context,更不想寫 connect 和 mapStateToProps;我想要的是一種輕量、簡單的狀態(tài)共享方案,簡簡單單引用、簡簡單單使用。

隨著 Hooks 的誕生、流行,我的想法得以如愿。

接著介紹一下我目前在用的方案,將 Hooks 與發(fā)布/訂閱模式結(jié)合,就能實現(xiàn)一種簡單、實用的狀態(tài)共享方案。因為代碼不多,下面將給出完整的實現(xiàn)。

import {
  Dispatch,
  SetStateAction,
  useCallback,
  useEffect,
  useReducer,
  useRef,
  useState,
} from 'react';

/**
 * @see https://github.com/facebook/react/blob/bb88ce95a87934a655ef842af776c164391131ac/packages/shared/objectIs.js
 * inlined Object.is polyfill to avoid requiring consumers ship their own
 * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is
 */
function is(x: any, y: any): boolean {
  return (x === y && (x !== 0 || 1 / x === 1 / y)) || (x !== x && y !== y);
}

const objectIs = typeof Object.is === 'function' ? Object.is : is;

/**
 * @see https://github.com/facebook/react/blob/933880b4544a83ce54c8a47f348effe725a58843/packages/shared/shallowEqual.js
 * Performs equality by iterating through keys on an object and returning false
 * when any key has values which are not strictly equal between the arguments.
 * Returns true when the values of all keys are strictly equal.
 */
function shallowEqual(objA: any, objB: any): boolean {
  if (is(objA, objB)) {
    return true;
  }

  if (
    typeof objA !== 'object' ||
    objA === null ||
    typeof objB !== 'object' ||
    objB === null
  ) {
    return false;
  }

  const keysA = Object.keys(objA);
  const keysB = Object.keys(objB);

  if (keysA.length !== keysB.length) {
    return false;
  }

  // Test for A's keys different from B.
  for (let i = 0; i < keysA.length; i++) {
    if (
      !Object.prototype.hasOwnProperty.call(objB, keysA[i]) ||
      !is(objA[keysA[i]], objB[keysA[i]])
    ) {
      return false;
    }
  }

  return true;
}

const useForceUpdate = () => useReducer(() => ({}), {})[1] as VoidFunction;

type ISubscriber<T> = (prevState: T, nextState: T) => void;

export interface ISharedState<T> {
  /** 靜態(tài)方式獲取數(shù)據(jù), 適合在非組件中或者數(shù)據(jù)無綁定視圖的情況下使用 */
  get: () => T;
  /** 修改數(shù)據(jù),賦予新值 */
  set: Dispatch<SetStateAction<T>>;
  /** (淺)合并更新數(shù)據(jù) */
  update: Dispatch<Partial<T>>;
  /** hooks方式獲取數(shù)據(jù), 適合在組件中使用, 數(shù)據(jù)變更時會自動重渲染該組件 */
  use: () => T;
  /** 訂閱數(shù)據(jù)的變更 */
  subscribe: (cb: ISubscriber<T>) => () => void;
  /** 取消訂閱數(shù)據(jù)的變更 */
  unsubscribe: (cb: ISubscriber<T>) => void;
  /** 篩出部分 state */
  usePick<R>(picker: (state: T) => R, deps?: readonly any[]): R;
}

export type IReadonlyState<T> = Omit<ISharedState<T>, 'set' | 'update'>;

/**
 * 創(chuàng)建不同實例之間可以共享的狀態(tài)
 * @param initialState 初始數(shù)據(jù)
 */
export const createSharedState = <T>(initialState: T): ISharedState<T> => {
  let state = initialState;
  const subscribers: ISubscriber<T>[] = [];

  // 訂閱 state 的變化
  const subscribe = (subscriber: ISubscriber<T>) => {
    subscribers.push(subscriber);
    return () => unsubscribe(subscriber);
  };

  // 取消訂閱 state 的變化
  const unsubscribe = (subscriber: ISubscriber<T>) => {
    const index = subscribers.indexOf(subscriber);
    index > -1 && subscribers.splice(index, 1);
  };

  // 獲取當(dāng)前最新的 state
  const get = () => state;

  // 變更 state
  const set = (next: SetStateAction<T>) => {
    const prevState = state;
    // @ts-ignore
    const nextState = typeof next === 'function' ? next(prevState) : next;
    if (objectIs(state, nextState)) {
      return;
    }
    state = nextState;
    subscribers.forEach((cb) => cb(prevState, state));
  };

  // 獲取當(dāng)前最新的 state 的 hooks 用法
  const use = () => {
    const forceUpdate = useForceUpdate();

    useEffect(() => {
      let isMounted = true;
      // 組件掛載后立即更新一次, 避免無法使用到第一次更新數(shù)據(jù)
      forceUpdate();
      const un = subscribe(() => {
        if (!isMounted) return;
        forceUpdate();
      });
      return () => {
        un();
        isMounted = false;
      };
    }, []);

    return state;
  };

  const usePick = <R>(picker: (s: T) => R, deps = []) => {
    const ref = useRef<any>({});

    ref.current.picker = picker;

    const [pickedState, setPickedState] = useState<R>(() =>
      ref.current.picker(state),
    );

    ref.current.oldState = pickedState;

    const sub = useCallback(() => {
      const pickedOld = ref.current.oldState;
      const pickedNew = ref.current.picker(state);
      if (!shallowEqual(pickedOld, pickedNew)) {
        // 避免 pickedNew 是一個 function
        setPickedState(() => pickedNew);
      }
    }, []);

    useEffect(() => {
      const un = subscribe(sub);
      return un;
    }, []);

    useEffect(() => {
      sub();
    }, [...deps]);

    return pickedState;
  };

  return {
    get,
    set,
    update: (input: Partial<T>) => {
      set((pre) => ({
        ...pre,
        ...input,
      }));
    },
    use,
    subscribe,
    unsubscribe,
    usePick,
  };
};

擁有 createSharedState 之后,下一步就能輕易地創(chuàng)建出一個可共享的狀態(tài)了,在組件中使用的方式也很直接。

// 創(chuàng)建一個狀態(tài)實例
const countState = createSharedState(0);

const A = () => {
  // 在組件中使用 hooks 方式獲取響應(yīng)式數(shù)據(jù)
  const count = countState.use();
  return <div>A: {count}</div>;
};

const B = () => {
  // 使用 set 方法修改數(shù)據(jù)
  return <button onClick={() => countState.set(count + 1)}>Add</button>;
};

const C = () => {
  return (
    <button
      onClick={() => {
        // 使用 get 方法獲取數(shù)據(jù)
        console.log(countState.get());
      }}
    >
      Get
    </button>
  );
};

const App = () => {
  return (
    <>
      <A />
      <B />
      <C />
    </>
  );
};

對于復(fù)雜對象,還提供了一種方式,用于在組件中監(jiān)聽指定部分的數(shù)據(jù)變化,避免其他字段變更造成多余的 render:

const complexState = createSharedState({
  a: 0,
  b: {
    c: 0,
  },
});

const A = () => {
  const a = complexState.usePick((state) => state.a);
  return <div>A: {a}</div>;
};

但復(fù)雜對象一般更建議使用組合派生的方式,由多個簡單的狀態(tài)派生出一個復(fù)雜的對象。另外在有些時候,我們會需要一種基于原數(shù)據(jù)的計算結(jié)果,所以這里同時提供了一種派生數(shù)據(jù)的方式。

通過顯示聲明依賴的方式監(jiān)聽數(shù)據(jù)源,再傳入計算函數(shù),那么就能得到一個響應(yīng)式的派生結(jié)果了。

/**
 * 狀態(tài)派生(或 computed)
 * ```ts
 * const count1 = createSharedState(1);
 * const count2 = createSharedState(2);
 * const count3 = createDerivedState([count1, count2], ([n1, n2]) => n1 + n2);
 * ```
 * @param stores
 * @param fn
 * @param initialValue
 * @returns
 */
export function createDerivedState<T = any>(
  stores: IReadonlyState<any>[],
  fn: (values: any[]) => T,
  opts?: {
    /**
     * 是否同步響應(yīng)
     * @default false
     */
    sync?: boolean;
  },
): IReadonlyState<T> & {
  stop: () => void;
} {
  const { sync } = { sync: false, ...opts };
  let values: any[] = stores.map((it) => it.get());
  const innerModel = createSharedState<T>(fn(values));

  let promise: Promise<void> | null = null;

  const uns = stores.map((it, i) => {
    return it.subscribe((_old, newValue) => {
      values[i] = newValue;

      if (sync) {
        innerModel.set(() => fn(values));
        return;
      }

      // 異步更新
      promise =
        promise ||
        Promise.resolve().then(() => {
          innerModel.set(() => fn(values));
          promise = null;
        });
    });
  });

  return {
    get: innerModel.get,
    use: innerModel.use,
    subscribe: innerModel.subscribe,
    unsubscribe: innerModel.unsubscribe,
    usePick: innerModel.usePick,
    stop: () => {
      uns.forEach((un) => un());
    },
  };
}

至此,基于 Hooks 的狀態(tài)共享方的實現(xiàn)介紹就結(jié)束了。

在最近的項目中,有需要狀態(tài)共享的場景,我都選擇了上述方式,在 Web 項目和小程序 Taro 項目中均能使用同一套實現(xiàn),一直都比較順利。

使用感受

最后總結(jié)一下目前這種方式的幾個特點:

1.實現(xiàn)簡單,不引入其他概念,僅在 Hooks 的基礎(chǔ)上結(jié)合發(fā)布/訂閱模式,類 React 的場景都能使用,比如 Taro;

2.使用簡單,因為沒有其他概念,直接調(diào)用 create 方法即可得到 state 的引用,調(diào)用 state 實例上的 use 方法即完成了組件和數(shù)據(jù)的綁定;

3.類型友好,創(chuàng)建 state 時無需定義多余的類型,使用的時候也能較好地自動推導(dǎo)出類型;

4.避免了 Hooks 的“閉包陷阱”,因為 state 的引用是恒定的,通過 state 的 get 方法總是能獲取到最新的值:

const countState = createSharedState(0);

const App = () => {
  useEffect(() => {
    setInterval(() => {
      console.log(countState.get());
    }, 1000);
  }, []);
  // return ...
};

5.直接支持在多個 React 應(yīng)用之間共享,在使用一些彈框的時候是比較容易出現(xiàn)多個 React 應(yīng)用的場景:

const countState = createSharedState(0);

const Content = () => {
  const count = countState.use();
  return <div>{count}</div>;
};

const A = () => (
  <button
    onClick={() => {
      Dialog.info({
        title: 'Alert',
        content: <Content />,
      });
    }}
  >
    open
  </button>
);

6.支持在組件外的場景獲取/更新數(shù)據(jù)

7.在 SSR 的場景有較大局限性:state 是細碎、分散創(chuàng)建的,而且 state 的生命周期不是跟隨 React 應(yīng)用,導(dǎo)致無法用同構(gòu)的方式編寫 SSR 應(yīng)用代碼

以上,便是本文的全部內(nèi)容,實際上 Hooks 到目前流行了這么久,社區(qū)當(dāng)中已有不少新型的狀態(tài)共享實現(xiàn)方式,這里僅作為一種參考。

根據(jù)以上特點,這種方式有明顯的優(yōu)點,也有致命的缺陷(對于 SSR 而言),但在實際使用中,可以根據(jù)具體的情況來選擇合適的方式。比如在 Taro2 的小程序應(yīng)用中,無需關(guān)心 SSR,那么我更傾向于這種方式;如果在 SSR 的同構(gòu)項目中,那么定還是老老實實選擇 Redux。

總之,是多了一種選擇,到底怎么用還得視具體情況而定。?

以上就是基于React Hooks的小型狀態(tài)管理詳解的詳細內(nèi)容,更多關(guān)于React Hooks 小型狀態(tài)管理的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • create-react-app項目配置全解析

    create-react-app項目配置全解析

    這篇文章主要為大家介紹了create-react-app項目配置全解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-06-06
  • 模塊化react-router配置方法詳解

    模塊化react-router配置方法詳解

    這篇文章主要介紹了模塊化react-router配置方法詳解,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2019-06-06
  • React 高階組件入門介紹

    React 高階組件入門介紹

    本篇文章主要介紹了React高階組件入門介紹,這篇文章中我們詳細的介紹了什么是高階組件,如何使用高階組件,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-01-01
  • React過渡動畫組件基礎(chǔ)使用介紹

    React過渡動畫組件基礎(chǔ)使用介紹

    在開發(fā)中,我們想要給一個組件的顯示和消失添加某種過渡動畫,可以很好的增加用戶體驗。 當(dāng)然,我們可以通過原生的CSS來實現(xiàn)這些過渡動畫,這篇文章主要介紹了React過渡動畫組件使用
    2022-09-09
  • React SSR樣式及SEO的實踐

    React SSR樣式及SEO的實踐

    這篇文章主要介紹了React SSR樣式及SEO的實踐,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-10-10
  • 解決React報錯Functions are not valid as a React child

    解決React報錯Functions are not valid as 

    這篇文章主要為大家介紹了React報錯Functions are not valid as a React child解決詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-12-12
  • React中用@符號編寫文件路徑實現(xiàn)方法介紹

    React中用@符號編寫文件路徑實現(xiàn)方法介紹

    在Vue中,我們導(dǎo)入文件時,文件路徑中可以使用@符號指代src目錄,極大的簡化了我們對路徑的書寫。但是react中,要想實現(xiàn)這種方式書寫文件路徑,需要寫配置文件來實現(xiàn)
    2022-09-09
  • 淺談React組件在什么情況下會重新渲染

    淺談React組件在什么情況下會重新渲染

    當(dāng)我們使用React編寫組件時,組件的重新渲染是一個重要的概念,本文主要介紹了React組件在什么情況下會重新渲染,具有一定的參考價值,感興趣的可以了解一下
    2023-12-12
  • 詳解在create-react-app使用less與antd按需加載

    詳解在create-react-app使用less與antd按需加載

    這篇文章主要介紹了詳解在create-react-app使用less與antd按需加載,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-12-12
  • React18新增特性介紹

    React18新增特性介紹

    react歷次版本迭代主要想解決的是兩類導(dǎo)致網(wǎng)頁卡頓的問題,分別是cpu密集型任務(wù)和io密集型任務(wù)導(dǎo)致的卡頓問題,react18新增特性就是為了解決上述問題
    2022-09-09

最新評論