首先脑海中总体思路是这样子

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

什么是React自定义Hook

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

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

为什么需要Hooks

在 Hooks 出现之前,业务逻辑的复用是React 开发的一大痛点和难点。我们可能需要在各种生命周期里面做各种逻辑操作,这种分散在不同方法的逻辑实际上很难去复用,必须通过各种其他方式去组合比如高阶组件,render props等;自引入Hooks后,函数组件有了状态和生命周期的能力,就可以同通过自定义的Hooks进行封装,从实现了业务逻辑的复用。并且目前,在整个 React 生态中,几乎所有开发项目都已经是围绕着 Hooks 展开的了

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

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

  1. import React, { useState, useEffect } from 'react';
  2. export default function App() {
  3. // 正常我们这样子实现
  4. const [title, setTitle] = useState('默认标题')
  5. useEffect(() => {
  6. document.title = title;
  7. }, [title]);
  8. const onTitleChange = (event) => {
  9. setTitle(event.target.value);
  10. }
  11. return (<div>
  12. <h2>{title}</h2>
  13. <input type="text" onInput={onTitleChange}/>
  14. </div>)
  15. }
  • 抽取共用逻辑,封装成自定义Hook
  1. export function useSetPageTitle (initTitle) {
  2. const [title, setTitle] = useState(initTitle || '默认标题');
  3. useEffect(() => {
  4. document.title = title;
  5. }, [title]);
  6. return [title, setTitle]
  7. }
  • 在其他组件中使用刚刚写的useSetPageTitle
  1. import { useSetPageTitle } from '../App';
  2. export default function Chirld() {
  3. // 这里使用刚才写自定义Hook
  4. const [title, setTitle] = useSetPageTitle();
  5. return (<div>
  6. <h2>{title}</h2>
  7. <input type="text" onInput={onTitleChange}/>
  8. </div>>)
  9. }

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

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

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

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

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

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

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

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

  • 反例

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

  1. import React, { useState, useEffect } from 'react';
  2. import './App.css';
  3. import { useSetPageTitle } from './hooks';
  4. export default function App() {
  5. const [state, setState] = useState('A');
  6. // 反例
  7. if (state === 'A') {
  8. useEffect(() => {
  9. console.log('只执行一次')
  10. }, [state]);
  11. }
  12. useState({});
  13. useEffect(() => {
  14. // 获取数据
  15. }, []);
  16. useState({});
  17. console.log(title)
  18. return (
  19. <div className="App">
  20. <p> Hello React Hook! </p>
  21. </div>
  22. );
  23. }
  • 正例
  1. import React, { useState, useEffect } from 'react';
  2. export default function App() {
  3. const [state, setState] = useState('A');
  4. useEffect(() => {
  5. if (state === 'A') {
  6. console.log('执行相应的逻辑')
  7. }
  8. }, [state]);
  9. useState({});
  10. useEffect(() => {
  11. // 获取数据
  12. }, []);
  13. useState({});
  14. return (
  15. <div className="App">
  16. <p> Hello React Hook! </p>
  17. </div>
  18. );
  19. }

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

  1. eslint-plugin-react-hooks
  1. // 你的 ESLint 配置
  2. {
  3. "plugins": [
  4. // ...
  5. "react-hooks"
  6. ],
  7. "rules": {
  8. // ...
  9. "react-hooks/rules-of-hooks": "error", // 检查 Hook 的规则
  10. "react-hooks/exhaustive-deps": "warn" // 检查 effect 的依赖
  11. }
  12. }

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

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

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

  • 例如使用useMount模拟生命周期componentDidMount
  1. import React, { useState, useEffect } from 'react';
  2. function useMount (fn) {
  3. useEffect(() => {
  4. fn();
  5. }, []);
  6. }
  7. export default function App() {
  8. useMount(() => {
  9. // 你的逻辑
  10. })
  11. return (
  12. <div className="App">
  13. <p> Hello React Hook! </p>
  14. </div>
  15. );
  16. }
  • 利用useEffect返回函数是销毁才调用的机制来模拟unmount
  1. function useUnmount (fn) {
  2. useEffect(() => {
  3. return () => {
  4. fn();
  5. }
  6. }, []);
  7. }
  8. export default function App() {
  9. useUnmount(() => {
  10. console.log('销毁组件时输出')
  11. })
  12. return (
  13. <div className="App">
  14. <p> Hello React Hook! </p>
  15. </div>
  16. );
  17. }
  • 监听窗口变化
  1. import React, { useState, useEffect } from 'react';
  2. function useOnResize (fn) {
  3. useEffect(() => {
  4. window.addEventListener('resize',fn);
  5. return () => {
  6. window.removeEventListener('resize',fn)
  7. }
  8. }, []);
  9. }
  10. export default function App() {
  11. useOnResize(() => {
  12. console.log(document.body.clientWidth)
  13. })
  14. return (
  15. <div className="App">
  16. <p> Hello React Hook! </p>
  17. </div>
  18. );
  19. }

这里介绍一些有趣的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官方介绍