React Hooks 示例项目

发布于:2025-06-08 ⋅ 阅读:(18) ⋅ 点赞:(0)

下面创建一个使用Vite构建的React+TypeScript项目,并演示useStateuseEffectuseRefuseCallback 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的使用:

  1. useState

    • todos 状态:管理待办事项列表
    • inputText 状态:管理输入框的文本
  2. useEffect

    • 第一个useEffect:组件挂载时执行,用于聚焦输入框和从localStorage加载数据
    • 第二个useEffect:当todos变化时执行,将数据保存到localStorage
  3. useRef

    • inputRef:引用DOM元素(输入框),用于直接操作DOM(如聚焦)
    • todoIdCounter:存储不触发重渲染的值(ID计数器)
  4. useCallback

    • addTodo:记忆添加待办事项的函数,依赖于inputText
    • toggleTodo:记忆切换待办事项完成状态的函数,无依赖
    • deleteTodo:记忆删除待办事项的函数,无依赖

网站公告

今日签到

点亮在社区的每一天
去签到