下面创建一个使用Vite构建的React+TypeScript项目,并演示useState
、useEffect
、useRef
和useCallback
hooks的使用。
步骤1:创建Vite React+TypeScript项目
首先,让我们使用Vite创建一个新的React+TypeScript项目。在命令行中执行以下命令:
npm create vite@latest . --template react-ts
当提示时,选择React和TypeScript作为模板。
步骤2:安装依赖
创建项目后,安装依赖:
npm install
步骤3:创建Hooks示例组件
现在,让我们创建一个演示各种hooks的组件。首先,创建一个新文件src/components/HooksDemo.tsx
:
import { useState, useEffect, useRef, useCallback } from 'react';
interface Todo {
id: number;
text: string;
completed: boolean;
}
const HooksDemo: React.FC = () => {
// useState示例:管理待办事项列表
const [todos, setTodos] = useState<Todo[]>([]);
const [inputText, setInputText] = useState<string>('');
// useRef示例:引用DOM元素和保存不触发重渲染的值
const inputRef = useRef<HTMLInputElement>(null);
const todoIdCounter = useRef<number>(0);
// useEffect示例:处理副作用
useEffect(() => {
// 组件挂载时,聚焦输入框
if (inputRef.current) {
inputRef.current.focus();
}
// 从localStorage加载待办事项
const savedTodos = localStorage.getItem('todos');
if (savedTodos) {
setTodos(JSON.parse(savedTodos));
// 设置计数器为最大ID+1
const maxId = JSON.parse(savedTodos).reduce(
(max: number, todo: Todo) => Math.max(max, todo.id), 0
);
todoIdCounter.current = maxId + 1;
}
// 组件卸载时保存待办事项
return () => {
console.log('组件卸载,保存待办事项');
};
}, []); // 空依赖数组表示仅在挂载和卸载时执行
// 当todos变化时保存到localStorage
useEffect(() => {
localStorage.setItem('todos', JSON.stringify(todos));
}, [todos]); // 依赖于todos,当todos变化时执行
// useCallback示例:记忆函数,避免不必要的重渲染
const addTodo = useCallback(() => {
if (inputText.trim() !== '') {
const newTodo: Todo = {
id: todoIdCounter.current,
text: inputText,
completed: false
};
todoIdCounter.current += 1;
setTodos(prevTodos => [...prevTodos, newTodo]);
setInputText('');
}
}, [inputText]); // 依赖于inputText,当inputText变化时重新创建函数
const toggleTodo = useCallback((id: number) => {
setTodos(prevTodos =>
prevTodos.map(todo =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo
)
);
}, []); // 没有依赖,不会重新创建
const deleteTodo = useCallback((id: number) => {
setTodos(prevTodos => prevTodos.filter(todo => todo.id !== id));
}, []); // 没有依赖,不会重新创建
return (
<div className="hooks-demo">
<h1>React Hooks 示例</h1>
<div className="input-group">
<input
ref={inputRef}
type="text"
value={inputText}
onChange={(e) => setInputText(e.target.value)}
onKeyPress={(e) => e.key === 'Enter' && addTodo()}
placeholder="添加新待办事项..."
/>
<button onClick={addTodo}>添加</button>
</div>
<ul className="todo-list">
{todos.map(todo => (
<li key={todo.id} className={todo.completed ? 'completed' : ''}>
<span onClick={() => toggleTodo(todo.id)}>
{todo.completed ? '✓ ' : '○ '}
{todo.text}
</span>
<button onClick={() => deleteTodo(todo.id)}>删除</button>
</li>
))}
</ul>
<div className="hooks-explanation">
<h2>Hooks 解释</h2>
<p><strong>useState</strong>: 用于管理组件状态,如待办事项列表和输入文本。</p>
<p><strong>useEffect</strong>: 处理副作用,如DOM操作、数据获取和订阅。</p>
<p><strong>useRef</strong>: 引用DOM元素(输入框)和存储不触发重渲染的值(ID计数器)。</p>
<p><strong>useCallback</strong>: 记忆函数定义,避免在每次渲染时创建新函数,优化性能。</p>
</div>
</div>
);
};
export default HooksDemo;
步骤4:修改App.tsx以使用我们的组件
现在,让我们修改src/App.tsx
文件以使用我们的HooksDemo组件:
import HooksDemo from './components/HooksDemo'
import './App.css'
function App() {
return (
<div className="app">
<HooksDemo />
</div>
)
}
export default App
步骤5:添加一些CSS样式
让我们更新src/App.css
文件,添加一些样式:
#root {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
text-align: center;
}
.hooks-demo {
width: 100%;
max-width: 600px;
margin: 0 auto;
}
.input-group {
display: flex;
margin-bottom: 20px;
}
.input-group input {
flex: 1;
padding: 8px;
font-size: 16px;
border: 1px solid #ccc;
border-radius: 4px 0 0 4px;
}
.input-group button {
padding: 8px 16px;
background-color: #646cff;
color: white;
border: none;
border-radius: 0 4px 4px 0;
cursor: pointer;
}
.todo-list {
list-style: none;
padding: 0;
text-align: left;
}
.todo-list li {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px;
margin-bottom: 8px;
background-color: #f9f9f9;
border-radius: 4px;
}
.todo-list li.completed span {
text-decoration: line-through;
color: #888;
}
.todo-list li span {
cursor: pointer;
}
.todo-list li button {
background-color: #ff4d4f;
color: white;
border: none;
border-radius: 4px;
padding: 4px 8px;
cursor: pointer;
}
.hooks-explanation {
margin-top: 40px;
text-align: left;
padding: 20px;
background-color: #f0f0f0;
border-radius: 8px;
}
.hooks-explanation h2 {
margin-top: 0;
}
步骤6:运行项目
现在,您可以运行项目来查看hooks的演示:
npm run dev
各个Hook的作用解释
在这个待办事项应用中,我们演示了四个React hooks的使用:
useState
todos
状态:管理待办事项列表inputText
状态:管理输入框的文本
useEffect
- 第一个useEffect:组件挂载时执行,用于聚焦输入框和从localStorage加载数据
- 第二个useEffect:当todos变化时执行,将数据保存到localStorage
useRef
inputRef
:引用DOM元素(输入框),用于直接操作DOM(如聚焦)todoIdCounter
:存储不触发重渲染的值(ID计数器)
useCallback
addTodo
:记忆添加待办事项的函数,依赖于inputTexttoggleTodo
:记忆切换待办事项完成状态的函数,无依赖deleteTodo
:记忆删除待办事项的函数,无依赖