React实战_实现待办事项TodoList(Hook版)
Posted 一只前端小马甲
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了React实战_实现待办事项TodoList(Hook版)相关的知识,希望对你有一定的参考价值。
企业级产品的开发流程中,产品经理将表达交互的原型图给设计师,设计师最终将UI图提供给工程师,工程师此时需要将UI图分解,逆推需要实现的功能。如何合理分解UI图,正是本系列文章的核心。通过合理地分解UI图,确定组件功能的界限,帮助工程师探索和养成自己的React编程最佳实践。
目录
一、功能分析
图一是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 </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版)的主要内容,如果未能解决你的问题,请参考以下文章
第七讲、Vue3.x 实现一个完整的toDoList(待办事项)
React - 在我的待办事项应用程序中没有调用 mapStateToProps 函数