乐趣区

关于vue3:如何基于-vue3x-编写自己的-hook

什么是 hooks

函数式编程在前端开发中越来越风行,尤其是在古代前端框架 Vue3.xReact 16+ 中。它的长处包含代码可读性、可维护性、可测试性和复用性。

学习如何利用框架提供的钩子(hooks)编写自定义钩子函数是十分重要的技能之一。通过编写自定义钩子函数,咱们能够满足特定需要,使咱们的代码更加灵便和可扩大。

把握函数式编程和钩子的应用,可能进步咱们的开发效率,同时提供更好的用户体验和代码品质。在应用 Vue3.xReact 16+ 等古代前端框架时,函数式编程和自定义钩子函数将成为咱们必备的技能。

优良的 vue3.x hooks 库

  • vue-hooks-plus
  • vueuse

两个杰出的开源库,它们简直能够满足各种场景的需要。然而,学习如何独立编写代码也是十分重要的。通过亲自动手编写代码,咱们可能深刻了解底层原理,造就本人的解决问题的能力。

起步

假如你曾经理解并且把握 vue3.x ts,所以不做过多的赘述。废话不多说,间接上手~~~

useElementBounding 办法

这里以该办法作为例子解说。

功能分析

依据办法名称,咱们能够直观地理解到这个办法的作用是用于动静获取元素的坐标和尺寸信息。

该办法提供了一种便捷的形式来获取元素的地位和大小。通过调用这个办法,咱们能够动静地获取元素的坐标、宽度、高度等信息,以便在开发过程中进行相应的操作和布局调整。

参数剖析

  1. 待监听的 dom
  2. 配置监听条件 options

开发

配置项类型
interface UseElementBoundingOptions {
  /**
   *
   * When the component is mounted, initialize all values to 0
   *
   * @default true
   */
  reset?: boolean
  /**
   *
   * windowResize
   *
   * @default true
   */
  windowResize?: boolean
  /**
   *
   * windowScroll
   *
   * @default true
   */
  windowScroll?: boolean
  /**
   *
   * immediate
   *
   * @default true
   */
  immediate?: boolean
}

interface UseElementBoundingReturnType {
  width: Ref<number>
  height: Ref<number>
  top: Ref<number>
  left: Ref<number>
  bottom: Ref<number>
  right: Ref<number>
}
辅助函数
type TargetValue<T> = T | undefined | null

type TargetType = HTMLElement | Element | Window | Document | ComponentPublicInstance

export type BasicTarget<T extends TargetType = Element> =
  | (() => TargetValue<T>)
  | TargetValue<T>
  | Ref<TargetValue<T>>
  

function getTargetElement<T extends TargetType>(target: BasicTarget<T>, defaultElement?: T) {if (!isBrowser) {return undefined}

  if (!target) {return defaultElement}

  let targetElement: TargetValue<T>
  
  if (typeof target === 'function') {targetElement = target()
  } else if (isRef(target)) {targetElement = (target.value as ComponentPublicInstance)?.$el ?? target.value
  } else {targetElement = target}
  return targetElement
}
正式开发
export default function useElementBounding(
  target: BasicTarget,
  options?: UseElementBoundingOptions,
): UseElementBoundingReturnType {const { reset = true, windowResize = true, windowScroll = true, immediate = true} = options ?? {}
  const size = reactive({
    width: 0,
    height: 0,
    top: 0,
    left: 0,
    bottom: 0,
    right: 0,
  })
    
  // 定义更新函数
  const update = () => {
    // 获取 dom
    const targetDom = getTargetElement(target)

    if (!targetDom) {
      // 组件装置后,将所有值初始化为 0
      if (reset) {Object.keys(size).forEach(key => {if (keyisUseElementBoundingReturnTypeKey(key))
            size[key] = 0
        })
      }

      return
    }

    if (targetDom) {
      // 应用 getBoundingClientRect 办法,获取元素信息
      const {width, height, top, left, bottom, right} = targetDom.getBoundingClientRect()

      size.width = width
      size.height = height
      size.top = top
      size.left = left
      size.bottom = bottom
      size.right = right
    }
  }

  // 窗口尺寸产生更改时触发更新
  if (windowResize) {
    useEventListener('resize', update, {// 事件监听器不会调用 preventDefault() 办法来阻止默认行为。这样能够进步滚动的性能,并且缩小滚动操作的提早。passive: true,
    })
  }

  // 窗口滚动时触发更新
  if (windowScroll) {
    useEventListener('scroll', update, {
      capture: true,
      passive: true,
    })
  }

  // 元素尺寸更改时触发更新
  useResizeObserver(target, update)
  // 代理对象产生更改时触发更新
  watch(() => getTargetElement(target), update)

  onMounted(() => {immediate && update()
  })

  return {...toRefs(size),
  }
}
残缺代码
import {onMounted, reactive, toRefs, Ref, watch} from 'vue'
import useResizeObserver from '../useResizeObserver'
import useEventListener from '../useEventListener'

