这是我参与11月更文挑战的第3天,活动详情查看:2021最后一次更文挑战。
前言
Hook
是 React
16.8引入的新特性,之后 Hook
被越来越多的人提及和在项目中应用,相信不少人在面试中也被问到过 Hook
的作用。
个人认为 Hook
有两个重要作用:
- 扩展了函数组件的功能。
- 复杂的状态逻辑复用变得更加简单。
而要解释这两点作用,我们需要从 Hook API
和 自定义 Hook
展开介绍。
Hook API
React 提供了许多内置 Hook API,包括:useState
、 useEffect
、 useContext
、 useReducer
、 useCallback
、 useMemo
、 useRef
、useImperativeHandle
、 useLayoutEffect
、 useDebugValue
。
这些 API
帮助我们在使用函数组件的情况,也能拥有 class
组件的特性,其中使用最频繁的便是 useState
和 useEffect
:
useState
能够帮助我们使用 state
特性,在函数组件中保存状态,更新状态等:
// 保存状态
const [state, setState] = useState(initialState);
// 更新状态
setState(newState)
useEffect
能够帮助我们在函数组件渲染、更新时执行操作,类似 class
组件componentDidMount
、componentDidUpdate
:
// 类似componentDidMount
useEffect(()=>{
// do someting
},[])
// 类似componentDidUpdate
useEffect(()=>{
// do someting
},[value])
自定义 Hook
自定义 Hook
是对内置 Hook
的包装,以创造更多更丰富的功能,接下来将会结合开发实践中使用到的一些自定义 Hook
,介绍 React Hook
对状态逻辑复用发挥的作用。
useRefs
内置 Hook
useRef
返回一个可变的 ref
对象,返回的 ref
对象在组件的整个生命周期内保持不变,因此除了用来获取元素的 ref
访问 DOM
元素外,还可以用于存储变量。
useRefs
是对 useRef
内置 Hook
功能的扩展,当我们需要在组件内同时获取到多个元素的 ref
时,useRefs
能够用更简洁的代码达到目的。
function useRefs<RefType>(): [
(key: React.Key) => React.RefObject<RefType>,
(key: React.Key) => void
] {
// 通过 useRef 创建的一个 ref 对象存储一个 Map 类型的变量 cacheRefs
const cacheRefs = useRef(new Map<React.Key, React.RefObject<RefType>>());
// get 方法通过key值从 cacheRefs 中获取 ref 对象或创建一个新的 ref 对象
function getRef(key: React.Key) {
if (!cacheRefs.current.has(key)) {
cacheRefs.current.set(key, React.createRef());
}
return cacheRefs.current.get(key);
}
// remove 方法根据 key 值从 cacheRefs 中移除对应的 ref 对象
function removeRef(key: React.Key) {
cacheRefs.current.delete(key);
}
// 将 get 和 remove 方法作为自定义 hook 的返回值,
return [getRef, removeRef];
}
在函数组件中复用这个自定义 Hook:
const [getRefs,removeRefs] = useRefs<HTMLElement>();
...
<>
<div ref={getRefs('div_1')}></div>
<div ref={getRefs('div_2')}></div>
</>
useRequest
useRequest
是对 Http
请求的封装,通过与内置 Hook
useState
的结合,让我们采用 Hook
的形式调用 Http
请求,并且获取到返回的数据时自动更新组件,而不需要额外定义状态:
function useRequest(options) {
// http 请求相关的配置
const { url, ...init } = options;
// 请求返回的数据
const [data, setData] = useState(null);
// 请求返回的错误信息
const [error, setError] = useState(null);
// 请求的loading 状态
const [loading, setLoading] = useState(false);
// loader 方法通过 fetch API 发出 http 请求
function loader() {
setLoading(true);
return fetch(url, init)
.then((res) => {
setData(res.json());
setLoading(false);
})
.catch((error) => {
setError(error);
setLoading(false);
});
}
// 将 loader, data, error, loading 作为自定义 hook 的返回值
return { loader, data, error, loading };
}
在函数组件内复用这个自定义Hook:
const {data,loader} = useRequest({url: 'api/getData', method: 'GET'})
...
<Button onClick={()=>{loader()}}>request<Button>
...
<div>{data}</div>
useCheckPermission
useCheckPermission
中封装了项目中校验操作权限的状态逻辑。
通过自定义 Hook
的形式,能够将这部分的状态逻辑从组件中解耦合,并且能够在不同的组件中需要校验权限的方法复用这块逻辑。
通过将这部分状态逻辑抽离,也使得组件内的的代码更加清晰简洁。
这个自定义 Hook
中主要结合了内置 Hook
useEffect
,以及自定义 Hook
useRequest
。
function useCheckPermission() {
// 请求验证权限
const { loader, data } = useRequest({url: '/getPermission'});
// 组件渲染完成时,触发校验请求
useEffect(() => {
loader()
}, []);
// 返回校验结果
return {
hasPermission: data,
};
}
useTicker
useTicker
以 Hook
的方式实现定时器的功能。
通过使用内置 Hook
useRef
和 useEffect
, 能够在组件渲染完成后自动开始执行定时器,并且允许设置 callback
函数和时间间隔 time
。
当 callback
或 time
更新时,能够更新定时器,设置新的间隔时间 time
和执行新的 callback
。
function useTicker({ callback, time }) {
// 通过 useRef 设置 timer 变量,组件的整个生命周期内保持不变
const cacheRef = useRef({
timer: null,
});
// 设置定时器,将定时器保存在变量 cacheRef 中。
function ticker() {
cacheRef.current.timer = setTimeout(() => {
callback();
ticker();
}, time);
}
// 停止定时器
function stop() {
clearTimeout(cacheRef.current.timer);
cacheRef.current.timer = null;
}
// 当 callback 和 time 更新时,清除旧的定时器,设置新的定时器
useEffect(() => {
stop();
ticker();
}, [callback, time]);
// 组件渲染完成,开启定时器,组件卸载时,清除定时器
useEffect(() => {
ticker();
// 组件卸载时执行
return () => {
stop();
};
}, []);
// 将 stop 方法作为 hook 的返回值
return {
stop,
};
}
在组件内使用 useTicker:
const {stop} = useTicker({callback: doSometing, time: 1000})
function doSometing(){... }
总结
Hook
作为 React
的新特性,确实为我们提供了更多的可能性。通过自定义 Hook
的方式,我们可以将组件中可复用的状态逻辑抽离出来,保持其独立性。而当我们需要它时又可以很容易的将这些状态逻辑重新应用到组件内,与组件紧密的结合在一起,发挥它的作用。