HooX 是一個基於 hook 的輕量級的 React 狀態管理工具。使用它可方便的管理 React 應用的全局狀態,概念簡單,完美支持 TS。
從 React@16.8 的 hook 到 vue@3 的composition-api,基本能夠判定,函數式組件是將來趨勢。HooX提供了函數式組件下的狀態管理方案,以及徹底基於函數式寫法的一系列 API,讓用戶更加的擁抱函數式組件,走向將來更進一步。javascript
寫過 hook 的同窗確定知道,hook 帶來的邏輯抽象能力,讓咱們的代碼變得更有條件。可是:vue
實際舉個例子吧,好比一個列表,點擊加載下一頁,若是純 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 全局化的狀態管理器沒有的優點。
因爲 HooX 徹底採用 ts 實現,能夠完美的支持 TS 和類型推導。拿上面的代碼舉例:
另外因爲每一個 action/effect
都是單獨聲明,直接引用。因此不管他們定義在哪裏,編輯器都能直接定位到相應地實現,而不須要像 dva
那樣藉助於 vscode 插件。
對於一些歷史上的 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);
因爲全部的 props
都被注入了,所以最終返回的新組件,不須要任何 props 了(所以爲 never)。固然也能夠選擇注入部分 props。
因爲只注入了 list
跟 pageNav
,所以 nextPage
跟 initData
依舊會成爲組件須要的 props。
HooX的實現很是簡單,去除一些類型推導,約 100 行代碼,徹底基於 Context
+ Provider
,無任何黑科技,純 react 原生機制與能力,因此不用擔憂會出現一些奇奇怪怪的問題。
若是本身有什麼特殊訴求,徹底也能夠本身 fork 一份自行維護,維護成本極低。
說了這麼多,快來試試HooX吧~~~