React 组件基础与事件处理
🧩 React 组件基础与事件处理课程设计
1. 课程概述
本课程旨在系统讲解 React 中两个最核心的数据概念:Props(属性)和 State(状态),以及如何通过事件处理实现用户交互。理解它们的区别、用途和协作方式是掌握 React 开发的基础。学生将学会如何通过 Props 进行组件通信,如何使用 State 管理组件内部状态,并处理用户事件,最终构建出动态、交互性强的 React 组件。
2. 课程目标
目标类型 | 描述 |
---|---|
知识目标 | 理解 Props 是只读的、用于组件通信的数据。理解 State 是可变的、用于管理组件内部状态的数据。掌握 Props 和 State 的核心区别与适用场景。了解 React 事件处理的语法和特点,包括合成事件的概念。 |
技能目标 | 能熟练使用 Props 在组件间传递数据(包括函数)。能使用 useState Hook 在函数组件中定义和修改 State。能根据交互需求,为元素绑定事件处理函数并更新状态。能应用“状态提升”模式解决多个组件需要共享状态的场景。 |
3. 核心知识点详解与实例佐证
3.1 Props(属性):组件间通信的桥梁
-
核心概念:Props 是父组件向子组件传递数据的一种方式。Props 是只读的(read-only),子组件不能直接修改接收到的 Props,这保证了数据流的可预测性。
-
特点:
- 单向数据流:数据从父组件流向子组件。
- 可传递任何类型:可以是字符串、数字、数组、对象、甚至函数。
- 用于配置组件:通过不同的 Props,可以使组件展示不同的内容或拥有不同的行为。
-
代码示例:
// 1. 子组件 (ChildComponent.js) - 接收Props function WelcomeMessage(props) {return <h1>Hello, {props.name}! You are {props.age} years old.</h1>; }// 2. 父组件 (ParentComponent.js) - 传递Props function App() {const userName = "Alice";const userAge = 25;return (<div><WelcomeMessage name={userName} age={userAge} /><WelcomeMessage name="Bob" age={30} /></div>); }
3.2 State(状态):组件内部的状态管理
-
核心概念:State 是组件内部私有、可变的的数据。State 的变化会触发组件的重新渲染,从而使 UI 与数据保持同步。
-
特点:
- 局部性:State 是组件私有的,其他组件无法直接访问。
- 可变性:通过
setState
(类组件) 或 state setter 函数 (函数组件) 更新。 - 异步更新:出于性能考虑,React 可能会将多个
setState
调用合并为一次重新渲染。
-
代码示例(函数组件中使用 useState Hook):
import { useState } from 'react';function Counter() {// 声明一个名为 count 的 state 变量,初始值为 0// setCount 是用于更新 count 的函数const [count, setCount] = useState(0);const increment = () => {// 更新 state,触发重新渲染setCount(count + 1);};const decrement = () => {setCount(count - 1);};return (<div><p>Current Count: {count}</p><button onClick={increment}>+</button><button onClick={decrement}>-</button></div>); }
3.3 Props 与 State 的对比
为了更清晰地理解两者的区别,请看以下对比表:
特性 | Props | State |
---|---|---|
数据的来源 | 由父组件传递而来 | 在组件内部初始化和管理 |
可变性 | 只读,不可被接收它的子组件修改 | 可变,必须通过 setState 或 state setter 函数更新 |
用途 | 用于组件通信,从外部配置组件 | 用于管理组件内部的动态数据,响应交互 |
更新是否触发重渲染 | 父组件传递的 Props 变化时,子组件会重新渲染 | 组件自身 State 变化时,该组件会重新渲染 |
3.4 事件处理 (Event Handling)
-
核心概念:在 React 中处理用户交互(如点击、输入变化等)的事件处理函数。
-
特点:
- 命名:采用驼峰命名(如
onClick
,onChange
)。 - 绑定:在 JSX 中直接将函数分配给事件属性。
- 事件对象:React 封装了浏览器的原生事件对象,提供了跨浏览器的一致性,称为合成事件(Synthetic Event)。
- 命名:采用驼峰命名(如
-
代码示例:
function MyComponent() {const [inputValue, setInputValue] = useState('');const handleClick = () => {console.log('Button clicked!');};const handleChange = (event) => { // event 是合成事件对象setInputValue(event.target.value); // 更新 state 以反映输入值};const handleSubmit = (event) => {event.preventDefault(); // 阻止表单默认提交行为console.log('Form submitted with value:', inputValue);};return (<form onSubmit={handleSubmit}><input type="text" value={inputValue} onChange={handleChange} /><button type="button" onClick={handleClick}>Click Me</button><button type="submit">Submit</button></form>); }
3.5 状态提升 (Lifting State Up)
- 核心概念:当多个组件需要反映相同的变化数据时,建议将共享状态提升到它们最近的共同父组件中去管理,然后通过 props 向下传递。
- 为何需要:保持数据流的单向性和可预测性,便于管理和调试。
4. 综合实战案例:图片切换器
下面是一个融合了 Props、State 和事件处理的综合示例:
import { useState } from 'react';// 子组件:显示图片和标题,接收来自父组件的 props
function ImageViewer({ imageUrl, title, onNext, onPrevious, onToggleFavorite, isFavorite }) {return (<div className="image-viewer"><h2>{title}</h2><div className="image-container"><img src={imageUrl} alt={title} /></div><div className="controls"><button onClick={onPrevious}>Previous</button><button onClick={onNext}>Next</button><button onClick={onToggleFavorite}>{isFavorite ? 'Unfavorite' : 'Favorite'}</button></div></div>);
}// 父组件:管理状态和逻辑
function ImageGallery() {const [currentIndex, setCurrentIndex] = useState(0);const [favorites, setFavorites] = useState(new Set()); // 使用 Set 存储收藏图片的索引const images = [{ url: 'image1.jpg', title: 'Landscape' },{ url: 'image2.jpg', title: 'Portrait' },{ url: 'image3.jpg', title: 'Abstract' },];const handleNext = () => {setCurrentIndex((prevIndex) => (prevIndex + 1) % images.length); // 循环下一张};const handlePrevious = () => {setCurrentIndex((prevIndex) => (prevIndex - 1 + images.length) % images.length); // 循环上一张};const handleToggleFavorite = () => {setFavorites((prevFavorites) => {const newFavorites = new Set(prevFavorites);if (newFavorites.has(currentIndex)) {newFavorites.delete(currentIndex);} else {newFavorites.add(currentIndex);}return newFavorites;});};const currentImage = images[currentIndex];return (<div><ImageViewerimageUrl={currentImage.url}title={currentImage.title}onNext={handleNext}onPrevious={handlePrevious}onToggleFavorite={handleToggleFavorite}isFavorite={favorites.has(currentIndex)} // 通过 prop 告知子组件当前是否收藏/><p>Current Index: {currentIndex + 1} / {images.length}</p><p>Favorites: {favorites.size}</p></div>);
}
案例技能点解析:
- State 管理:
currentIndex
和favorites
状态存储在父组件ImageGallery
中。 - Props 传递:父组件将图片数据、事件处理函数和状态值通过 props 传递给子组件
ImageViewer
。 - 事件处理:子组件中的按钮点击触发父组件传递下来的事件处理函数(
onNext
,onPrevious
,onToggleFavorite
),这些函数更新父组件的状态。 - 状态提升:收藏状态和当前索引状态提升到父组件管理,以便多个子组件(未来可能扩展)可以共享和同步。
- 状态更新函数:使用函数式更新(例如
setCurrentIndex((prevIndex) => ...)
)确保基于最新状态更新。
5. 课后练习与挑战
-
基础练习:
- 创建一个
Button
组件,接收text
和onClick
两个 props,并在父组件中控制点击后的状态变化(如计数器)。 - 创建一个
UserCard
组件,接收一个包含用户信息(姓名、头像、简介)的user
prop 对象,并展示出来。
- 创建一个
-
进阶挑战:
- 构建一个简单的“待办事项列表”(Todo List)。
- 父组件管理一个
todos
的 state(数组)。 - 创建一个
TodoForm
组件,通过 props 接收一个onAddTodo
函数,用于向列表添加新项。 - 创建一个
TodoList
组件,通过 props 接收todos
数组和一个onToggleTodo
函数,用于渲染列表和切换某项的完成状态。
- 父组件管理一个
- 构建一个简单的“待办事项列表”(Todo List)。
-
思考题:
- 为什么 React 不允许子组件直接修改 props?这带来了哪些好处?
- 在哪些场景下你会选择使用状态提升?它解决了什么问题?
- 直接修改 state(例如
this.state.count = 5
)为什么是不允许的?setState
或 state setter 函数内部做了什么?