使用 HooX 管理 React 狀態的若干個好處

HooX  是一個基於 hook 的輕量級的 React 狀態管理工具。使用它可方便的管理 React 應用的全局狀態,概念簡單,完美支持 TS。

1. 更擁抱函數式組件

從 React@16.8 的 hook 到 vue@3 的composition-api,基本能夠判定,函數式組件是將來趨勢。HooX提供了函數式組件下的狀態管理方案,以及徹底基於函數式寫法的一系列 API,讓用戶更加的擁抱函數式組件,走向將來更進一步。javascript

2. 簡化純 hook 寫法帶來的繁雜代碼

寫過 hook 的同窗確定知道,hook 帶來的邏輯抽象能力,讓咱們的代碼變得更有條件。可是:vue

  1. useCallback/useMemo 真的是寫的很是很是多
  2. 因爲做用域問題,一些方法內的 state 常常不知道到底對不對

實際舉個例子吧,好比一個列表,點擊加載下一頁,若是純 hook 書寫,會怎麼樣呢?java

import { useState, useEffect } from 'react'

const fetchList = (...args) => fetch('./list-data', ...args)

export default function SomeList() {
  const [list, setList] = useState([])
  const [pageNav, setPageNav] = useState({ page: 1, size: 10 })
  const { page, size } = pageNav

  // 初始化請求
  useEffect(() => {
    fetchList(pageNav).then(data => {
      setList(data)
    })
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [])

  // 獲取下一頁內容
  const nextPage = () => {
    const newPageNav = {
      page: page + 1,
      size
    }
    fetchList(newPageNav).then(data => {
      setList(data)
      setPageNav(newPageNav)
    })
  }

  return (
    <div>
      <div className="list">
        {list.map((item, key) => (
          <div className="item" key={key}>
            ...
          </div>
        ))}
      </div>
      <div className="nav">
        {page}/{size}
        <div className="next" onClick={nextPage}>
          下一頁
        </div>
      </div>
    </div>
  )
}

很常規的操做。如今,我但願給「下一頁」這個方法,加個防抖,那應該怎麼樣呢?是這樣嗎?react

// 獲取下一頁內容
const nextPage = debounce(() => {
  const newPageNav = {
    page: page + 1,
    size
  }
  fetchList(newPageNav).then(data => {
    setList(data)
    setPageNav(newPageNav)
  })
}, 1000)

乍一看好像沒有問題。但實際上是有很大隱患的!由於每次 render 都會帶來一個全新的 nextPage 。若是在這1秒鐘內,組件由於狀態或props的變動等緣由致使從新 render,那這時再點擊觸發的已是一個全新的方法,根本起不到防抖的效果。那該怎麼作?必須配合 useMemo ,而後代碼就會變成這樣:git

// 獲取下一頁內容
const nextPage = useMemo(
  () =>
    debounce(() => {
      const newPageNav = {
        page: page + 1,
        size
      }
      fetchList(newPageNav).then(data => {
        setList(data)
        setPageNav(newPageNav)
      })
    }, 1000),
  [page, size]
)

nextPage  內部依賴於  page/size ,因此 useMemo  第二個參數必須加上他們。不過依舊不夠爽的是,每當個人 page  跟 size  變化時, nextPage  依舊會從新生成。github

難受。api

還有一個問題,因爲使用 list  跟 pageNav  使用了兩個  useState ,每次更新都會帶來一次渲染。若是咱們合成一個 state ,因爲 useState  返回的 setState  是重置狀態,每次都要傳遞全量數據,好比這樣:編輯器

const [{ list, pageNav }, setState] = useState({
  list: [],
  pageNav: { page: 1, size: 10 }
})

const { page, size } = pageNav

// 初始化請求
useEffect(() => {
  fetchList(pageNav).then(data => {
    setState({
      list: data,
      pageNav
    })
  })
  // eslint-disable-next-line react-hooks/exhaustive-deps
}, [])

毫無心義的設置了一次 pageNav ,難受。更復雜一點兒的場景能夠採用傳遞函數,獲取舊數據,稍微緩解一點,可是也仍是挺麻煩的。ide

固然啦,市面上也有一些庫能夠解決合併更新的問題,好比 react-use  的useSetState函數

不過,這裏提供了另一種,一勞永逸的解決方案---HooX

下面咱們上HooX來實現這個邏輯。

import { useEffect } from 'react'
import createHoox from 'hooxjs'
import { debounce } from 'lodash'

const fetchList = (...args) => fetch('./list-data', ...args)

const { useHoox, getHoox } = createHoox({
  list: [],
  pageNav: { page: 1, size: 10 }
})

const nextPage = debounce(() => {
  const [{ pageNav }, setHoox] = getHoox()
  const newPageNav = {
    page: pageNav.page + 1,
    size: pageNav.size
  }
  fetchList(newPageNav).then(data => {
    setHoox({
      list: data,
      pageNav: newPageNav
    })
  })
})

const initData = () => {
  const [{ pageNav }, setHoox] = getHoox()
  fetchList(pageNav).then(data => {
    setHoox({ list: data })
  })
}

export default function SomeList() {
  const [{ list, pageNav }] = useHoox()

  const { page, size } = pageNav

  // 初始化請求
  useEffect(initData, [])

  return (
    <div>
      <div className="list">
        {list.map((item, key) => (
          <div className="item" key={key}>
            ...
          </div>
        ))}
      </div>
      <div className="nav">
        {page}/{size}
        <div className="next" onClick={nextPage}>
          下一頁
        </div>
      </div>
    </div>
  )
}

因爲咱們把這些更新狀態的操做都抽離出組件跟 hook 內部了,再加上 getHoox  能獲取最新的數據狀態,天然就沒了 useMemo ,也不須要傳遞什麼依賴項。當咱們的場景愈來愈複雜,函數/數據在組件間的傳遞愈來愈多時,這塊的受益就愈來愈大。這是某些將 hook 全局化的狀態管理器沒有的優點。

3. 完美的 TS 支持及編輯器提示

因爲 HooX 徹底採用 ts 實現,能夠完美的支持 TS 和類型推導。拿上面的代碼舉例:
image.png

image.png

另外因爲每一個 action/effect  都是單獨聲明,直接引用。因此不管他們定義在哪裏,編輯器都能直接定位到相應地實現,而不須要像 dva  那樣藉助於 vscode 插件。

4. 可兼容 class 組件

對於一些歷史上的 class 組件,又想用全局狀態,又不想改造怎麼辦?也是有辦法的,hoox 提供了 connect ,能夠方便的將狀態注入到 class 組件中。一樣的舉個例子(TS):

class SomeList extends React.PureComponent<{
  list: any[]
  pageNav: { page: number; size: number }
  nextPage: () => void
  initData: () => void
}> {

  componentDidMount() {
    this.props.initData();
  }

  render() {
    const { list, pageNav, nextPage } = this.props;
    const { page, size } = pageNav;
    return (
      <div>
        <div className="list">
          {list.map((item, key) => (
            <div className="item" key={key}>
              ...
            </div>
          ))}
        </div>
        <div className="nav">
          {page}/{size}
          <div className="next" onClick={nextPage}>
            下一頁
          </div>
        </div>
      </div>
    );
  }
}

const ConnectedSomeList = connect(state => ({
  list: state.list,
  pageNav: state.pageNav,
  nextPage,
  initData,
}))(SomeList);

image.png

因爲全部的 props  都被注入了,所以最終返回的新組件,不須要任何 props 了(所以爲 never)。固然也能夠選擇注入部分 props。

image.png

因爲只注入了 list  跟 pageNav ,所以 nextPage  跟 initData  依舊會成爲組件須要的 props。

5. 實現簡單,沒有 Hack,穩定

HooX的實現很是簡單,去除一些類型推導,約 100 行代碼,徹底基於 Context + Provider ,無任何黑科技,純 react 原生機制與能力,因此不用擔憂會出現一些奇奇怪怪的問題。

若是本身有什麼特殊訴求,徹底也能夠本身 fork 一份自行維護,維護成本極低。

說了這麼多,快來試試HooX吧~~~

相關文章
相關標籤/搜索