前端实现-使用React实现简易todolist | 豆包MarsCode AI 刷题

41 阅读12分钟

React实现代办事项步骤

步骤一:创建项目

npx create-react-app react01 创建项目
cd react01 
npm start  启动项目

步骤二:构建App.js和TodoList.js组件

我们将用其实现管理待办事项的添加、编辑和删除操作。在React中,组件是构建UI的基本单元。在这个应用中,我们将创建组件,用于管理待办事项的状态,并将其显示在UI上。

步骤三:项目代码

import TodoList from './component/TodoList'
import React from 'react'
function App() {
 
  return (
    <div>
      <TodoList/>
    </div>

  );
}
export default App;`
```js
import React, { useState } from 'react';
const TodoList = () => {
    const [todos, setTodos] = useState([]);
    const [newTodo, setNewTodo] = useState('');
    const [editIndex, setEditIndex] = useState(null);
    const [editText, setEditText] = useState('');
   //添加
    const handleAddTodo = () => {
        if (newTodo.trim()) {
            setTodos([...todos, newTodo]);
            setNewTodo('');
        }
    };
    //删除
    const handleDeleteTodo = (index) => {
        const updatedTodos = [...todos];
        updatedTodos.splice(index, 1);
        setTodos(updatedTodos);
    };
    //编辑
    const handleEditTodo = (index) => {
        setEditIndex(index);
        setEditText(todos[index]);
    };
    //保存
    const handleSaveTodo = () => {
        const updatedTodos = [...todos];
        updatedTodos[editIndex] = editText;
        setTodos(updatedTodos);
        setEditIndex(null);
        setEditText('');
    };

    const handleChange = (event) => {
        setNewTodo(event.target.value);
    };

    const handleEditChange = (event) => {
        setEditText(event.target.value);
    };

    return (
        <div style={{ display: 'flex', alignItems: 'center', flexDirection: 'column', }}>
            <h1>TodoList</h1>
            <div>
                <input
                    type="text"
                    value={newTodo}
                    onChange={handleChange}
                    placeholder="输入代办事项"
                    style={{
                        width: '200px',
                        padding: '8px',
                        fontSize: '16px',
                        border: '1px solid purple',
                        borderRadius: '4px'
                    }}
                />
                <button
                    onClick={handleAddTodo}
                    style={{
                        width: '80px',
                        height: '36px',
                        backgroundColor: '#C7B8E4', 
                        color: 'white',
                        border: 'none',
                        marginLeft: '10px',
                        borderRadius: '5px',
                        cursor: 'pointer',
                        fontSize: '18px',
                    }}
                >添加</button>
            </div>
            <ul style={{ listStyleType: 'none', }}>
                {todos.map((todo, index) => (
                    <li key={index} style={{ borderRadius: '4px', padding: '10px', border: '1px solid #ddd', width: '300px', fontSize: '18px' }}>
                        {editIndex === index ? (
                            <>
                                <input
                                    type="text"
                                    value={editText}
                                    onChange={handleEditChange}
                                    style={{
                                        width: '200px',
                                        height: '30px',
                                        fontSize: '16px',
                                        border: '1px solid purple',
                                        borderRadius: '4px'
                                    }}
                                />
                                <button
                                    onClick={handleSaveTodo}
                                    style={{
                                        marginTop: '10px',
                                        backgroundColor: '#C7B8E4',
                                        color: 'white', border: 'none',
                                        borderRadius: '5px',
                                        cursor: 'pointer',
                                        fontSize: '16px',
                                        width: '60px',
                                        height: '30px',
                                        marginLeft: '10px'
                                    }}
                                >保存</button>
                            </>
                        ) : (
                            <>
                                {todo}
                                <button onClick={() => handleEditTodo(index)}
                                    style={{
                                        marginTop: '10px',
                                        marginLeft: '20px',
                                        backgroundColor: '#C7B8E4',
                                        color: 'white', border: 'none',
                                        borderRadius: '5px',
                                        cursor: 'pointer',
                                        fontSize: '16px',
                                        width: '60px',
                                        height: '30px',
                                    }}
                                >编辑</button>
                                <button onClick={() => handleDeleteTodo(index)}
                                    style={{
                                        marginTop: '10px',
                                        marginLeft: '20px',
                                        backgroundColor: '#C7B8E4',
                                        color: 'white', border: 'none',
                                        borderRadius: '5px',
                                        cursor: 'pointer',
                                        fontSize: '16px',
                                        width: '60px',
                                        height: '30px',
                                    }}
                                >删除</button>
                            </>
                        )}
                    </li>
                ))}
            </ul>
        </div>
    );
};

