欢迎来到我的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技术栈
文章目录
- 组件的组合使用-TodoList
- ▶️拆分组件
- 项目结构
- index.js
- App.jsx
- App.css
- Header部分
- Header-index.jsx
- Header-index.css
- List部分
- List-index.jsx
- List-index.css
- Item部分
- Item-index.jsx
- Item-index.css
- Footer部分
- Footer-index.jsx
- Footer-index.css
- ✅案例总结
组件的组合使用-TodoList
功能: 组件化实现此功能
1.显示所有todo列表
2.输入文本,点击按钮显示到列表的首位,并清除输入的文本
▶️拆分组件
项目结构
index.js
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>);
App.jsx
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.stateconst newTodos = [todoObj, ...todos]this.setState({ todos: newTodos });}// 用于勾选和取消勾选todoupdateTodo = (id, done) => {// 获取todosconst { todos } = this.state// 匹配处理数据const newTodos = todos.map((todoObj) => {if (todoObj.id === id) return { ...todoObj, done }else return todoObj})this.setState({ todos: newTodos });}// 用于删除一个tododeleteTodo = (id) => {const { todos } = this.state// 删除指定ID的todo对象const newTodos = todos.filter((todoObj) => {// 数组的过滤方法,返回id不等于传进来的id值的那些todoObj对象,说明排除掉了点击了删除id对应的那个todoObjreturn todoObj.id !== id})this.setState({ todos: newTodos });}// 用于全选checkAllTodo = (done) => {const { todos } = this.stateconst 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;
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部分
Header-index.jsx
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') returnconsole.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-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);}
List部分
List-index.jsx
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.propsreturn (<ul className="todo-main">{todos.map(todo => {return <Item key={todo.id} {...todo} updateTodo={updateTodo} deleteTodo={deleteTodo} />})}</ul>);}}export default List;
List-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部分
Item-index.jsx
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.propsreturn (<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-index.css
/*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部分
Footer-index.jsx
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.lengthreturn (<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-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;}
✅案例总结
案例总结:
- 拆分组件,实现静态组件,注意:className、style的写法
- 动态初始化列表,如何确定将数据放在哪个组件的state中?
- ——某个组件使用:放在自身state中
- ——某些组件使用:放在他们共同的父组件state中(官方称此操作为:状态提升)
- 关于父子组件的通信:(父子组件通信详解)
- 【父组件】给【子组件】传递数据:通过props传递
- 【子组件】给【父组件】传递数据:通过props传递,要求父提前给子传递一个函数
- 注意
defaultChecked
和checked
的区别,类似的还有:defaultValue
和value
- 状态在哪里,操作状态的方法就在哪里
好啦~今天的案例分享就到这里了,如果有疑问或者文章出现错误的话请一定要联系我哟~
非常感谢你的阅读,你的支持将会是我最大的动力
关注✨点赞收藏
回见~