總結本身使用過的Hooks數據流方式

Hooks正式推出也有一年出頭了,總結這一年中,本身使用過的Hooks數據流方式react

目前接觸過的數據流方案大體是2種方式redux

  • 基於React的數據流
  • 不基於React的數據流

基於React的數據流實現

簡單講來,就是使用官方提供的Hooks,好比最多見的useState,這種方式也是咱們用的最多的api

useState

經過useState能將曾經類組件的state值拆分爲多個閉包

function App() {
  const [state, setState] = useState({ foo: 0 });
  const [loading, setLoading] = useState(false);
  // ...
}
複製代碼

使用useState最大的好處是簡潔明瞭,我的以爲有2個缺點app

  1. 缺點在於拆分過多,使用太多的useState後很差管理
  2. 若是某一個state值過於複雜,在改變值時的合併是比較難處理的

針對這兩個小問題,也有另外一個hooks解決框架

useReducer

useReducer能夠說是官方簡版redux異步

const reducer = (state, { type, loading }) => {
  if (type === "FOO") return { ...state, foo: 1 };
  if (type === "SET_LOADING") return { ...state, loading };
  return state;
};
function App() {
  const [state, dispatch] = useReducer(reducer, { foo: 0, loading: false });
  // ...
}
複製代碼

redux還有一些小差別的就是react提倡將初始值賦值在useReudcer的參數中,而不是reducerstateide

上面2種方式在單組件或者是父子層級組件使用仍是比較方便,若是想像redux同樣不關乎層級共享數據呢?在Hooks中也提供了對應的方法工具

useContext + useReducer

這種方式是我在這一年中使用最多的跨組件共享狀態的方式了學習

