react入门项目——Todo List(内附完整代码)

575 阅读3分钟

前言

很多人在学习完react的相关部分知识后,没有一个好的项目可以实操学习,下面,我给大家推荐一个适合有react基础的入门项目————Todo List。

image.png 实现效果如下。

项目实操

创建todolist项目

首先在vscode打开一个文件夹,并在系统终端打开,运行 npm init vite 命令:

  • 如果你未安装vite,它会询问你是否想要继续安装必要的包,根据提示进行安装;
  • 如果已安装,输入项目名称为todolist;
  • 选择React框架;
  • 选择纯JavaScript类型;
  • 结束后,输入cd todolist,跳转至todolist文件夹下;
  • 输入npm i(npm 会读取 package.json 文件中的 dependenciesdevDependencies 字段,然后安装列出的所有包,安装在项目根目录下的 node_modules 文件夹中);
  • 最后输入npm run dev启动前端项目。

文件结构

image.png

在todolist文件夹的src文件夹下,创建一个utils文件用于存放storage.js,再在src下创建如图所示的部分文件用于后续编程。

项目代码

按照上图所示目录结构,将需要修改和添加的代码按文件顺序贴至下方,部分不需更改的未在下方展示。

utils>storage.js

/**
 * @func 基于localStorage封装的Storage类,单例模式
 * @author jj
 -* @date 2024-7-4
 */

// 语法糖
class Storage {
    // static instance;
    static getInstance() {
        // JS 动态 static 属性
        // JS 没有类,都是对象
        if (!Storage.instance) {
            Storage.instance = new Storage();
        }
        return Storage.instance;
    }

    getItem(key) {
        return localStorage.getItem(key);
    }
    setItem(key, value) {
        localStorage.setItem(key, value);
    }
}   

// new Storage();

export default Storage;

App.css

.todo-app {
  text-align: center;
  margin: 0 auto;
  max-width: 400px;
}
.todo-app__title {
  font-size: 2em;
  margin: 20px 0;
}

App.jsx

import { Component } from 'react'
import TodoForm from './TodoForm.jsx'
import TodoList from './TodoList.jsx'
import './App.css'
import Storage from './utils/storage.js'

const instance = Storage.getInstance();

class App extends Component {
  constructor(props) {
    super(props)
    const savedTodos = JSON.parse(localStorage.getItem('todos')) || []

    this.state = {
      todos: savedTodos
    }
  }

  componentDidUpdate() {
    instance.setItem('todos', JSON.stringify(this.state.todos))
  }

  addTodo =(text) => {
    this.setState({
      todos: [
        ...this.state.todos,
        {
          text,
          completed: false
        }
      ]
        })

  }

  deleteTodo = (index) => {
    // focus 数据,不再理底层的API 
    const newTodos = [...this.state.todos]
    newTodos.splice(index, 1)
    this.setState({
      todos: newTodos
    })
  }

  toggleTodo = (index) => {
    const newTodos = [...this.state.todos]
    newTodos[index].completed = !newTodos[index].completed
    this.setState({
      todos: newTodos
    })
  }

  editTodo = (index, newText) => {
    const newTodos = [...this.state.todos]
    newTodos[index].text = newText
    this.setState({
      todos: newTodos
    })
  }

  render() {
    const { todos } = this.state
    return (
      <div className="todo-app">
        <h1 className="todo-app__title">Todo List</h1>
        <TodoForm addTodo={this.addTodo}/>
        <TodoList 
          todos = {todos}
          toggleTodo = {this.toggleTodo}
          deleteTodo = {this.deleteTodo}
          editTodo = {this.editTodo}
        />
      </div>
    )
  }
}
export default App

index.css

/* reset 通用样式  */
body, div, dl, dt, dd, ul, li, h1, h2, h3, h4, h5, h6, pre, code, form, fieldset, legend, input, button, textarea, blockquote {
  margin: 0;
  padding: 0;
}

TodoForm.jsx

import { Component  } from "react"; 
import './TodoForm.css'
class TodoForm extends Component {

    constructor(props) {
        super(props)
        // 私有
        this.state = {
            inputText: ''
        }
    }

    handleChange = (event) => {
        this.setState({
            inputText: event.target.value
        })
    }

    handleSubmit = (e) => {
        e.preventDefault()
        if (this.state.inputText.trim()) {
            this.props.addTodo(this.state.inputText)
            this.setState({
                inputText: ''
            })
        }
    }

    render() {
        return (
            <form className="todoo-Form" 
            onSubmit={this.handleSubmit}>
                <input
                    type="text" 
                    value= {this.state.inputText}
                    className="todo-form__input"
                    onChange={this.handleChange}
                />
                <button type="submit"
                className="todo-form__button">Add</button>
            </form>
        )
    }
}

export default TodoForm

TodoItem.css

.todo-item {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 10px;
    border-bottom: 1px solid #ccc;
}

.todo-item_completed .todo-item__text {
    text-decoration: line-through;
    color: #888;
}
.todo-item__text {
    cursor:pointer
}

.todo-item__edit-input {
    flex: 1;
    margin-right: 10px;
}

.todo-item__edit-btn,
.todo-item__delete-btn,
.todo-item__save-btn {
    margin-left: 5px;
    cursor: pointer;
} 

TodoItem.jsx

import { Component } from "react";
import './TodoItem.css'
class TodoItem extends Component {
    constructor(props) {
        super(props)
        this.state = {
            isEditing: false,
            editText: this.props.todo.text
        }
    }

    handleEditChange = (e) => {
        this.setState({
            editText: e.target.value
        })
    }

    handleEditSave = (e) => {
        this.props.editTodo(this.props.index, this.state.editText)
        this.setState({
            isEditing: false
        })
    }

    render() {
        const {
            todo,
            index,
            toggleTodo,
            deleteTodo
        } = this.props
        const { isEditing, editText } = this.state
        const { text, completed } = todo
        // JS 运行区域
        return (
            <li className={`todo-item ${completed ? 'todo-item_completed' : ''}`}>
                {isEditing ? (
                    <div>
                        <input
                            type="text"
                            value={editText}
                            onChange={this.handleEditChange}
                            className="todo-item__edit-input"
                        />
                        <button 
                        className="todo-item__save-btn"
                        onClick={this.handleEditSave}
                        >Save</button>
                    </div>
                ) : <div>
                    <span className="todo-item__text"
                        onClick={() => toggleTodo(index)}>
                        {text}
                        {completed ? '✅' : '❌'}
                    </span>
                    <button
                        onClick={() => this.setState({ isEditing: true })}
                        className="todo-item__edit-btn"
                    >编辑</button>
                    <button
                        className="todo-item__delete"
                        onClick={() => deleteTodo(index)}>删除</button>
                </div>}

            </li>
        )
    }
}

export default TodoItem

TodoList.css

.todo-list {
    list-style: none;
    padding: 0;
}

TodoList.jsx

import { Component } from "react";
import TodoItem from "./TodoItem";
import './TodoList.css'
// 容器组件
class TodoList extends Component {
    render() {
        const {
            todos,
            deleteTodo,
            toggleTodo,
            editTodo
         } = this.props;
        
        return (
            <ul className="todo-list">
                {
                todos.map((todo, index) => (
                    <TodoItem 
                        key={index}
                        index={index}
                        todo={todo}
                        deleteTodo={deleteTodo}
                        toggleTodo={toggleTodo}
                        editTodo={editTodo}
                    />
                ))}
            </ul>

        )
    }
}
export default TodoList;