React Props和Hooks详解
引言
想象您正在用乐高积木搭建东西:
组件是您单独的乐高积木(<Button />、<Profile />、<Header />)
Props是您给积木的指令
Hooks是您可以给积木的特殊能力
第一部分:Props(组件间如何通信)
核心概念
Props("properties"的缩写)用于将数据从父组件传递给子组件。这是一条单行道:数据只能向下流动。
类比:在餐厅点餐
您(父组件):您是点披萨的顾客
厨师(子组件):厨师是<Pizza />组件
您的订单(Props):您告诉厨师,"我想要一个大的披萨,加意大利辣香肠和额外奶酪。"这些具体指令——size: 'large'、topping: 'pepperoni'、extraCheese: true——就是props
厨师(<Pizza />)接收这些指令并按照您的要求制作披萨。厨师不能改变您的订单;他们只能读取并执行。这是props最重要的规则。
Props的关键特性
只读:子组件永远不能改变它接收到的props。它只能使用它们
数据向下流动:数据从父组件传递给子组件,再到孙组件,以此类推。子组件不能将props"向上"传递给父组件
它们是配置:Props是您配置和自定义可重用组件的方式
简单代码示例
让我们创建一个可重用的WelcomeMessage组件:
- // 这是我们的"厨师"组件。它是可重用的。
- // 它期望接收一个`name` prop。
- function WelcomeMessage({ name }) { // 我们"解构"props来直接获取name
- return (
- <h1>你好,{name}!欢迎来到我们的应用。h1>
- );
- }
- // 这是我们的"顾客"或父组件。
- function App() {
- return (
- <div>
- {/* 我们使用WelcomeMessage组件并给它props */}
- <WelcomeMessage name="张三" />
- <WelcomeMessage name="李四" />
- <WelcomeMessage name="王五" />
- div>
- );
- }
这里发生了什么?
App组件是父组件
它渲染WelcomeMessage组件三次
每次,它传递不同的name prop
WelcomeMessage组件接收那个name并显示它。我们创建了一个可重用的"模板",每次使用props进行不同配置
第二部分:Hooks(给组件记忆和超能力)
核心概念
在hooks之前,如果您想让组件有自己的内部"记忆"(状态)或在某事发生时做某事(生命周期事件),您必须使用繁琐的"类组件"。Hooks让您可以在简单、干净的"函数组件"中做所有这些事情。
Hooks是总是以单词use开头的特殊函数(例如,useState、useEffect)。
让我们专注于两个最重要的hooks。
1. useState(记忆Hook)
类比:个人白板
想象您的组件旁边有一个小白板:
useState hook给您的组件这个白板
组件可以在板上写东西(初始状态)
它可以看板上有什么(当前状态值)
它得到一个特殊的魔法笔,这是唯一更新板上内容的方式(setter函数)
当组件使用其魔法笔更新白板时,React知道某些东西已经改变,并自动重新渲染组件以显示新信息。
简单代码示例
让我们构建一个简单的计数器:
- import React, { useState } from 'react';
- function Counter() {
- // 1. 给我们的组件一个叫'count'的"白板"(状态)。
- // 初始值是0。
- // 我们得到当前值(`count`)和魔法笔(`setCount`)。
- const [count, setCount] = useState(0);
- return (
- <div>
- {/* 2. 显示白板上的当前值 */}
- <p>您点击了 {count} 次p>
- {/* 3. 当这个按钮被点击时,使用魔法笔更新白板 */}
- <button onClick={() => setCount(count + 1)}>
- 点击我
- button>
- div>
- );
- }
这里发生了什么?
useState(0)设置状态。count变量从0开始
组件渲染,显示"您点击了 0 次"
当您点击按钮时,onClick函数调用setCount(count + 1)
React看到状态被更新了。它重新运行Counter函数
这次,count变量是1
组件再次渲染,显示"您点击了 1 次"
2. useEffect(动作Hook)
类比:"在____发生之后,做这个。"
useEffect允许您的组件执行"副作用"——与React外部世界交互的动作。这包括:
在组件首次出现后从API获取数据
设置定时器
手动更改文档标题
它接受两个参数:
要运行的函数("effect")
依赖项数组("触发器")
依赖项数组告诉useEffect何时运行effect:
[](空数组):只在组件首次在屏幕上渲染后运行一次这个effect(非常适合初始数据获取)
[someVariable](带变量的数组):每次someVariable改变时运行这个effect
根本没有数组:在每次渲染后运行(这通常是一个错误,可能导致无限循环)
简单代码示例
让我们在Counter组件的计数改变时在控制台记录一条消息:
- import React, { useState, useEffect } from 'react';
- function CounterWithEffect() {
- const [count, setCount] = useState(0);
- // 这是我们的"动作Hook"
- useEffect(() => {
- // 这是effect:在控制台记录一条消息
- console.log(`新的计数是:${count}`);
- // 这是触发器:每当`count`改变时运行effect
- }, [count]);
- // 这个effect只在组件首次渲染后运行一次
- useEffect(() => {
- console.log('组件已经挂载!');
- }, []);
- return (
- <div>
- <p>您点击了 {count} 次p>
- <button onClick={() => setCount(count + 1)}>
- 点击我
- button>
- div>
- );
- }
这里发生了什么?
当组件首次渲染时,第二个useEffect运行,您会在控制台看到"组件已经挂载!"。第一个useEffect也运行,打印"新的计数是:0"
当您点击按钮时,调用setCount
状态改变,所以组件重新渲染
因为count改变了,第一个useEffect的触发器被激活,它再次运行,打印"新的计数是:1"。第二个useEffect不会再次运行,因为它的依赖项数组是空的
总结:Props vs Hooks
|
概念
|
Props
|
Hooks (useState, useEffect)
|
|
目的
|
将数据从父组件传递给子组件
|
给组件内部记忆和动作
|
|
数据流
|
自上而下(单向)
|
内部(管理自己的数据)
|
|
类比
|
配置/指令(在餐厅点餐)
|
记忆/能力(个人白板)
|
|
可以改变吗?
|
不能。子组件只读。
|
可以。通过其setter函数改变(例如,setCount)。
|
|
示例
|
<UserCard name="张三" />
|
const [count, setCount] = useState(0);
|
Props和Hooks一起工作。父组件可能使用useEffect获取数据,使用useState将其存储在状态中,然后将该数据的部分作为props传递给子组件。
实际应用示例
让我们看一个结合使用Props和Hooks的完整示例:
- import React, { useState, useEffect } from 'react';
- // 子组件:接收props
- function UserCard({ user, onUserClick }) {
- return (
- <div onClick={() => onUserClick(user.id)}>
- <h3>{user.name}h3>
- <p>{user.email}p>
- div>
- );
- }
- // 父组件:使用hooks管理状态
- function UserList() {
- const [users, setUsers] = useState([]);
- const [loading, setLoading] = useState(true);
- // 使用useEffect获取数据
- useEffect(() => {
- fetchUsers();
- }, []);
- const fetchUsers = async () => {
- try {
- const response = await fetch('https://api.example.com/users');
- const data = await response.json();
- setUsers(data);
- } catch (error) {
- console.error('获取用户失败:', error);
- } finally {
- setLoading(false);
- }
- };
- const handleUserClick = (userId) => {
- console.log('用户被点击:', userId);
- };
- if (loading) {
- return 加载中...;
- }
- return (
-
- {users.map(user => (
- key={user.id}
- user={user}
- onUserClick={handleUserClick}
- />
- ))}
- );
- }
最佳实践
Props最佳实践
保持props简单:避免传递复杂的对象
使用prop-types:验证props类型
提供默认值:使用默认参数
- function Button({ text = '点击', onClick, disabled = false }) {
- return (
- <button onClick={onClick} disabled={disabled}>
- {text}
- button>
- );
- }
Hooks最佳实践
只在顶层调用hooks:不要在循环、条件或嵌套函数中调用
使用依赖项数组:避免无限循环
清理副作用:在useEffect中返回清理函数
- useEffect(() => {
- const timer = setInterval(() => {
- console.log('定时器运行');
- }, 1000);
- // 清理函数
- return () => clearInterval(timer);
- }, []);
总结
Props和Hooks是React开发的核心概念:
Props让组件之间能够通信,传递数据和回调函数
Hooks让函数组件拥有状态和副作用能力
两者结合使用,可以构建强大而灵活的React应用
掌握这些概念将帮助您构建更好的React组件和更复杂的应用程序。
