为什么会有 Hook
介绍
Hooks
之前,首先要给大家说一下React
的组件创建方式
,一种是类组件
,一种是纯函数
组件,并且React
团队希望,组件不要变成复杂的容器,最好只是数据流的管道。开发者根据需要,组合管道即可。也就是说组件的最佳写法
应该是函数
,而不是类。。
但是我们知道,在以往开发中类组件
和纯函数组件
的区别是很大的,纯函数组件有着类组件不具备的多种特点,简单列举几条
- 纯函数组件
没有状态
- 纯函数组件
没有生命周期
- 纯函数组件
没有this
- 只能是
纯函数
这就注定,我们所推崇的函数组件,只能做UI
展示的功能,涉及到状态的管理与切换,我们不得不用类组件
或者redux
,但我们知道类组件的也是有缺点的,比如,遇到简单的页面,你的代码会显得很重,并且每创建一个类组件,都要去继承一个React实例
,至于Redux
,更不用多说,很久之前Redux
的作者就说过,“能用React解决的问题就不用Redux
”,等等一系列的话。关于React
类组件redux
的作者又有话说
- 大型组件很难
拆分
和重构
,也很难测试
。 - 业务逻辑分散在组件的各个方法之中,导致
重复逻辑
或关联逻辑
。 - 组件类引入了复杂的编程模式,比如
render
props
和高阶组件
。
类组件虽然功能齐全却很重,纯函数很轻便却有上文几点重大限制,所以
React
团队设计了React Hooks
,React Hooks
就是加强版的函数组件,我们可以完全不使用class
,就能写出一个全功能的组件
Hook 解决了什么问题
Hook
解决了我们五年来编写和维护成千上万的组件时遇到的各种各样看起来不相关的问题。无论你正在学习React
,或每天使用,或者更愿尝试另一个和React
有相似组件模型的框架,你都可能对这些问题似曾相识。
React
没有提供将可复用性行为“附加”到组件的途径(例如,把组件连接到 store
)。如果你使用过 React
一段时间,你也许会熟悉一些解决此类问题的方案,比如 render
props
和 高阶组件
。但是这类方案需要重新组织你的组件结构,这可能会很麻烦,使你的代码难以理解。如果你在 React DevTools
中观察过 React
应用,你会发现由 providers
,consumers
,高阶组件
,render props
等其他抽象层组成的组件会形成“嵌套地狱
”。尽管我们可以在 DevTools
过滤掉它们,但这说明了一个更深层次的问题:React
需要为共享状态逻辑提供更好的原生途径。
你可以使用 Hook 从组件中提取状态逻辑
,使得这些逻辑可以单独测试并复用。Hook
使你在无需修改组件结构的情况下复用状态逻辑
。这使得在组件间
或社区内
共享 Hook
变得更便捷。
官方提供了哪些 Hook
如果你刚开始接触
Hook
,那么可能需要先自行查看React
官方文档
- 基础 Hook
useSstate
useEffect
useContext
- 额外的 Hook
useReducer
useCallback
useMemo
useRef
useImperativeHandle
useLayoutEffect
useDebugValue
这篇文章的重点不会对官方 Hook
的使用做过多的讲解,当然,想要用好 Hook
,首先要先把官方提供的这些 Hook
学明白,才能在项目开发的时候如鱼得水,其次就是做一些自定义 Hook
的封装,用来提高开发效率
当然,项目开发总不能遇到一个功能就去封装一个自定义
Hook
,这里为大家推荐两个比较好用的Hooks
库:aHooks
react-use
,这两个库使用在项目开发中真的能大大提高开发效率
,可以毫不夸张的说能够降低你的50%
的业务代码,告别996
什么是 ahooks
官网: https://ahooks.js.org/zh-CN
ahooks
是一个React Hooks
库,致力提供常用且高质量的 Hooks。在使用之前,你需要掌握React
及React Hooks
基础用法。ahooks
是由蚂蚁umi
团队、淘系 ice
团队以及阿里体育
团队共同建设的React Hooks
工具库。ahooks
基于React Hooks
的逻辑封装能力,提供了大量常见好用的Hooks
,可以极大降低代码复杂度,提升开发效率。ahooks
致力成为和antd/fusion
一样的React
基础设施,帮助开发者在逻辑层面省去大量的重复工作。
安装
# 安装依赖 npm i ahooks --save # 使用 Hooks import { useRequest } from 'ahooks';
有哪些实用 Hooks
- ????异步请求
-
useRequest
— 一个强大的管理异步数据请求的Hook
。
-
- ????table
-
useAntdTable
— 封装了常用的antd Form
与antd Table
联动逻辑,并且同时支持antd V3 和 V4
。 -
useFusionTable
— 封装了常用的Fusion Form
与Fusion Table
联动逻辑。
-
- ????视图类的
-
useDrag
&useDrop
— 一对帮助你处理在拖拽中进行数据转移的hooks
-
useDynamicList
— 一个帮助你管理列表状态,并能生成唯一key
的Hook
。 -
useSelections
— 常见联动checkbox
逻辑封装,支持多选,单选,全选逻辑,还提供了是否选择,是否全选,是否半选的状态。 -
useVirtualList
— 提供虚拟化列表能力的Hook
,用于解决展示海量数据渲染时首屏渲染缓慢和滚动卡顿问题。
-
- ????副作用
-
useDebounce
— 用来处理防抖值的Hook
。 -
useDebounceFn
— 用来处理防抖函数的Hook
。 -
useInterval
— 一个可以处理 setInterval 的Hook
。 -
useThrottle
— 用来处理节流值的Hook
。 -
useThrottleFn
— 用来处理节流函数的Hook
。 -
useTimeout
— 一个可以处理setTimeout
计时器函数的 Hook。
-
- ????生命周期
-
useDebounceEffect
— 为useEffect
增加防抖的能力。 -
useMount
— 只在组件mount
时执行的 hook。 -
useThrottleEffect
— 为useEffect
增加节流的能力。 -
useTrackedEffect
— 在useEffect
的基础上,追踪触发 effect 的依赖变化。 -
useUnmount
— 只在组件unmount
时执行的 hook。 -
useUnmountedRef
— 获取当前组件是否已经卸载的hook
,用于避免因组件卸载后更新状态而导致的内存泄漏 -
useUpdate
— 强制组件重新渲染的 hook。 -
useUpdateEffect
— 一个只在依赖更新时执行的useEffect
hook。 -
useUpdateLayoutEffect
— 一个只在依赖更新时执行的useLayoutEffect
Hook。
-
- ????状态
-
useUrlState
— 一个同步组件内部状态和query
参数的 hook。 -
useBoolean
— 优雅的管理boolean
值的 Hook。 -
useControllableValue
— 使组件的状态即可以自己管理,也可以被外部控制 -
useCookieState
— 一个可以将状态持久化存储在cookie
中的 Hook 。 -
useCountDown
— 一个用于管理倒计时的 Hook。 -
useCounter
— 一个可以管理count
的 Hook。 -
useHistoryTravel
— 优雅的管理状态变化历史,可以快速在状态变化历史中穿梭 - 前进跟后退。 -
useLocalStorageState
— 一个可以将状态持久化存储在localStorage
中的 Hook 。 -
useMap
— 一个可以管理Map
类型状态的 Hook。 -
useNetwork
— 一个可以管理网络连接状态的 Hook。 -
usePrevious
— 保存上一次渲染时状态的 Hook。 -
useSessionStorageState
— 一个可以将状态持久化存储在sessionStorage
中的 Hook。 -
useSet
— 一个可以管理Set
类型状态的 Hook。 -
useSetState
— 管理object
类型state
的 Hooks,用法与class
组件的this.setState
基本一致。 -
useToggle
— 用于在两个状态值间切换的 Hook。 -
useWebSocket
— 用于处理WebSocket
的 Hook。 -
useWhyDidYouUpdate
— 帮助开发者排查是什么改变导致了组件的rerender
。
-
- ????Dom 相关
-
useClickAway
— 优雅的管理目标元素外点击事件的 Hook。 -
useDocumentVisibility
— 可以获取页面可见状态的 Hook。 -
useEventListener
— 优雅使用addEventListener
的 Hook。 -
useEventTarget
— 常见表单控件(通过e.target.value
获取表单值) 的onChange
跟value
逻辑封装,支持自定义值转换和重置功能。 -
useExternal
— 一个用于动态地向页面加载或卸载外部资源的 Hook。 -
useFavicon
— 用于设置与切换页面favicon
。 -
useFullscreen
— 一个用于处理dom
全屏
的 Hook。 -
useHover
— 一个用于追踪dom
元素是否有鼠标悬停
的 Hook。 -
useInViewport
— 一个用于判断dom
元素是否在可视范围之内
的 Hook。 -
useKeyPress
— 一个优雅的管理keyup
和keydown
键盘事件的 Hook,支持键盘组合键
,定义键盘事件的key
和keyCode
别名输入 。 -
useMouse
— 一个跟踪鼠标位置
的 Hook。 -
useResponsive
— 获取响应式信息。 -
useScroll
— 获取元素的滚动状态。 -
useSize
— 一个用于监听dom
节点尺寸变化的 Hook。 -
useTextSelection
— 实时获取用户当前选取的文本内容及位置。 -
useTitle
— 用于设置页面标题的 Hook。
-
- ????高级 Hook
-
useCreation
— 是useMemo
或useRef
的替代品,确保实例不会被重新创建。 -
useEventEmitter
— 多组件间事件通知。 -
useLockFn
— 给异步函数加锁,防止重复调用。 -
usePersistFn
— 持久化function
的 Hook。 -
useReactive
— 提供一种数据响应式的操作体验,定义数据状态不需要写 useState , 直接修改属性即可刷新视图。 -
useSafeState
— 用法与React.useState
完全一样,但是在组件卸载后异步回调内的setState
不再执行,避免因组件卸载后更新状态而导致的内存泄漏。
-
什么是 react-use
是一个国外大佬开发,目前
start 25.9K
,github 地址: https://github.com/streamich/react-use
安装
# 安装依赖 npm i react-use --save # 使用 Hooks import { useBattery } from 'react-use';
有哪些实用 Hooks
- ????传感器
-
useBattery
— 跟踪设备电池状态。 -
useGeolocation
— 跟踪用户设备的地理位置状态。 -
useHover
anduseHoverDirty
— 跟踪鼠标悬停某个元素的状态。 -
useIdle
— 跟踪用户是否处于非活动状态。 -
useKey
,useKeyPress
,useKeyboardJs
, 和useKeyPressEvent
— 追踪按键。 -
useLocation
— 跟踪页面导航栏的位置状态。 -
useMedia
— 跟踪 CSS 媒体查询的状态。 -
useMediaDevices
— 跟踪连接的硬件设备的状态。 -
useMotion
— 跟踪设备的运动传感器的状态。 -
useMouse
anduseMouseHovered
— 跟踪鼠标位置的状态。 -
useNetwork
— 跟踪用户的互联网连接状态。 -
useOrientation
— 跟踪设备屏幕方向的状态。 -
usePageLeave
— 当鼠标离开页面边界时触发。 -
useScroll
— 跟踪 HTML 元素的滚动位置。 -
useSize
— 跟踪 HTML 元素的维度。 -
useStartTyping
— 检测用户何时开始输入。 -
useWindowScroll
— 跟踪 窗口 滚动位置。 -
useWindowSize
— 跟踪 窗口 尺寸。
-
- ????用户界面
-
useAudio
— 播放音频并公开其控件。 -
useClickAway
— 当用户点击目标区域外时触发回调。 -
useCss
— 动态调整 CSS。 -
useDrop
anduseDropArea
— 跟踪文件,链接和复制粘贴。 -
useFullscreen
— 全屏显示元素或视频。 -
useSpeech
— 从文本字符串合成语音。 -
useVideo
— 播放视频,跟踪其状态,以及公开播放控件。 -
useWait
— UI 的复杂等待管理。
-
- ????动画效果
-
useRaf
— 在每个requestAnimationFrame
上重新呈现组件。 -
useSpring
— 根据弹簧动力学随时间插入数字。 -
useTimeout
— 超时后返回true
。 -
useTween
— 重新渲染组件,同时补间 0 到 1 之间的数字。 -
useUpdate
— 返回一个回调,在调用时重新呈现组件。
-
- ????副作用
-
useAsync
— 解析一个async
函数。 -
useAsyncFn
— 异步函数的状态管理。 -
useAsyncRetry
—useAsync
带有retry()
方法。 -
useBeforeUnload
— 当用户尝试重新加载或关闭页面时显示浏览器警报。 -
useCopyToClipboard
— 将文本复制到剪贴板。 -
useDebounce
— 防抖函数。 -
useFavicon
— 设置页面的favicon
。 -
useLocalStorage
— 管理localStorage
中的值。 -
useLockBodyScroll
— 锁定body
元素的滚动。 -
useSessionStorage
— 管理sessionStorage
中的值。 -
useThrottle
anduseThrottleFn
— 节流一个函数。 -
useTitle
— 设置页面标题。
-
- ????生命周期
-
useEffectOnce
— 仅运行一次的修改后的useEffect
。 -
useEvent
— 订阅事件。 -
useLifecycles
— 调用mount
和unmount
回调。 -
useRefMounted
— 跟踪组件是否已挂载。 -
usePromise
— 仅在安装组件时解析promise
。 -
useLogger
— 在组件经历生命周期时登录控制台。 -
useMount
— 调用mount
回调。 -
useUnmount
— 调用unmount
回调。 -
useUpdateEffect
— 仅在更新时运行一个effect
。 -
useDeepCompareEffect
— 运行一个effect
通过深度比较其依赖项。
-
- ????状态
-
createMemo
—memoized hooks
的工厂钩子。 -
useGetSet
— 返回状态getter get()
而不是原始状态。 -
useGetSetState
— 就像useGetSet
和useSetState
有个孩子。 -
useObservable
— 跟踪Observable
的最新值。 -
useSetState
— 创建类似this.setState
的setState
方法。 -
useToggle
anduseBoolean
— 跟踪布尔值的状态。 -
useCounter
anduseNumber
— 跟踪数字的状态。 -
useList
— 跟踪数组的状态。 -
useMap
— 跟踪对象的状态。
-
以上介绍的两个
Hooks
库,运用在项目开发中可以说是能够减少你的50%
业务代码,真的告别996
,利用更多的时间来学习新的知识,不要做业务开发的傀儡 ????
当然在我们平时开发过程中,肯定会遇到一些项目中业务逻辑之类的封装或者是以上两个 Hooks 库没有涉及到的场景,这种情况下就需要自己去自定义 Hook
了,千万要记住: 不要因为自己的一时偷懒直接复制代码,复制一时爽,重构火葬场 ????????????
自定义 Hook
官方介绍:自定义
Hook
只是将两个函数之间一些共同的代码提取到单独的函数中。自定义Hook
是一种自然遵循Hook
设计的约定,而并不是 React 的特性。
没错,就是函数和函数之间的逻辑复用,但是必须要遵循 Hook
的设计约定,不遵循的话,由于无法判断某个函数是否包含对其内部 Hook
的调用,React
将无法自动检查你的 Hook
是否违反了 Hook
的规则。以下是本人封装的以 Modal
的形式来展示错误信息
的 Hook
,仅供参考:
import React, { useState, useEffect } from 'react' import { Modal, Table } from 'antd' import { ColumnsType } from 'antd/es/table' interface IData { message: string } // 截取两个字符串之间字符 function str_substr(start, end, str) { // 获取开始下标和结束下标 let firstIndex = str.indexOf(start) let lastIndex = str.lastIndexOf(end) return str.substring(firstIndex + 1, lastIndex) } const useErrModal = (data: string) => { const [dataSource, setDataSource] = useState<Array<IData>>([]) const tableContainer = () => ( <Table size={'small'} dataSource={dataSource} columns={columns} pagination={false} /> ) const columns: ColumnsType<IData> = [ { title: '错误明细', dataIndex: 'message', key: 'message' } ] // data 数据按照 [, ] 切割,数据事例 : "数据部分导入失败:[第1行数据检验失败原因:税收编码为必填项且必须为19位的有效编码, 第2行数据检验失败原因:税率为必填项且为0开头的小数且小数点后不能超过5位,税收编码为必填项且必须为19位的有效编码]" let splitStr = str_substr('[', ']', data) let arr = splitStr.split(', ') let list = [] arr.map((item, key) => { list.push({ message: item }) }) setDataSource(list) Modal.warning({ title: '错误信息汇总', okText: '确定', content: tableContainer }) } export default useErrModal
参考资料
[1]ahooks: https://ahooks.js.org/zh-CN
[2]react-use: https://github.com/streamich/react-use
[3]hooks 详解: https://www.jianshu.com/p/d600f749bb19
[4]hooks 官方文档: https://react.docschina.org/docs/hooks-intro.html