当前位置: 首页 > article >正文

React第二十四章(自定义hooks)

自定义hooks

前几章我们已经介绍了React内置的hooks(useState, useEffect, useContext, useReducer, useRef, useMemo, useCallback, useLayoutEffect, useImperativeHandle, useDebugValue 等等),接下来我们介绍如何自定义hooks。

为什么需要自定义hooks?

因为在实际开发中,React的内置hooks并不能满足我们所有的需求,比如一些复杂的业务逻辑,或者是一些使用场景,需要我们自己来使用自定义hooks实现。

自定义hooks的规则

  1. 自定义hooks必须以use开头
  2. 自定义hooks可以调用其他hooks(内置的hooks和自定义的hooks)

案例

例如我们做一个水印的需求,这个在业务中是很常见的需求,为此我们封装一个自定义hooks,来实现这个需求。

  1. 首先我们定义一个水印的配置项
import { useEffect, useState } from "react"
export interface WatermarkOptions {
    content: string // 水印文本
    width?: number  // 水印宽度
    height?: number // 水印高度
    fontSize?: number // 水印字体大小
    fontColor?: string // 水印字体颜色
    zIndex?: number // 水印层级
    rotate?: number // 水印旋转角度
    gapX?: number // 水印横向间距
    gapY?: number // 水印纵向间距
}
  1. 然后我们定义水印的默认值
const defaultOptions = (): Partial<WatermarkOptions> => {
    //默认铺满整个页面
    const { width, height } = document.documentElement.getBoundingClientRect()
    return {
        width: width,
        height: height,
        fontSize: 16,
        fontColor: 'black',
        zIndex: 9999,
        rotate: -30,
        gapX: 200,
        gapY: 100
    }
}
  1. 串联整体实现思路,首先水印如何实现呢,我们通过canvas来实现,根据配置项,设置canvas对应的属性,然后通过toDataURL生成水印图片,最后创建一个元素,将水印图片设置为元素的背景图片,并设置样式,如果要操作DOM元素,我们需要通过useEffect副作用函数操作。
import { useEffect, useState } from "react"
export interface WatermarkOptions {
    content: string // 水印文本
    width?: number  // 水印宽度
    height?: number // 水印高度
    fontSize?: number // 水印字体大小
    fontColor?: string // 水印字体颜色
    zIndex?: number // 水印层级
    rotate?: number // 水印旋转角度
    gapX?: number // 水印横向间距
    gapY?: number // 水印纵向间距
}
// 默认配置
const defaultOptions = (): Partial<WatermarkOptions> => {
    const { width, height } = document.documentElement.getBoundingClientRect()
    return {
        width: width,
        height: height,
        fontSize: 16,
        fontColor: 'black',
        zIndex: 9999,
        rotate: -30,
        gapX: 200,
        gapY: 100
    }
}

export const useWatermark = (options: WatermarkOptions) => {
    const [watermarkOptions, setWatermarkOptions] = useState<WatermarkOptions>(options)
    const opts = Object.assign({}, defaultOptions(), watermarkOptions)
    const updateWatermark = (newOptions: Partial<WatermarkOptions>) => {
        setWatermarkOptions(prev => ({
            ...prev,
            ...newOptions
        }))
    }
    useEffect(() => {
        const canvas = document.createElement('canvas')
        const ctx = canvas.getContext('2d') as CanvasRenderingContext2D
        canvas.width = opts.gapX!
        canvas.height = opts.gapY!
        //默认
        ctx.translate(opts.gapX! / 2, opts.gapY! / 2) 
        ctx.rotate((opts.rotate! * Math.PI) / 180) 
        ctx.font = `${opts.fontSize}px sans-serif`
        ctx.textAlign = 'center'
        ctx.fillStyle = opts.fontColor!
        ctx.globalAlpha = 0.15
        ctx.fillText(opts.content, 0, 0)
        const watermarkDiv = document.createElement('div')
        watermarkDiv.id = 'watermark'
        watermarkDiv.style.position = 'fixed'
        watermarkDiv.style.top = '0'
        watermarkDiv.style.left = '0'
        watermarkDiv.style.width = `${opts.width}px`
        watermarkDiv.style.height = `${opts.height}px`
        watermarkDiv.style.pointerEvents = 'none'
        watermarkDiv.style.zIndex = `${opts.zIndex}`
        watermarkDiv.style.overflow = 'hidden'
        watermarkDiv.style.backgroundImage = `url(${canvas.toDataURL()})`
        watermarkDiv.style.backgroundSize = `${opts.gapX}px ${opts.gapY}px`
        document.body.appendChild(watermarkDiv)
        
        return () => {
            document.body.removeChild(watermarkDiv)
        }
    }, [opts])

    return [updateWatermark, opts] as const
}
  1. 代码详解
