前言
很多人在学习完react的相关部分知识后,没有一个好的项目可以实操学习,下面,我给大家推荐一个适合有react基础的入门项目————Todo List。
实现效果如下。
项目实操
创建todolist项目
首先在vscode打开一个文件夹,并在系统终端打开,运行 npm init vite 命令:
- 如果你未安装vite,它会询问你是否想要继续安装必要的包,根据提示进行安装;
- 如果已安装,输入项目名称为todolist;
- 选择React框架;
- 选择纯JavaScript类型;
- 结束后,输入
cd todolist,跳转至todolist文件夹下; - 输入
npm i(npm会读取package.json文件中的dependencies和devDependencies字段,然后安装列出的所有包,安装在项目根目录下的node_modules文件夹中); - 最后输入
npm run dev启动前端项目。
文件结构
在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;