大家好!在 React 的世界中,有一个强大的秘密武器,它往往隐藏在显而易见的地方,由于缺乏理解或熟悉而没有得到充分利用。
这个强大的工具,被称为自定义 React hooks,可以彻底改变我们编写 React 应用程序代码的方式。通过提取组件中的有状态逻辑,自定义 hooks 允许我们大大提高代码的效率、可读性和重用性。
然而,自定义 hooks 不仅仅是一个开发人员工具箱中的功能或工具。它们代表了我们开发范式的转变,一个引领我们重新思考在 React 应用程序中传统管理状态和副作用方式的新思维模式。
在本文中,我们将消除自定义 hooks 的概念的迷雾,深入探究它们的结构,并探索创建我们自己的过程。我们将采取实际的方法,举例来演示它们在真实场景中的用法。
我们的目标不仅是理解自定义 hooks 是什么或它们如何工作。这是全面接受其背后的哲学并赏识它们为我们的代码带来的优雅、简单和强大。
关键在于:hooks 很酷,但是自定义 hooks?它们就是可以使你的 React 代码水平更进一步的秘密配方。就像一个功能强大的工具,可以完全定制你的特定需求。一个为你量身定做的解决方案。
准备潜入自定义 React Hooks 的世界吧!
你可能会想:"我们已经有 hooks 了,为什么还需要自定义 hooks?"这是一个非常好的问题,答案很简单。自定义 hooks 是关于使你的代码更可读、更易管理和最重要的可重用。
在软件开发中,可读性非常重要。编写代码不仅要让它工作,还要让它易于理解。使用自定义 hooks,您可以将复杂的逻辑封装到单独的函数中。这样,当其他人(甚至将来的你)查看代码时,他们可以轻松理解发生了什么。
在处理大型应用程序时,管理代码可能是一个具有挑战性的任务。通过使用自定义 hooks,您可以将代码拆分为更小、更易管理的部分。就像整洁地将代码组织到不同的盒子中,每个盒子都服务于特定的目的。
编程的基本原则之一是 DRY,即 Don’t Repeat Yourself。自定义 hooks 允许您编写一段逻辑一次,并在多个组件中重用它。这意味着更少的重复,更少的错误机会和更高的效率。
简而言之,自定义 hooks 是你的 React 工具箱中的宝贵工具。它们可以帮助您编写更干净,更易维护的代码,使您作为开发人员的生活更轻松,更愉快。
💡 注意: 使用一个开源的工具链比如 Bit 来发布、版本化和在所有的项目中重用你的自定义 hooks ,使用一个简单的
bit import your.username/yourCustomHook
命令,减少代码重复并最小化模板。
既然我们已经知道为什么需要自定义 hooks,那么让我们深入研究它们的实际样子和功能。 这一点很重要,因为理解自定义 hook 的结构将帮助您在未来编写自己的自定义 hook。
自定义 hook 只是一个 JavaScript 函数。唯一的规则是它的名称应该以 “use” 开头。这是一个约定,可以帮助你和其他人识别这个函数是一个 hook。例如,一个自定义钩子可能被命名为 useFetchData
。
在自定义 hook 内部,你可以使用其他 hooks,比如 useState
、useEffect
等。例如,useFetchData
钩子可以使用 useState
和 useEffect
钩子来获取数据并在状态中维护它。
自定义钩子可以返回任何你想要它返回的内容。这可以是一个单一的值、一个数组或者甚至是一个对象。你返回什么取决于你的组件需要什么。在我们的 useFetchData
示例中,自定义钩子可能会返回获取的数据以及加载状态和错误状态。
下面是一个简单的例子来说明自定义 hook, 这里有一个自定义钩子的示例来说明其结构:
function useFetchData(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
fetch(url)
.then(response => response.json())
.then(data => {
setData(data);
setLoading(false);
})
.catch(error => {
setError(error);
setLoading(false);
});
}, [url]);
return { data, loading, error };
}
在这个例子中,useFetchData
是一个自定义钩子,它从提供的URL获取数据。它使用useState
来管理数据、加载状态和错误状态,并使用useEffect
来处理数据获取。然后这个钩子返回一个包含数据、加载状态和错误状态的对象。
所以,这就是自定义钩子的结构。一开始它们可能看起来有点复杂,但是一旦你理解了它们的结构,它们可以极大地简化你的 React 代码并使其更易于管理。
既然我们已经解剖了自定义钩子的结构,是时候将这些知识付诸行动,自己创建一个了。别担心,这听起来没有那么可怕。事实上,有了这些简单的步骤,你很快就可以创建自己的自定义钩子了!
首先,我们需要定义一个 JavaScript 函数。还记得我们之前讨论过的命名约定吗?函数名称应以 “use” 开头。例如,让我们创建一个处理表单输入的钩子。我们将其命名为 useFormInput
。
function useFormInput() {
// 逻辑将在这里
}
接下来,我们将使用 React 中的内置钩子向自定义钩子添加逻辑。 在我们的 useFormInput
钩子中,我们将使用 useState
钩子来管理输入值。
function useFormInput() {
const [value, setValue] = useState("");
// 更多逻辑将在这里
}
现在,我们需要处理对输入值的更改。 我们将创建一个在输入更改时更新值的函数。
function useFormInput() {
const [value, setValue] = useState("");
function handleChange(event) {
setValue(event.target.value);
}
// 更多逻辑将在这里
}
最后,我们需要返回组件需要的任何内容。在这种情况下,我们的组件将需要该值和 handleChange 函数。 所以,我们将返回一个包括两者的对象。
function useFormInput() {
const [value, setValue] = useState("");
function handleChange(event) {
setValue(event.target.value);
}
return { value, handleChange };
}
就是这样!你刚刚创建了自己的自定义钩子。你现在可以在函数组件中使用这个钩子来处理表单输入。它是可重用的、干净的,并且使你的组件更具可读性。 最棒的是?你可以为任何你想要的创建一个自定义钩子,使你的代码更高效,更易于管理。
既然你已经适应了创建自定义钩子,那么让我们更进一步。 我们将探索十个可立即在项目中使用的自定义钩子的实用示例。 让我们深入研究!
useLocalStorage
是一个自定义的钩子,它简化了在 React 应用中使用本地存储的方法。它允许您从本地存储读取、写入和删除数据。
function useLocalStorage(key, initialValue) {
const [storedValue, setStoredValue] = useState(() => {
try {
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch (error) {
console.log(error);
return initialValue;
}
});
const setValue = value => {
try {
const valueToStore = value instanceof Function ? value(storedValue) : value;
setStoredValue(valueToStore);
window.localStorage.setItem(key, JSON.stringify(valueToStore));
} catch (error) {
console.log(error);
}
};
return [storedValue, setValue];
}
useDocumentTitle
是一个自定义钩子,可以轻松地从 React 组件更改文档标题。
function useDocumentTitle(title) {
useEffect(() => {
document.title = title;
}, [title]);
}
useEventListener
是一个自定义钩子,可以轻松地向任何 DOM 元素添加事件侦听器。
function useEventListener(eventName, handler, element = window) {
useEffect(() => {
const isSupported = element && element.addEventListener;
if (!isSupported) return;
element.addEventListener(eventName, handler);
return () => {
element.removeEventListener(eventName, handler);
};
}, [eventName, element, handler]);
}
useOnClickOutside
是一个自定义钩子,当用户点击特定组件之外时触发事件。这对于关闭下拉菜单等很方便。
function useOnClickOutside(ref, handler) {
useEffect(() => {
const listener = event => {
if (!ref.current || ref.current.contains(event.target)) return;
handler(event);
};
document.addEventListener("mousedown", listener);
document.addEventListener("touchstart", listener);
return () => {
document.removeEventListener("mousedown", listener);
document.removeEventListener("touchstart", listener);
};
}, [ref, handler]);
}
useHover
是一个自定义钩子,用于检测鼠标是否悬停在元素上。
function useHover() {
const [value, setValue] = useState(false);
const ref = useRef(null);
const handleMouseOver = () => setValue(true);
const handleMouseOut = () => setValue(false);
useEffect(
() => {
const node = ref.current;
if (node) {
node.addEventListener('mouseover', handleMouseOver);
node.addEventListener('mouseout', handleMouseOut);
return () => {
node.removeEventListener('mouseover', handleMouseOver);
node.removeEventListener('mouseout', handleMouseOut);
};
}
},
[ref] // Recall only if ref changes
);
return [ref, value];
}
useOnlineStatus
是一个自定义钩子,用于检测用户当前是否在线或离线。
function useOnlineStatus() {
const [isOnline, setOnline] = useState(navigator.onLine);
const goOnline = () => setOnline(true);
const goOffline = () => setOnline(false);
useEffect(() => {
window.addEventListener('online', goOnline);
window.addEventListener('offline', goOffline);
return () => {
window.removeEventListener('online', goOnline);
window.removeEventListener('offline', goOffline);
};
}, []);
return isOnline;
}
useWindowSize
是一个自定义钩子,允许你访问当前窗口大小。
function useWindowSize() {
const [size, setSize] = useState([window.innerWidth, window.innerHeight]);
useLayoutEffect(() => {
const updateSize = () => {
setSize([window.innerWidth, window.innerHeight]);
};
window.addEventListener('resize', updateSize);
updateSize();
return () => window.removeEventListener('resize', updateSize);
}, []);
return size;
}
useMediaQuery
是一个自定义钩子,允许你在组件中使用媒体查询。
function useMediaQuery(query) {
const [matches, setMatches] = useState(window.matchMedia(query).matches);
useEffect(() => {
const mediaQueryList = window.matchMedia(query);
const documentChangeHandler = () => setMatches(mediaQueryList.matches);
mediaQueryList.addListener(documentChangeHandler);
documentChangeHandler();
return () => {
mediaQueryList.removeListener(documentChangeHandler);
};
}, [query]);
return matches;
}
useDebounce
是一个自定义钩子,有助于延迟函数调用和消除给定的值。
function useDebounce(value, delay) {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const handler = setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => {
clearTimeout(handler);
};
}, [value, delay]);
return debouncedValue;
}
useInterval
是一个自定义钩子,允许你使用指定的间隔设置递归函数调用。这在需要重复执行函数时很有用,比如更新计时器或轮询服务器。
function useInterval(callback, delay) {
const savedCallback = useRef();
// Remember the latest callback.
useEffect(() => {
savedCallback.current = callback;
}, [callback]);
// Set up the interval.
useEffect(() => {
function tick() {
savedCallback.current();
}
if (delay !== null) {
let id = setInterval(tick, delay);
return () => clearInterval(id);
}
}, [delay]);
}
我将暂停到这里,但我相信这些示例应该足以让你了解自定义钩子的可能性。
你可以为你需要的任何创建一个自定义钩子。
所以继续吧,开始创建和使用自定义钩子,使你的代码比以往任何时候都更干净、可重用!