React练习实例-TodoList
目标
- 顶部输入框中输入任务(字符串),敲击回车键后,中间新出现一个代办项
- 鼠标放在单个代办项,右侧出现删除按钮,点击删除代办项
- 选中多个代办项,点击右下角“清除已完成”按钮,删除所有被选中的待办项
组件设计
除整体App组件外,初步设计为4个组件:
- Header:顶部输入框
- List:中间所有的代办列表
- Item:待办列表中的一项
- Footer:底部显示统计状态信息和“删除所有”按钮所在栏
详细设计
肯定涉及到官方入门程序中“状态提升”的概念:兄弟组件将自己的状态交给父组件管理(自己就变成了一个“受控组件”)
- Header首先要有一个成员变量接收输入的字符串
- 其次要新建一个Item待办项,并将输入串交给它
- 我们还希望同时页面能自动重新渲染,这就涉及到了某个state。具体哪一个呢?
- Footer要同步更新总共的待办项数量
这么看来,应该在App中准备两个变量,嗯…突然想到了这一系列动作其实都是“回车键”触发的,所以其实暂时不用state
但是…手动重新渲染页面吗?…还是用state吧
仔细想,这个
敲下回车->Header获取输入框的内容,交给List(事实上应该是修改了App的一个属性),并清空输入框->List根据获取到的字符串,新建一个Item组件->修改Footer显示的Item数量->重新渲染页面
我该用怎样的数据结构来保存这些Item?需要能够被选中删除,而且不定长
答案是:一个对象数组,但是怎么获取任意对象?
实现
输入并按下回车键,新增一项到待办列表
- 首先(由Header)监听键盘事件,当回车键回弹后触发
<!--为输入框绑定键盘事件--> <input type="text" placeholder="请输入你的任务名称,按回车键确认" onKeyUp={this.handleKeyUp}/>
// 事件处理函数中判断是否是“回车键”以及输入是否为空 handleKeyUp = (e) => { if (e.key !== 'Enter' || e.target.value.trim() === '') return; }
- 因为Header获取的输入需要传递给List,很明显兄弟组件之间的传值,状态提升将变量保存在父组件App中,至少是个数组
但是这里的每一项又是有状态的(比如默认是否选中(完成)),于是这里被定义为一个对象数组
// 为什么这里要放在state中?因为每次回车触发向数组中添加一个对象后,我都希望立即出现在下面的列表中,即页面的重新渲染,state可以方便地做到这一点 constructor(props) { super(props); this.state = { items: [ {id: '1', label: '吃饭', done: true}, {id: '2', label: '睡觉', done: true}, {id: '3', label: '敲代码', done: false} ] } }
- 保存在App状态中的数据改变了还不够,还需要List获取并遍历,构造Item组件
父传子就很简单通过props
// 思考:页面重新渲染,传参也会重新传一遍吗? <List items={items}/>
然后就是List遍历这个数组并构造Item组件,注意这里指定key
其实这里不是很明白怎么回事
return ( <ul className="todo-main"> { items.map((item) => { /*return <Item key={item.id} id={item.id} name={item.name} done={item.done}/>*/ return <Item key={item.id} {...item}/> }) } </ul> )
Item组件需要把对象的信息展示出来
return ( <li className="item"> <label> <input type="checkbox" defaultChecked={done}/> <span>{label}</span> </label> </li> )
- 子组件Header修改父组件state,是通过父组件向子组件传递一个可以修改自己state地函数来做到的
// 这个函数接收一个待办项对象为参数,把他添加到列表数组并更新state触发刷新 // 思考:为什么这里构造了一个新数组而不是直接修改? addItem = (item) => { const {items} = this.state; const newItems = [item, ...items]; this.setState({items: newItems}); }
// 把这个函数交给子组件 <Header addItem={this.addItem}/>
- Header中直接获取的仅仅是一个字符串,我们需要额外的参数构造一个对象并交给父组件提供的方法
nanoid是一个库,提供一个UUID
const item = {id: nanoid(), label: e.target.value, done: false}; this.props.addItem(item); e.target.value = "";
至此,功能点1完成(当然,Footer中的实时统计信息还没做)
鼠标放在单个代办项,右侧出现删除按钮,点击删除代办项
首先是鼠标移至待办项上,待办项的交互响应:
- 背景色改变:这个可以通过 伪类选择器
:hover
轻易实现 - “删除”按钮出现,这个只能绑定监听事件,监听鼠标移入和移出
<li className="item" onMouseEnter={this.handleMouse(true)} onMouseLeave={this.handleMouse(false)}> </li>
处理事件中仅仅是修改了保存在state中的标志位mouseEnter
,当然,这也是为了能够自动渲染
这里双层return是怎么回事?
handleMouse = (flag) => { return () => { this.setState({mouseEnter: flag}); } }
而这个标志位又决定了“删除按钮”是否显示
<button className="btn btn-danger" style={{display: mouseEnter ? 'block' : 'none'}}>删除</button>
至此,功能2完成
来看一眼目前的样子
接下来还有什么?
- 点击“删除”按钮,删除单项
- 多选,点击全部删除,删除多项
问题在于,怎么确定(从数组中定位要删除的哪一项)?
这两个删除能够统一为一个方法吗
要获取/动态操作每一项是否被选中
本文作者:YaosGHC
本文链接:https://www.cnblogs.com/yaocy/p/17040892.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步