当面试官问自定义Hook的时候他想知道什么

avatar
Web前端 @CVTE_希沃

希沃ENOW大前端

公司官网:CVTE(广州视源股份)

团队:CVTE旗下未来教育希沃软件平台中心enow团队

本文作者:

一兵名片.png

前言

在听到这么一个问题的时候,首先脑海中总体思路是这样子的:

  • 认识自定义Hook的基本概念
  • 能够使用自定义Hook进行复用逻辑的封装
  • 了解自定义Hook的最佳实践与工作原理
  • 进一步深入Hook底层原理思想
  • 创造新的轮子思想,借鉴Reack Hook思想

什么是React自定义Hook

HookReact 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。React官方提供了常用的StateHookEffectHook分别用以管理函数式组件状态和副作用。(React Hook官方介绍)

除了官方提供的StateHook和EffectHook外,我们可以自己将常用的组件逻辑抽取到可重用的函数中,该函数须以React约定的形式来命名与使用,用以共享组件逻辑。

那么如何编写我们自己的自定义Hook

通过一个简单的例子来看一下 比如我们需要在拿到某个数据后设置页面标题,而且需要在各个页面里面使用

import React, { useState, useEffect } from 'react';

export default function App() { 
    // 正常我们这样子实现
    const [title, setTitle] = useState('默认标题')
    
    useEffect(() => {
        document.title = title;
    }, [title]);
    
    const onTitleChange = (event) => {
        setTitle(event.target.value);
    }
    
    return (<div>
        <h2>{title}</h2>
        <input type="text"  onInput={onTitleChange}/>
    </div>)
}
  • 抽取共用逻辑,封装成自定义Hook

export function useSetPageTitle (initTitle) {
  const [title, setTitle] = useState(initTitle || '默认标题');

  useEffect(() => {
    document.title = title;
  }, [title]);

  return [title, setTitle]
}
  • 在其他组件中使用刚刚写的useSetPageTitle

import { useSetPageTitle } from '../App';

export default function Chirld() { 
    // 这里使用刚才写自定义Hook
    const [title, setTitle] = useSetPageTitle();
    
    return (<div>
        <h2>{title}</h2>
        <input type="text"  onInput={onTitleChange}/>
    </div>>)
}

这样子一个自定义的hook就成型了,是不是瞬间感觉逼格提高了,原来我也可以写个这么高大上的自定义Hook,哈哈哈。

聪明的你们一定发现了下面的特点

React约定自定义 Hook 必须以 use 开头

上面的例子中使用【useSetPageTitle】。不遵循的话,由于无法判断某个函数是否包含对其内部 Hook 的调用,React 将无法自动检查你的 Hook 是否违反了 Hook 的规则。

自定义Hook可自由搭配其他hook使用

你可以自由使用其他内部Hook和其他自定义Hook.上面演示例子中使用useStateuseEffect.

只在 React 的函数组件中最顶层使用 Hook

这样做是为了确保 Hook 在每一次渲染中都按照同样的顺序被调用。如果不是最顶层会导致状态或者执行方法出错进而导致BUG

  • 反例

state='A'条件满足时执行调用Hook顺序是正常,当后续渲染条件不满足时,则React调用Hook顺序出错,则会导致方法和状态逻辑执行出错。

import React, { useState, useEffect } from 'react';
import './App.css';
import { useSetPageTitle } from './hooks';

export default function App() {
  const [state, setState] = useState('A');
  
  // 反例
  if (state === 'A') { 
      useEffect(() => {
        console.log('只执行一次')
      }, [state]);
  }
 
  useState({});

  useEffect(() => {
    // 获取数据
  }, []);

  useState({});

  console.log(title)

  return (
    <div className="App">

        <p> Hello React Hook! </p>
         
    </div>
  );
}
  • 正例
import React, { useState, useEffect } from 'react';

export default function App() {

  const [state, setState] = useState('A');
  
  useEffect(() => {
    if (state === 'A') { 
        console.log('执行相应的逻辑')
    }
   
  }, [state]);

  useState({});

  useEffect(() => {
    // 获取数据
  }, []);

  useState({});



  return (
    <div className="App">
      <p> Hello React Hook! </p>
    </div>
  );
}

为了避免出错我们可以使用ESLint插件来检测强制执行这些规则。 另外,亲测React官方提供的create-react-app @3.x与@4.x在eject后已经集成了插件

eslint-plugin-react-hooks
// 你的 ESLint 配置
{
  "plugins": [
    // ...
    "react-hooks"
  ],
  "rules": {
    // ...
    "react-hooks/rules-of-hooks": "error", // 检查 Hook 的规则
    "react-hooks/exhaustive-deps": "warn" // 检查 effect 的依赖
  }
}

那么我们可以在哪些场景识别抽取自定义hook呢?

自定义 Hook 解决了以前在React组件中无法灵活共享逻辑的问题。那么业务开发中可创建各种的自定义Hook,公共辅助函数、状态复用、逻辑复用、效果复用、操作复用、生命周期模拟以及多种组合复用等只有想不到,相信很难有这么聪明的你们做不到的场景实现。

下面举几个简单栗子,抛砖引玉

  • 例如使用useMount模拟生命周期componentDidMount
import React, { useState, useEffect } from 'react';

function  useMount (fn) {
  useEffect(() => {
    fn();
  }, []);
}

export default function App() {

  useMount(() => {
     // 你的逻辑
  })


  return (
    <div className="App">
      <p> Hello React Hook! </p>
    </div>
  );
}
  • 利用useEffect返回函数是销毁才调用的机制来模拟unmount

function  useUnmount (fn) {
    useEffect(() => {
        return () => {
            fn();
        }
    }, []);
}

export default function App() {

   useUnmount(() => {
        console.log('销毁组件时输出')
   })
 

  return (
    <div className="App">
      <p> Hello React Hook! </p>
    </div>
  );
}
  • 监听窗口变化
import React, { useState, useEffect } from 'react';

function  useOnResize (fn) {
    useEffect(() => {
        window.addEventListener('resize',fn);
        return () => {
            window.removeEventListener('resize',fn)
        }
    }, []);
}

export default function App() {

   useOnResize(() => {
        console.log(document.body.clientWidth)
   })
 
  return (
    <div className="App">
      <p> Hello React Hook! </p>
    </div>
  );
}

这里介绍一些有趣的Hooks库供使用与参考

由蚂蚁 umi 团队、淘系 ice 团队以及阿里体育团队共同建设的 React Hooks 工具库ahooks

Set of a helpful hooks, for different specific to some primitives types state changing helpers.

可以使创建弹窗,提示,菜单变得非常容易,提供了创建DOM层次之外的元素的功能

用于发起Http请求的优秀Hook

发现更多优秀Hook库可在下面评论贴上,整一个优秀Hooks库的集合

进阶Hooks优秀实践与工作原理

笔者并没有实际看过React源码,仅看过一些React Hook工作原理的文章。 这里抛转引玉,看过几个比较好来分享。欢迎留言更多优秀实践和原理剖析。

创造新轮子

迎(bu)接(yao)新(geng)轮(xin)子(le),向(biao)着(shi)新(xue)轮(bu)子(dong)前(le)进

参考资料

hooks-custom官方介绍