const Context = createContext({});
const reducer = (state, { type, loading }) => {
  if (type === "FOO") return { ...state, foo: 1 };
  if (type === "SET_LOADING") return { ...state, loading };
  return state;
};
function App() {
  const [state, dispatch] = useReducer(reducer, { foo: 0, loading: false });
  return (
    <Context.Provider value={{ state, dispatch }}> // ...children </Context.Provider> ); } function Foo() { const { state, dispatch } = useContext(Context); // ... } 複製代碼

這種方式能將本身的hooks跨組件共享狀態了,使用仍是比較方便,惟一的缺點就是本身須要使用createContext來建立Context而且掛載Provider,會多一點步驟,也須要本身管理Context,這能夠說是純手動

unstated-next

unstated-next是一個200 字節的狀態管理解決方案

function useCounter(initialState = 0) {
  const [count, setCount] = useState(initialState)
  return { count, setCount }
}

const Counter = createContainer(useCounter);

function Foo() {
  const counter = Counter.useContainer();
  // ...
}

function App() {
  return (
    <Counter.Provider> <Foo /> </Counter.Provider> ) } 複製代碼

unstated-next的實現使用的全是Reactapi,源碼也短,下面會對他的源碼進行分析

使用unstated-next讓咱們不須要本身建立和管理Context了,從純手動切換到了半自動

UmiJS中提供的useModel

UmiJS中提供了一個useModel方法,能夠很方便的將hooks全局使用,它的默認規則是src/models下導出的hooks會做用於全局

// src/models/count.ts
export default () => {
  const [count, setCount] = useState(0);
  return { count, setCount };
};
// 
function App() {
  const { count } = useModel('count');
  // ...
}
複製代碼

useModel等因而省去了上面的useContext和掛載<Prvoider>的步驟,由框架處理了,在React Developer Tools能夠看到最外層是有一個存了導出hooks的值的Provider的,我想他的實現方式應該和unstated-next相似

從本身建立和管理Context,掛載Provider,到本身掛載Provder,再到只須要寫hooks邏輯,過程就是手動——半自動——全自動

它的缺點就是範圍侷限了,僅限於UmiJS框架

基於React的數據流優缺點

僅在使用過程當中我的的總結

優勢

  • 基於React,沒有額外的學習過程,簡單易用

缺點

  • 沒法作到精確刷新
  • 只是簡單是數據流管理,並不包含常見的異步數據的處理

Context沒法作到精確刷新

數據是一個總體,不能作到精確刷新,一旦改變React就會自動觸發刷新

function Foo() {
  const { state: { foo } } = useContext(Context);
  // ...
}
function Bar() {
  const { state: { bar } } = useContext(Context);
  // ...
}
複製代碼

若是在<Foo>中調用了dispatch()state.foo進行了更改,<Bar>也會刷新

不基於React的數據流實現

不基於React的數據流實現就是數據存儲不在React裏,數據改變不會直接觸發組件刷新,而是經過其餘的方式觸發組件從新渲染,我只使用過2種

  • Redux+React-Redux
  • DvaJs

React-Redux

Hooks推出後,React-Redux也更新了Hooks方法,使用useSelector()來取得state

const Counter = () => {
  const counter = useSelector(state => state.counter)
  // ...
}
複製代碼

它的優勢是會精確刷新,不會像Context同樣致使總體刷新,由於useSelector的從新渲染是本身控制的,而不是交給React處理

Redux+React-Redux的缺點我想用過的都知道,就是須要管理不少文件

DvaJS

DvaJS其實沒有提供Hooks的數據流方式

DvaJS單獨使用不多,基本是使用UmiJS,實際在Umi中有Hooks的方式去獲取數據

雲謙大佬可能全身心投入UmiJS開發,已經有很長一段時間沒更新了,可是我以爲做爲一個集成度很是高的優秀數據流管理。

僅僅使用過2個月的我對DvaJS的總結

優勢

  • 集成度很高,約定式,使用了redux-sage解決異步數據流問題
  • redux+react-redux簡單不少,再也不會有文件管理問題
  • 約定式的動態增長reducer,讓一些數據能夠懶加載

小小的總結

在類組件時代,沒法拆分state,類組件感受就很重,組件級state多了也很難管理,Context流行度也不算高,使用<Context.Consumer>讓組件更重了,後來有了contextType也沒有useContext這麼簡潔,因此感受以前流行Redux也是有緣由的,由於React自己沒有提供好的狀態管理

Hooks時代,一切都變得更簡潔,官方提供了useReducer這樣的簡潔版Redux,同時組件級的state也更簡單,使用自定義的Hooks,讓數據和視圖耦合更低了,useContext+useReducer的方案能夠解決大部分須要共享狀態的場景

使用了挺久的React,我感受不少場景都不會全局共享狀態,我如今作的項目就是後臺管理系統,頁面的數據也不會和別的頁面關聯,我都使用Context一把梭了,因此我更喜歡輕量級的Hooks數據流解決方案

看看unstated-next源碼

有時候感受本身真的變成了搬運工,缺少本身的想法,就很呆,上面提到,我很長一段時間都是用useContext來共享狀態,每次都是手動擋,前幾天忽然想,爲何本身要作重複的工做,一搜果真有大佬已經寫好了

// Provider傳入的Props
export interface ContainerProviderProps<State = void> {
  initialState?: State;
  children: React.ReactNode;
}
// createContainer建立的Container類型
export interface Container<Value, State = void> {
  Provider: React.ComponentType<ContainerProviderProps<State>>;
  useContainer: () => Value;
}

// 建立一個Container
export function createContainer<Value, State = void>(
  // 自定義數據的hook
  useHook: (initialState?: State) => Value
): Container<Value, State> {
  // Context用來傳遞數據
  let Context = React.createContext<Value | null>(null);

  function Provider(props: ContainerProviderProps<State>) {
    // 用初始數據初始化自定義的hook
    let value = useHook(props.initialState);
    // 將hook的返回值賦值給Provider
    return <Context.Provider value={value}>{props.children}</Context.Provider>;
  }

  // 使用Container,值就是自定義的hook的返回值
  function useContainer(): Value {
    let value = React.useContext(Context);
    if (value === null) {
      throw new Error("Component must be wrapped with <Container.Provider>");
    }
    return value;
  }

  return { Provider, useContainer };
}

export function useContainer<Value, State = void>(
  container: Container<Value, State>
): Value {
  return container.useContainer();
}
複製代碼

源碼很簡單,就是一個閉包

做者說 「我相信 React 在狀態管理方面已經很是出色」、「我但願社區放棄像 Redux 這樣的狀態管理庫,並找到使用 React 內置工具鏈的更好方法」,我以爲頗有道理

雖然感受這篇沒有什麼技術含量,可是沒有靈感,也有三週沒有分享文章了


最後,祝你們身體健康,工做順利!

歡迎你們關注個人公衆號~


參考文章:精讀《React Hooks 數據流》

相關文章
相關標籤/搜索