在前端开发领域,React 以其高效的组件化架构和灵活的数据管理机制备受青睐。本文将基于一个 React + Stylus + Vite 构建的 Todos 应用展开深入剖析,详细解读每一个组件的代码逻辑、核心概念以及它们之间的交互关系。
一、技术栈概述
React
React 是 Facebook 开发的用于构建用户界面的 JavaScript 库,它采用虚拟 DOM(Virtual Document Object Model)技术。虚拟 DOM 是真实 DOM 的轻量级抽象,React 通过对比虚拟 DOM 的变化,批量更新真实 DOM,从而显著提升性能。例如,当一个组件的状态发生变化时,React 会先在虚拟 DOM 上计算出需要更新的部分,然后一次性将这些变化应用到真实 DOM 上,避免了频繁直接操作真实 DOM 带来的性能损耗。
Stylus
Stylus 是一款功能强大的 CSS 预处理器,它允许开发者使用变量、嵌套规则、混合(Mixin)和函数等特性来编写 CSS。这使得 CSS 代码更具结构化和可维护性。例如,我们可以定义一个变量 $primary - color: #007BFF;,然后在整个样式表中使用这个变量来设置颜色,当需要修改主色调时,只需修改变量的值即可,无需逐个查找和替换颜色代码。
Vite
Vite 是新一代前端构建工具,它利用浏览器原生 ES 模块导入的特性,实现了快速的冷启动。在开发过程中,Vite 采用即时的模块热替换(HMR),当代码发生变化时,Vite 能精确地更新变化的部分,而无需重新加载整个页面,大大提升了开发效率。比如在修改一个组件的样式时,浏览器会即时显示修改后的效果,无需手动刷新页面。
二、组件解析
TodoInput 组件
javascript
import { useState } from'react'
const TodoInput = (props) => {
const { onAdd } = props;
const [inputValue, setInputValue] = useState('')
const handleSubmit = (e) => {
e.preventDefault();
onAdd(inputValue);
setInputValue('')
}
return (
<form className="todo - input" onSubmit={handleSubmit}>
<input type="text"
value={inputValue}
onChange={e => setInputValue(e.target.value)} />
<button type="submit">Add</button>
</form>
)
}
export default TodoInput
-
功能概述:此组件为用户提供一个输入框,用于输入待办事项,并将输入内容提交给父组件进行处理。
-
代码解析:
-
状态管理:
const [inputValue, setInputValue] = useState('')使用 React 的useStateHook 创建了一个名为inputValue的状态变量,初始值为空字符串。useState是 React 中用于在函数式组件中添加状态的重要工具,它返回一个数组,第一个元素是当前状态值,第二个元素是用于更新该状态值的函数。在这里,inputValue用于存储用户在输入框中输入的内容,setInputValue则用于更新这个状态。
-
属性接收:
const { onAdd } = props;通过对象解构从props中提取onAdd属性。在 React 中,props(properties 的缩写)是父组件向子组件传递数据和函数的主要方式。这里的onAdd是一个函数,由父组件传递过来,用于处理子组件提交的新任务。
-
事件处理:
const handleSubmit = (e) => { }定义了一个名为handleSubmit的函数,该函数会在表单提交时被触发。e.preventDefault();这行代码阻止了表单的默认提交行为。在 HTML 中,表单提交时会导致页面刷新,而在 React 应用中,我们通常不希望出现这种情况,因此需要阻止默认行为。onAdd(inputValue);调用从父组件传递过来的onAdd函数,并将当前输入框的值inputValue作为参数传递给它。这样,父组件就可以接收到新的待办事项并进行相应处理。setInputValue('')清空inputValue的状态,使输入框回到初始状态,为用户输入下一个待办事项做好准备。
-
渲染部分:
- 返回一个包含输入框和提交按钮的表单。
<input type="text" value={inputValue} onChange={e => setInputValue(e.target.value)} />这是一个文本输入框。value属性将输入框的值与inputValue状态绑定,实现单向绑定,即输入框的值始终反映inputValue的状态。onChange事件监听器在输入框内容发生变化时触发,它通过setInputValue(e.target.value)更新inputValue的状态,从而实现输入框内容与状态的同步。<button type="submit">Add</button>这是一个提交按钮,当用户点击该按钮时,会触发表单的submit事件,进而调用handleSubmit函数。
-
TodoList 组件
javascript
const TodoList = (props) => {
const { todos, onDelete, onToggle } = props;
return (
<ul className="todo - list">
{
todos.length === 0? (
<li className="empty">
No todos yet!
</li>
) : (
todos.map(todo => (
<li key={todo.id}
className={todo.completed? 'completed' : ''}
>
<label>
<input type="checkbox"
checked={todo.completed}
onChange={() => onToggle(todo.id)} />
<span>{todo.text}</span>
</label>
<button onClick={() => onDelete(todo.id)}>X</button>
</li>
))
)
}
</ul>
)
}
export default TodoList
-
功能概述:该组件负责展示待办事项列表,并为每个任务提供删除和切换完成状态的功能。
-
代码解析:
-
属性接收:
- 通过对象解构从
props中获取todos(待办事项数组)、onDelete(删除任务的函数)和onToggle(切换任务完成状态的函数)。这些属性由父组件传递过来,使TodoList组件能够获取到最新的待办事项数据以及相应的操作函数。
- 通过对象解构从
-
条件渲染:
todos.length === 0? ( ) : ( )使用三元运算符进行条件渲染。如果todos数组的长度为 0,说明没有待办事项,此时渲染<li className="empty">No todos yet!</li>,提示用户暂无待办事项。否则,遍历todos数组,为每个任务渲染一个列表项。
-
列表项渲染:
todos.map(todo => ( ) )使用数组的map方法遍历todos数组,为每个todo对象生成一个列表项。<li key={todo.id} className={todo.completed? 'completed' : ''}>每个列表项的key属性设置为任务的id,这在 React 中非常重要,它帮助 React 高效地识别和更新列表中的元素。className根据任务的completed状态动态添加completed类名,用于在样式上区分已完成和未完成的任务。<input type="checkbox" checked={todo.completed} onChange={() => onToggle(todo.id)} />这是一个复选框,checked属性根据任务的completed状态设置是否选中,实现单向绑定。onChange事件在复选框状态改变时触发,调用onToggle函数并传入当前任务的id,通知父组件切换该任务的完成状态。<span>{todo.text}</span>显示任务的文本内容。<button onClick={() => onDelete(todo.id)}>X</button>这是一个删除按钮,onClick事件在按钮点击时触发,调用onDelete函数并传入当前任务的id,通知父组件删除该任务。
-
TodoStats 组件
javascript
const TodoStats = (props) => {
const {
total,
active,
completed,
onClearCompleted,
} = props
return (
<div className="todo - stats">
<p>Total: {total}</p>
<p>Active: {active}</p>
<p>Completed: {completed}</p>
{completed > 0 && (
<button
onClick={onClearCompleted}
className="clear - btn"
>
Clear Completed
</button>
)}
</div>
)
}
export default TodoStats
-
功能概述:此组件用于展示待办事项的统计信息,包括总任务数、未完成任务数和已完成任务数,并在有已完成任务时提供一个按钮,用于清除所有已完成的任务。
-
代码解析:
-
属性接收:
- 通过对象解构从
props中获取total(总任务数)、active(未完成任务数)、completed(已完成任务数)以及onClearCompleted(清除已完成任务的函数)。这些属性由父组件传递过来,为组件提供展示和操作所需的数据和函数。
- 通过对象解构从
-
渲染部分:
<p>Total: {total}</p>、<p>Active: {active}</p>和<p>Completed: {completed}</p>分别显示总任务数、未完成任务数和已完成任务数。{completed > 0 && ( ) }使用逻辑与运算符进行条件渲染。当completed大于 0 时,说明存在已完成的任务,此时渲染清除已完成任务的按钮。<button onClick={onClearCompleted} className="clear - btn">Clear Completed</button>这是一个按钮,onClick事件在按钮点击时触发,调用onClearCompleted函数,通知父组件清除所有已完成的任务。className设置为clear - btn,用于在样式表中定义按钮的样式。
-
三、App 组件与组件通信
javascript
import { useState, useEffect } from'react'
import './styles/app.styl'
import TodoList from './components/TodoList'
import TodoInput from './components/Todoinput'
import TodoStats from './components/TodoStats'
function App() {
const [todos, setTodos] = useState(() => {
const saved = localStorage.getItem('todos')
return saved? JSON.parse(saved) : []
})
const addTodo = (text) => {
setTodos([...todos, {
id: Date.now(),
text,
completed: false
}])
}
const deleteTodo = (id) => {
setTodos(todos.filter(todo => todo.id!== id))
}
const toggleTodo = (id) => {
setTodos(todos.map(todo =>
todo.id === id? {...todo, completed:!todo.completed } : todo
))
}
const clearCompleted = () => {
setTodos(todos.filter(todo =>!todo.completed))
}
const activeCount = todos.filter(todo =>!todo.completed).length
const completedCount = todos.filter(todo => todo.completed).length
useEffect(() => {
localStorage.setItem('todos', JSON.stringify(todos))
}, [todos])
return (
<div className="todo - app">
<h1>My Todo List</h1>
<TodoInput onAdd={addTodo} />
<TodoList todos={todos} onDelete={deleteTodo}
onToggle={toggleTodo}
/>
<TodoStats
total={todos.length}
active={activeCount}
completed={completedCount}
onClearCompleted={clearCompleted}
/>
</div>
)
}
export default App
-
功能概述:
App组件是整个应用的核心,它负责管理应用的状态,包括待办事项列表,以及处理各个子组件之间的通信和交互。 -
状态管理与数据初始化:
-
useState与数据持久化:const [todos, setTodos] = useState(() => { } )使用useStateHook 创建了todos状态,用于存储所有待办事项。useState的初始值是一个函数,这是useState的一种高级用法,称为惰性初始化。在这个函数中,首先通过localStorage.getItem('todos')尝试从本地存储中获取已保存的待办事项列表。如果获取到了数据(saved不为null),则使用JSON.parse(saved)将其解析为 JavaScript 对象并返回;否则返回一个空数组。这样,应用在加载时可以从本地存储中恢复之前保存的待办事项数据,实现数据的持久化。
-
-
数据操作函数:
-
addTodo函数:const addTodo = (text) => { }定义了一个用于添加新待办事项的函数。它接收一个参数text,代表用户输入的新任务内容。setTodos([...todos, { id: Date.now(), text, completed: false }])使用展开运算符...将原有的todos数组和一个新的任务对象合并成一个新数组,并通过setTodos更新todos状态。新任务对象包含一个唯一的id(使用Date.now()获取当前时间戳作为id)、任务文本text和初始的完成状态completed: false。
-
deleteTodo函数:const deleteTodo = (id) => { }定义了一个用于删除待办事项的函数,接收要删除任务的id作为参数。setTodos(todos.filter(todo => todo.id!== id))使用数组的filter方法创建一个新数组,该数组过滤掉了id与传入id相等的任务对象,然后通过setTodos更新todos状态,从而实现删除指定任务的功能。
-
toggleTodo函数:const toggleTodo = (id) => { }定义了一个用于切换任务完成状态的函数,接收要切换状态的任务id作为参数。setTodos(todos.map(todo => todo.id === id? {...todo, completed:!todo.completed } : todo ))使用数组的map方法遍历todos数组。对于每个任务对象,如果其id与传入的id相等,则创建一个新的任务对象,将其completed状态取反;否则保持原任务对象不变。最后通过setTodos更新todos状态,实现切换指定任务完成状态的功能。
-
clearCompleted函数:const clearCompleted = () => { }定义了一个用于清除所有已完成任务的函数。setTodos(todos.filter(todo =>!todo.completed))使用数组的filter方法创建一个新数组,该数组只包含未完成的任务对象(即completed为false的任务),然后通过setTodos更新todos状态,实现清除所有已完成任务的功能。
-
-
统计信息计算:
-
未完成和已完成任务数计算:
const activeCount = todos.filter(todo =>!todo.completed).length通过filter方法过滤出completed为false的任务,然后获取其长度,得到未完成任务的数量。const completedCount = todos.filter(todo => todo.completed).length通过filter方法过滤出completed为true的任务,然后获取其长度,得到已完成任务的数量。这些统计信息将传递给TodoStats组件进行展示。
-
-
副作用操作:
-
useEffect与数据同步:useEffect(() => { localStorage.setItem('todos', JSON.stringify(todos)) }, [todos])使用useEffectHook 来执行副作用操作。useEffect会在组件挂载和更新后执行传入的回调函数。在这里,当todos状态发生变化时(依赖数组[todos]中的值发生变化),回调函数会将todos数组转换为 JSON 字符串,并通过localStorage.setItem('todos', JSON.stringify(todos))保存到本地存储中,确保本地存储中的数据与应用状态保持同步。
-
- 组件通信:
-
父子组件通信:
TodoInput组件通过onAdd={addTodo}将App组件中的addTodo函数传递给TodoInput组件,实现子组件向父组件提交新任务的功能。当用户在TodoInput组件中输入内容并提交时,会调用addTodo函数,将新任务添加到App组件的todos数组中。这体现了 React 中父组件通过向子组件传递函数,让子组件能够触发父组件状态更新的机制。TodoList组件通过todos={todos}接收App组件中的todos数组,从而展示最新的待办事项列表。同时,通过onDelete={deleteTodo}和onToggle={toggleTodo}接收App组件传递的deleteTodo和toggleTodo函数,使得TodoList组件中的删除和切换任务完成状态的操作能够更新App组件的todos状态。这展示了父组件向子组件传递数据和操作数据的函数,实现子组件与父组件状态同步的过程。TodoStats组件通过total={todos.length}、active={activeCount}、completed={completedCount}接收App组件计算出的待办事项统计信息,用于展示。并且通过onClearCompleted={clearCompleted}接收App组件的clearCompleted函数,实现当用户点击清除已完成任务按钮时,调用该函数更新App组件的todos状态。这体现了父组件向子组件传递数据和操作函数,以实现子组件展示和修改父组件状态相关功能。
-
兄弟组件通信:
TodoInput、TodoList和TodoStats作为兄弟组件,它们之间并不直接通信,而是通过共同的父组件App进行间接通信。App组件作为数据和逻辑的管理者,持有共享数据todos以及对这些数据进行操作的函数。App组件将数据和函数通过props传递给各个子组件,使得子组件之间能够通过影响父组件的状态来间接实现数据共享和交互。例如,TodoInput组件添加新任务,会改变App组件的todos状态,进而影响TodoList和TodoStats组件的展示;TodoList组件中删除任务或切换任务完成状态,同样会改变App组件的todos状态,从而影响TodoStats组件展示的统计信息。这种通过父组件进行数据和状态管理的方式,是 React 中实现兄弟组件通信的常见模式,有助于保持组件之间的低耦合性,提高代码的可维护性和扩展性。
-
通过对 React Todos 应用的全面且深入的剖析,我们详细了解了每个组件的功能、代码逻辑、状态管理以及组件间的通信机制。这不仅加深了我们对 React 核心概念和应用开发技巧的理解,也为构建更为复杂和强大的前端应用奠定了坚实的基础。无论是初学者学习 React 的基本原理,还是有经验的开发者深入研究组件通信和状态管理的最佳实践,这个案例都具有重要的参考价值。