Table of Contents

1、概述

自定义 hook 是 React 16.8 之后,在 React 中,最重要的特性,他极大的提高了 React 的开发体验,并且也在后续的发展中,已经成为了整个行业的标杆型解决方案

但是他并不复杂,他仅仅只是一个普通函数封装,不过和封装普通函数相比的区别就在于,他可以在函数内部使用 React 的 Hook,从而实现状态逻辑的复用

因此,自定义 hook 就是对 useStateuseEffect 等 React Hook 的封装,所以它必须运用于组件函数内部

page.tsx
01
function Page() {
02
const [count, setCount] = useState(0)
03
04
return (
05
<div>
06
<div>{count}</div>
07
<button onClick={() => setCount(count + 1)}>click</button>
08
</div>
09
)
10
}

由于该计数器中,使用了 useState 来定义状态,那么,我们就可以单独将该计数器的状态逻辑封装到一个函数中,封装之后的逻辑变成了这样

useCount.ts
page.tsx
1
function useCount() {
2
const [count, setCount] = useState(0)
3
return { count, setCount }
4
}

2、案例:获取鼠标位置

在前面的章节中,我们使用 render props 的方式,通过封装了一个 Mouse 组件的方式,来提取了获取鼠标位置信息的能力,在现在的主流开发中,我们应该使用自定义 hook 来实现这个功能

预览

System Cursor Tracker

Axis_X0000
Axis_Y0000
Live Feed / Realtime
index.tsx
mouse.tsx
01
import useMouse from './mouse'
02
03
export default function App() {
04
const { x, y } = useMouse()
05
return (
06
<div className='flex items-center justify-center p-4'>
07
<div className='relative group'>
08
<div className='p-8'>
09
<header className='flex items-center gap-3 mb-8'>
10
<div className='w-2 h-2 bg-indigo-500' />
11
<h1 className='text-[10px] uppercase tracking-[0.4em] font-bold text-slate-400 dark:text-slate-500'>
12
System Cursor Tracker
13
</h1>
14
</header>
15
16
<div className='space-y-6'>
17
<div className='flex items-baseline justify-between border-b border-slate-100 dark:border-slate-900 pb-2'>
18
<span className='text-[10px] font-mono text-slate-400 uppercase tracking-widest'>Axis_X</span>
19
<span className='text-4xl font-black text-slate-900 dark:text-white font-mono tabular-nums tracking-tighter'>
20
{x.toString().padStart(4, '0')}
21
</span>
22
</div>
23
24
<div className='flex items-baseline justify-between border-b border-slate-100 dark:border-slate-900 pb-2'>
25
<span className='text-[10px] font-mono text-slate-400 uppercase tracking-widest'>Axis_Y</span>
26
<span className='text-4xl font-black text-slate-900 dark:text-white font-mono tabular-nums tracking-tighter'>
27
{y.toString().padStart(4, '0')}
28
</span>
29
</div>
30
</div>
31
32
<footer className='mt-8 pt-4 border-t border-slate-50 dark:border-slate-900 flex justify-between'>
33
<span className='text-[9px] text-slate-300 dark:text-slate-700 uppercase tracking-widest italic'>
34
Live Feed / Realtime
35
</span>
36
<div className='flex gap-1'>
37
<div className='w-1 h-1 bg-slate-200 dark:bg-slate-800' />
38
<div className='w-1 h-1 bg-slate-200 dark:bg-slate-800' />
39
<div className='w-1 h-1 bg-indigo-500 animate-pulse' />
40
</div>
41
</footer>
42
</div>
43
</div>
44
</div>
45
)
46
}

3、案例:待办事项列表

我们也可以把前面的待办事项列表,通过自定义 hook 重构,通过代码学习

预览
待办事项列表
已完成 1 / 3 项任务
学习 React useState
完成项目文档
准备技术分享
数组状态统计:
Total Items03
Completed01
Pending02
Finish Rate33%
index.tsx
useTodos.ts
TodoHeader.tsx
TodoList.tsx
TodoItem.tsx
TodoInput.tsx
TodoStats.tsx
01
import TodoHeader from './TodoHeader'
02
import TodoInput from './TodoInput'
03
import TodoList from './TodoList'
04
import TodoStats from './TodoStats'
05
import useTodos from './useTodos'
06
07
export default function ArrayState() {
08
const {
09
todos,
10
newTodo,
11
editingId,
12
editingText,
13
addTodo,
14
deleteTodo,
15
toggleComplete,
16
startEditing,
17
saveEdit,
18
cancelEdit, setNewTodo, setEditingText,
19
} = useTodos()
20
21
const handleKeyPress = (e: React.KeyboardEvent) => {
22
if (e.key === 'Enter') {
23
addTodo()
24
}
25
}
26
27
const completedCount = todos.filter(todo => todo.completed).length
28
const totalCount = todos.length
29
30
return (
31
<div className="flex flex-col gap-4 p-4">
32
<TodoHeader
33
completedCount={completedCount}
34
totalCount={totalCount}
35
/>
36
37
<TodoInput
38
value={newTodo}
39
onChange={setNewTodo}
40
onAdd={addTodo}
41
onKeyPress={handleKeyPress}
42
/>
43
44
<TodoList
45
todos={todos}
46
editingId={editingId}
47
editingText={editingText}
48
onToggleComplete={toggleComplete}
49
onDelete={deleteTodo}
50
onStartEdit={startEditing}
51
onSaveEdit={saveEdit}
52
onCancelEdit={cancelEdit}
53
onEditingTextChange={setEditingText}
54
/>
55
56
<TodoStats
57
totalCount={totalCount}
58
completedCount={completedCount}
59
/>
60
</div>
61
)
62
}

3、总结

在使用自定义 hook 时,我们需要注意一下几点

  • 为了区分自定义 hook 和普通函数,我们通常会在函数名前面加上 use 前缀
  • 自定义 hook 是对 useStateuseEffect 等 React Hook 的封装,所以它必须运用于组件函数内部
  • 自定义 hook 是对重复逻辑的封装,他的主要目的是逻辑复用

由于自定义 hook 在逻辑复用上的便利性,我们在后续的学习过程中,还会大量的使用自定义 hook,例如我们在前面章节学到的开关思维,就是自定义 hook 的典型应用

专栏首页
到顶
专栏目录