乐趣区

关于前端:通过8个常用hook手把手教你封装hooks

对于应用 react 的同学来说,hook 肯定不生疏,然而如何封装 hook 以及在业务中怎么应用封装的 hook,很多同学并没有一个很好的实际,这篇文章就通过 10 个罕用的 hook 让大家学会封装 hook,可能在本人的业务中应用,进步复用率,缩小开发成本

前沿

hook 到底有什么作用呢?它能够让你对一些性能组件反复逻辑进行封装,拆散组件并将其性能细化,让组件逻辑变的简单明了,逻辑共享变的更容易,缩小了代码的重复性,保护和更新变的更简略易懂

hook 的实质就是让咱们的组件不再应用 class 组件,所以,如果你的我的项目还在用 react 的 class 组件的形式,是不能应用 hook 的

react 也内置了一些对应的 hook,比方咱们罕用的 useState、useEffect 等等,这里就不多说了

让咱们开始封装本人的一个 hook 库吧

useToggle

import {useState} from "react"

export default function useToggle(defaultValue) {const [value, setValue] = useState(defaultValue)

  function toggleValue(value) {
    setValue(currentValue =>
      typeof value === "boolean" ? value : !currentValue
    )
  }

  return [value, toggleValue]
}

通过代码可能看出这个 hook 的作用,实质就是进行状态的切换,你能够将其了解成一个 react 组件,也能够只是将其了解成一个函数,这个函数承受一个初始值,用 useState 进行状态的存储,通过函数 toggleValue 进行状态的切换,而后函数返回两个内容,一个是 以后的 value,一个是 toggleValue 函数,进行状态的切换,只不过组件返回的是一段 jsx 代码,这里返回的是一个数组

在应用方面就变的很简略了

export default function ToggleComponent() {const [value, toggleValue] = useToggle(false)

  return (
    <div>
      <div>{value.toString()}</div>
      <button onClick={toggleValue}>Toggle</button>
      <button onClick={() => toggleValue(true)}>Make True</button>
      <button onClick={() => toggleValue(false)}>Make False</button>
    </div>
  )
}

useStorage

前端的数据存储离不开 localStorage 和 sessionStorage,那如何依据这个内容写一个自定义 hook 呢?

import {useCallback, useState, useEffect} from "react"

export function useLocalStorage(key, defaultValue) {return useStorage(key, defaultValue, window.localStorage)
}

export function useSessionStorage(key, defaultValue) {return useStorage(key, defaultValue, window.sessionStorage)
}

function useStorage(key, defaultValue, storageObject) {const [value, setValue] = useState(() => {const jsonValue = storageObject.getItem(key)
    if (jsonValue != null) return JSON.parse(jsonValue)

    if (typeof defaultValue === "function") {return defaultValue()
    } else {return defaultValue}
  })

  useEffect(() => {if (value === undefined) return storageObject.removeItem(key)
    storageObject.setItem(key, JSON.stringify(value))
  }, [key, value, storageObject])

  const remove = useCallback(() => {setValue(undefined)
  }, [])

  return [value, setValue, remove]
}

这两个 hook 性能差不多,接管两个参数,key 和 defaultValue,当然你还能够扩大过期工夫相干内容

useEffect 监听 key 或者 value 是否变动做出一系列操作,通过 JSON.stringify 格式化成字符串,并通过 value 是否是 undefined 进行删除操作

应用也比较简单

export default function StorageComponent() {const [age, setAge, removeAge] = useLocalStorage("age", 26)

  return (
    <div>
      <div>
        {name} - {age}
      </div>
      <button onClick={() => setAge(40)}>Set Age</button>
      <button onClick={removeAge}>Remove Age</button>
    </div>
  )
}

useAsync

import {useCallback, useEffect, useState} from "react"

export default function useAsync(callback, dependencies = []) {const [loading, setLoading] = useState(true)
  const [error, setError] = useState()
  const [value, setValue] = useState()

  const callbackMemoized = useCallback(() => {setLoading(true)
    setError(undefined)
    setValue(undefined)
    callback()
      .then(setValue)
      .catch(setError)
      .finally(() => setLoading(false))
  }, dependencies)

  useEffect(() => {callbackMemoized()
  }, [callbackMemoized])

  return {loading, error, value}
}

