使用React 实现一个简单的待办事项列表 | 青训营

一、效果展示

从界面上看分"上、下"两个部分:

  • 上:新增待办
  • 下:待办列表

二、数据分析

当"上"部分添加数据后,"下"部分会显示新增的数据,因此考虑使用数组list 保存待办事项数据,具体的每一项是个JS对象

ini 复制代码
let list = [
{
    // 待办事项JS对象
}, 
{}, 
...];

"下"部分会切换显示相应类型的待办事项,因此考虑JS对象 具有completed 字段保存状态,且具有name 字段保存事项名称,以及id作为唯一标识。

csharp 复制代码
let plan = {
    id: 0,            // 唯一标识
    name: 'xxx',      // 待办事项名称
    completed: false  // 待办事项完成状态
}

三、组件拆分

最简单的拆分方式,按照界面可以粗粒度地拆为两部分:

  • AddTodo组件:对应界面"上"部分
  • TodoList组件:对应界面"下"部分

深入一点思考,细化组件的功能:

AddTodo组件,可以改变 列表list,因此有个方法去执行这个改变;

TodoList组件,可以显示 list,因此有个状态 list保存数据;可以改变 列表list,因此有个方法去执行这个改变;

更进一步,AddTodo组件和TodoList组件关联度很高,可以考虑都放在Todos组件下;TodoList组件中的每行看起来都是差不多,可以考虑复用成TodoItem组件;

四、组件实现

AddTodo组件

前面分析过,AddTodo组件具有改变list功能,我们需要创建一个组件内部的方法clickHandler,其中能获取input的值,并执行父组件的方法,用于改变父组件中的list。

javascript 复制代码
import React, { createRef } from 'react';
const inputRef = createRef();                // 使用hook
 
export default function AddTodo({ onAdd }) {
   
    const clickHandler = () => {
        let value = inputRef.current.value.trim();
 
        if (value) {
            // 执行父组件的方法,修改list数据
            onAdd(value);
            // 添加后将input框置空,提高用户体验
            inputRef.current.value = "";
        }
    }
 
    return (
        <React.Fragment>
            <input ref={inputRef} />
            <button onClick={clickHandler}>添加</button>
        </React.Fragment>
    )
}

使用createRef()方法来创建一个ref对象inputRef。组件接受一个名为onAdd的props,用于添加一个新的待办事项。

点击按钮时,clickHandler函数会被调用。它首先从inputRef中获取输入框的值并进行了去除首尾空格的处理。然后判断值是否存在,如果存在则调用传入的onAdd函数,并将值作为参数传递给该函数。同时,将输入框的值置为空字符串,以提高用户体验。

最后,组件返回一个包含一个input和一个按钮的React.Fragment元素。input元素使用了ref属性将inputRef与之关联起来,以便能够在clickHandler函数中访问到input的值。按钮元素的onClick事件绑定了clickHandler函数。

TodoList组件

TodoList组件具有显示特定类型list和改变list功能,我们可以在Todos组件创建方法clickHandler,将其作为props传递给TodoList组件,TodoList以及子组件均为"受控组件"。显然TodoList依赖list做渲染,也能通过props从父组件获取。

javascript 复制代码
import React from 'react';
import TodoItem from './todo-item';
 
export default function TodoList({ list, onClick }) {
 
 
    return (
        <ul>
            {
                list.map(item => <TodoItem                    // 渲染TodoItem
                    key={item.id}
                    text={item.text}
                    onClick={() => onClick(item.id)}
                    completed={item.completed} />)
            }
        </ul>
    )
}

组件接受两个个props:list、onClick。list表示待办事项的数组,onClick表示点击待办事项时要执行的函数。

在返回的JSX代码中,map()方法将每个待办事项映射为一个TodoItem组件。

每个TodoItem组件都有一个唯一的key属性,使用item.id作为key,text属性表示待办事项的文本内容,onClick属性表示点击待办事项时要执行的函数,completed属性表示待办事项是否已完成。

最后,组件返回一个ul元素,其中包含了通过过滤和映射得到的TodoItem组件列表。

TodoItem组件

TodoItem组件是完全受控的组件,它的显示内容完全由props决定,根据UI图我们可以知道它需要text、completed和onClick属性。

javascript 复制代码
import React from 'react';
 
