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吧~~~