使用 HooX 管理 React 状态的若干个好处

HooX是一个基于 hook 的轻量级的 React 状态管理工具。使用它可方便的管理 React 应用的全局状态,概念简单,完美支持 TS。

1. 更拥抱函数式组件

React@16.8的 hook 到 vue@3 的composition-api,基本可以断定,函数式组件是未来趋势。HooX提供了函数式组件下的状态管理方案,以及完全基于函数式写法的一系列 API,让用户更加的拥抱函数式组件,走向未来更进一步。

2. 简化纯 hook 写法带来的繁杂代码

写过 hook 的同学肯定知道,hook 带来的逻辑抽象能力,让我们的代码变得更有条件。但是:

  1. useCallback/useMemo 真的是写的非常非常多
  2. 由于作用域问题,一些方法内的 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第二个参数必须加上他们。不过依旧不够爽的是,每当我的 pagesize变化时, nextPage依旧会重新生成。

难受。

还有一个问题,由于使用 listpageNav使用了两个  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-useuseSetState

不过,这里提供了另外一种,一劳永逸的解决方案---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。

由于只注入了 listpageNav,因此 nextPageinitData依旧会成为组件需要的 props。

5. 实现简单,没有 Hack,稳定

HooX的实现非常简单,去除一些类型推导,约 100 行代码,完全基于 Context+ Provider,无任何黑科技,纯 react 原生机制与能力,所以不用担心会出现一些奇奇怪怪的问题。

如果自己有什么特殊诉求,完全也可以自己 fork 一份自行维护,维护成本极低。

说了这么多,快来试试HooX吧~

原文链接:segmentfault.com

上一篇:使用 JS 及 React Hook 时需要注意过时闭包的坑(文中有解决方法)
下一篇:这些前端资源,你值得拥有

相关推荐

  • 高频数据交换下Flutter与ReactNative的对比

    (标题图片来自网络,侵删) 后端使用go写的socketio服务模拟期货行情数据,每10ms推送10条行情数据 ReactNative已经尽力优化了。 Flutter由于没fluttersock...

    2 年前
  • 高性能迷你React框架 anu1.3.0 发布

    anujs1.3.0是一款高性能Reactlike框架,是目前世界上对React16兼容最好的迷你库。 自React16起,相继推出createContext,createPortal, creat...

    2 年前
  • 高德地图 react-amap 实战

    clipboard.png(https://img.javascriptcn.com/5a33946ad8c0ea8ee7870f74f331d0c0 "clipboard.png") reacta...

    9 个月前
  • 高品质 React UI 组件

    (https://img.javascriptcn.com/cca319311c2ea59a2b5cdaa63b997828)(https://link.funteas.com?target=http...

    2 年前
  • 骚操作!在react中使用vuex

    原文地址(https://github.com/zyl1314/blog/issues/12) 前言 笔者最近在学习使用,提到react就绕不过去。redux是一个状态管理架构,被广泛用于rea...

    2 年前
  • 项目文档说明:react + Ant Design 的 blog-react-admin

    效果图(https://img.javascriptcn.com/734ce56fe3a37ab11e9744473f56bae1 "效果图") 前言 此 blogreactadmin 项目是基...

    2 年前
  • 面试题:Hooks 与 React 生命周期的关系

    React 生命周期很多人都了解,但通常我们所了解的都是 单个组件 的生命周期,但针对 Hooks 组件、多个关联组件(父子组件和兄弟组件) 的生命周期又是怎么样的喃?你有思考和了解过吗,接下来我们将...

    10 个月前
  • 面试官:请你在React中引入Vue3的@vue/reactivity,实现响应式。

    前言 React的状态管理是一个缤纷繁杂的大世界,光我知道的就不下数十种,其中有最出名immutable阵营的redux,有mutable阵营的mobx,reacteasystate,在hooks诞生...

    4 个月前
  • 面试官我想做个Reacter(React路由)

    路由的基础用法回顾,源码study,文章首发于docs,求个star 依赖 路由依赖于 reactrouter ,但是一般 pc 路由使用 reactrouterdom ,它依赖于 reactrout...

    3 个月前
  • 面试中React与Vue的比对

    1.virtual dom 用JS模拟DOM结构,DOM变化的对比,放在JS层做,以提高重绘性能 DOM操作昂贵,JS运行效率高,要减少DOM操作 使用:snabbdom的使用 ...

    2 年前

官方社区

扫码加入 JavaScript 社区