HooX 是一個基於 hook 的輕量級的 React 狀態管理工具。使用它可方便的管理 React 應用的全局狀態,概念簡單,完美支持 TS。javascript
從 React@16.8 的 hook 到 vue@3 的composition-api,基本能夠判定,函數式組件是將來趨勢。HooX提供了函數式組件下的狀態管理方案,以及徹底基於函數式寫法的一系列 API,讓用戶更加的擁抱函數式組件,走向將來更進一步。vue
寫過 hook 的同窗確定知道,hook 帶來的邏輯抽象能力,讓咱們的代碼變得更有條理。可是:java
實際舉個例子吧,好比一個列表,點擊加載下一頁,若是純 hook 書寫,會怎麼樣呢?react
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>
)
}
複製代碼
很常規的操做。如今,我但願給「下一頁」這個方法,加個防抖,那應該怎麼樣呢?是這樣嗎?git
// 獲取下一頁內容
const nextPage = debounce(() => {
const newPageNav = {
page: page + 1,
size
}
fetchList(newPageNav).then(data => {
setList(data)
setPageNav(newPageNav)
})
}, 1000)
複製代碼
乍一看好像沒有問題,但實際上是有很大隱患的!由於每次 render 都會帶來一個全新的 nextPage
。若是在這1秒鐘內,組件由於狀態或props的變動等緣由致使從新 render,那這時再點擊觸發的已是一個全新的方法,根本起不到防抖的效果。那該怎麼作?必須配合 useMemo
,而後代碼就會變成這樣:github
// 獲取下一頁內容
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
依舊會從新生成。api
難受。編輯器
還有一個問題,因爲使用 list
跟 pageNav
使用了兩個 useState
,每次更新都會帶來一次渲染。若是咱們合成一個 state
,因爲 useState
返回的 setState
是重置狀態,每次都要傳遞全量數據,好比這樣:ide
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
,**難受。**更復雜一點兒的場景能夠採用傳遞函數,獲取舊數據,稍微緩解一點,可是也仍是挺麻煩的。函數
固然啦,市面上也有一些庫能夠解決合併更新的問題,好比 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吧~~~