React Hook 实践小结

hello~亲爱的观众老爷们你们好~最近负责重构某个内部系统,既然是内部系统,那固然能够尽情搞事情,分析需求后决定采用 React 最新版本进行重构。既然是最新的版本,那固然是使用 Hooks 进行开发了。开发的过程并不是一路顺风,但也算是踩过很多坑也从新爬出来了,小结后有了这篇文章~javascript

注意~这篇文章是实践相关的讨论,若是不太清楚 Hooks 的同窗,可能这篇文章不太适合你。同时,我在实践的过程当中,使用了若干 React 官方不建议的模式,本文主要是对此进行讨论,但愿对你编写基于 HooksReact 代码时有所帮助~如下是正文:html

在循环,条件中调用 Hook

React 的官网在 Hook 规则 一节中,有这么一段话:前端

不要在循环,条件或嵌套函数中调用 Hook, 确保老是在你的 React 函数的最顶层调用他们。java

然而,我在实际的开发过程当中,(为了省事)常常违背了这条原则。先理解一下为什么官方不建议在循环,条件或嵌套函数中调用 Hook。函数组件内的 Hook 是基于链表进行注册的,也就是一个有着固定顺序的序列,以下所示:react

Hook1 ⟶️ Hook2 ⟶️ Hook3 ...⟶️... HookN
复制代码

假设 Hook2 处于判断条件之中,一旦 condition 修改,执行的顺序就会发生改变:数组

if (condition) {
  useHook2();
}

执行顺序变为:Hook1 ⟶️ Hook3 ...⟶️... HookN
复制代码

顺序会发生偏移,从而致使 React 内部报错。缓存

大体了解代码原理后,就能理解为什么官方不建议在循环或条件中使用 Hook。然而,这个问题的根本缘由是顺序产生了变化,于是致使 bug 的出现。那若是咱们能保证执行顺序呢?好比:在本地开发环境中须要增长一些调试代码,但不但愿线上出现对应的代码,通常咱们会这么写:服务器

if (process.env.NODE_ENV === 'development') {
  //do sth...
}
复制代码

发布正式代码时,这段调试代码会被打包工具正确去除。因为环境变量是固定的(同一环境之中基本上不会产生变化),于是即便 Hook 处于条件判断之中,函数中 Hook 的顺序是固定的,使用是并不会产生问题:函数

function Test() {
  let test;
  let setTest;
  if (process.env.NODE_ENV === 'development') { 
    [test, setTest] = useState(1);  // eslint-disable-line
  }                                           

  // 添加 eslint-disable-line,是由于 ESLint 在开发环境中会检测 `Hook` 是否在合理的上下文之中(即不在条件判断或循环之中),否则会报错并中止渲染

  return (
    <div> <p>{test}</p> <button onClick={() => setTest(test + 1)}>click</button> </div>
  )
}
复制代码

在循环条件中同理(只要循环次数固定,也不会有问题)。只要肯定 Hook 的顺序不变,何尝不可在条件或循环中使用,只要你清楚知道本身在干什么~工具

依赖欺骗与精确依赖

不管是 React 的文档仍是 Hooks 相关的文章,不少都建议咱们对 Hook 的依赖数组诚实,如实填写 Hook 内的变量,以免 bug 的产生:

useEffect(() => {
    reportToService(userName, userId, ...)
}, [userName, userId, ...])
复制代码

然而,这并不应是死板的教条~想象一下上述代码的场景,这是一段上报数据到服务器的逻辑。若是用户登出后,用户名之类的信息必然产生变化,那么 useEffect 会从新执行,假设这是一段统计 PV 的代码,那就存在重复统计的问题。于是,只要你清楚知道本身在干什么,依赖项实际上是能够根据实际状况填写的,不必强行将所有用到的变量填进去。

而精确依赖,就是 React 不可变数据的一个体现,某程度上说,应该说是避免错误填写依赖,考虑如下例子:

function Test() {
  const [test, setTest] = useState({
    key1: 1,
    key2: 2
  });

  useEffect(() => {
    console.log(test.key2);
  }, [test]);

  return (
    <div> <p>{test.key1}</p> <button onClick={() => setTest(test => { return { ...test, key1: test.key1 + 1 } })}> click </button> </div>
  )
}
复制代码

