雖然能把一些複雜的邏輯通通整理起來很舒服,但有時候你只是想省一滴滴的麻煩,或是想圖一點乾淨或是清楚的程式碼,因此本篇要來看看這些可能只是弄弄命名、重新包裝,或許不一定需要,或許只是讓內心小宇宙開心一點的零碎 hook 們。
第一次 render 後只執行一次 useEffect
function useInitEffect(cb) {
useEffect(() => {
cb()
}, [])
}
useInitEffect(() => {
console.log("ON INIT")
})
當元件 un-mount 時才執行的 useEffect
function useUnmountEffect(cb) {
useEffect(() => {
return () => cb()
}, [])
}
useUnmountEffect(() => {
console.log("ON UN MOUNT")
})
跳過第一次 render 的執行,後續 render 才會執行
function useAfterInitEffect(cb, deps) {
const initRef = useRef(true)
useEffect(() => {
if (initRef.current) {
initRef.current = false
return
}
return cb()
}, deps)
}
useAfterInitEffect(() => {
console.log("ON AFTER INIT")
})
Next.js 中如果想要抓到 queryString,要先透過 useRouter 再進一步得到 object 之後,再獲取 query,如果覺得這樣太麻煩,那就...
function useQuery() {
return useRouter().query
}
const {id, status} = useQuery()
react-router 或是 Next.js 中如果想透過程式碼的方式(非元件,如: <Link/>
)進行葉面轉換,要先 useNavigate / useRouter 然後進一步填入需要的東西,如果覺得太麻煩...
以 react-router 的 useNavigate() 舉例,預先整理好一組 key-value 的網址物件,然後透過傳入 key 再進一步使用:
function useNav(loaction) {
const naviagte = useNavigate()
return () => naviagte(URLs[location])
}
const goHome = useNav("home")
const goUserPage = useNav("user")
OR
function useNav() {
const naviagte = useNavigate()
return (location) => naviagte(URLs[location])
}
const nav = useNav()
nav("home")
nav("user")
如果你很明確知道要大量使用 input 時 (雖然你可能會選擇套件處理XD):
function useFormValue (init) {
const [state, setState] = useState(init)
const handleChange = name => evt => {
setState(prev => ({...prev, [name]: evt.target.value }))
}
return [state, handleChange]
}
<input value={state.email} onChange={handleChange('email')} />
與前一個雷同,當你只是想換第幾頁時:
function usePage (init = 1) {
const [state, setState] = useState(init)
const handleChange = (page) => setState(page)
const handlePrev = () => setState(p => p - 1)
const handleNext = () => setState(p => p + 1)
reutrn {page: state, handleChange, handlePrev, handleNext}
}
當想使用 <form/>
, onSubmit
, 以及 formData
時會用到的內容:
function useForm() {
const formId = useId()
const onSubmit = (evt) => {
evt.preventDefault()
const formData = new FormData(evt.target)
const data = {}
for(const [k,v] of formData){
data[k] = v
}
return data
}
return [formId, onSubmit]
}
const handleCreateMember = (evt) => {
const data = onSubmit(evt)
//操作 data 等等
}
<form id={formId}>
<input />
</form>
<button type="submit" form={formId} />
當你想幫一個非同步事件加上通知的包裝:
function useNotify(cb) {
const [isPending, setIsPending] = useState(false)
const [error, setError] = useState(null)
const handler = async () => {
setIsPending(true)
//處理中通知可以塞這裡
try {
const res = await cb()
//成功通知可以塞這裡
return res
} catch (error) {
//失敗通知可以塞這裡
setError(error)
} finally {
setIsPending(false)
}
}
return {handler, isPending, error}
}
不過很多 notification 套件都有提供 async/promise 的 method 可以使用,像是 react-toastify 的這一篇
SWR 中有提到可以將 hook 變成可重複使用的方法,變化與設計的方式就因人而異:
function useMember(query) {
return useSWR(member.url + query)
}
function useOrder(id) {
return useSWR(order.url + id)
}
class MemberService {
url
useMemberList (...) {
return useSWR(...)
}
useOneMember(...) {
return useSWR(...)
}
}
class Service {
url
constructor (name) {
url = url + api[name]
this.useList = (...) => useSWR(...)
this.useOne = (...) => useSWR(...)
this.createOne = (...) => fetch(..., { method: 'POST' })
//...
}
}
本篇沒什麼特別,雖然不一定用得到或包裝成獨立的 hook,但或許在特定的情境會非常好用,當然設計方式就會因應情境而有不同,看你想解決什麼問題再來下手看看。