React学习案例——TodoList
1、实现效果
2、静态页面
index.html:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>React App</title>
<link rel="stylesheet" href="index.css">
</head>
<body>
<div id="root">
<div class="todo-container">
<div class="todo-wrap">
<div class="todo-header">
<input type="text" placeholder="请输入你的任务名称,按回车键确认"/>
</div>
<ul class="todo-main">
<li>
<label>
<input type="checkbox"/>
<span>xxxxx</span>
</label>
<button class="btn btn-danger" style="display:none">删除</button>
</li>
<li>
<label>
<input type="checkbox"/>
<span>yyyy</span>
</label>
<button class="btn btn-danger" style="display:none">删除</button>
</li>
</ul>
<div class="todo-footer">
<label>
<input type="checkbox"/>
</label>
<span>
<span>已完成0</span> / 全部2
</span>
<button class="btn btn-danger">清除已完成任务</button>
</div>
</div>
</div>
</div>
</body>
</html>
index.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*/
.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*/
.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*/
li {
list-style: none;
height: 36px;
line-height: 36px;
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*/
.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;
}
3、功能界面的组件化编码流程(通用)
-
拆分组件:拆分界面,抽取组件。
-
实现静态组件:使用组件实现静态页面效果。
-
实现动态组件。
3.1 动态显示初始化数据3.1.1 数据类型 3.1.2 数据名称 3.1.2 保存在哪个组件?
3.2 交互(从绑定事件监听开始)
4、class组件实现过程
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>react案例-todolist</title>
<!-- 加载 React。-->
<!-- 注意: 部署时,将 "development.js" 替换为 "production.min.js"。-->
<script src="https://unpkg.com/react@16/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigin></script>
<!-- <script src="https://unpkg.com/babel-standalone@6.15.0/babel.min.js"></script> -->
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
<link rel="stylesheet" href="./css/index.css">
</head>
<body>
<div id="root"></div>
<script type="text/babel">
class TodoHeader extends React.Component {
constructor(props) {
super(props);
this.state = {
taskName: ""
};
this.handleInputChange = this.handleInputChange.bind(this);
this.onKeyUp = this.onKeyUp.bind(this);
}
handleInputChange(e) {
this.setState({
taskName: e.target.value
});
}
onKeyUp(e) {
// console.log(e.keyCode);
if (e.keyCode === 13) {
this.props.addOneTask(this.state.taskName);
this.setState({
taskName: ""
});
}
}
render() {
const { taskName } = this.state;
return (
<div className="todo-header">
<input
type="text"
value={taskName}
placeholder="请输入你的任务名称,按回车键确认"
onChange={this.handleInputChange}
onKeyUp={this.onKeyUp}
/>
</div>
);
}
}
class ListItem extends React.Component {
constructor(props) {
super(props);
this.state = {
isDone: ""
};
this.handleCheck = this.handleCheck.bind(this);
this.deleteOneTask = this.deleteOneTask.bind(this);
}
componentDidMount() {
this.setState({
isDone: this.props.item.status
});
}
handleCheck(e) {
console.log(this.state.isDone);
// this.setState的更新是异步的
this.setState(function (prestate, preprops) {
console.log(prestate, !prestate.isDone);
this.props.changeStatus(!prestate.isDone, this.props.item);
return { isDone: !prestate.isDone };
});
}
deleteOneTask() {
// console.log(this.props.item, "删除项");
this.props.deleteOneTask(this.props.item);
}
render() {
const item = this.props.item;
const isDone = this.state.isDone;
console.log(item);
return (
<li>
<label>
<input type="checkbox" checked={isDone} onChange={this.handleCheck} />
<span>{item.name}</span>
</label>
<button
className="btn btn-danger"
style={{ display: item.status ? "block" : "none" }}
onClick={this.deleteOneTask}
>
删除
</button>
</li>
);
}
}
class TodoMain extends React.Component {
constructor(props) {
super(props);
this.changeStatus = this.changeStatus.bind(this);
this.deleteOneTask = this.deleteOneTask.bind(this);
}
changeStatus(updateStatus, updateItem) {
this.props.changeStatus(updateStatus, updateItem);
}
// 删除一项已完成的任务
deleteOneTask(deleteItem) {
this.props.deleteOneTask(deleteItem);
}
render() {
const todoList = this.props.todoList;
console.log(todoList);
return (
<ul className="todo-main">
{todoList.map((item) => (
<ListItem
item={item}
key={item.name}
changeStatus={this.changeStatus}
deleteOneTask={this.deleteOneTask}
/>
))}
</ul>
);
}
}
class TodoFooter extends React.Component {
constructor(props) {
super(props);
this.clearCompletedTask = this.clearCompletedTask.bind(this);
this.handleCheckedChange = this.handleCheckedChange.bind(this);
this.state = {
isClearDone: false
};
}
handleCheckedChange() {
this.setState({
isClearDone: !this.state.isClearDone
});
// this.setState((prestate) => {
// return {
// isClearDone: !prestate.isClearDone
// }
// });
}
clearCompletedTask() {
console.log(this.state.isClearDone);
if (this.state.isClearDone) {
this.props.clearCompletedTask();
this.setState({
isClearDone: !this.state.isClearDone
})
}
}
render() {
const todoList = this.props.todoList;
return (
<div className="todo-footer">
<label>
<input
type="checkbox"
checked={this.state.isClearDone}
onChange={this.handleCheckedChange}
/>
</label>
<span>
<span>
已完成{todoList.filter((item) => item.status == true).length}
</span>{" "}
/ 全部{todoList.length}
</span>
<button className="btn btn-danger" onClick={this.clearCompletedTask}>
清除已完成任务
</button>
</div>
);
}
}
class TodoWrap extends React.Component {
constructor(props) {
super(props);
this.state = {
todoList: [
{ id: 1, name: "xxxxx", status: true },
{ id: 2, name: "yyyyy", status: false }
]
};
this.addOneTask = this.addOneTask.bind(this);
this.changeStatus = this.changeStatus.bind(this);
this.deleteOneTask = this.deleteOneTask.bind(this);
this.clearCompletedTask = this.clearCompletedTask.bind(this);
}
// 新增一项待办任务
addOneTask(taskName) {
if (taskName) {
let obj = { id: Math.ceil(Math.random() * 100), name: taskName, status: false };
this.setState({
todoList: [...this.state.todoList, obj]
});
}
}
// 更改任务状态
changeStatus(updateStatus, updateItem) {
console.log("父组件:", updateStatus, updateItem);
this.setState({
todoList: this.state.todoList.map((item) =>
item.id == updateItem.id ? { ...item, status: updateStatus } : item
)
});
}
// 删除一项已完成的任务
deleteOneTask(deleteItem) {
this.setState({
todoList: this.state.todoList.filter(
(item) => item.id != deleteItem.id
)
});
}
// 清除已完成任务
clearCompletedTask() {
this.setState({
todoList: this.state.todoList.filter((item) => item.status == false)
});
}
render() {
const todoList = this.state.todoList;
return (
<div className="todo-wrap">
<TodoHeader addOneTask={this.addOneTask} />
<TodoMain
todoList={todoList}
changeStatus={this.changeStatus}
deleteOneTask={this.deleteOneTask}
/>
<TodoFooter
todoList={todoList}
clearCompletedTask={this.clearCompletedTask}
/>
</div>
);
}
}
class TodoContainer extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div className="todo-container">
<TodoWrap />
</div>
);
}
}
// react 17
ReactDOM.render(<TodoContainer />, document.getElementById("root"));
// react 18
// const root = ReactDOM.createRoot(document.getElementById("root"));
// root.render(<TodoContainer />);
</script>
</body>
</html>
5、函数组件实现过程
目录结构:
App.js
import logo from './logo.svg';
import './App.css';
import TodoContainer from './components/TodoContainer';
function App() {
return (
<div className="App">
{/* <header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.js</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header> */}
<TodoContainer></TodoContainer>
</div>
);
}
export default App;
TodoContainer:index.jsx
import React from 'react';
import TodoWrap from './TodoWrap';
import './todolist.css';
export default function TodoContainer() {
return (
<div className="todo-container">
<TodoWrap />
</div>
)
}
TodoWrap:index.jsx
import React, { useState } from "react";
import TodoHeader from "./TodoHeader";
import TodoMain from "./TodoMain";
import TodoFooter from "./TodoFooter";
export default function TodoWrap() {
const arr = [
{ id: 1, name: "xxxxx", status: true },
{ id: 2, name: "yyyyy", status: false },
]
const [todoList, setTodoList] = useState(arr);
// 新增一项待办任务
function addOneTask(taskName) {
let obj = { id: Math.ceil(Math.random() * 1000), name: taskName, status: false };
console.log(obj, 'add');
setTodoList([...todoList, obj]);
}
// 改变每项任务的勾选状态
const changeStatus = function (isDone, changeItem) {
console.log(isDone, changeItem, '祖父组件');
let temp = todoList.map(element => {
if (element.id === changeItem.id) {
element.status = isDone;
}
return element;
});
setTodoList(temp);
}
// 删除一项任务
const deleteOneTask = (item) => {
console.log(item, '父组件2-delete');
let temp2 = todoList.filter(element => element.id !== item.id);
setTodoList(temp2);
}
// 清除所有已完成的任务
const clearCompletedTask = () => {
let temp3 = todoList.filter(item => !item.status);
setTodoList(temp3);
};
return (
<div className="todo-wrap">
<TodoHeader addOneTask={addOneTask}></TodoHeader>
<TodoMain todoList={todoList} changeStatus={changeStatus} deleteOneTask={deleteOneTask}></TodoMain>
<TodoFooter todoList={todoList} clearCompletedTask={clearCompletedTask} ></TodoFooter>
</div>
)
}
TodoHeader:index.jsx
import React, { useState } from 'react';
export default function TodoHeader(props) {
const [taskName, setTaskName] = useState("");
// 按下回车键后添加一项任务
function onKeyUp(e) {
// console.log(e.keyCode);
if(e.keyCode === 13) {
props.addOneTask(taskName);
setTaskName("");
}
}
return (
<div className="todo-header">
<input
type="text"
value={taskName}
placeholder="请输入你的任务名称,按回车键确认"
onChange={(e) => setTaskName(e.target.value)}
onKeyUp={onKeyUp}
/>
</div>
)
}
TodoMain:index.jsx
import React from 'react';
import ListItem from "./ListItem";
export default function TodoMain(props) {
const todoList = props.todoList;
// console.log(todoList);
// function changeStatus(isDone, item) {
// console.log(isDone, item, '父组件');
// props.changeStatus(isDone, item);
// }
// const deleteOneTask = (val) => {
// console.log(val, '父组件1-delete');
// props.deleteOneTask(val);
// }
return (
<div>
<ul className="todo-main">
{todoList.map((item) => (
<ListItem
item={item}
key={item.name}
changeStatus={(isDone, element) => props.changeStatus(isDone, element)}
deleteOneTask={(val) => props.deleteOneTask(val)}
/>
))}
</ul>
<ul style={{ display: todoList.length > 0 ? "none" : "block" }}>
<li>暂无待办任务</li>
</ul>
</div>
)
}
ListItem.jsx
import React, { useState } from 'react';
export default function ListItem(props) {
const { item } = props;
const [isDone, setIsDone] = useState(item.status);
console.log(item, '子组件ListItem');
const changeStatus = () => {
// 异步修改更新数据
setIsDone(!isDone);
// console.log(isDone, '子组件');
props.changeStatus(!isDone, item);
}
const deleteTask = (item) => {
console.log(item, '子组件delete');
props.deleteOneTask(item);
}
return (
<li>
<label>
<input type="checkbox" checked={isDone} onChange={changeStatus} />
<span>{item.name}</span>
</label>
<button
className="btn btn-danger"
style={{ display: item.status ? "block" : "none" }}
onClick={() => deleteTask(item)}
>
删除
</button>
</li>)
}
TodoFooter:index.jsx
import React, { useState } from 'react'
export default function TodoFooter(props) {
const { todoList } = props;
const [isClearDone, setIsClearDone] = useState(false);
// 清除所有已完成任务
const clearCompletedTask = () => {
console.log(isClearDone);
if (isClearDone) {
props.clearCompletedTask();
setIsClearDone(false);
}
};
return (
<div className="todo-footer">
<label>
<input
type="checkbox"
checked={isClearDone}
onChange={() => setIsClearDone(!isClearDone)}
/>
</label>
<span>
<span>
已完成{todoList.filter((item) => item.status === true).length}
</span>{" "}
/ 全部{todoList.length}
</span>
<button className="btn btn-danger" onClick={clearCompletedTask}>
清除已完成任务
</button>
</div>)
}