React实战_实现待办事项TodoList(Hook版)

Posted 一只前端小马甲

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了React实战_实现待办事项TodoList(Hook版)相关的知识,希望对你有一定的参考价值。

企业级产品的开发流程中,产品经理将表达交互的原型图给设计师,设计师最终将UI图提供给工程师,工程师此时需要将UI图分解,逆推需要实现的功能。如何合理分解UI图,正是本系列文章的核心。通过合理地分解UI图,确定组件功能的界限,帮助工程师探索和养成自己的React编程最佳实践。

目录

一、功能分析

二、数据考虑

三、组件拆分

四、目录结构

五、组件实现

AddTodo组件

TodoList组件

TodoItem组件

Todos组件

Filter组件

Link组件

六、总结


一、功能分析

图一 TodoList设计图

图一是TodoList设计图,从界面上看分“上、中、下”三个部分:

  • 上:新增待办
  • 中:待办列表
  • 下:改变列表分类
图二 TodoList功能分析

 

二、数据分析

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

let list = [

    // 待办事项JS对象
, 
, 
...];

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

let plan = 
    id: 0,            // 唯一标识
    name: 'xxx',      // 待办事项名称
    completed: false  // 待办事项完成状态

当然,我们需要一个全局状态filter保存“下”部分中选择的类型,便于“中”部分显示相应类型的列表数据。

最终得到组件状态的数据结构如下,这个状态可以考虑放在组件顶层

let state = 
  list =[
    
      id: 0,            // 唯一标识
      name: 'xxx',      // 待办事项名称
      completed: false  // 待办事项完成状态
    ,
    ,
    ...],

  filter: 'all'
;

 

三、组件拆分

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

  • AddTodo组件:对应界面“上”部分
  • TodoList组件:对应界面“中”部分
  • Filter组件:对应界面“下”部分

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

图三 组件拆分

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

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

Filter组件,可以显示当前选中类型filter,因此有个状态filter保存数据;可以改变选中类型filter,因此有个方法去执行这个改变。

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

可以看到,显示功能必定对应状态,改变功能必定对应方法,这里涉及组件React组件设计方法,可以参考笔者另一篇博文《React实战_如何设计高质量组件》。

 

四、目录结构

filter文件夹下存放Filter组件相关文件,具有views文件夹和index.js文件。

todos文件夹下存放Todos组件相关文件,具有views文件夹和index.js文件。

views文件夹下存放更细粒度的组件,index.js文件作为组件模块的统一入口(目前仅输出views,后期可扩展输出状态)。

图四 目录结构

五、组件实现

第二部分中说过,将组件的状态统一放在顶层,通过props将传递给子组件。根据“分而治之”的思想,我们并不是将整个state都放在根组件,而是将list放在Todos组件,filter放在Filter组件分别管理,这样能避免混乱。

AddTodo组件

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

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>
    )

TodoList组件

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

import React from 'react';
import TodoItem from './todo-item';
import  FilterTypes  from '../../constants';

export default function TodoList( list, onClick, filter ) 


    return (
        <ul>
            
                list.filter(item =>                         // 根据filter过滤不符合的类型
                    if (filter === FilterTypes.COMPLETED) 
                        return item.completed
                     else if (filter === FilterTypes.UNCOMPLETED) 
                        return !item.completed
                     else 
                        return item
                    
                ).map(item => <TodoItem                    // 渲染符合条件的TodoItem
                    key=item.id
                    text=item.text
                    onClick=() => onClick(item.id)
                    completed=item.completed />)
            
        </ul>
    )

TodoItem组件

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

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>
    )

Todos组件

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

import React,  useState  from 'react';
import deepcopy from 'deepcopy';

import AddTodo from './add-todo';
import TodoList from './todo-list';

export default function ( filter ) 
    // 整个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 filter=filter onClick=clickHandler />
        </div>
    )

Filter组件

Filter组件具有显示和改变filter类型的功能,我们可以在TodoApp根组件创建方法setFilter,将其作为props传递给Filter组件,Filter以及子组件均为“受控组件”。显然Filter依赖filter做渲染,也能通过props从父组件获取。

import React from 'react';
import Link from './link';
import  FilterTypes  from '../../constants';

const types = Object.keys(FilterTypes);

export default function Filter( filter, setFilter ) 

    return (
        <div style= display: 'flex' >
            
                types.map(
                    type =>
                        <Link
                            key=type
                            type=type
                            filter=filter
                            onClick=setFilter />)
            
        </div>
    )

Link组件

Link组件具有显示当前状态功能,其状态完全由父组件控制,可以通过props从父组件获取filter。

import React from 'react';
import  FilterTypes  from '../../constants';

export default function Link( filter, onClick, type ) 
    return (
        <div
            className=filter === FilterTypes[type] ? 'link link--active' : 'link'
            onClick=() => onClick(FilterTypes[type])>
            type
        </div>
    )

六、总结

本文主要介绍了如何通过React实现,完全基于函数式组件的待办事项TodoList。假设工程师的起点都是拿到UI图,本文对TodoList的UI图进行了功能分析、数据分析,在此基础上进行了组件拆分,定下了初步的目录结构,最终按照之前的分析工作实现各个粒度的组件,大的组件都封装成一个模块,由统一的index.js文件暴露给调用者,保证了组件的高内聚、低耦合。

以上是关于React实战_实现待办事项TodoList(Hook版)的主要内容,如果未能解决你的问题,请参考以下文章

jQuery模仿ToDoList实现简单的待办事项列表

第七讲、Vue3.x 实现一个完整的toDoList(待办事项)

React - 在我的待办事项应用程序中没有调用 mapStateToProps 函数

HTML+CSS+JavaScript实现待办事项(纯DOM实现)

无法在初始渲染中显示待办事项列表

vue todolist待办事项完整