前言
在现代前端开发中,React以其组件化思想和数据驱动理念成为了最受欢迎的框架之一。今天,我将带大家通过一个TodoList应用的开发过程,深入理解React的核心概念和开发模式。不同于传统的DOM操作,React让我们能够专注于业务逻辑,而非繁琐的界面更新。
ps:今天讲的主要是逻辑,应用页面可能丑点,多担待一些,完成后jym可以根据自己的喜好做点好看的渲染。
一、项目初始化与工程化
首先,我们需要使用Vite来搭建React项目。Vite是新一代的前端构建工具,相比传统的Webpack,它提供了更快的启动速度和热更新体验。
npm create vite@latest todoListComponent --template react
cd todoListComponent
npm install
npm run dev
Vite就像建筑工地上的塔吊和搅拌机,为我们处理了项目构建、模块打包、热更新等工程化问题,让我们可以专注于业务代码的编写。
二、组件化思想解析
什么是组件?
组件是React应用的基本构建块,它将相关的HTML、CSS和JavaScript逻辑组合在一起,形成一个独立的、可复用的功能单元。就像乐高积木一样,我们可以通过组合不同的组件来构建复杂的用户界面。
为什么需要组件化?
- 关注点分离:每个组件只关注自己的功能和样式
- 可复用性:相同的组件可以在不同地方重复使用
- 维护性:组件独立,修改一个组件不会影响其他部分
- 协作开发:不同开发者可以同时开发不同组件
完整项目展示:
额外小知识
在讲解应用之前我想先分享一下我学习时遇到的小bug
,或者应该叫useState()
的机制。
以下是原码,App 只调用这个组件:
// 内置的hooks 函数
import{useState} from 'react'//导入useState钩子函数,用于管理组件状态
import './Todo.css'
import TodoFrom from './TodoFrom'
import Todos from './Todos'
function TodoList(){
const [title,setTitle]=useState('Todo List')
const [todos,setTodos]=useState([
{
id:1,
text:'1',
completed:false
}
])
setTimeout(()=>{
setTodos([...todos,{id:2,text:'2',completed:false}])
setTitle('Todo List 2')
},3000)
return(
<div className='container'>
<h1 className='title'>{ title } </h1>
{
todos.map(todo=>(
<li>{todo.text}</li>
))
}
</div>
)
}
export default TodoList;// 导出组件
为什么会这样呢?我们问问Trae:
原来组件每次更新或渲染时会重新执行整个函数体,setTimeout要慎用。
好,你每次都会重新执行函数是吧:
// 内置的hooks 函数
import{useState} from 'react'//导入useState钩子函数,用于管理组件状态
import './Todo.css'
import TodoFrom from './TodoFrom'
import Todos from './Todos'
function TodoList(){
const [num,setNum] = useState(0)
return(
<div className='container'>
<p>num:{num}</p>
<button onClick={()=>setNum(num+1)}>+</button>
</div>
)
}
export default TodoList;// 导出组件
???什么意思,我的num
初始值不是0
吗?你怎么加到2
的?如果每次刷新不应该一直是0+1
吗?
再次询问Trae:
哦,原来useState
的初始值仅在组件首次渲染时生效,后续渲染会保留状态的最新值,所以 num
不会在重新渲染时变回初始值 0
。
理解完成以上内容,我们再看看下面代码能发现什么?
// 内置的hooks 函数
import{useState} from 'react'//导入useState钩子函数,用于管理组件状态
import './Todo.css'
import TodoFrom from './TodoFrom'
import Todos from './Todos'
function TodoList(){
const [num,setNum] = useState(0)
const now = function(){
setTimeout(()=>{
console.log(num)
},3000)
}
return(
<div className='container'>
<p>num:{num}</p>
<div>
<button onClick={() => setNum(num + 1)}>+</button>
</div>
<button onClick={now}>nowState</button>
</div>
)
}
export default TodoList;// 导出组件
这里我是先点击+
,再点击nowState
,3 秒后输出1
,确实没问题。但是我第二次是先点击nowState
再点击+
,3 秒后输出还是1
,也就是说num确实更新了,但是更新的东西这次的setTimeout捕获不到。
得出结论:React 的状态更新是异步的,当调用 setNum
时,组件不会立即重新渲染,状态也不会马上更新。这意味着在 setTimeout
回调函数执行时,它获取到的 num
是旧状态值。
小结:react确实难学,一个useState就有这么多细化的知识点,而且我还没有全部探索完成。让我们继续构建应用吧。
三、TodoList组件拆分
让我们看看如何将一个TodoList应用拆分为合理的组件结构:
src/
├── components/
│ ├── TodoForm.jsx // 输入表单组件
│ ├── TodoList.jsx // 主容器组件
│ └── Todos.jsx // 列表展示组件
├── App.jsx // 根组件
└── Todo.css // 样式文件
1. TodoList主组件
作为容器组件,它管理着整个应用的状态和数据流:
import { useState } from 'react'
import './Todo.css'
import TodoForm from './TodoForm'
import Todos from './Todos'
function TodoList() {
const [title, setTitle] = useState('Todo List')
const [todos, setTodos] = useState([
{
id: 1,
text: '1',
completed: false
}
])
const handleAdd = (text) => {
setTodos([
...todos,
{
id: todos.length + 1,
text: text,
completed: false
}
])
}
return (
<div className='container'>
<h1 className='title'>{title}</h1>
<TodoForm onAdd={handleAdd} />
<Todos todos={todos} />
</div>
)
}
export default TodoList
这个组件使用了React的useState
钩子来管理三个状态:
title
:应用标题todos
:待办事项列表handleAdd
:添加新待办事项的方法
2. TodoForm表单组件
一开始我的TodoFrom代码:
import { useState } from 'react'
function TodoForm() {
const [text, setText] = useState('')
const handleSubmit = (e) => {
e.preventDefault()
console.log(e.target[0].value)
}
return (
<form onSubmit={handleSubmit}>
<input
type="text"
placeholder="请输入待办事项:"
value={text}
/>
<button type="submit">添加</button>
</form>
)
}
export default TodoForm
这里我并没有添加onChange
处理程序,所以无论我怎么动输入框都无法改变值,我没有让其响应我的输入数据。
所以找Trae 改改:
import { useState } from 'react'
function TodoFrom() {
const [text,setText] = useState('2')
// 处理输入框内容变化的函数
const handleChange = (e) => {
setText(e.target.value);
}
const handleSubmit = (e) =>{
// 阻止默认行为
// 由js 来控制
e.preventDefault();// event api 阻止默认行为
console.log(e.target[0].value)
}
return (
<form action="http://www.baidu.com" onSubmit={handleSubmit}>
{/* 添加 onChange 事件处理函数 */}
<input type="text" placeholder="请输入待办事项:" value={text} onChange={handleChange}/>
<button type="submit">添加</button>
</form>
)
}
export default TodoFrom;
但是还是有问题,它只会在输出面板输出该值,而不是加载到页面上,我总不能让用户自己点击检查来看看加载了没吧
。
页面展示不用多说,一定是要和TodoList
交互的,那这里就需要用到props
了。我们看看Trae的解释:它是一种从父组件向子组件传递信息的机制,让子组件能根据接收到的数据进行不同的渲染或执行相应的逻辑。
哦,所以TodoFrom
只管收集数据,剩下的展示交给TodoList
即可。
import { useState } from 'react'
function TodoForm(props) {
const [text, setText] = useState('')
const onAdd = props.onAdd
const handleSubmit = (e) => {
e.preventDefault()
onAdd(text)
setText('')
}
const handleChange = (e) => {
setText(e.target.value)
}
return (
<form onSubmit={handleSubmit}>
<input
type="text"
placeholder="请输入待办事项:"
value={text}
onChange={handleChange}
/>
<button type="submit">添加</button>
</form>
)
}
export default TodoForm
3. Todos列表组件
负责渲染待办事项列表:
function Todos(props) {
const todos = props.todos
return (
<ul>
{todos.map(todo => (
<li key={todo.id}>{todo.text}</li>
))}
</ul>
)
}
export default Todos
四、数据驱动与响应式更新
React最强大的特性之一就是它的响应式数据绑定。我们不再需要手动操作DOM来更新界面,只需要关心数据状态的变化。
数据状态管理
在TodoList
组件中,我们使用useState
来声明和管理状态:
const [todos, setTodos] = useState([
{
id: 1,
text: '1',
completed: false
}
])
当调用setTodos
更新状态时,React会自动比较新旧状态的差异,并高效地更新DOM。
数据流向
React中的数据流动是单向的,从父组件流向子组件:
TodoList
组件通过todos={todos}
将数据传递给Todos
组件TodoList
组件通过onAdd={handleAdd}
将添加方法传递给TodoForm
组件- 当用户在
TodoForm
中输入并提交时,调用onAdd
方法更新父组件的状态
这种单向数据流使得应用的状态变化更加可预测和易于调试。
五、组件通信模式
在这个TodoList应用中,我们看到了几种常见的组件通信方式:
- 父传子:通过props传递数据(如
todos
传递给Todos
组件) - 子传父:通过回调函数(如
onAdd
方法传递给TodoForm
) - 兄弟组件通信:通过共同的父组件(
TodoForm
和Todos
通过TodoList
通信)
对于更复杂的应用,我们可能会使用Context API或状态管理库如Redux。
六、React开发的核心思维
通过这个TodoList应用的开发,我们可以总结出React开发的几个核心思维:
- 组件化思维:将UI拆分为独立、可复用的组件
- 数据驱动:关注数据状态而非DOM操作
- 单向数据流:数据从父组件流向子组件
- 声明式编程:描述"UI应该是什么样子",而非"如何更新UI"
结语
React的组件化开发模式彻底改变了前端开发的方式。通过这个TodoList应用的开发,我们不仅学会了如何拆分组件、管理状态,更重要的是理解了数据驱动的开发理念。记住,在React中,UI只是数据状态的映射,我们只需要关心数据的变化,React会负责高效地更新界面。
希望这篇文章能帮助你入门React开发。如果你有任何问题或想法,欢迎在评论区留言讨论。接下来,你可以尝试为这个TodoList添加更多功能,比如编辑待办事项、添加分类标签等,来进一步巩固你的React技能。