原文合集地址如下,有需要的朋友可以关注
任何相对独立、复用性强的逻辑,都可以 extract 为自定义 Hook,自定义 Hook 是一种复用 React 的状态逻辑的函数。
自定义 Hook 的主要特点是:
- 抽象组件间的状态逻辑,方便复用
- 让功能组件更纯粹,更易于维护
- 自定义 Hook 可以调用其他 Hook
为什么要用自定义 Hook?
- 提炼能复用的逻辑
许多组件有相似的状态逻辑,使用自定义 Hook 可以很方便地提取出来复用。 - 解决复杂组件的可读性问题
使用自定义 Hook 将复杂组件拆分为更小的功能独立的函数,有助于提高代码的可读性。 - 管理数据更新
使用独立的 Hook 函数来管理数据请求、处理异步逻辑、数据缓存等,易于维护。 - 分离状态逻辑
自定义 Hook 让函数组件更纯粹,只负责 UI,状态逻辑则交给 Hook。 - 调用其他 Hook
自定义 Hook 本身还可以调用 useState、useEffect 等其他 React Hook。
以下是我总结的一些常用的hooks
1、useUpdateEffect
useUpdateEffect作用
useUpdateEffect
是一个自定义的 React Hook,用于在组件更新时执行副作用操作。它类似于 React 的 useEffect
,但是会忽略组件的初始渲染阶段,只在组件更新时执行副作用操作。
在 React 中,useEffect
会在组件的每次渲染(包括初始渲染)完成后执行副作用操作。但有时候我们只想在组件更新时执行某些操作,而不关心初始渲染阶段的操作。这就是 useUpdateEffect
的用途。
以下是一个示例:
import { useEffect, useState } from 'react';
function MyComponent() {
const [count, setCount] = useState(0);
useEffect(() => {
console.log('useEffect - Component has rendered');
});
useUpdateEffect(() => {
console.log('useUpdateEffect - Component has updated');
});
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
在上述示例中,当点击 "Increment" 按钮时,count
的值会增加并触发组件的重新渲染。useEffect
会在每次渲染后执行,而 useUpdateEffect
只会在组件更新时执行。
通过使用 useUpdateEffect
,你可以在组件更新时执行一些特定的副作用操作,如请求数据、更新状态等,而不需要关心初始渲染阶段的操作。
为什么会需要用到useUpdateEffect
在某些情况下,我们希望在 React 组件更新时执行一些特定的副作用操作,而不在初始渲染阶段执行这些操作。这种情况下,我们可以使用类似于 useUpdateEffect
的自定义 Hook。
以下是一些使用 useUpdateEffect
的常见情况:
-
避免初始渲染时执行副作用:有些副作用操作可能只需要在组件更新时执行,例如发送网络请求、更新特定状态等。使用
useUpdateEffect
可以确保这些副作用操作在初始渲染时被跳过,只在组件更新时执行。 -
监听特定状态的变化:有时我们只关心特定状态的变化,并希望在状态发生变化时执行相应的操作。通过将状态值作为
useUpdateEffect
的依赖项,可以确保副作用操作只在这些状态发生变化时触发。 -
更新外部资源或库:有些第三方库或外部资源可能需要在组件更新时进行更新或重新初始化。使用
useUpdateEffect
可以确保在组件更新时调用相应的函数或方法,以便正确地更新这些外部资源。
通过使用 useUpdateEffect
,我们可以更加精确地控制副作用操作的触发时机,避免不必要的重复执行,以及在需要时处理特定的更新逻辑。
需要注意的是,React 自带的 useEffect
可以处理大多数情况下的副作用操作,而 useUpdateEffect
是在某些特定场景下的补充工具。在大多数情况下,使用 useEffect
即可满足需求。
自定义useUpdateEffect
要自定义一个类似于 useUpdateEffect
的自定义 Hook,你可以借助 React 的 useEffect
和 useRef
Hooks 来实现。以下是一个示例代码:
import { useEffect, useRef } from 'react';
function useUpdateEffect(effect, dependencies) {
const isMounted = useRef(false);
useEffect(() => {
if (isMounted.current) {
effect();
} else {
isMounted.current = true;
}
}, dependencies);
}
// 使用示例
function MyComponent() {
const [count, setCount] = useState(0);
useUpdateEffect(() => {
console.log('Component has updated');
}, [count]);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
在上述示例中,我们创建了一个名为 useUpdateEffect
的自定义 Hook。它接受两个参数:effect
和 dependencies
。在内部,我们使用了 useRef
来创建一个标记是否已经完成初始渲染的变量 isMounted
。
在 useEffect
中,我们检查 isMounted
的值。如果 isMounted
的值为 true
,则表示组件已经完成了初始渲染,此时执行传入的 effect
函数。否则,将 isMounted
的值设置为 true
,表示组件已完成初始渲染。
在使用时,你可以像使用 useEffect
一样,传入 effect
函数和依赖项数组 dependencies
,并且 effect
函数只会在组件更新时执行。
2、useTitle
useTitle 是一个相对经典的自定义 React Hook ,用来控制浏览器标题:
定义useTitle
import { useState, useEffect } from 'react';
function useTitle(initialTitle) {
const [title, setTitle] = useState(initialTitle);
useEffect(() => {
document.title = title;
}, [title]);
return setTitle;
}
使用useTitle:
function Page() {
const setTitle = useTitle('Default Title');
return (
<Button onClick={() => setTitle('New Title')}>
Click me
</Button>
)
}
点击按钮后,浏览器标题会变成"New Title"。
它的工作原理是:
- 保存标题的 state ,并记录修改 setTitle()
- 用 useEffect 监测 title 变化,设置 document.title
所以一旦我们调用 setTitle('New Title') 改变 state ,useEffect 就会执行,设置新的浏览器标题。
useTitle 的优点是: - 抽象出设置标题的逻辑,任何组件都可以共享
- 让组件更纯粹,只需要调用 setTitle() 接口即可
我们甚至可以抽象为更通用的 Hook:
js
function useDocumentTitle(title) {
useEffect(() => {
document.title = title;
}, [title]);
}
function Page() {
useDocumentTitle('Default Title');
// ...
}
通过自定义 Hook ,可以方便地在任何组件控制标题。
3、useForceUpdate
定义useForceUpdate
import { useState } from 'react';
function useForceUpdate() {
const [value, setValue] = useState(0);
return () => {
setValue(value => value + 1);
};
}
useForceUpdate的使用
const forceUpdate = useForceUpdate();
// 模拟更新组件
forceUpdate();
这个 Hook 返回了一个更新函数。在调用这个函数时,使用useState
强制组件重新渲染。
这是基于以下原理实现的:
- useState()会触发组件重新渲染
- state变化后,组件函数会重新执行
函数式组件只有 state 或 props 变化时才会更新。
使用此 Hook 我们可以主动触发组件更新。
比如在使用过时数据时:
// 过时数据
const { data } = useSomeHook();
// 更新组件
const forceUpdate = useForceUpdate();
setInterval(() => {
forceUpdate();
}, 5000);
每5秒强制组件一次,保证拿到最新数据。
4、useDebounce
定义
const useDebounce = (value, delay) => {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const handler = setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => {
clearTimeout(handler);
};
}, []); // 设为空数组[]
useEffect(() => {
clearTimeout(handler);
handler = setTimeout(() => {
setDebouncedValue(value);
}, delay);
}, [value]); // 只依赖 value
return debouncedValue;
};
使用
const inputValue = useDebounced(searchTerm, 500);
这里 每当searchTerm
变化时,会设置一个 500ms 的定时器。只有500ms内没有再改变searchTerm
,才会更新debouncedValue
。
这实现了防抖功能:在一定时间内停止触发, 只执行最后的动作。
5、useThrottle
定义
const useThrottle = (value, limit) => {
const [throttledValue, setThrottledValue] = useState(value);
useEffect(() => {
const handler = setTimeout(() => {
setThrottledValue(value);
}, limit);
return () => {
clearTimeout(handler);
};
}, []); // 应设为空数组[]
useEffect(() => {
clearTimeout(handler);
handler = setTimeout(() => {
setThrottledValue(value);
}, limit);
}, [value, limit]);
return throttledValue;
};
使用
const throttledValue = useThrottle(inputValue, 1000);
这里 每次inputValue
变化时,会开始一个计时器。1s后才会更新throttledValue
,实现了节流功能。
6、useInterval
定义
const useInterval = (callback, delay) => {
const savedCallback = useRef();
useEffect(() => {
savedCallback.current = callback;
});
useEffect(() => {
function tick() {
savedCallback.current();
}
if (delay !== null) {
let id = setInterval(tick, delay);
return () => clearInterval(id);
}
}, [delay]);
}
使用
useInterval(() => {
// ...
}, 1000);
这里每1000ms就会调用一次回调函数,实现了定时执行指定函数的功能。
有任何问题欢迎留言讨论学习