export default TodoList;

步骤四:效果显示

image.png

image.png

image.png

image.png

image.png

image.png

思路分析

1. 状态管理
  • todos:用于存储所有待办事项的数组。
  • newTodo:用于存储用户输入的新待办事项的文本。
  • editIndex:用于存储当前正在编辑的待办事项的索引。
  • editText:用于存储当前正在编辑的待办事项的文本。
2. 添加待办事项
  • 用户在输入框中输入新的待办事项,并点击“添加”按钮。
  • handleAddTodo 函数被调用,检查输入框的内容是否为空。
  • 如果不为空,则将新待办事项添加到 todos 数组中,并清空输入框内容。
3. 编辑待办事项
  • 用户点击某个待办事项旁边的“编辑”按钮。
  • handleEditTodo 函数被调用,设置当前编辑的待办事项的索引和文本。
  • 显示一个输入框供用户编辑待办事项内容,并提供一个“保存”按钮。
4. 保存编辑后的待办事项
  • 用户在输入框中编辑完待办事项后,点击“保存”按钮。
  • handleSaveTodo 函数被调用,将编辑后的待办事项更新到 todos 数组中,并退出编辑模式。
5. 删除待办事项
  • 用户点击某个待办事项旁边的“删除”按钮。
  • handleDeleteTodo 函数被调用,根据传入的索引从 todos 数组中删除相应的待办事项。
6. 渲染待办事项列表
  • 使用 map 方法遍历 todos 数组,渲染每个待办事项。
  • 如果当前待办事项处于编辑状态,则显示一个输入框和保存按钮。
  • 否则,显示待办事项文本和编辑/删除按钮。

引入CSS样式方式

1. 使用内联样式

直接在JSX元素中使用style属性定义样式。这种方法适用于简单的样式或需要动态计算样式的场景。

function MyComponent() {
  const styles = {
    backgroundColor: 'purple',
    color: 'white',
    padding: '10px',
    borderRadius: '5px'
  };

  return <div style={styles}>Hello, World!</div>;
}
Copy

2. 使用CSS模块

CSS模块允许你创建局部作用域的CSS文件,并自动为类名添加唯一哈希值,避免全局命名冲突。适合大型项目,以提高可维护性和减少样式冲突。

创建CSS模块文件

例如,创建一个名为MyComponent.module.css的文件:

/* MyComponent.module.css */
.container {
  background-color: purple;
  color: white;
  padding: 10px;
  border-radius: 5px;
}
在React组件中引用CSS模块
import React from 'react';
import styles from './MyComponent.module.css';

function MyComponent() {
  return (
    <div className={styles.container}>
      Hello, World!
    </div>
  );
}

3. 使用全局CSS文件

将样式写在一个全局的CSS文件中,并通过import语句引入到React组件中。这种方法适用于全局样式或不需要局部作用域的样式。

创建全局CSS文件

例如,创建一个名为globalStyles.css的文件:

/* globalStyles.css */
.container {
  background-color: purple;
  color: white;
  padding: 10px;
  border-radius: 5px;
}
在React组件中引用全局CSS文件
import React from 'react';
import './globalStyles.css';

