react指北:你真的会用hooks吗-下篇

214 阅读5分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第3天,点击查看活动详情

再再言

其实刚开始写的时候,完全没想到我会写成上中下三篇,但是写着写着发现篇幅不小,所以就来到了第三篇。

不过这应该是最后一篇了。

我们今天就来谈谈hooks的封装。

看代码

我们不可能真的搬出几千行代码的组件去讲解,我们就先通过一个简单的例子来看看hooks的封装。

const Displayer = () => {
  const [isLoading, setIsLoading] = useState(false)
  const [data, setData] = useState(null)
  const request = useCallback(async () => {
    if (isLoading) {
      return
    }
    setIsLoading(true)
    try {
      setData(await fetchUser())
    } catch (e) {
      setData(null)
    }
    setIsLoading(false)
  }, [isLoading])

  return <div onClick={request}>{isLoading ? '正在请求' : '点击请求用户信息'}</div>
}
  

上面是简单的点击请求数据的例子。我们用了三个hooks,其中两个useState,一个useCallback,在useCallback里面做了一个防止多次重复请求的判断。 因为这个例子比较简单,所以看上去还好,但是依旧,略显繁琐,如果你有多个类似的操作,很快这个组件的代码量就会开始起飞了。并且大量的use,降低了代码的可读性。

但是如果我们对上面的例子进行一些改造,就会清晰很多:

hooks封装:

const useRequest = (request) => {
  const [isLoading, setIsLoading] = useState(false)
  const [data, setData] = useState(null)
  const sendRequest = useCallback(async () => {
    if (isLoading) {
      return
    }
    setIsLoading(true)
    try {
      setData(await request())
    } catch (e) {
      setData(null)
    }
    setIsLoading(false)
  }, [isLoading, request])
  const result = useMemo(() => {
    return { isLoading, data }
  }, [isLoading, data])
  return [result, sendRequest]
}

组件里面调用自定义hooks:

const Displayer = () => {
  const [{ isLoading }, request] = useRequest(fetchUser)
  return <div onClick={request}>{isLoading ? '正在请求' : '点击请求用户信息'}</div>
}

我们看到,虽然useRequest的逻辑并没有少,但是组件里面的逻辑清晰了很多,我们把接口请求的逻辑封装在了一个hooks里面,且这个hooks一般不会频繁去改动,这样的话,后续频繁改动的业务层的代码量就少了很多,整体语义化更强,视图和逻辑也做了一层物理隔离,整体变得更容易维护了。

而这,也是react引入hooks的真正意义。

哪些情况下需要封装hooks

上面我们通过一个简单例子阐述了一下hooks封装的意义,肯定有同学就会问了,那么哪些情况下需要封装hooks呢?

显然,我们不可能要求所有人把所有的逻辑都先封装成自定义的hooks,然后在组件里面引用,所以根据我日常工作遇到的情况,我对需要封装成hooks的情况做了一下简单的总结:

  1. 如果你的组件某段逻辑需要用到两个或者两个以上的hooks,建议封装成一个自定义hooks使用,例如上面的例子,我们就是把三个hooks整合封装成了一个自定义hooks。
  2. 如果你的某个hooks逻辑比较复杂,尤其是如果你在useEffect中做了很多事情,建议这种情况单独封装成一个hooks引用处理。
  3. 使用的hooks中存在一些公共逻辑可以被收敛的。

上面三条原则,其中第一条是比较明确的,但是往往实际中,做的最不好的就是第一条。因为很多人已经习惯一上来就先手一发useState,再接一发useEffect,或者再连一发useCallback。hooks三连,看上去很帅,但是实际上是让fc变得难以维护的万恶之源。

第三条的原则,比较抽象,需要你有一些代码可维护性和代码复用的思想,也需要你对全局的业务逻辑有一些把控。

**三条原则中,个人认为最重要的就是第一条,有条件的也可以在团队cr中严格把控。 **

如何封装自定义hooks

hooks的封装其实并不难,简单来讲,自定义hooks就是一个自定义的function,里面包含了一些react的内置的hooks。为了和普通的方法区分开来,防止被误用,规范上会要求自定义hooks也遵循use开头的原则,但是如果你不用use开头,调用的时候也不会报错,但是在cr的时候会被打死。

一个简单的自已hooks:


const useTitle = () => {
  const [title, setTitle] = useState(document.title)
  useEffect(() => {
    document.title = title
  }, [title])
  return [title, setTitle]
}

通过上面的代码,我们就封装了一个简单的hooks,用来更新组件title的同时更新documen的title。

当然,提到封装,一般都需要我们具备一些封装的基本思想,抽象、组合等,但是在我大量的经验来看,这部分思想在封装hooks的时候不是特别重要,尤其,大部分你不是在为团队封装一个高可复用的hooks。我们更多的是站在代码可维护性的角度去做这件事的。所以,就算你还是一个菜鸟,不具备任何的抽象封装能力,也依然可以做到这件事。

相信我,前提是你要认真对待这件事情。

完结

ok,到了这里之后,关于如何用好hooks这个主题基本算是结束了。但是实际中,你仍然需要用心的去感受文中的一些例子,尤其是记住hooks封装的原则。

请记住,维护代码,人人有责。