HooX 是一个基于 hook 的轻量级的 React 状态管理工具。使用它可方便的管理 React 应用的全局状态,概念简单,完美支持 TS。
1. 更拥抱函数式组件
从 React@16.8 的 hook 到 vue@3 的composition-api,基本可以断定,函数式组件是未来趋势。HooX提供了函数式组件下的状态管理方案,以及完全基于函数式写法的一系列 API,让用户更加的拥抱函数式组件,走向未来更进一步。
2. 简化纯 hook 写法带来的繁杂代码
写过 hook 的同学肯定知道,hook 带来的逻辑抽象能力,让我们的代码变得更有条件。但是:
- useCallback/useMemo 真的是写的非常非常多
- 由于作用域问题,一些方法内的 state 经常不知道到底对不对
实际举个例子吧,比如一个列表,点击加载下一页,如果纯 hook 书写,会怎么样呢?
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>
)
}
很常规的操作。现在,我希望给“下一页”这个方法,加个防抖,那应该怎么样呢?是这样吗?
// 获取下一页内容
const nextPage = debounce(() => {
const newPageNav = {
page: page + 1,
size
}
fetchList(newPageNav).then(data => {
setList(data)
setPageNav(newPageNav)
})
}, 1000)
乍一看好像没有问题。但其实是有很大隐患的!因为每次 render 都会带来一个全新的 nextPage
。如果在这1秒钟内,组件因为状态或props的变更等原因导致重新 render,那这时再点击触发的已经是一个全新的方法,根本起不到防抖的效果。那该怎么做?必须配合 useMemo
,然后代码就会变成这样:
// 获取下一页内容
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
依旧会重新生成。
难受。
还有一个问题,由于使用 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
,难受。更复杂一点儿的场景可以采用传递函数,获取旧数据,稍微缓解一点,但是也还是挺麻烦的。
当然啦,市面上也有一些库可以解决合并更新的问题,比如 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 和类型推导。拿上面的代码举例:
另外由于每个 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);
由于所有的 props
都被注入了,因此最终返回的新组件,不需要任何 props 了(因此为 never)。当然也可以选择注入部分 props。
由于只注入了 list
跟 pageNav
,因此 nextPage
跟 initData
依旧会成为组件需要的 props。
5. 实现简单,没有 Hack,稳定
HooX的实现非常简单,去除一些类型推导,约 100 行代码,完全基于 Context
+ Provider
,无任何黑科技,纯 react 原生机制与能力,所以不用担心会出现一些奇奇怪怪的问题。
如果自己有什么特殊诉求,完全也可以自己 fork 一份自行维护,维护成本极低。
说了这么多,快来试试HooX吧~~~
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。