function MyComponent() {
  return (
    <div className="container">
      Hello, World!
    </div>
  );
}

4. 使用CSS-in-JS库

CSS-in-JS库(如styled-components、emotion)允许你在JavaScript中编写CSS样式,并将它们与React组件绑定在一起。这种方法提供了更好的开发体验和更高的灵活性。

使用styled-components

首先安装styled-components库:

npm install styled-components

然后创建一个React组件并使用styled-components:

import React from 'react';
import styled from 'styled-components';

const Container = styled.div`
  background-color: purple;
  color: white;
  padding: 10px;
  border-radius: 5px;
`;

function MyComponent() {
  return (
    <Container>
      Hello, World!
    </Container>
  );
}

涉及到的知识点

1. 状态管理

  • 使用useState钩子来管理应用的状态,如待办事项列表(todos)、新的待办事项文本(newTodo)、编辑状态索引(editIndex)以及编辑中的文本(editText)。

2. 事件处理

  • 通过事件处理器(如handleAddTodohandleDeleteTodohandleEditTodohandleSaveTodo)来响应用户的交互,比如添加、删除、编辑待办事项。

3. 条件渲染

  • 根据editIndex的状态,动态渲染输入框和按钮,允许用户编辑特定的待办事项。

4. 输入处理

  • 使用onChange事件处理器来更新状态,如handleChange用于更新newTodohandleEditChange用于更新editText

5. 列表渲染

  • 使用map函数遍历todos数组,并为每个待办事项生成一个列表项(<li>元素),展示待办事项文本或输入框。

6. 样式

  • 应用了一些基本样式,包括边框、圆角、背景色等,使应用看起来更美观。

7. 组件化思维

  • 尽管这是一个简单的应用,但它体现了组件化的设计思想,即将不同的功能封装在独立的功能模块中,便于维护和扩展。

8. React Hooks

  • 学习了如何使用React Hooks(特别是useState)来管理组件内部的状态,这是现代React开发的重要技能之一。

9. 用户交互

  • 实现了用户交互的基本流程,包括添加新事项、编辑现有事项、删除事项,这对于构建用户友好的应用程序至关重要。

封装组件

1. 组件的基本结构

一个React组件通常由以下几个部分组成:

  • 导入依赖:导入React和其他可能需要的库。
  • 定义组件:定义一个函数组件或类组件。
  • 状态管理:如果组件需要管理状态,使用useStateuseReducer等Hooks。
  • 生命周期方法:对于类组件,可以使用生命周期方法;对于函数组件,可以使用相应的Hooks(如useEffect)。
  • 渲染逻辑:编写JSX来描述组件的UI。

2. 函数组件 vs 类组件

  • 函数组件:简单、易读,适合大多数场景。推荐使用函数组件和Hooks。
  • 类组件:功能强大,但相对复杂。主要用于需要生命周期方法的场景。

3. 使用Hooks

  • useState:用于管理组件的状态。
  • useEffect:用于执行副作用操作,如数据获取、订阅或手动更改DOM。
  • useContext:用于访问上下文。
  • useReducer:用于复杂的状态逻辑,类似于Redux。

4. 组件通信

  • Props:父组件向子组件传递数据和回调函数。
  • 回调函数:子组件可以通过props中的回调函数通知父组件。
  • Context:用于跨层级组件之间的数据共享。
  • Refs:用于访问DOM节点或在组件上存储可变的实例值。

5. 组件拆分

  • 单一职责原则:每个组件只负责一项任务。
  • 高内聚低耦合:确保组件内部紧密相关,而与其他组件保持松散耦合。
  • 复用性:设计可复用的组件,减少重复代码。

比如拿上述待办事项例子,为了更好地组织代码并提高可维护性,我们可以将 TodoList 中的单个待办事项(TodoItem)提取成一个单独的组件。下面是经过拆分后的代码:

TodoItem 组件

