React 组件定义、组合与传值课程设计
·
🧩 React 组件定义、组合与传值:课程设计
1. 课程概述
本课程旨在系统讲解 React 组件的定义方式、组合模式 以及组件间的数据传递方法。组件是 React 应用的基石,理解如何创建、组合组件并管理它们之间的数据流,是构建复杂且可维护用户界面的关键。学生将学会使用函数组件和类组件,掌握 Props 的多种传递技巧,并理解单向数据流在实践中的应用。
2. 课程目标
| 目标类型 | 描述 |
|---|---|
| 知识目标 | 理解 函数组件 和 类组件 的定义方式与区别。理解 Props 的概念、特性和使用方法(包括只读性)。掌握 组件组合 的思想与模式(包含关系、特殊化)。理解 单向数据流 和组件间通信的各种方式(父子、子父、兄弟、跨级)。 |
| 技能目标 | 能使用 函数组件 和 类组件 定义组件。能通过 Props 进行父到子的数据传递和方法传递。能通过 回调函数 实现子到父的数据传递。能使用 状态提升 实现兄弟组件间的通信。能使用 Context 进行跨层级组件的数据传递。能根据场景选择合适的组件通信方式。 |
3. 核心知识点与实例代码
3.1 React 组件定义
React 组件是构建用户界面的独立、可复用的单元,它接收输入(称为 “props”)并返回描述页面该部分内容的 React 元素。
函数组件 (Function Components)
函数组件是使用 JavaScript 函数定义的组件,它接收一个 props 对象作为参数,并返回一个 React 元素。
- 知识目标:理解函数组件的简洁性和无状态性(在 Hooks 之前)。
- 技能目标:能编写基本的函数组件,并能正确接收和使用 props。
// 定义一个简单的函数组件
function Welcome(props) {
return <h1>Hello, {props.name}!</h1>;
}
// 使用箭头函数定义
const Welcome = (props) => {
return <h1>Hello, {props.name}!</h1>;
};
// 使用解构赋值简化 props 获取
const Welcome = ({ name, age }) => {
return (
<div>
<h1>Hello, {name}!</h1>
<p>You are {age} years old.</p>
</div>
);
};
// 使用组件
function App() {
return (
<div>
<Welcome name="Alice" age={25} />
<Welcome name="Bob" age={30} />
</div>
);
}
类组件 (Class Components)
类组件是使用 ES6 class 定义的组件,它继承自 React.Component,并且必须包含一个 render() 方法。
- 知识目标:理解类组件可以拥有本地状态(state)和生命周期方法。
- 技能目标:能编写基本的类组件,能在
render()方法中返回 JSX,并能通过this.props访问传入的属性。
// 定义一个类组件
class Welcome extends React.Component {
render() {
// 使用 this.props 访问传入的属性
return <h1>Hello, {this.props.name}!</h1>;
}
}
// 使用组件(与函数组件相同)
function App() {
return (
<div>
<Welcome name="Alice" />
<Welcome name="Bob" />
</div>
);
}
函数组件 vs. 类组件
为了更清晰地理解两者的区别和选择,请看下表:
| 特性 | 函数组件 | 类组件 |
|---|---|---|
| 语法 | JavaScript 函数 | ES6 Class |
| 状态管理 | 需使用 Hooks (如 useState) |
使用 this.state 和 setState |
| 生命周期 | 需使用 Hooks (如 useEffect) |
使用生命周期方法 (如 componentDidMount) |
| 代码简洁性 | 更简洁,逻辑更集中 | 相对繁琐,需要绑定 this 等操作 |
| 学习曲线 | 较平缓,易于理解 | 较陡峭,需理解 Class 和 this |
| 推荐使用 | React 16.8+ 后的主流方式 | 旧项目维护,或需要 Error Boundaries 时 |
3.2 组件组合 (Component Composition)
组件组合是 React 的核心思想之一,它通过将多个小组件嵌套组合来构建复杂的 UI,而不是通过继承。
- 知识目标:理解“组合优于继承”的设计原则,理解如何使用
childrenprop 和自定义 props 来实现组合。 - 技能目标:能设计可组合的组件结构,能利用
children实现内容的灵活嵌套。
// 1. 使用 children prop 实现包含关系
function Card({ title, children }) {
return (
<div className="card">
<h2>{title}</h2>
<div className="card-content">{children}</div> {/* 子内容在此渲染 */}
</div>
);
}
function App() {
return (
<Card title="用户信息">
<p>姓名:张三</p> {/* 这些元素就是 children */}
<p>年龄:25</p>
</Card>
);
}
// 2. 使用自定义 props 实现特殊化
function ContactCard({ contact }) {
return (
<Card title="联系方式">
<p>电话:{contact.phone}</p>
<p>邮箱:{contact.email}</p>
</Card>
);
}
3.3 组件传值 (Component Communication)
数据在 React 组件中沿着组件树自上而下单向流动。以下是常见的传值场景。
父组件向子组件传递数据 (Props Down)
父组件通过 props 将数据传递给子组件,这是 React 中最基本、最常用的通信方式。
- 知识目标:理解 Props 是只读的,子组件不能直接修改接收到的 Props。
- 技能目标:能熟练地在父组件中传递 props,在子组件中接收和使用 props。
// 父组件
function ParentComponent() {
const user = { name: 'Alice', age: 25 };
const greeting = "Hello from Parent!";
return (
<div>
{/* 传递字符串、对象、数字等任何类型的数据 */}
<ChildComponent
message={greeting}
userInfo={user}
score={100}
/>
</div>
);
}
// 子组件
function ChildComponent(props) {
// 可以通过 props 对象访问
return (
<div>
<p>{props.message}</p>
<p>Name: {props.userInfo.name}, Age: {props.userInfo.age}</p>
</div>
);
}
// 或者在函数参数中直接解构
function ChildComponent({ message, userInfo, score }) {
return (
<div>
<p>{message}</p>
<p>Name: {userInfo.name}, Age: {userInfo.age}</p>
<p>Score: {score}</p>
</div>
);
}
子组件向父组件传递数据 (Callback Up)
子组件通过调用父组件传递下来的回调函数,将数据作为参数传递给父组件。
- 知识目标:理解数据反向流动的模式,理解回调函数的作用。
- 技能目标:能在父组件中定义回调函数并通过 props 传递给子组件,能在子组件中调用该回调并传递数据。
// 父组件
function ParentComponent() {
const [dataFromChild, setDataFromChild] = useState('');
// 定义回调函数,用于接收子组件数据
const handleDataFromChild = (childData) => {
setDataFromChild(childData);
console.log('Received from child:', childData);
};
return (
<div>
<p>Data from child: {dataFromChild}</p>
{/* 将回调函数传递给子组件 */}
<ChildComponent onSendData={handleDataFromChild} />
</div>
);
}
// 子组件
function ChildComponent({ onSendData }) {
const handleClick = () => {
// 调用父组件传递下来的回调函数,并传递数据
onSendData('Hello from Child Component!');
};
return (
<div>
<button onClick={handleClick}>Send Data to Parent</button>
</div>
);
}
兄弟组件间通信 (State Lifting Up)
兄弟组件之间不能直接传递数据,需要通过提升状态到它们共同的父组件来实现共享。
- 知识目标:理解“状态提升”的概念和必要性。
- 技能目标:能将需要共享的状态提升至最近的共同父组件,并通过 props 向下传递和回调函数向上传递来实现兄弟组件间的同步。
// 共同的父组件
function ParentComponent() {
// 将状态提升到父组件
const [sharedData, setSharedData] = useState('');
// 用于更新状态的回调函数
const handleDataChange = (newData) => {
setSharedData(newData);
};
return (
<div>
{/* BrotherA 可以修改状态 */}
<BrotherA onDataChange={handleDataChange} />
{/* BrotherB 可以显示状态 */}
<BrotherB sharedData={sharedData} />
</div>
);
}
// 兄弟组件A(负责发送数据)
function BrotherA({ onDataChange }) {
const [inputValue, setInputValue] = useState('');
const sendData = () => {
onDataChange(inputValue);
};
return (
<div>
<input
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
/>
<button onClick={sendData}>Update Brother B</button>
</div>
);
}
// 兄弟组件B(负责接收并显示数据)
function BrotherB({ sharedData }) {
return (
<div>
<p>Message from Brother A: {sharedData}</p>
</div>
);
}
跨层级组件通信 (Context API)
对于需要跨越多层组件传递数据的场景(如主题、用户信息等),使用 Context 可以避免“prop drilling”(逐层传递 props)的麻烦。
- 知识目标:理解 Context 的应用场景(全局数据共享)。
- 技能目标:能使用
createContext,Provider,useContext来创建和消费 Context。
// 1. 创建一个 Context
const UserContext = React.createContext(); // 可以提供一个默认值
// 2. 在顶层组件提供数据(Provider)
function App() {
const [currentUser, setCurrentUser] = useState({ name: 'John', id: 1 });
return (
// 使用 Provider 包裹组件树,并传递 value
<UserContext.Provider value={{ currentUser, setCurrentUser }}>
<Header />
<Sidebar />
<MainContent />
</UserContext.Provider>
);
}
// 3. 在底层任何组件消费数据(useContext)
function Header() {
// 使用 useContext Hook 获取 Context 的值
const { currentUser } = useContext(UserContext);
return (
<header>
<p>Welcome, {currentUser.name}!</p>
</header>
);
}
// 另一个深层级的组件也可以直接获取
function SomeNestedComponent() {
const { setCurrentUser } = useContext(UserContext);
const handleLogout = () => {
setCurrentUser(null);
};
return <button onClick={handleLogout}>Logout</button>;
}
4. 综合实战案例:用户评论列表
下面是一个融合了组件定义、组合和各种传值方式的综合示例:
import React, { useState, useContext } from 'react';
// 创建一个 Context 用于主题切换
const ThemeContext = React.createContext();
// 主应用组件
function App() {
const [theme, setTheme] = useState('light');
const [comments, setComments] = useState([
{ id: 1, text: 'Great post!', author: 'Alice' },
{ id: 2, text: 'Thanks for sharing.', author: 'Bob' }
]);
// 添加新评论的函数(状态提升到App)
const addComment = (commentText, author) => {
const newComment = {
id: Date.now(),
text: commentText,
author: author || 'Anonymous'
};
setComments([...comments, newComment]);
};
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
<div className={`app ${theme}`}>
<Header />
{/* CommentForm 接收一个回调函数 onAddComment */}
<CommentForm onAddComment={addComment} />
{/* CommentList 接收 comments 数组作为 prop */}
<CommentList comments={comments} />
</div>
</ThemeContext.Provider>
);
}
// Header 组件(消费 ThemeContext)
function Header() {
const { theme, setTheme } = useContext(ThemeContext);
const toggleTheme = () => {
setTheme(prevTheme => prevTheme === 'light' ? 'dark' : 'light');
};
return (
<header>
<h1>React Comment App</h1>
<button onClick={toggleTheme}>
Switch to {theme === 'light' ? 'Dark' : 'Light'} Mode
</button>
</header>
);
}
// 评论表单组件(子传父)
function CommentForm({ onAddComment }) {
const [text, setText] = useState('');
const [author, setAuthor] = useState('');
const handleSubmit = (e) => {
e.preventDefault();
if (text.trim()) {
onAddComment(text, author); // 调用父组件传递的回调
setText(''); // 清空输入框
setAuthor('');
}
};
return (
<form onSubmit={handleSubmit}>
<div>
<label>
Your Name:
<input
type="text"
value={author}
onChange={(e) => setAuthor(e.target.value)}
placeholder="Optional"
/>
</label>
</div>
<div>
<label>
Comment:
<textarea
value={text}
onChange={(e) => setText(e.target.value)}
required
/>
</label>
</div>
<button type="submit">Add Comment</button>
</form>
);
}
// 评论列表组件(父传子)
function CommentList({ comments }) {
return (
<div className="comment-list">
<h2>Comments ({comments.length})</h2>
{comments.length === 0 ? (
<p>No comments yet.</p>
) : (
comments.map(comment => (
<CommentItem key={comment.id} comment={comment} />
))
)}
</div>
);
}
// 评论项组件(接收单个 comment 对象作为 prop)
function CommentItem({ comment }) {
return (
<div className="comment-item">
<strong>{comment.author}:</strong>
<p>{comment.text}</p>
</div>
);
}
export default App;
案例技能点解析:
- 组件定义:使用了函数组件(
App,Header,CommentForm,CommentList,CommentItem)。 - 组件组合:
App组件组合了多个子组件,CommentList又组合了多个CommentItem。 - 父传子 (Props Down):
App向CommentList传递comments数组;CommentList向每个CommentItem传递comment对象。 - 子传父 (Callback Up):
App向CommentForm传递addComment回调函数,CommentForm在提交表单时调用它来向上传递新的评论数据。 - Context:
ThemeContext被App提供,并被Header组件消费,用于切换主题,避免了通过 props 层层传递theme和setTheme。 - 状态提升:评论数据
comments和更新函数setComments被提升到共同的父组件App中管理,使得CommentForm和CommentList可以共享和同步状态。
5. 课后练习与思考
-
基础练习:
- 创建一个
Button组件,接收text(按钮文字)和onClick(点击回调)两个 props。 - 创建一个
UserCard组件,接收一个包含用户信息(name,avatar,bio)的对象作为 prop,并展示出来。
- 创建一个
-
进阶挑战:
- 构建一个简单的“待办事项列表”(Todo List)。
App组件管理一个todos数组状态。TodoForm组件通过回调函数向App添加新任务。TodoList组件接收todos数组和另一个回调函数onToggleTodo,用于渲染列表和切换某项的完成状态。
- 使用 Context 实现一个全局的主题切换功能,让应用中的多个组件都能根据主题改变样式。
- 构建一个简单的“待办事项列表”(Todo List)。
-
思考题:
- 为什么 React 强调“组合优于继承”?组合模式带来了哪些好处?
- 在什么情况下你会选择使用 Context API?它又能解决什么问题?
- 函数组件和类组件的主要区别是什么?在现代 React 开发中,更推荐使用哪种?为什么?
更多推荐



所有评论(0)