export default function TodoItem({ text, onClick, completed }) {
    return (
        <div onClick={onClick}>
            <span>{text}&nbsp;&nbsp;&nbsp;&nbsp;</span>
            <span>
                完成:
                <input type="checkbox" readOnly checked={completed} />
            </span>
        </div>
    )
}

组件接受三个props:text、onClick和completed。

在返回的JSX代码中,组件被包裹在一个div元素中,并绑定了onClick事件。当用户点击这个div时,会调用传入的onClick函数。

div元素内部包含两个span元素,第一个span元素用于显示待办事项的文本内容,其中 用于插入多个空格以增加文本的间距。

第二个span元素用于表示待办事项的完成状态,包含了一个只读的复选框input元素。复选框的checked属性根据completed的值决定是否被选中。

最后,组件返回这个包含了文本和完成状态的div元素。当用户点击这个div时,会调用传入的onClick函数。

Todos组件

Todos组件相对复杂点,因为list和clickHandler方法都放在里面,它将AddTodo组件和TodoList组件关联了起来。

javascript 复制代码
import React, { useState } from 'react';
import deepcopy from 'deepcopy';
 
import AddTodo from './add-todo';
import TodoList from './todo-list';
 
export default function () {
    // 整个todos组件中的状态list保存在这里
    const [list, setList] = useState([]);
 
    // 传递给AddTodo组件
    const addHandler = (text) => {
        let item = {
            id: (Math.random() * 10000).toFixed(0),
            text: text,
            completed: false
        }
        setList([...list, item]);
    }
    // 传递给能TodoList组件
    const clickHandler = (id) => {
        let newList = deepcopy(list);
        newList.forEach(item => {
            if (item.id === id) {
                item.completed = !item.completed;
            }
        });
        setList(newList);
    }
 
    return (
        <div className="todos">
            {/* 渲染AddTodo组件 */}
            <AddTodo onAdd={addHandler} />
 
            {/* 渲染TodoList组件 */}    
            <TodoList list={list}  onClick={clickHandler} />
        </div>
    )
}

使用了React的useState()钩子,以及导入了deepcopy库。

在组件内部,使用useState()定义了一个名为list的状态变量以及一个名为setList的更新状态的函数。初始状态为空数组。

在组件返回的JSX代码中,渲染了AddTodo组件和TodoList组件。

AddTodo组件中的onAdd属性绑定了addHandler函数,用于添加待办事项。addHandler函数接受一个参数text,根据text创建一个新的待办事项对象item,并将其添加到list状态中。需要通过使用扩展运算符(...)将原来的list数组解构出来,再添加新的item,以确保React能够正确地触发重新渲染。

TodoList组件中的list属性绑定了list状态,onClick属性绑定了clickHandler函数。clickHandler函数用于处理待办事项的点击事件。通过使用deepcopy库创建列表的深拷贝newList,遍历该列表并根据待办事项的id找到对应的项,并将其completed属性取反。最后,使用setList函数更新list状态为新的列表newList。

最后,根组件返回一个包含AddTodo组件和TodoList组件的div元素。

五、总结

总的来说,实现这个业务逻辑不难,但是在实操中就会容易出错。

相关推荐
滑滑滑2 天前
后端实践-优化一个已有的 Go 程序提高其性能 | 豆包MarsCode AI刷题
青训营笔记
柠檬柠檬2 天前
Go 语言入门指南:基础语法和常用特性解析 | 豆包MarsCode AI刷题
青训营笔记
用户967136399652 天前
计算最小步长丨豆包MarsCodeAI刷题
青训营笔记
用户52975799354723 天前
字节跳动青训营刷题笔记2| 豆包MarsCode AI刷题
青训营笔记
clearcold3 天前
浅谈对LangChain中Model I/O的见解 | 豆包MarsCode AI刷题
青训营笔记
夭要7夜宵3 天前
【字节青训营】 Go 进阶语言:并发概述、Goroutine、Channel、协程池 | 豆包MarsCode AI刷题
青训营笔记
用户336901104444 天前
数字分组求和题解 | 豆包MarsCode AI刷题
青训营笔记
dnxb1234 天前
GO语言工程实践课后作业:实现思路、代码以及路径记录 | 豆包MarsCode AI刷题
青训营笔记
用户916357440954 天前
AI刷题-动态规划“DNA序列编辑距离” | 豆包MarsCode AI刷题
青训营笔记
热的棒打鲜橙4 天前
数字分组求偶数和 | 豆包MarsCode AI刷题
青训营笔记