Hook 是什么
React 内置的 Hooks
import { useState, useEffect, useCallback, useMemo } from 'react'
function Component() {
const [count, setCount] = useState(0) // 这是 Hook
useEffect(() => { // 这也是 Hook
console.log('mounted')
}, [])
const handleClick = useCallback(() => { // 这也是 Hook
setCount(count + 1)
}, [count])
return <button onClick={handleClick}>{count}</button>
}
React 常用的内置 Hooks:
useState- 状态管理useEffect- 副作用useCallback- 函数缓存useMemo- 值缓存useRef- 引用useContext- ContextuseReducer- 复杂状态- 等等...
什么是自定义 Hooks?
就是你自己写的、可以复用的逻辑!
举个例子
不用自定义 Hook(代码重复)
// 组件 A - 需要 localStorage
function ComponentA() {
const [value, setValue] = useState(() => {
const stored = localStorage.getItem('key')
return stored ? JSON.parse(stored) : 'default'
})
useEffect(() => {
localStorage.setItem('key', JSON.stringify(value))
}, [value])
return <input value={value} onChange={e => setValue(e.target.value)} />
}
// 组件 B - 也需要 localStorage(重复代码!)
function ComponentB() {
const [theme, setTheme] = useState(() => {
const stored = localStorage.getItem('theme')
return stored ? JSON.parse(stored) : 'light'
})
useEffect(() => {
localStorage.setItem('theme', JSON.stringify(theme))
}, [theme])
return <button onClick={() => setTheme('dark')}>{theme}</button>
}
用自定义 Hook(复用逻辑)
// hooks/useLocalStorage.ts - 自定义 Hook
import { useState, useEffect } from 'react'
export function useLocalStorage<T>(key: string, initialValue: T) {
// 从 localStorage 读取初始值
const [value, setValue] = useState<T>(() => {
try {
const stored = localStorage.getItem(key)
return stored ? JSON.parse(stored) : initialValue
} catch {
return initialValue
}
})
// 值变化时保存到 localStorage
useEffect(() => {
try {
localStorage.setItem(key, JSON.stringify(value))
} catch (error) {
console.error('Error saving to localStorage:', error)
}
}, [key, value])
return [value, setValue] as const
}
// 组件 A - 简洁!
function ComponentA() {
const [value, setValue] = useLocalStorage('key', 'default')
return <input value={value} onChange={e => setValue(e.target.value)} />
}
// 组件 B - 也很简洁!
function ComponentB() {
const [theme, setTheme] = useLocalStorage('theme', 'light')
return <button onClick={() => setTheme('dark')}>{theme}</button>
}
看到区别了吗?
- √ 逻辑复用,不用重复写
- √ 代码更清晰
- √ 易于测试
- √ 易于维护
更多自定义 Hooks 例子
1. useDebounce - 防抖(搜索框常用)
// hooks/useDebounce.ts
import { useState, useEffect } from 'react'
export function useDebounce<T>(value: T, delay: number = 500): T {
const [debouncedValue, setDebouncedValue] = useState<T>(value)
useEffect(() => {
const timer = setTimeout(() => {
setDebouncedValue(value)
}, delay)
return () => clearTimeout(timer)
}, [value, delay])
return debouncedValue
}
// 使用 - 搜索框
function SearchBox() {
const [searchTerm, setSearchTerm] = useState('')
const debouncedSearchTerm = useDebounce(searchTerm, 500)
useEffect(() => {
// 只在用户停止输入 500ms 后才调用 API
if (debouncedSearchTerm) {
fetch(`/api/search?q=${debouncedSearchTerm}`)
.then(res => res.json())
.then(data => console.log(data))
}
}, [debouncedSearchTerm])
return (
<input
value={searchTerm}
onChange={e => setSearchTerm(e.target.value)}
placeholder="搜索..."
/>
)
}
2. useMediaQuery - 响应式判断
// hooks/useMediaQuery.ts
import { useState, useEffect } from 'react'
export function useMediaQuery(query: string): boolean {
const [matches, setMatches] = useState(false)
useEffect(() => {
const media = window.matchMedia(query)
if (media.matches !== matches) {
setMatches(media.matches)
}
const listener = () => setMatches(media.matches)
media.addEventListener('change', listener)
return () => media.removeEventListener('change', listener)
}, [query, matches])
return matches
}
// 使用 - 响应式布局
function ResponsiveComponent() {
const isMobile = useMediaQuery('(max-width: 768px)')
const isDesktop = useMediaQuery('(min-width: 1024px)')
return (
<div>
{isMobile && <MobileMenu />}
{isDesktop && <DesktopMenu />}
</div>
)
}
3. useOnClickOutside - 点击外部关闭
// hooks/useOnClickOutside.ts
import { useEffect, RefObject } from 'react'
export function useOnClickOutside<T extends HTMLElement>(
ref: RefObject<T>,
handler: (event: MouseEvent | TouchEvent) => void
) {
useEffect(() => {
const listener = (event: MouseEvent | TouchEvent) => {
const el = ref?.current
if (!el || el.contains(event.target as Node)) {
return
}
handler(event)
}
document.addEventListener('mousedown', listener)
document.addEventListener('touchstart', listener)
return () => {
document.removeEventListener('mousedown', listener)
document.removeEventListener('touchstart', listener)
}
}, [ref, handler])
}
// 使用 - 下拉菜单
function Dropdown() {
const [isOpen, setIsOpen] = useState(false)
const dropdownRef = useRef<HTMLDivElement>(null)
useOnClickOutside(dropdownRef, () => {
setIsOpen(false) // 点击外部关闭
})
return (
<div ref={dropdownRef}>
<button onClick={() => setIsOpen(!isOpen)}>打开菜单</button>
{isOpen && (
<ul>
<li>选项 1</li>
<li>选项 2</li>
</ul>
)}
</div>
)
}
4. useCopyToClipboard - 复制到剪贴板
// hooks/useCopyToClipboard.ts
import { useState } from 'react'
export function useCopyToClipboard() {
const [copiedText, setCopiedText] = useState<string | null>(null)
const copy = async (text: string) => {
if (!navigator?.clipboard) {
console.warn('Clipboard not supported')
return false
}
try {
await navigator.clipboard.writeText(text)
setCopiedText(text)
return true
} catch (error) {
console.error('Copy failed', error)
setCopiedText(null)
return false
}
}
return { copiedText, copy }
}
// 使用
function CopyButton({ text }: { text: string }) {
const { copiedText, copy } = useCopyToClipboard()
return (
<button onClick={() => copy(text)}>
{copiedText === text ? '已复制!' : '复制'}
</button>
)
}
所以,/hooks/ 目录是干什么的?
存放这些自定义 Hooks!
hooks/
├── index.ts // 统一导出
├── useLocalStorage.ts // localStorage Hook
├── useDebounce.ts // 防抖 Hook
├── useThrottle.ts // 节流 Hook
├── useMediaQuery.ts // 媒体查询 Hook
├── useOnClickOutside.ts // 点击外部 Hook
├── useCopyToClipboard.ts // 复制 Hook
├── useToggle.ts // 开关 Hook
└── useAsync.ts // 异步 Hook
使用方式
// 在任何组件中使用
import { useLocalStorage, useDebounce, useMediaQuery } from '@/hooks'
function MyComponent() {
const [user, setUser] = useLocalStorage('user', null)
const [search, setSearch] = useState('')
const debouncedSearch = useDebounce(search, 300)
const isMobile = useMediaQuery('(max-width: 768px)')
// ... 你的逻辑
}
总结
React Hook 包括:
React 内置 Hooks(你不用创建目录)
useState,useEffect,useCallback,useMemo等- 直接从
react导入使用
自定义 Hooks(需要创建
/hooks/目录)- 你自己写的,可复用的逻辑
- 通常会用到 React 内置 Hooks
- 命名必须以
use开头(React 规则)
为什么需要 /hooks/ 目录?
- √ 复用逻辑 - 不用重复写代码
- √ 代码整洁 - 组件更简洁
- √ 易于测试 - 可以单独测试 Hook
- √ 易于维护 - 逻辑集中管理
