对于应用 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 函数
- 只能在函数最外层调用 hook,不要在循环、条件判断或者子函数中调用
- 只能在 React 的函数组件中调用 hook 不要在其余 JavaScript 函数中调用,当然你也能够在自定义函数中调用自定义 hook,比方咱们实现的 useFetch 就是基于 useAsync