React:初学实现Todolist

本文详细介绍了如何使用React构建一个TodoList应用,通过拆分组件为header、main、item和footer,实现任务的增删改查功能。组件间通信主要通过父组件App作为桥梁,利用props传递方法来更新数据。此外,文章还探讨了状态管理和父子组件间的通信方式。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

React Todolist案例

效果展示

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tkMaHHzI-1654313997777)(C:\Users\JZT\Desktop\13213298784260402660

拆分组件

在这里插入图片描述

拆分为四个组件

header:负责输入新的任务。

main:任务展示区,负责列表展示每个任务。

item:任务展示区具体的每个任务,可以对单个任务进行操作。

footer:底部任务处理区,可以同时对多个任务进行处理。

具体实现

初学React,所以考虑到组件之间需要互相通信,所以选择将各组件都需要使用的信息放在各组件共同的父组件App组件上,以及将操作数据的方法都定义在App组件上。那么子组件怎么修改数据呢?且看下例:

当按下回车键,子组件header需要将输入的任务加入到任务列表中,因为添加任务到任务列表的事件在App组件定义,所以在使用Header组件时需要将该事件传给header组件

<Header addTodo={this.addTodo}/>

然后header组件就通过props拿到该事件,当按下回车键后先将拿到的任务包装成任务对象,再传递给拿到的添加任务函数,就可以完成添加任务。然后main组件拿到的任务列表数据改变就会重新渲染,将任务渲染到任务列表上。所以这里兄弟组件间的通信是由父组件App作为桥梁的。

// 键盘抬起事件
handleKeyUp = (event) => {
    const { keyCode, target } = event;
    if (keyCode !== 13) return;
    if (target.value.trim() === '') {
        alert('输入不能为空!')
        return;
    }
    // 封装任务对象
    const todo = { id: nanoid(), name: target.value, done: false }
    // 调用父组件App中的方法,添加任务
    this.props.addTodo(todo);
    target.value = '';
}

App组件

App.jsx

import React, { Component } from 'react'
import Header from './components/Header';
import Footer from './components/Footer';
import List from './components/List';
import './App.css';
export default class App extends Component {
  state = {
    // 任务列表
    todos: [
      { id: '001', name: '吃饭', done: true },
      { id: '002', name: '睡觉', done: true },
      { id: '003', name: '打代码', done: false },
      { id: '004', name: '逛街', done: false }
    ]
  }
  // 增加任务
  addTodo = (todoObj) => {
    const { todos } = this.state;
    const newTodos = [todoObj, ...todos];
    this.setState({ todos: newTodos });
  }
  // 更新任务选中状态
  updateTodo = (id, checked) => {
    const { todos } = this.state;
    const newTodos = todos.map(item => {
      if (item.id === id) {
        item.done = checked;
      }
      return item;
    })
    this.setState({ todos: newTodos });
  }
  // 删除任务
  deleteTodo = (id) => {
    const { todos } = this.state;
    const newTodos = todos.filter(item => {
      return item.id !== id
    })
    this.setState({ todos: newTodos});
  }
  // 清除全部已选
  deleteDone = () => {
    const { todos } = this.state;
    const newTodos = todos.filter(item => {
      return item.done !== true;
    })
    this.setState({ todos: newTodos });
  }
  // 全选
  ChooseDone = (done) =>  {
    const { todos } = this.state;
    const newTodos = todos.map(item => {
      return { ...item, done};
    })
    this.setState({ todos: newTodos });
  }
  render() {
    const { todos } = this.state;
    return (
      <div className="todo-container">
        <div className="todo-wrap">
          <Header addTodo={this.addTodo}/>
          <List todos={todos} updateTodo={this.updateTodo} deleteTodo={this.deleteTodo}/>
          <Footer todos={todos} ChooseDone={ this.ChooseDone } deleteDone={ this.deleteDone }/>
        </div>
      </div>
    );
  }
}

App.css

/*base*/
body {
  background: #fff;
}
.btn {
  display: inline-block;
  padding: 4px 12px;
  margin-bottom: 0;
  font-size: 14px;
  line-height: 20px;
  text-align: center;
  vertical-align: middle;
  cursor: pointer;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
  border-radius: 4px;
}
.btn-danger {
  color: #fff;
  background-color: #da4f49;
  border: 1px solid #bd362f;
}
.btn-danger:hover {
  color: #fff;
  background-color: #bd362f;
}
.btn:focus {
  outline: none;
}
.todo-container {
  width: 600px;
  margin: 0 auto;
}
.todo-container .todo-wrap {
  padding: 10px;
  border: 1px solid #ddd;
  border-radius: 5px;
}

header组件

index.jsx

import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { nanoid } from 'nanoid'
import './index.css'
export default class Header extends Component {
	// 对接收的props进行:类型、必要性的限制
	static propTypes = {
		addTodo: PropTypes.func.isRequired
	}
	// 键盘抬起事件
	handleKeyUp = (event) => {
		const { keyCode, target } = event;
        //	判断是不是回车键
		if (keyCode !== 13) return;
        // 输入为空提示
		if (target.value.trim() === '') {
			alert('输入不能为空!')
			return;
		}
        // 封装任务对象
		const todo = { id: nanoid(), name: target.value, done: false }
        // 调用父组件App中的方法,添加任务
		this.props.addTodo(todo);
        // 重置输入框内容
		target.value = '';
	}
	render() {
		return (
			<div className="todo-header">
				<input onKeyUp={ this.handleKeyUp} type="text" placeholder="请输入你的任务名称,按回车键确认" />
			</div>
		)
	}
}

index.css

/*header*/
.todo-header input {
  width: 560px;
  height: 28px;
  font-size: 14px;
  border: 1px solid #ccc;
  border-radius: 4px;
  padding: 4px 7px;
}
.todo-header input:focus {
  outline: none;
  border-color: rgba(82, 168, 236, 0.8);
  box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
}

main组件

index.jsx

import React, { Component } from 'react'
import Item from '../Item'
import PropTypes from 'prop-types'
import './index.css'
export default class List extends Component {
	//对接收的props进行:类型、必要性的限制
	static propTypes = {
		todos:PropTypes.array.isRequired,
		updateTodo:PropTypes.func.isRequired,
		deleteTodo:PropTypes.func.isRequired,
	}
	render() {
		const { todos, updateTodo, deleteTodo } = this.props;
		return (
			<ul className="todo-main">
				{
					todos.map(todo => {
						return <Item key={todo.id} {...todo} updateTodo={updateTodo} deleteTodo={deleteTodo}/>
					})
				}
			</ul>
		)
	}
}

index.css

/*main*/
.todo-main {
  margin-left: 0px;
  border: 1px solid #ddd;
  border-radius: 2px;
  padding: 0px;
}
.todo-empty {
  height: 40px;
  line-height: 40px;
  border: 1px solid #ddd;
  border-radius: 2px;
  padding-left: 5px;
  margin-top: 10px;
}

item组件

index.jsx

import React, { Component } from 'react'
import './index.css'
export default class Item extends Component {
	state = {
        // 鼠标移入移出标志
		mouse: false
	}
	// 改变单个任务的选中条件
	handleChangeCheck = (id) => {
		return (event) => {
			this.props.updateTodo(id, event.target.checked);
		}
	}
	// 鼠标移入高亮
	mouseChange(flag) {
		return () => {
			this.setState({ mouse: flag })
		}
	}
	// 删除
	handleDelete(id) {
		return () => {
			this.props.deleteTodo(id);
		}
	}
	render() {
		const { id, name, done } = this.props;
		const { mouse } = this.state;
		return (
			<li style={{ backgroundColor: mouse ? '#ddd' : 'white' }} onMouseEnter={this.mouseChange(true)} onMouseLeave={this.mouseChange(false)}>
				<label>
					<input type="checkbox" checked={done} onChange={this.handleChangeCheck(id)}/>
					<span>{name}</span>
				</label>
				<button className="btn btn-danger" style={{ display: mouse ? 'block' : 'none' }} onClick={this.handleDelete(id)}>删除</button>
			</li>
		)
	}
}
/*item*/
li {
  list-style: none;
  height: 36px;
  line-height: 31px;
  padding: 0 5px;
  border-bottom: 1px solid #ddd;
}
li label {
  float: left;
  cursor: pointer;
}
li label li input {
  vertical-align: middle;
  margin-right: 6px;
  position: relative;
  top: -1px;
}
li button {
  float: right;
  display: none;
  margin-top: 3px;
}
li:before {
  content: initial;
}
li:last-child {
  border-bottom: none;
}

footer组件

index.jsx

import React from "react";
import './index.css'
// style={{display:mouse ? 'block': 'none'}}
export default class Footer extends React.Component{
  // 清除全部已完成
  handleDelDone = () => {
    this.props.deleteDone();
  }
  // 全选
  handleChooseDone = (event) => {
    this.props.ChooseDone(event.target.checked);
  }
  render() {
    const { todos } = this.props;
    const doneNum = todos.reduce((pre, cur) => pre + (cur.done ? 1 : 0), 0)
    const total = todos.length;
    return (
      <div className="todo-footer">
        <label>
          <input type="checkbox" onChange={this.handleChooseDone} checked={doneNum === total && total ? true : false}/>
        </label>
        <span>
          <span>已完成{ doneNum }</span> / 全部{total}
        </span>
        <button className="btn btn-danger" onClick={this.handleDelDone} >清除已完成任务</button>
      </div>
    )
  }
}

index.css

/*footer*/
.todo-footer {
  height: 40px;
  line-height: 40px;
  padding-left: 6px;
  margin-top: 5px;
}
.todo-footer label {
  display: inline-block;
  margin-right: 20px;
  cursor: pointer;
}
.todo-footer label input {
  position: relative;
  top: -1px;
  vertical-align: middle;
  margin-right: 5px;
}
.todo-footer button {
  float: right;
  margin-top: 5px;
}

总结

  1. 拆分组件、实现静态组件,注意:className、style的写法

  2. 动态初始化列表,如何确定将数据放在哪个组件的state中?

    • 某个组件使用:放在其自身的state中
    • 某些组件使用:放在他们共同的父组件state中(官方称此操作为:状态提升)
  3. 关于父子之间通信:

    • 【父组件】给【子组件】传递数据:通过props传递
    • 【子组件】给【父组件】传递数据:通过props传递,要求父提前给子传递一个函数
  4. 状态在哪里,操作状态的方法就在哪里

最近学React,发现它和Vue的很多地方都很相似,但也有很多区别,特别是JSX语法,现在我还不是很习惯OVO,还要继续努力啊。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值