React + Todos:打造个性化任务管理器的实践指南
前言
想必大家应该都用vue或者html写过todos,那么有尝试过用react来写todos吗?
react划分组件树,用组件的思想,其核心理念是“组件化”,组件开发、交流、互动、汇报的基本单位,DOM 树太微观了,组件树很好表达页面的构成和功能,vue和react都是根组件接管一切,那么vue和react有什么区别呢?
- vue 有各种指令
- react 不支持双向绑定
- 表单受控组件 this.state.inputText控制
- 单向的数据绑定 value = {}结合onChange 事件实现数据绑定,无v-model
正文
实现效果:
准备工作:
- 在终端分别输入:
npm init vite
:新建react项目npm i
:安装包
- 新建一个文件夹
components
,再新建下面的子文件:
TodoForm.jsx
TodosList.jsx
TodoItem.jsx
TodoItem.css
index.css
清除默认的内外边距,这里我没有用*
,是因为性能不太好,那么下面这种方式就可以适当的性能优化:
- 性能优化
- 页面的加载速度
- 生成DOM树
- 渲染CSSOM树
- script 放到底部去
- DOM + CSSOM = 渲染树
- 布局树 (+盒子模型+bfc+zIndex)
- GPU 去绘制页面了 像素 静态页面
- CSS 选择器:
*
, 标签 , 从右到左匹配的
- 页面的加载速度
/* 性能不太好 */
body, div, dl, dt, dd, ul, li, h1, h2, h3, h4, h5, h6, pre, code, form, fieldset, legend, input, button, textarea, blockquote{
margin: 0;
padding: 0;
}
/* CSS选择器的匹配规则是从右到左 少用标签*/
App.jsx
大概的思路分析:
- 导入了
Component
类:这是创建 React 组件的基础,所有的类组件都继承自这个类 - 导入了两个自定义的 React 组件:
TodosForm
和TodosList
App
类继承了Component
类:
constructor
方法初始化组件状态: 从localStorage
中恢复的待办事项列表:如果localStorage
中没有找到待办事项,则默认为一个空数组
- 定义了几个方法,分别为:
addTodo
方法
用于向待办事项列表中添加一个新的待办事项,接收一个 text
参数,指的是待办事项,并将其添加到状态中的 todos
数组中
deleteTodo
方法
用于删除特定索引位置的待办事项。为了不直接修改状态数组,这里首先创建了状态的一个副本,然后使用 splice
方法移除指定项,并更新状态
toggleTodo
方法
用于切换待办事项的完成状态。同样,先复制状态数组,然后修改对应索引处的 completed
属性
editTodo
方法
用于编辑待办事项的描述。与前两个方法类似,它也通过复制状态数组来避免直接修改状态
- 通过一个生命周期方法
componentDidUpdate
来在组件更新后被调用,每当组件状态改变并重新渲染后,都会将最新的待办事项列表存储到localStorage
中,等到下次加载时恢复 - 最后是
render
方法:返回要渲染到 DOM 中的 JSX:
- 返回一个包含标题和两个子组件 (
TodosForm
和TodosList
) 的<div>
元素 - 子组件接收了来自
App
组件的方法作为属性(props),以便能够与状态进行交互
import { Component } from 'react';
import TodosForm from './components/TodosForm';
import TodosList from './components/TodosList';
// es6 模块化 es6 module
// class extends super static 传统面向对象的能力
class App extends Component{
constructor(props) {
super(props); // 将父类的构造函数执行一下
// 私有数据 声明自己的属性
const savedTodos = JSON.parse(localStorage.getItem('todos')) || [];
this.state = {
todos: savedTodos
}
}
// 修改状态 数据流
addTodo = (text) => {
// Component 上有setState方法,修改状态,响应式更新
// 状态就如纸巾
this.setState({
todos: [
...this.state.todos,
{
text,
completed: false
}
]
})
}
// 数据编程
// 数据和界面状态是一一对应的
// 删除
deleteTodo = (index) => {
const newTodos = [...this.state.todos];
newTodos.splice(index, 1); // 删除某一项,修改原数组
this.setState({
todos: newTodos
})
}
// 切换
toogleTodo = (index) => {
const newTodos = [...this.state.todos];
newTodos[index].completed = !newTodos[index].completed;
this.setState({
todos: newTodos
})
}
// 修改
editTodo = (index, newText) => {
const newTodos = [...this.state.todos];
newTodos[index].text = newText;
this.setState({
todos: newTodos
})
}
componentDidUpdate() {
localStorage.setItem('todos', JSON.stringify(this.state.todos));
}
// 抽象方法 abstract function
// 界面不一样的,重写
render(){
return (
<div>
<h1>Hello React</h1>
<div>
<TodosForm addTodo={this.addTodo} />
<TodosList
todos = {this.state.todos}
deleteTodo={this.deleteTodo}
toogleTodo={this.toogleTodo}
editTodo={this.editTodo}
/>
</div>
</div>
)
}
}
export default App;
TodoForm.jsx
提供一个表单让用户输入新的待办事项,并在提交时将这些待办事项进行渲染。
大概的思路:
- 导入了
Component
类 TodosForm
类继承了Component
类,在构造函数constructor
中:
super(props)
被调用用来初始化父类的构造函数,确保this.props
和this.state
正确绑定- 初始化状态
state
包含一个键inputText
,其值被设置为'聚会'
,为输入框的初始值
- 定义了两个函数(事件处理器):
handleChange
函数:
用于处理输入框的值变化,当用户在输入框中输入文本时,它会被触发,更新 inputText
的状态值为当前输入框的值
handleSubmit
函数:
处理表单的提交事件
-
通过
e.preventDefault()
阻止表单的默认提交行为(例如页面刷新),确保应用逻辑可以正确执行 -
如果输入框中的文本不为空(经过
trim()
去除前后空白字符后),则调用从父组件传递过来的addTodo
方法,将新的待办事项添加到列表中 -
将
inputText
的状态值重置为空字符串,清空输入框 -
render
方法:
- 返回了 JSX
- 定义表单的结构为:
- 一个文本输入框:值被设置为
this.state.inputText
的值,确保输入框与状态同步,当输入框内容变化时,onChange
事件会触发handleChange
方法 - 一个提交按钮:当点击提交按钮或按下回车键时,
onSubmit
事件会触发handleSubmit
方法,处理表单的提交
import { Component } from 'react';
import './TodosForm.css'
class TodosForm extends Component {
constructor(props) {
super(props);
this.state = {
inputText: '聚会'
}
}
handleChange = (e) => {
this.setState({
inputText: e.target.value
})
}
handleSubmit = (e) => {
e.preventDefault();
if (this.state.inputText.trim()) {
this.props.addTodo(this.state.inputText);
this.setState({
inputText: ''
})
}
}
render() {
return (
<form className='todo-form' onSubmit={this.handleSubmit}>
<input
type="text"
placeholder="请输入待办事项"
className='todo-form__input'
value={this.state.inputText}
onChange={this.handleChange}/>
<button className="todo-form__button" type='submit'>add</button>
</form>
);
}
}
export default TodosForm;
TodosList.jsx
用于渲染待办事项列表
大概的思路:
- 导入
Component
类,用于创建类组件 - 导入了
TodoItem
组件:用来渲染单个待办事项的子组件 - 继承了
Component
类,在render
方法内,使用解构赋值从this.props
中提取出todos
、deleteTodo
、toggleTodo
和editTodo
函数(这些函数是从父组件传入的,用于处理待办事项的删除、切换完成状态和编辑) render
方法:
- 返回 JSX
- 定义待办事项列表的结构:
-
<h1>
标签,用于显示标题 "Todo List" -
使用
.map()
方法遍历todos
数组中的每一个待办事项 -
对于数组中的每一个元素,创建一个
TodoItem
组件实例,将当前待办事项的数据和相关处理函数作为 props 传递给它 -
这里的
key
属性设置为当前元素的index
,有助于 React 在更新列表时更高效地识别和重用组件 -
将
TodosList
组件导出为默认模块,使其可以在其他文件中被导入和使用
import { Component } from 'react';
import TodoItem from './TodoItem';
import './TodosList.css';
class TodosList extends Component {
render() {
const { todos, deleteTodo, toogleTodo, editTodo, index } = this.props;
return (
<div>
<h1>Todo List</h1>
{
todos.map((todo, index) => {
return <TodoItem
key={index}
index={index}
todo={todo}
deleteTodo={deleteTodo}
toggleTodo={toogleTodo}
editTodo={editTodo}
/>;
})
}
</div>
);
}
}
export default TodosList;
TodoItem.jsx
用于渲染和管理单个待办事项的显示和交互
大概的思路:
- 导入
Component
类,用于创建类组件 TodoItem
类继承了Component
类,在构造函数constructor
中,初始化组件的本地状态state
:状态包含两个键:
isEditing
表示是否处于编辑模式,初始值为false
editText
存储编辑时的文本内容,初始值为从父组件传入的待办事项的文本
- 定义了两个函数:
handleEditChange
函数:
处理编辑模式下输入框内容的变化,更新 editText
的状态值
handleEditSave
函数:
处理编辑模式下的保存操作
-
调用从父组件传入的
editTodo
函数 -
传递当前待办事项的索引和编辑后的文本
-
更新全���的待办事项列表
-
将
isEditing
状态设为false
-
render
方法:
- 使用解构赋值从
this.props
中提取出todo
对象以及处理函数deleteTodo
、toggleTodo
和editTodo
- 从
todo
对象中提取出text
和completed
属性 - 返回 JSX
- 定义待办事项的布局:
- 包裹在一个
<li>
标签中 - 其类名根据待办事项的完成状态动态变化,以应用不同的样式
- 根据
isEditing
状态的值,组件会呈现两种不同的视图:
- 如果
isEditing
为true
,则显示一个输入框和一个保存按钮,允许用户编辑待办事项的文本 - 如果
isEditing
为false
,则显示待办事项的文本、一个编辑按钮和一个删除按钮。文本被包装在一个<span>
标签中,点击时会调用toggleTodo
函数,切换待办事项的完成状态
import { Component } from 'react';
import './TodoItem.css';
class TodoItem extends Component {
constructor(props) {
super(props)
this.state = {
isEditing: false,
editText:this.props.todo.text
}
}
handleEditChange = (event) => {
this.setState({
editText: event.target.value
})
}
handleEditSave = () => {
this.props.editTodo(this.props.index, this.state.editText)
this.setState({
isEditing: false
})
}
render () {
const { todo, deleteTodo, toggleTodo, editTodo, index } = this.props
console.log(todo);
const { text, completed } = todo
return (
<li className={`todo-item ${completed ? 'todo-item--completed' : ''}`}>
{
this.state.isEditing?(
<div>
<input
type="text"
value={this.state.editText}
onChange={this.handleEditChange}
/>
<button onClick={this.handleEditSave}>保存</button>
</div>
):(
<div>
<span className="todo-item__text"
onClick={() => toggleTodo(index)}>
{todo.text}
</span>
<button onClick={() => this.setState({isEditing: true})}>编辑</button>
<button className="todo-item__delete-btn"
onClick={() => deleteTodo(index)}>Delete</button>
</div>
)
}
</li>
);
}
}
export default TodoItem;
TodoItem.css
用于实现当点击待办事项时,实现待办事项文本的样式变化
.todo-item--completed .todo-item__text{
text-decoration: line-through;
color: #888;
}
结语
转载自:https://juejin.cn/post/7387999151412232227