自 React 16.8 之后,推荐使用函数式组件
Hooks 相关知识点参考:React Hooks完全上手指南
以下记录一些项目中经常用到的自定义hooks
useAsync:异步接口请求
import { useState, useCallback } from 'react'
export default const useAsync = (asyncFunc) => {
// 设置三个异步逻辑相关的 state
const [data, setData] = useState(null)
const [loading, setLoading] = useState(false)
const [error, setError] = useState(null)
// 定义一个 callback 用于执行异步逻辑
const execute = useCallback(() => {
// 请求开始时,设置 loading 为 true,清除已有数据和 error 状态
setLoading(true)
setData(null)
setError(null)
return asyncFunc().then(res => {
// 请求成功,将数据写进 state,设置 loading 为 false
setData(res)
setLoading(false)
}).catch(err => {
// 请求失败,设置 loading 为 false,并设置错误状态
setError(error)
setLoading(false)
})
}, [asyncFunc])
return { execute, loading, data, error }
}
在组件中使用示例
import React from 'react'
import useAsync from './useAsync'
export default function UserList(props) {
// 通过 useAsync 这个函数,只需要提供异步逻辑的实现
const {
execute: fetchUsers,
data: users,
loading,
error,
} = useAsync(async () => {
const res = await fetch('http://xxx/xxx')
const json = await res.json
return json.data
})
return (
// 根据状态渲染 UI...
)
}
useUpdate:更新时执行
import { useRef, useEffect } from 'react'
export default function useUpdate(
callback = () => {},
dependences,
initialData = false
) {
const isInitialMount = useRef(true)
useEffect(() => {
// 第一次,也就是mount阶段 不执行onChange,只有后续更新的时候才调用
// 因为在页面中,一般mount阶段会写请求数据之类的操作
if (isInitialMount.current && !initialData) {
isInitialMount.current = false
} else {
callback()
}
}, dependences)
}
使用示例
useUpdate(() => {
console.log(count)
}, [count])
useForceUpdate:强制更新
import { useReducer, useLayoutEffect, useRef } from 'react'
export default function useForceUpdate() {
// 函数组件没有forceUpdate,用这种方法代替
const [ignored, forceUpdate] = useReducer(x => x + 1, 0)
const resolveRef = useRef(null)
useLayoutEffect(() => {
resolveRef.current && resolveRef.current()
}, [ignored])
const promise = () => {
return new Promise((resolve, reject) => {
forceUpdate()
resolveRef.current = resolve
})
}
return { forceUpdate: promise }
}
使用示例
// 函数组件没有forceUpdate,用这种方法代替
const { forceUpdate } = useForceUpdate()
useEffect(() => {
forceUpdate().then(() => {
// 得等到上一次渲染完成后,才能拿到最新的宽度和高度
chart?.forceFit()
})
}, [count])
useEvent:封装事件处理函数
主要有两个特点
- 在组件多次
render
时保持引用一致。- 函数内始终能获取到最新的
props
与state
。
import { useLayoutEffect, useCallback, useRef } from 'react'
export default function useEvent(handler) {
const handlerRef = useRef(null)
// 视图渲染完成后更新 handlerRef.current 指向
// 事件回调触发的时机是在视图完成渲染之后,这样可以稳定获取到最新的state与props
useLayoutEffect(() => {
handlerRef.current = handler
})
// 用useCallback包裹,使得render时返回的函数引用一致
return useCallback((...args) => {
const fn = handlerRef.current
return fn(...args)
}, [])
}
使用示例
import { useState } from 'react'
function Demo() {
const [text, setText] = useState('')
// 不管render多少次,onInputChange都是不变的,做节流、防抖等
const onInputChange = useEvent((value) => {
setText(value)
});
return
}
usePrevious:记录上一次的值
import { useEffect, useRef } from 'react'
export default function usePrevious(value) {
const ref = useRef()
useEffect(() => {
ref.current = value
}, [value])
return ref.current
}
使用示例
const [count, setCount] = useState(1)
const preCount = usePrevious(count)
useScroll:监听滚动位置
import { useState, useEffect } from 'react'
// 获取横向,纵向滚动条位置
export default const getPosition = () => {
return {
x: document.body.scrollLeft,
y: document.body.scrollTop,
}
}
export default const useScroll = () => {
// 定一个 position 这个 state 保存滚动条位置
const [position, setPosition] = useState(getPosition())
useEffect(() => {
const handler = () => {
setPosition(getPosition())
}
// 监听 scroll 事件,更新滚动条位置
document.addEventListener('scroll', handler)
return () => {
// 组件销毁时,取消事件监听
document.removeEventListener('scroll', handler)
}
}, [])
return position
}
返回顶部功能示例
import React, { useCallback } from 'react'
import useScroll from './useScroll'
export default function ScrollTop (props) {
const { y } = useScroll()
const goTop = useCallback(() => {
document.body.scrollTop = 0
}, [])
const style = {
position: 'fixed',
right: '10px',
bottom: '10px',
}
// 当滚动条位置纵向超过300时,显示返回顶部按钮。否则不渲染任何UI
if (y <= 300) return null
return (
)
}