这个组件负责渲染单个待办事项,并处理编辑和删除操作。

import React from 'react';

const TodoItem = ({ todo, onEdit, onDelete }) => {
    const [isEditing, setIsEditing] = React.useState(false);
    const [editedText, setEditedText] = React.useState(todo);

    const handleEdit = () => {
        setIsEditing(true);
    };

    const handleSave = () => {
        onEdit(editedText);
        setIsEditing(false);
    };

    const handleDelete = () => {
        onDelete();
    };

    return (
        <li style={{ borderRadius: '4px', padding: '10px', border: '1px solid #ddd', width: '300px', fontSize: '18px' }}>
            {isEditing ? (
                <>
                    <input
                        type="text"
                        value={editedText}
                        onChange={(e) => setEditedText(e.target.value)}
                        style={{
                            width: '200px',
                            height: '30px',
                            fontSize: '16px',
                            border: '1px solid purple',
                            borderRadius: '4px'
                        }}
                    />
                    <button
                        onClick={handleSave}
                        style={{
                            marginTop: '10px',
                            backgroundColor: '#C7B8E4',
                            color: 'white',
                            border: 'none',
                            borderRadius: '5px',
                            cursor: 'pointer',
                            fontSize: '16px',
                            width: '60px',
                            height: '30px',
                            marginLeft: '10px'
                        }}
                    >保存</button>
                </>
            ) : (
                <>
                    {todo}
                    <button onClick={handleEdit}
                        style={{
                            marginTop: '10px',
                            marginLeft: '20px',
                            backgroundColor: '#C7B8E4',
                            color: 'white',
                            border: 'none',
                            borderRadius: '5px',
                            cursor: 'pointer',
                            fontSize: '16px',
                            width: '60px',
                            height: '30px',
                        }}
                    >编辑</button>
                    <button onClick={handleDelete}
                        style={{
                            marginTop: '10px',
                            marginLeft: '20px',
                            backgroundColor: '#C7B8E4',
                            color: 'white',
                            border: 'none',
                            borderRadius: '5px',
                            cursor: 'pointer',
                            fontSize: '16px',
                            width: '60px',
                            height: '30px',
                        }}
                    >删除</button>
                </>
            )}
        </li>
    );
};

export default TodoItem;
Copy

TodoList 组件

这个组件负责管理所有待办事项的状态,并将单个待办事项渲染到 TodoItem 组件中。

import React, { useState } from 'react';
import TodoItem from './TodoItem'; // 假设TodoItem组件在这个路径下

const TodoList = () => {
    const [todos, setTodos] = useState([]);
    const [newTodo, setNewTodo] = useState('');

    const handleAddTodo = () => {
        if (newTodo.trim()) {
            setTodos([...todos, newTodo]);
            setNewTodo('');
        }
    };

    const handleDeleteTodo = (index) => {
        const updatedTodos = [...todos];
        updatedTodos.splice(index, 1);
        setTodos(updatedTodos);
    };

    const handleEditTodo = (index, newText) => {
        const updatedTodos = [...todos];
        updatedTodos[index] = newText;
        setTodos(updatedTodos);
    };

    const handleChange = (event) => {
        setNewTodo(event.target.value);
    };

    return (
        <div style={{ display: 'flex', alignItems: 'center', flexDirection: 'column', }}>
            <h1>TodoList</h1>
            <div>
                <input
                    type="text"
                    value={newTodo}
                    onChange={handleChange}
                    placeholder="输入代办事项"
                    style={{
                        width: '200px',
                        padding: '8px',
                        fontSize: '16px',
                        border: '1px solid purple',
                        borderRadius: '4px'
                    }}
                />
                <button
                    onClick={handleAddTodo}
                    style={{
                        width: '80px',
                        height: '36px',
                        backgroundColor: '#C7B8E4', // 浅紫色
                        color: 'white',
                        border: 'none',
                        marginLeft: '10px',
                        borderRadius: '5px',
                        cursor: 'pointer',
                        fontSize: '18px',
                    }}
                >添加</button>
            </div>
            <ul style={{ listStyleType: 'none', }}>
                {todos.map((todo, index) => (
                    <TodoItem
                        key={index}
                        todo={todo}
                        onEdit={(text) => handleEditTodo(index, text)}
                        onDelete={() => handleDeleteTodo(index)}
                    />
                ))}
            </ul>
        </div>
    );
};