次要内容还是针对 useState 和 useEffect 的封装,互相联合组成了 useAsync 的封装,callback 传入的是一个 Promise 函数,将 loading、error、value 对立解决,并针对 useEffect 的执行机会增加了 dependencies 参数

const {loading, error, value} = useAsync(() => {return new Promise((resolve, reject) => {
    const success = false
    setTimeout(() => {success ? resolve("Hi") : reject("Error")
    }, 1000)
  })
})

useFetch

依据咱们封装的 useAsync,通过进一步解决,咱们还可能失去更好用的 useFetch,之后在我的项目中再应用就不须要用本人封装的 fetch.js 了,毕竟其中没有 loading 或者 value 绑定在 state 的操作,能够用更好用的 useFetch

const DEFAULT_OPTIONS = {headers: { "Content-Type": "application/json"},
}

export default function useFetch(url, options = {}, dependencies = []) {return useAsync(() => {return fetch(url, { ...DEFAULT_OPTIONS, ...options}).then(res => {if (res.status === 200) return res.data
      return Promise.reject(res)
    })
  }, dependencies)
}

应用形式

const {loading, error, value} = useFetch(
    url,
    {method: 'post'}
  )

useEffectOnce

这个实现起来比较简单

import {useEffect} from "react"

export default function useEffectOnce(cb) {useEffect(cb, [])
}

应用同样

useEffectOnce(() => alert("Hi"))

useRenderCount

查看某个页面渲染了多少次

import {useEffect, useRef} from "react"

export default function useRenderCount() {const count = useRef(1)
  useEffect(() => count.current++)
  return count.current
}

应用

const renderCount = useRenderCount()

useTimeout

import {useCallback, useEffect, useRef} from "react"

export default function useTimeout(callback, delay) {const callbackRef = useRef(callback)
  const timeoutRef = useRef()

  useEffect(() => {callbackRef.current = callback}, [callback])

  const set = useCallback(() => {timeoutRef.current = setTimeout(() => callbackRef.current(), delay)
  }, [delay])

  const clear = useCallback(() => {timeoutRef.current && clearTimeout(timeoutRef.current)
  }, [])

  useEffect(() => {set()
    return clear
  }, [delay, set, clear])

  const reset = useCallback(() => {clear()
    set()}, [clear, set])

  return {reset, clear}
}

这个 hook 实质就是提早多长时间执行 callback 函数,对外裸露了两个办法,别离是重置 reset 和 clear 革除定时器,能够更不便进行定时器操作,应用 ref 保留定时器和回调函数

应用形式

const {clear, reset} = useTimeout(() => setCount(0), 1000)

通过按钮点击或者函数调用来对定时器进行操作

useDebounce

同样的,对 useTimeout 进一步进行封装,能够实现 debounce 的操作,次要目标是为了解决某个办法在指定工夫内反复调用,用 hook 的形式能够很不便的解决这种问题

export default function useDebounce(callback, delay, dependencies) {const { reset, clear} = useTimeout(callback, delay)
  useEffect(reset, [...dependencies, reset])
  useEffect(clear, [])
}

其中通过 dependencies 的变动能够管制 reset,管制执行的频率

const [count, setCount] = useState(10)
useDebounce(() => alert(count), 1000, [count])

count 在 1s 之内变动频繁的话,是不会触发 alert 的,当然也能够通过一个是否立刻执行的参数进行一些相应的管制,这里就不提了,有趣味的同学能够自主欠缺一下

总结

总体来看,封装 hook 还是挺简略的,你能够了解为就是把一些罕用的原生的 hook 或者一些函数的再次封装,联合 state 或者 effect 将一些通用的逻辑提取,让页面变动更简略,更专一于页面自身本人的逻辑

同时也须要留神 hook 的一些应用规定,实质它就是一个 js 函数

  1. 只能在函数最外层调用 hook,不要在循环、条件判断或者子函数中调用
  2. 只能在 React 的函数组件中调用 hook 不要在其余 JavaScript 函数中调用,当然你也能够在自定义函数中调用自定义 hook,比方咱们实现的 useFetch 就是基于 useAsync
退出移动版