ctx.translate(opts.gapX! / 2, opts.gapY! / 2) 
ctx.rotate((opts.rotate! * Math.PI) / 180) 
ctx.font = `${opts.fontSize}px sans-serif`
ctx.textAlign = 'center'
ctx.fillStyle = opts.fontColor!
ctx.globalAlpha = 0.15
ctx.fillText(opts.content, 0, 0)

大家对于这块可能比较懵, 我们这里详细解释一下,首先我们通过gapX,gapY,给canvas设置了宽高这里默认值是,200,100,也就是200px*100px的一个长方形,但是canvas默认的坐标是(0,0), 这样会导致文字显示不出来,所以我们把文字挪到中心点,那么中心点怎么求呢?就是宽高/2,就算出中心点,然后通过ctx.translate(opts.gapX! / 2, opts.gapY! / 2),将文字挪到中心点。
然后通过ctx.rotate((opts.rotate! * Math.PI) / 180),将文字旋转,然后通过ctx.font = ${opts.fontSize}px sans-serif``,设置字体,然后通过ctx.textAlign = 'center',设置文字对齐方式,然后通过ctx.fillStyle = opts.fontColor!,设置文字颜色,然后通过ctx.globalAlpha = 0.15,设置文字透明度,最后通过ctx.fillText(opts.content, 0, 0),绘制文字。

在这里插入图片描述

  1. 在组件中使用
import React from 'react'
import { useWatermark } from './hooks/useWatermark';
const App: React.FC = () => {
   const [updateWatermark, opts] = useWatermark({
      content: '小满马上拨款',
   }) // 水印
   const update = () => {
      updateWatermark({
         content: '更新水印',
      })
   }
   return <>
      <div>{JSON.stringify(opts)}</div>
      <button onClick={update}>更新水印</button>
   </>;
}
export default App;

在这里插入图片描述

那么在工作中,我们需要频繁的定义hooks吗?

我们并不需要重复的造轮子,已经有很多现成的库可以使用,比如ahooks,react-use,SWR,react-hook-form等等,这些库都是经过社区验证的,可以放心使用。

这里使用ahooks 举例

安装ahooks

在这里插入图片描述

文档地址:https://ahooks.js.org/zh-CN/hooks/use-request/index

npm install --save ahooks
# or
yarn add ahooks
# or
pnpm add ahooks
# or
bun add ahooks
小案例1:useMount 组件首次渲染完成执行
import React from 'react'
import { useMount } from 'ahooks'

const App: React.FC = () => {
   useMount(() => {
      console.log('mounted')
   })
   return <>
      <div></div>
   </>;
}

export default App;
小案例2:useRequest 请求

useRequest 是ahooks 中非常强大的一个hook,可以用来处理请求,比如自动请求/手动请求,轮询,防抖,节流,屏幕聚焦重新请求,错误重试,loading delay,SWR(stale-while-revalidate),缓存等等。

import React from 'react'
import { useMount, useRequest } from 'ahooks'

const App: React.FC = () => {
   const { data, run } = useRequest(() => {
      return fetch('https://api.github.com/users/github').then(res => res.json())
   }, {
      debounceWait: 300, // 防抖
      manual: true, // 手动触发
   })
   return <>
      <div>{JSON.stringify(data)}</div>
      <button onClick={run}>请求</button>
   </>;
}

export default App;

其他有趣的hooks,大家可以去官网查看,这里就不一一列举了。


http://www.kler.cn/a/516775.html

相关文章:

  • 【Flutter】platform_view之AppKitView在哪个flutter版本添加的
  • SQL进阶实战技巧:用户会话内行为模式挖掘
  • 计算机网络之运输层
  • Langchain+文心一言调用
  • Vue基础(2)
  • uniapp+Vue3(<script setup lang=“ts“>)模拟12306城市左右切换动画效果
  • 利用 SAM2 模型探测卫星图像中的农田边界
  • 【CES2025】超越界限:ThinkAR推出8小时满电可用的超轻AR眼镜AiLens
  • Formality:时序变换(二)(不可读寄存器移除)
  • C# Interlocked 类使用详解
  • 深度学习|表示学习|卷积神经网络|局部链接是什么?|06
  • 【博客之星】2024年度总结
  • YOLO(You Only Look Once)--实时目标检测的革命性算法
  • 【ChatGPT】意义空间与语义运动定律 —— AI 世界的神秘法则
  • C# 与.NET 日志变革:JSON 让程序“开口说清话”
  • 使用Layout三行布局(SemiDesign)
  • 单片机-STM32 WIFI模块--ESP8266 (十二)
  • 后端开发基础——JavaWeb(根基,了解原理)浓缩
  • 关于av_get_channel_layout_nb_channels函数
  • Scrapy之一个item包含多级页面的处理方案
  • docker运行长期处于activating (start)
  • 【十年java搬砖路】oracle链接失败问题排查
  • 基于ollama,langchain,springboot从零搭建知识库四【设计通用rag系统】
  • 掌握Spring事务隔离级别,提升并发处理能力
  • element-plus 的table section如何实现单选
  • 亚博microros小车-原生ubuntu支持系列:6-整体检测