export default TodoList;

6. 样式管理

  • 内联样式:直接在JSX中定义样式。
  • CSS Modules:为每个组件生成唯一的类名。
  • Styled Components:使用模板字符串定义样式,支持动态样式。

7. 组件优化

  • 性能优化:使用React.memouseMemo来避免不必要的渲染。
  • 懒加载:使用动态导入(import())来懒加载组件。
  • 虚拟列表:对于大量列表项,使用虚拟化库(如react-virtualized)来提高性能。

总结

通过实现一个简单的待办事项应用,我不仅加深了对React核心概念的理解,还在实践中掌握了如何构建一个具有交互性的前端应用。以下是我在这次实现过程中获得的一些具体经验和技能提升:

1. 组件化设计

  • 组件拆分:我学会了如何将复杂的功能分解成多个小组件,每个组件只关注单一功能。例如,TodoList组件负责展示所有待办事项,而TodoItem组件则负责单个待办事项的显示和操作。
  • 组件复用:通过组件化设计,我可以轻松地复用组件,从而提高开发效率并保持代码的一致性。

2. 状态管理

  • useState:我深入理解了如何使用useState钩子来管理组件内部的状态。例如,TodoList组件通过useState来存储待办事项列表,同时在添加、删除和切换状态时更新该状态。
  • 状态更新:我还了解到状态更新并不是立即执行的,而是异步的,这有助于避免不必要的渲染和性能问题。

3. 数据流与事件处理

  • Props传递:通过props,我能够将数据和回调函数从父组件传递到子组件。例如,TodoList组件通过props将待办事项列表传递给TodoItem组件,并将事件处理函数传递给子组件。
  • 事件处理:我学会了如何处理用户的点击、输入等事件,并通过回调函数将事件信息传递回父组件。例如,当用户点击“删除”按钮时,会触发onDeleteTodo回调函数,进而更新父组件中的状态。

4. 用户界面与交互

  • 条件渲染:我学会了根据状态的不同来条件性地渲染不同的UI元素。例如,可以根据待办事项是否完成来改变其样式或显示不同的图标。
  • 用户输入:通过处理用户输入,我实现了动态添加新的待办事项。例如,用户可以在文本框中输入新的待办事项,然后点击“添加”按钮将其添加到列表中。

5. 性能优化

  • 避免不必要的渲染:通过合理使用React.memo等优化手段,我减少了不必要的组件渲染,提高了应用性能。
  • 懒加载:虽然在这个简单的示例中没有直接涉及,但在实际项目中,我会考虑使用懒加载技术来优化应用的加载速度。

6. 代码组织与维护

  • 文件结构:我学会了如何合理地组织文件和目录结构,使代码更加清晰和易于维护。例如,将不同功能相关的组件放在同一个文件夹下。
  • 命名约定:通过选择合理的变量名和函数名,我提高了代码的可读性和可维护性。

7. 整体项目体验

通过这个待办事项应用的实现,我不仅巩固了对React核心概念的理解,还提升了整体的前端开发能力。例如,我学会了如何从零开始构建一个完整的应用,并且在过程中不断迭代和完善功能。

这次实践让我深刻认识到,React不仅仅是一种技术框架,更是一种思维方式。它强调组件化设计、状态管理和数据流控制,使得前端开发变得更加模块化和高效。通过不断实践和优化,我有信心在未来开发更加复杂和功能丰富的Web应用。