以上例子能够正常运行,ESLint 也不会报错。然而当咱们不断点击按钮时,useEffect 会不断执行,打印出 test.key2 的值。这是因为按钮点击后, test 是一个新的对象,于是 useEffect 的依赖项发生了变化,因而不断被执行。但这是毫无心义的,咱们真正想监听的的是 test.key2,于是依赖的项应该精准地填写为 test.key2,修改后 useEffect 只会在 test.key2 变化后才会执行。

精确依赖是一个很小的细节,但常常会致使重复执行 Hook,在定位相似问题时不妨先检查一下依赖了错误的变量。

Hook 既不是 setState,也不是生命周期函数

因为我很长一段时间没写 React,于是一开始写 Hook 的时候仍是带着浓重的 class 色彩,基本能够说只是将 class “翻译”成 function 而已。

在使用 useState 的时候,往里面丢一个很大的对象,模仿以前 state 的写法,但这是典型的反模式。尽管数据的封装是必须的,如将用户相关的数据统合在 userInfo 对象之中,然而将所有内容像以前的 class 同样,放在一个 state 中,是不可取的,会致使这个 Hook 变得至关繁重,也不利于逻辑复用,违背了 Hook 最初的目的。

Hook 不是 setState 仍是比较好理解,但 Hook 不是生命周期函数就不是那么好习惯了。例如咱们常常在 componentWillUnmount 中解除定时器、解绑事件等等:

componentWillUnmount() {
   clearTimeout(timer);
   removeEventListener('click', handler);
   ...
}
复制代码

然而,在 Hook 中,把解除定时器、解绑事件等操做所有写到一个 useEffect 中去,并非最佳实践。经过上文咱们了解到,尽可能要作到精确依赖,避免没必要要的开销。而从逻辑复用的角度而言,将代码按照功能拆开,更有利于复用。于是咱们应该写成:

useEffect(() => {
   const timer = setTimeout(() => {
       ...
   });
   
   return () => {
     clearTimeout(timer);
   }
}, []);

useEffect(() => {
   element.addEventListener('click', handler);
   
   return () => {
     element.removeEventListener('click', handler);
   }
}, []);

...
复制代码

class 中 每一个生命周期函数只有一个,而 function 中相应的 Hooks 能够有多个~分拆有利于代码清晰与逻辑复用。

小议性能

性能是个很大的话题,在 Hooks 中性能相关的问题,基本能够独立一篇文章来写了~因为 React 遵循的是不可变数据,所以它的更新是批量的:

(某个节点状态发生改变,它以及它的子孙组件都须要从新计算 Virtual DOM)

按照最佳实践,咱们应该使用 useMemouseCallback 等等进行缓存,避免重复生成变量而致使无心义的重算 Virtual DOM。但喜欢“捣乱”的我,稍微提出一点反模式以作抛砖引玉之用——咱们的应用可能没那么大以致于须要全面考虑性能。

使用 Hook 进行开发时,为了保证变量不变,须要使用不少 useMemouseCallback 之类的钩子,为了极致的性能每一个变量与方法都要被这些钩子包囊。有时候感受就像是不断地书写模板代码,回忆起以前被 Redux 支配的恐惧了么~ Hook 也有点这个感受。

要明确一点,性能确实很重要。但也要明白,对于通常的前端应用而言,在 1ms 仍是 10ms 内完成 diff,其实意义是不大的。按照经验而言,低端手机百万级别内的运算,并不会产生明显的卡顿。然而 DOM 就至关慢了,低端机大概 6000 个 DOM 从新渲染,就会产生很是明显的卡顿。于是,就我的的角度而言,保持应用的高性能值得称道,但不建议在中小的应用中追求极致性能,在碰到性能瓶颈时再进行优化也何尝不可。

小结

以上就是本文的所有内容啦!这是我在项目中使用 Hook 进行开发后的一点思考,毕竟“尽信书,不如无书”,只有经过实践才能掌握新的技术。

以上是我的的一点浅见,感谢各位看官大人看到这里。知易行难,但愿本文对你有所帮助~谢谢!

相关文章
相关标签/搜索