import {BasicTarget, getTargetElement} from '../utils/domTarget'

export interface UseElementBoundingOptions {
  /**
   *
   * When the component is mounted, initialize all values to 0
   *
   * @default true
   */
  reset?: boolean
  /**
   *
   * windowResize
   *
   * @default true
   */
  windowResize?: boolean
  /**
   *
   * windowScroll
   *
   * @default true
   */
  windowScroll?: boolean
  /**
   *
   * immediate
   *
   * @default true
   */
  immediate?: boolean
}

function keyisUseElementBoundingReturnTypeKey(key: string): key is keyof UseElementBoundingReturnType {return ['width', 'height', 'top', 'left', 'bottom', 'right'].includes(key)
}

export interface UseElementBoundingReturnType {
  width: Ref<number>
  height: Ref<number>
  top: Ref<number>
  left: Ref<number>
  bottom: Ref<number>
  right: Ref<number>
}

export default function useElementBounding(
  target: BasicTarget,
  options?: UseElementBoundingOptions,
): UseElementBoundingReturnType {const { reset = true, windowResize = true, windowScroll = true, immediate = true} = options ?? {}
  const size = reactive({
    width: 0,
    height: 0,
    top: 0,
    left: 0,
    bottom: 0,
    right: 0,
  })
    
  // 定义更新函数
  const update = () => {
    // 获取 dom
    const targetDom = getTargetElement(target)

    if (!targetDom) {
      // 组件装置后,将所有值初始化为 0
      if (reset) {Object.keys(size).forEach(key => {if (keyisUseElementBoundingReturnTypeKey(key))
            size[key] = 0
        })
      }

      return
    }

    if (targetDom) {
      // 应用 getBoundingClientRect 办法,获取元素信息
      const {width, height, top, left, bottom, right} = targetDom.getBoundingClientRect()

      size.width = width
      size.height = height
      size.top = top
      size.left = left
      size.bottom = bottom
      size.right = right
    }
  }

  // 窗口尺寸产生更改时触发更新
  if (windowResize) {
    useEventListener('resize', update, {// 事件监听器不会调用 preventDefault() 办法来阻止默认行为。这样能够进步滚动的性能,并且缩小滚动操作的提早。passive: true,
    })
  }

  // 窗口滚动时触发更新
  if (windowScroll) {
    useEventListener('scroll', update, {
      capture: true,
      passive: true,
    })
  }

  // 元素尺寸更改时触发更新
  useResizeObserver(target, update)
  // 代理对象产生更改时触发更新
  watch(() => getTargetElement(target), update)

  onMounted(() => {immediate && update()
  })

  return {...toRefs(size),
  }
}

啰嗦两句

其实代码量并不多,核心思想就是利用 vue3.x 的响应式办法进行封装。该代码 源码地址。

卖个瓜

vue-hooks-plus 也是小编参加奉献与保护的的一个开源 vue3 hooks 库,能够来看看,没事还能够点个赞~~~谢谢~~~

退出移动版