欢迎来到我的React系列文章
本系列文章从React入门开始,涵盖了React的一切基础,属于是从零开始的一个系列
文章会以图文结合-动图-代码实现-解释代码的形式带领大家走进React的世界
持续更新中~希望大家能够喜欢,系列文章React–从基础到实战
博客主页codeMak1r的博客
关注✨点赞收藏
- React入门与概览(JSX语法)
- 面向组件编程——组件实例的三大核心属性state、props和refs超详解
- 受控组件与非受控组件(vue和react的双向绑定分别是怎么实现的?)
- React函数的柯里化(什么?这玩意儿不仅能装x,还能优化代码?)
- 四行代码让我的cpu跑上90度——走进组件生命周期
- 图文详解react组件生命周期——React v16.8
- react新生命周期图文详解——最新版
- react-getSnapshotBeforeUpdate()生命周期函数详解
- 使用create-react-app(CRA)创建react项目
- react父子组件传值(通信)
- <前端组件化>拆分组件思想-吃饭、睡觉、打代码案例(附源码)(本文)
本例使用的是React v18.1技术栈
功能: 组件化实现此功能
1.显示所有todo列表
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
import React, { Component } from 'react';
import Header from './components/Header';
import List from './components/List';
import Footer from './components/Footer';
import './App.css'
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: true }
]
}
// addTodo用于添加一个todo,接受的参数是todo对象
addTodo = (todoObj) => {
const { todos } = this.state
const newTodos = [todoObj, ...todos]
this.setState({ todos: newTodos });
}
// 用于勾选和取消勾选todo
updateTodo = (id, done) => {
// 获取todos
const { todos } = this.state
// 匹配处理数据
const newTodos = todos.map((todoObj) => {
if (todoObj.id === id) return { ...todoObj, done }
else return todoObj
})
this.setState({ todos: newTodos });
}
// 用于删除一个todo
deleteTodo = (id) => {
const { todos } = this.state
// 删除指定ID的todo对象
const newTodos = todos.filter((todoObj) => {
// 数组的过滤方法,返回id不等于传进来的id值的那些todoObj对象,说明排除掉了点击了删除id对应的那个todoObj
return todoObj.id !== id
})
this.setState({ todos: newTodos });
}
// 用于全选
checkAllTodo = (done) => {
const { todos } = this.state
const newTodos = todos.map((todoObj) => {
return { ...todoObj, done }
})
this.setState({ todos: newTodos });
}
// 用于清除已完成任务
clearAllDone = () => {
const { todos } = this.state
// 过滤数据
const newTodos = todos.filter((todoObj) => {
return todoObj.done !== true
})
this.setState({ todos: newTodos });
}
render() {
return (
<div className="todo-container">
<div className="todo-wrap">
<Header addTodo={this.addTodo} />
<List todos={this.state.todos}
updateTodo={this.updateTodo}
deleteTodo={this.deleteTodo} />
<Footer todos={this.state.todos}
checkAllTodo={this.checkAllTodo}
clearAllDone={this.clearAllDone} />
</div>
</div>
);
}
}
export default App;
/*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;
}
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { nanoid } from 'nanoid'
import './index.css'
class Header extends Component {
// 键盘事件的回调
handleKeyUp = (event) => {
if (event.key !== 'Enter') return
console.log(event.target.value, event.keyCode, event.key)
// 添加的todo名字不能为空
if (event.target.value.trim() === '') {
alert('输入不能为空')
return
}
const todoObj = { id: nanoid(), name: event.target.value, done: false }
this.props.addTodo(todoObj)
// 清空输入框
event.target.value = ''
}
// 对接收的props进行类型限制
static propTypes = {
addTodo: PropTypes.func.isRequired,
}
render() {
return (
<div className="todo-header">
<input onKeyUp={this.handleKeyUp} type="text" placeholder="请输入你的任务名称,按回车键确认" />
</div>
);
}
}
export default Header;
/*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);
}
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import Item from '../Item';
import './index.css'
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>
);
}
}
export default List;
/*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;
}
import React, { Component } from 'react';
import './index.css'
class Item extends Component {
// 标识鼠标移入、移出
state = { mouse: false }
// 鼠标移入移出的回调
handleMouse = (flag) => {
return () => {
this.setState({ mouse: flag });
}
}
// checkbox勾选的回调
handleCheck = (id) => {
return (event) => {
console.log(id, event.target.checked)
this.props.updateTodo(id, event.target.checked)
}
}
// 删除一个todo的回调
handleDelete = (id, name) => {
return () => {
if (window.confirm('确定删除' + name + '吗?')) {
this.props.deleteTodo(id)
}
}
}
render() {
const { id, name, done } = this.props
return (
<li style={{ backgroundColor: this.state.mouse ? '#ddd' : 'white' }}
onMouseEnter={this.handleMouse(true)}
onMouseLeave={this.handleMouse(false)}>
<label>
<input type="checkbox"
checked={done}
onChange={this.handleCheck(id)} />
<span>{name}</span>
</label>
<button onClick={this.handleDelete(id, name)}
className="btn btn-danger"
style={{ display: this.state.mouse ? 'block' : 'none' }}>删除</button>
</li>
);
}
}
export default Item;
/*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;
}
import React, { Component } from 'react';
import './index.css'
class Footer extends Component {
// 全选checkbox的回调
handleCheckAll = (event) => {
console.log(event.target.checked)
this.props.checkAllTodo(event.target.checked)
}
// 清除所有已完成任务的回调
handleClearAllDone = () => {
this.props.clearAllDone()
}
render() {
const { todos } = this.props
// 已完成的个数
const doneCount = todos.reduce((prev, currentTodo) => {
return prev + (currentTodo.done ? 1 : 0)
}, 0)
// 总数
const total = todos.length
return (
<div className="todo-footer">
<label>
<input type="checkbox"
checked={doneCount === total && total !== 0 ? true : false}
onChange={this.handleCheckAll} />
</label>
<span>
<span>已完成{doneCount}</span> / 全部{total}
</span>
<button onClick={this.handleClearAllDone}
className="btn btn-danger">清除已完成任务</button>
</div>
);
}
}
export default Footer;
/*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;
}
案例总结:
- 拆分组件,实现静态组件,注意:className、style的写法
- 动态初始化列表,如何确定将数据放在哪个组件的state中?
- ——某个组件使用:放在自身state中
- ——某些组件使用:放在他们共同的父组件state中(官方称此操作为:状态提升)
- 关于父子组件的通信:(父子组件通信详解)
- 【父组件】给【子组件】传递数据:通过props传递
- 【子组件】给【父组件】传递数据:通过props传递,要求父提前给子传递一个函数
- 注意
defaultChecked
和checked
的区别,类似的还有:defaultValue
和value
- 状态在哪里,操作状态的方法就在哪里
好啦~今天的案例分享就到这里了,如果有疑问或者文章出现错误的话请一定要联系我哟~
非常感谢你的阅读,你的支持将会是我最大的动力
关注✨点赞收藏
回见~