效果图:
设计步骤:
- 拆分组件 拆分界面 抽离组件
- 实现静态页面,不用实现仍的交互
- 实现动态页面
a. 动态显示初始化数据
ⅰ. 数据类型
ⅱ. 数据名称
ⅲ. 保存在哪一个组件
b. 交互(从绑定事件开始)
首先,将TodoList拆分成三个组件:
组件目录:
首先实现的是:静态页面,就jsx和css配合,不用涉及变量,画页面
然后,绑定事件,涉及父子组件传值、遍历的时候数据结构设计。
搜索框组件:Search
Search.jsx
import { nanoid } from 'nanoid';
import "./index.css";
export default function Search(props) {
const handleInput = (e) => {
if (e.keyCode === 13) {
// 将获取到的事件传递给Tasks
props.addTask({id:nanoid(),isFinish:false,text:e.target.value});
e.target.value = '';
}
}
return <input type="text" onKeyDown={handleInput} placeholder="请输入你的任务名称,按回车键确认"/>;
}
Search.css
input[type="text"] {
border: 1px solid #ccc;
border-radius: 4px;
width: 100%;
margin: 10px auto;
padding: 5px 0;
}
input[type="text"]:focus {
border: 1px solid #ccc;
border-radius: 4px;
}
Tasks.jsx
import {Component} from 'react';
import Task from './Task/Task';
import Search from '../Search/Search';
import { nanoid } from 'nanoid';
import './Tasks.css';
export default class Tasks extends Component {
constructor(props) {
super(props);
this.state = {
tasks: [{id: nanoid(),isFinish:true,text:'吃饭'},{id: nanoid(),isFinish: false,text:'睡觉'}],
flag: false
}
}
handleDeleteTask = (e,task) => {
let {tasks,flag} =this.state;
// 弹出提示框 是否删除
const historyTasks = tasks.slice();
var remainTasks = [];
if (e.target.id === 'dall') {
remainTasks = historyTasks.filter(item => item.isFinish !== true);
flag = flag === true && !flag;
} else {
remainTasks = historyTasks.filter(item => item.id !== task.id);
}
this.setState({tasks: remainTasks,flag});
}
handleChange = (e,task) => {
let {tasks,flag} = this.state;
const historyTasks = tasks.slice();
var dealTask = [];
if (e.target.id === 'all') {
dealTask = historyTasks.map(item => {
let {id,text} = item;
return flag === true
? {id,isFinish:false,text}
: {id,isFinish: true, text};
})
} else{
dealTask = historyTasks.map(item => {
let {id,isFinish,text} = item;
if (item.id === task.id) {
item = {id,isFinish:!isFinish,text};
}
return item;
})
}
let {length} = dealTask.filter(task => task.isFinish === true);
flag = flag !==0 && length === historyTasks.length;
this.setState({tasks:dealTask,flag});
}
handleAddTask = (task) => {
let tasks = [task, ...this.state.tasks];
this.setState({tasks: tasks});
}
render() {
const {tasks,flag} = this.state;
let {length} = tasks.filter(task => task.isFinish === true);
return (
<div>
<Search addTask={this.handleAddTask}/>
<Task tasks={this.state.tasks} handleChange={this.handleChange} handleDeleteTask={this.handleDeleteTask}/>
<div className="task_item">
<div>
<input type="checkbox" id="all" onChange={this.handleChange} checked={flag === true}/>
<span>已完成{length}/全部{this.state.tasks.length}</span>
</div>
<button onClick={this.handleDeleteTask} id="dall">删除所有已完成任务</button>
</div>
</div>
)
}
}
Tasks.css
.tasks_container {
border: 1px solid #ccc;
}
.tasks_container .task_item:not(:last-child){
border-bottom: 1px solid #ccc;
}
Task.jsx
import {Component} from 'react';
import "./Task.css";
export default class Task extends Component {
handleChange = (task) => {
return (e) => {
this.props.handleChange(e,task);
}
}
handleDeleteTask = (task) => {
return (e) => {
this.props.handleDeleteTask(e,task);
}
}
render() {
let tasks = this.props.tasks;
return (
<div>
{tasks.map(task=> {
return <div className="task_item" key={task.id}>
<div>
<input type="checkbox" id="task" onChange={this.handleChange(task)} checked={task.isFinish === true}/>
<span>{task.text}</span>
</div>
<button onClick={this.handleDeleteTask(task)}>删除</button>
</div>
})}
</div>
);
}
}
Task.css
.task_item{
display:flex;
padding:5px 0;
color:#333333;
width:100%;
}
.task_item>div{
flex:1;
}
.task_item>button{
border:none;
background-color:red;
border-radius: 4px;
color:#eee;
display: none;
}
.task_item:hover{
background-color:#ccc;
}
.task_item:hover button{
display:inline-block;
}
App.js
import Tasks from "./components/Tasks/Tasks";
import "./styles.css";
export default function App(){
return (
<div className="App_container">
<Tasks/>
</div>
);
}
styles.css
.App_container {
font-size: 16px;
border: 1px solid #ccc;
box-shadow: 1px 1px 2px 2px #ccc;
padding: 10px;
width: 60%;
}
index.js
import React, { StrictMode } from "react";
import ReactDOM from "react-dom";
import App from "./App";
const rootElement = document.getElementById("root");
ReactDOM.render(
<StrictMode>
<App />
</StrictMode>,
rootElement
);
遇到过的问题:
- Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.
翻译:
超过最大更新深度。当组件在componentWillUpdate或componentDidUpdate内部反复调用setState时,就会发生这种情况。React限制嵌套更新的数量,以防止无限循环。 - 子组件是不能更改props传递的内容的,需要在父组件中更新才行
- 划分组件的时候,要考虑数据结构,方便更新,减少嵌套层数。
- 绑定事件处理函数的时候,使用回调传递参数
handleChange = (task) => {
return (e) => {
this.props.handleChange(e,task);
}
}
- 遍历时,引入
nanoid
依赖,生成唯一id,作为key
备注:该项目是B站的资源,但是自己先搞一遍,然后再去看视频,看看思路差异。