跳到主要内容

核心知识点

1. JSX (JavaScript XML)

说明

JSX 是 JavaScript 的一种语法扩展,它允许我们在 JavaScript 代码中编写类似 HTML 的结构。React 推荐使用 JSX 来描述用户界面 (UI) 的外观。JSX 最终会被 Babel 等编译器转换为 React.createElement() 的函数调用,这些调用会创建 React 元素(描述 UI 的普通 JavaScript 对象)。使用 JSX 可以让代码更具可读性,并且在编写 UI 结构时更直观。

示例代码

import React from "react";

function Greeting(props) {
const userName = props.name || "Guest";
// 使用 JSX 语法
const element = <h1 className="greeting">Hello, {userName}!</h1>;
return element;

// 上述 JSX 等同于以下的 React.createElement 调用:
// return React.createElement(
// 'h1',
// { className: 'greeting' },
// 'Hello, ',
// userName,
// '!'
// );
}

function App() {
return (
<div>
<Greeting name="Alice" />
<Greeting /> {/* 使用默认值 Guest */}
</div>
);
}

export default App;

2. Components (组件)

说明

组件是 React 应用的核心构建块。它们允许你将 UI 拆分成独立、可复用的代码片段,并且可以对每个部分进行单独管理。组件本质上是 JavaScript 函数或类,它们接收输入(称为 props)并返回 React 元素,描述了屏幕上应该显示的内容。

  • 函数组件 (Functional Components): 使用 JavaScript 函数定义的组件。这是现代 React 中推荐的方式,特别是结合 Hooks 使用时,代码更简洁。
  • 类组件 (Class Components): 使用 ES6 Class 语法定义的组件。它们提供了更多的特性,如生命周期方法和内部状态 state(尽管 Hooks 现在也能在函数组件中实现这些)。

组件组合 (Composition): 组件可以将其他组件包含在它们的输出中。这使得我们可以创建复杂的 UI,同时保持各个部分的独立性。

示例代码

函数组件

import React from "react";

// 一个简单的函数组件
function WelcomeMessage(props) {
return <p>Welcome, {props.user}!</p>;
}

// App 组件组合了 WelcomeMessage 组件
function App() {
return (
<div>
<h1>My Application</h1>
<WelcomeMessage user="Bob" />
<WelcomeMessage user="Charlie" />
</div>
);
}

export default App;

类组件

import React, { Component } from "react";

// 一个简单的类组件
class Clock extends Component {
constructor(props) {
super(props);
// 类组件通过 this.state 管理内部状态
this.state = { date: new Date() };
}

// 类组件的生命周期方法示例
componentDidMount() {
this.timerID = setInterval(() => this.tick(), 1000);
}

componentWillUnmount() {
clearInterval(this.timerID);
}

tick() {
// 通过 this.setState 更新状态
this.setState({ date: new Date() });
}

render() {
// render 方法返回要渲染的 React 元素
return (
<div>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}

// App 组件组合了 Clock 组件
class App extends Component {
render() {
return (
<div>
<h1>Class Component Example</h1>
<Clock />
</div>
);
}
}

export default App;

3. Props (属性)

说明

Props(properties 的缩写)是组件之间传递数据的主要方式,通常是从父组件传递到子组件。Props 对于子组件来说是只读的,子组件不应该直接修改接收到的 props。这种单向数据流(从父到子)使得应用的数据流向更清晰、更易于调试。Props 可以是任何 JavaScript 值,包括字符串、数字、布尔值、数组、对象甚至函数。

示例代码

import React from "react";

// 子组件 Button,接收来自父组件的 text 和 onClick 函数作为 props
function Button(props) {
return <button onClick={props.onClick}>{props.text}</button>;
}

// 父组件 App,定义数据和函数,并通过 props 传递给 Button 组件
function App() {
const handleClick = () => {
alert("Button clicked!");
};

const buttonText = "Click Me";

return (
<div>
<h1>Props Example</h1>
<Button text={buttonText} onClick={handleClick} />
<Button text="Another Button" onClick={() => alert("Second button!")} />
</div>
);
}

export default App;

4. State (状态)

说明

State 是组件内部私有的、可变的数据存储。当组件的 state 发生变化时,React 会自动重新渲染该组件及其子组件,以反映最新的状态。State 主要用于处理那些会随时间或用户交互而变化的数据。

  • 函数组件: 使用 useState Hook 来声明和管理 state。
  • 类组件: 使用 this.state 属性来存储 state,并使用 this.setState() 方法来更新 state(永远不要直接修改 this.state)。setState 是异步的,它会触发组件的重新渲染。

示例代码

函数组件 (使用 useState)

import React, { useState } from "react";

function Counter() {
// 声明一个名为 count 的 state 变量,初始值为 0
// useState 返回一个数组:[当前 state 值, 更新 state 的函数]
const [count, setCount] = useState(0);
const [text, setText] = useState("");

const increment = () => {
setCount((currentCount) => currentCount + 1); // 使用函数形式更新,避免闭包问题
};

const handleInputChange = (event) => {
setText(event.target.value);
};

return (
<div>
<p>You clicked {count} times</p>
<button onClick={increment}>Increment</button>
<button onClick={() => setCount(0)}>Reset</button>

<input
type="text"
value={text}
onChange={handleInputChange}
placeholder="Type something"
/>
<p>Current text: {text}</p>
</div>
);
}

export default Counter;

类组件 (使用 this.statesetState)

import React, { Component } from "react";

class Timer extends Component {
constructor(props) {
super(props);
// 在构造函数中初始化 state
this.state = { seconds: 0 };
}

componentDidMount() {
this.interval = setInterval(() => {
// 使用 setState 更新 state
// 注意:基于前一个 state 更新时,推荐使用函数形式
this.setState((prevState) => ({
seconds: prevState.seconds + 1,
}));
}, 1000);
}

componentWillUnmount() {
clearInterval(this.interval);
}

render() {
return <div>Seconds elapsed: {this.state.seconds}</div>;
}
}

export default Timer;

5. Lifecycle Methods (生命周期方法) - 主要针对类组件

说明

类组件拥有一系列特殊的“生命周期方法”,允许开发者在组件的不同阶段(挂载、更新、卸载)执行自定义逻辑。

  • Mounting (挂载): 组件实例被创建并插入 DOM。
    • constructor(): 初始化 state、绑定事件处理函数。
    • static getDerivedStateFromProps(): 在 render 前调用,用于根据 props 更新 state(少用)。
    • render(): 返回 UI 描述(React 元素)。这是必须的方法。
    • componentDidMount(): 组件成功挂载(插入 DOM 树)后调用。适合执行网络请求、设置订阅、与 DOM 交互。
  • Updating (更新): 组件因 props 或 state 改变而重新渲染。
    • static getDerivedStateFromProps(): 同上。
    • shouldComponentUpdate(nextProps, nextState): 决定是否需要重新渲染,用于性能优化(少用,可用 React.PureComponentReact.memo 代替)。
    • render(): 重新渲染 UI。
    • getSnapshotBeforeUpdate(prevProps, prevState): 在最近一次渲染输出提交到 DOM 前调用。它使得组件能在发生更改之前从 DOM 中捕获一些信息(例如,滚动位置)。返回值将作为 componentDidUpdate 的第三个参数。
    • componentDidUpdate(prevProps, prevState, snapshot): 更新发生后立即调用。适合在 props 或 state 变化后执行 DOM 操作或网络请求(注意避免无限循环)。
  • Unmounting (卸载): 组件从 DOM 中移除。
    • componentWillUnmount(): 在组件卸载及销毁之前立即调用。适合执行清理操作,如清除定时器、取消网络请求、移除事件监听器或订阅。
  • Error Handling (错误处理):
    • static getDerivedStateFromError(error): 渲染阶段出错时调用,用于渲染备用 UI。
    • componentDidCatch(error, info): 子组件抛出错误后调用,用于记录错误信息。

注意: 函数组件使用 useEffect Hook 来模拟大部分生命周期行为。

示例代码 (类组件)

import React, { Component } from "react";

class LifecycleDemo extends Component {
constructor(props) {
super(props);
this.state = { count: 0, message: "Initial Message", data: null };
console.log("LifecycleDemo: Constructor");
}

static getDerivedStateFromProps(props, state) {
console.log("LifecycleDemo: getDerivedStateFromProps");
// 示例:如果 props 传入了 initialCount,更新 state count (不常见)
// if (props.initialCount && state.count === 0) {
// return { count: props.initialCount };
// }
return null; // 通常返回 null 表示不更新 state
}

componentDidMount() {
console.log("LifecycleDemo: componentDidMount");
// 模拟数据获取
setTimeout(() => {
console.log("LifecycleDemo: Data fetched");
this.setState({ data: "Fetched Data!" });
}, 2000);
document.addEventListener("click", this.handleDocumentClick);
}

shouldComponentUpdate(nextProps, nextState) {
console.log("LifecycleDemo: shouldComponentUpdate");
// 示例:只有 count 变化时才更新 (简单优化)
// return nextState.count !== this.state.count || nextState.data !== this.state.data;
return true; // 默认总是更新
}

getSnapshotBeforeUpdate(prevProps, prevState) {
console.log("LifecycleDemo: getSnapshotBeforeUpdate");
// 示例:如果 count 变化了,可以记录之前的 DOM 状态
if (prevState.count < this.state.count) {
// 假设有一个可滚动的列表
// const list = document.getElementById('myList');
// return list.scrollHeight - list.scrollTop;
}
return null;
}

componentDidUpdate(prevProps, prevState, snapshot) {
console.log("LifecycleDemo: componentDidUpdate");
if (prevState.count !== this.state.count) {
console.log(
`Count changed from ${prevState.count} to ${this.state.count}`
);
// 可以在这里根据 state 变化执行操作
// 如果 snapshot 不为 null,可以使用它
if (snapshot !== null) {
// const list = document.getElementById('myList');
// list.scrollTop = list.scrollHeight - snapshot;
}
}
if (prevState.data === null && this.state.data !== null) {
console.log("Data has been loaded.");
}
}

componentWillUnmount() {
console.log("LifecycleDemo: componentWillUnmount");
// 清理操作
document.removeEventListener("click", this.handleDocumentClick);
// 清除定时器、取消订阅等
}

// 错误边界示例 (通常放在父组件)
// static getDerivedStateFromError(error) {
// return { hasError: true };
// }
// componentDidCatch(error, info) {
// console.error("Error caught:", error, info);
// }

incrementCount = () => {
this.setState((state) => ({ count: state.count + 1 }));
};

handleDocumentClick = () => {
console.log("Document clicked (listener in LifecycleDemo)");
};

render() {
console.log("LifecycleDemo: Render");
// if (this.state.hasError) { return <h1>Something went wrong.</h1>; }

return (
<div>
<h2>Lifecycle Demo</h2>
<p>Count: {this.state.count}</p>
<p>Data: {this.state.data || "Loading..."}</p>
<button onClick={this.incrementCount}>Increment</button>
</div>
);
}
}

export default LifecycleDemo;

6. Event Handling (事件处理)

说明

React 处理事件的方式与原生 DOM 类似,但存在一些语法上的差异:

  • React 事件名采用小驼峰式命名(camelCase),例如 onClick 而不是 onclick
  • 使用 JSX 时,你传递一个函数作为事件处理程序,而不是一个字符串。
  • 不能通过返回 false 来阻止默认行为;必须显式调用 event.preventDefault()

React 会传递一个合成事件 (SyntheticEvent) 对象给事件处理函数。这是一个跨浏览器兼容的原生事件包装器,拥有与原生事件相同的接口(如 stopPropagation(), preventDefault())。

示例代码

import React, { useState } from "react";

function EventHandlingDemo() {
const [message, setMessage] = useState("No event yet.");

// 事件处理函数
const handleClick = (event) => {
// event 是 SyntheticEvent
console.log("Button clicked!", event);
event.preventDefault(); // 如果这是一个 submit 按钮,阻止表单提交
setMessage("Button was clicked!");
};

const handleInputChange = (event) => {
setMessage(`Input value: ${event.target.value}`);
};

const handleFormSubmit = (event) => {
event.preventDefault(); // 阻止页面刷新
setMessage(
`Form submitted with value: ${event.target.elements.myInput.value}`
);
};

return (
<div>
<h2>Event Handling</h2>
<button onClick={handleClick}>Click Me</button>
<br />
<br />
<form onSubmit={handleFormSubmit}>
<label>
Input:
<input type="text" name="myInput" onChange={handleInputChange} />
</label>
<button type="submit">Submit Form</button>
</form>
<p>{message}</p>
</div>
);
}

// 对于类组件,通常在构造函数中绑定 this 或使用 Class Fields 语法定义箭头函数
// class EventHandlingDemoClass extends React.Component {
// constructor(props) {
// super(props);
// this.state = { message: 'No event yet.' };
// // 绑定 this
// // this.handleClick = this.handleClick.bind(this);
// }
//
// // 使用 Class Fields 语法 (箭头函数自动绑定 this)
// handleClick = (event) => {
// this.setState({ message: 'Button clicked!' });
// }
//
// render() {
// return <button onClick={this.handleClick}>{this.state.message}</button>;
// }
// }

export default EventHandlingDemo;

7. Conditional Rendering (条件渲染)

说明

在 React 中,你可以根据应用的 state 或 props 创建不同的元素或组件。这使得 UI 可以根据特定条件动态变化。常见的条件渲染方法包括:

  • if 语句: 在组件的渲染逻辑中使用标准的 if/else
  • 元素变量: 使用变量存储元素,然后根据条件决定渲染哪个变量。
  • 逻辑 && 运算符 (短路求值): 如果条件为真,则渲染 && 右侧的表达式;否则不渲染。适用于只在满足条件时渲染某部分的情况。
  • 三元运算符 condition ? exprIfTrue : exprIfFalse: 根据条件渲染两个不同选项之一。
  • 返回 null: 如果不希望渲染任何内容,可以让组件返回 null

示例代码

import React, { useState } from "react";

function UserGreeting() {
return <h1>Welcome back!</h1>;
}

function GuestGreeting() {
return <h1>Please sign up.</h1>;
}

function LoginButton(props) {
return <button onClick={props.onClick}>Login</button>;
}

function LogoutButton(props) {
return <button onClick={props.onClick}>Logout</button>;
}

function ConditionalRenderingDemo() {
const [isLoggedIn, setIsLoggedIn] = useState(false);
const [showWarning, setShowWarning] = useState(true);

const handleLoginClick = () => setIsLoggedIn(true);
const handleLogoutClick = () => setIsLoggedIn(false);
const toggleWarning = () => setShowWarning(!showWarning);

// 1. 使用元素变量
let button;
if (isLoggedIn) {
button = <LogoutButton onClick={handleLogoutClick} />;
} else {
button = <LoginButton onClick={handleLoginClick} />;
}

return (
<div>
<h2>Conditional Rendering</h2>
{/* 2. 使用三元运算符 */}
{isLoggedIn ? <UserGreeting /> : <GuestGreeting />}
{button} {/* 渲染元素变量 */}
<hr />
{/* 3. 使用逻辑 && 运算符 */}
{showWarning && (
<p style={{ color: "red" }}>Warning: This is important!</p>
)}
<button onClick={toggleWarning}>
{showWarning ? "Hide" : "Show"} Warning
</button>
{/* 4. 返回 null 不渲染 (示例) */}
{!isLoggedIn && showWarning ? null : <p>Status indicator visible.</p>}
</div>
);
}

export default ConditionalRenderingDemo;

8. Lists and Keys (列表与 Keys)

说明

在 React 中渲染数据列表通常涉及使用 JavaScript 的 map() 方法将数组转换为 React 元素列表。当渲染这样的列表时,必须为列表中的每个元素提供一个特殊的 key prop。

key 是一个字符串或数字,用于帮助 React 识别哪些列表项被更改、添加或删除。Keys 应该在兄弟节点之间是唯一稳定的。使用数组索引作为 key 通常是不推荐的,除非列表是静态的(不会重新排序、添加或删除项),否则可能导致性能问题和状态混乱。最好的 key 通常是数据项中自带的唯一 ID。

Key 只需要在直接映射的元素上指定,不需要传递给子组件内部。

示例代码

import React, { useState } from "react";

function ListItem(props) {
// 注意:key 不会作为 prop 传递给组件内部
// console.log(props.key); // undefined
return (
<li>
ID: {props.id} - Value: {props.value}
</li>
);
}

function NumberList() {
const initialItems = [
{ id: "a", value: 1 },
{ id: "b", value: 2 },
{ id: "c", value: 3 },
];
const [items, setItems] = useState(initialItems);

const addItem = () => {
const newItemId = `id-${Date.now()}`;
const newValue = Math.floor(Math.random() * 100);
setItems([...items, { id: newItemId, value: newValue }]);
};

const listItems = items.map((item) => (
// 正确!Key 必须在 map() 返回的直接元素上指定
// 使用数据项的唯一 ID 作为 key
<ListItem key={item.id} id={item.id} value={item.value} />
));

// 错误示例:使用 index 作为 key (如果列表会变化,则不推荐)
// const listItemsWithIndexKey = items.map((item, index) =>
// <ListItem key={index} id={item.id} value={item.value} />
// );

return (
<div>
<h2>Lists and Keys</h2>
<button onClick={addItem}>Add Random Item</button>
<ul>{listItems}</ul>
{/* <p>List with index keys (avoid if list changes):</p>
<ul>
{listItemsWithIndexKey}
</ul> */}
</div>
);
}

export default NumberList;

9. Forms (表单)

说明

HTML 表单元素(如 <input>, <textarea>, <select>)在 React 中有两种主要处理方式:

  1. 受控组件 (Controlled Components): 表单元素的值由 React state 控制。通过 value prop 设置表单元素的值,并通过 onChange 事件处理函数更新对应的 state。这是推荐的方式,因为它使得 React state 成为“唯一数据源”。
  2. 非受控组件 (Uncontrolled Components): 表单数据由 DOM 自身处理。可以通过 Refs 来访问表单元素的值。适用于简单场景或集成非 React 代码。

示例代码 (受控组件)

import React, { useState } from "react";

function ReservationForm() {
const [name, setName] = useState("");
const [isGoing, setIsGoing] = useState(true);
const [numberOfGuests, setNumberOfGuests] = useState(2);
const [specialRequests, setSpecialRequests] = useState("");
const [flavor, setFlavor] = useState("coconut"); // For select

const handleSubmit = (event) => {
event.preventDefault(); // 阻止默认的表单提交行为
alert(`Submitting:
Name: ${name}
Is Going: ${isGoing}
Guests: ${numberOfGuests}
Requests: ${specialRequests}
Flavor: ${flavor}`);
};

return (
<form onSubmit={handleSubmit}>
<h2>Controlled Form</h2>
<label>
Name:
<input
type="text"
value={name} // Controlled by state
onChange={(e) => setName(e.target.value)}
/>
</label>
<br />

<label>
Is going:
<input
type="checkbox"
checked={isGoing} // Controlled by state
onChange={(e) => setIsGoing(e.target.checked)}
/>
</label>
<br />

<label>
Number of guests:
<input
type="number"
value={numberOfGuests} // Controlled by state
onChange={(e) => setNumberOfGuests(parseInt(e.target.value, 10))}
/>
</label>
<br />

<label>
Special Requests:
<textarea
value={specialRequests} // Controlled by state
onChange={(e) => setSpecialRequests(e.target.value)}
rows="3"
/>
</label>
<br />

<label>
Pick your favorite flavor:
<select value={flavor} onChange={(e) => setFlavor(e.target.value)}>
<option value="grapefruit">Grapefruit</option>
<option value="lime">Lime</option>
<option value="coconut">Coconut</option>
<option value="mango">Mango</option>
</select>
</label>
<br />

<button type="submit">Submit</button>
</form>
);
}

export default ReservationForm;

10. Hooks (钩子)

说明

Hooks 是 React 16.8 引入的特性,允许你在函数组件中使用 state 和其他 React 特性,而无需编写类。它们使得在组件之间复用状态逻辑更容易,并且有助于简化组件结构。

常用的内置 Hooks 包括:

  • useState: 允许函数组件拥有内部 state。(见 State 部分示例)
  • useEffect: 允许函数组件执行副作用操作(如数据获取、设置订阅、手动更改 DOM)。它接收一个函数(effect)和一个可选的依赖项数组。Effect 函数在每次渲染后运行(除非指定了依赖项)。如果返回一个函数,该函数将在组件卸载或下次 effect 运行前执行(用于清理)。
    • 无依赖项数组:每次渲染后都运行 effect。
    • 空依赖项数组 []: 只在组件挂载时运行一次 effect,清理函数在卸载时运行(类似 componentDidMountcomponentWillUnmount)。
    • 有依赖项数组 [dep1, dep2]: 仅当数组中的任何依赖项发生变化时,才重新运行 effect(类似 componentDidUpdate 的特定依赖)。
  • useContext: 订阅 React Context 并获取其当前值。(见 Context API 部分示例)
  • useRef: 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数。useRef 返回的 ref 对象在组件的整个生命周期内保持不变。主要用于:
    • 访问 DOM 节点。
    • 在多次渲染之间存储一个可变值,而该值的变化不会触发组件重新渲染。
  • useReducer: useState 的替代方案,适用于更复杂的 state 逻辑或下一个 state 依赖于前一个 state 的情况。接收一个 reducer 函数 (state, action) => newState 和初始 state。
  • useCallback: 返回一个 memoized(记忆化)的回调函数。只有当它的依赖项之一发生变化时,才会返回新的函数实例。用于优化,尤其是在将回调传递给依赖 props 引用的优化子组件时(如 React.memo 包裹的组件)。
  • useMemo: 返回一个 memoized 值。只有当它的依赖项之一发生变化时,才会重新计算该值。用于优化,避免在每次渲染时都执行高开销的计算。
  • Custom Hooks (自定义 Hook): 将组件逻辑提取到可重用的函数中。自定义 Hook 是一个名称以 use 开头的 JavaScript 函数,它可以调用其他 Hooks。

示例代码 (useEffect, useReducer, useCallback, useMemo, Custom Hook)

import React, {
useState,
useEffect,
useReducer,
useRef,
useCallback,
useMemo,
} from "react";

// --- useEffect Example ---
function TimerWithEffect() {
const [seconds, setSeconds] = useState(0);
const [isActive, setIsActive] = useState(true);

useEffect(() => {
let intervalId = null;
console.log("Effect running, isActive:", isActive);

if (isActive) {
intervalId = setInterval(() => {
console.log("Tick");
setSeconds((s) => s + 1);
}, 1000);
} else {
console.log("Effect: Timer paused or stopped.");
}

// 清理函数
return () => {
console.log("Effect cleanup: Clearing interval", intervalId);
if (intervalId) {
clearInterval(intervalId);
}
};
}, [isActive]); // 依赖项:仅当 isActive 变化时重新运行 effect

return (
<div>
<h2>useEffect Timer</h2>
<p>Seconds: {seconds}</p>
<button onClick={() => setIsActive(!isActive)}>
{isActive ? "Pause" : "Resume"}
</button>
<button
onClick={() => {
setIsActive(false);
setSeconds(0);
}}
>
Reset
</button>
</div>
);
}

// --- useReducer Example ---
const counterReducer = (state, action) => {
switch (action.type) {
case "increment":
return { count: state.count + (action.payload || 1) };
case "decrement":
return { count: state.count - 1 };
case "reset":
return { count: 0 };
default:
throw new Error(`Unhandled action type: ${action.type}`);
}
};

function CounterWithReducer() {
const [state, dispatch] = useReducer(counterReducer, { count: 0 }); // 初始 state

return (
<div>
<h2>useReducer Counter</h2>
<p>Count: {state.count}</p>
<button onClick={() => dispatch({ type: "increment", payload: 2 })}>
+2
</button>
<button onClick={() => dispatch({ type: "decrement" })}>-1</button>
<button onClick={() => dispatch({ type: "reset" })}>Reset</button>
</div>
);
}

// --- useCallback & useMemo Example ---
// 一个昂贵的计算函数 (仅为示例)
const expensiveCalculation = (num) => {
console.log("Calculating...", num);
let result = 0;
for (let i = 0; i < num * 1000000; i++) {
result += Math.random();
}
return result;
};

// 一个子组件,接收一个函数 prop
const MemoizedChild = React.memo(({ onClick, text }) => {
console.log("MemoizedChild rendered");
return <button onClick={onClick}>{text}</button>;
});

function OptimizationDemo() {
const [count, setCount] = useState(0);
const [otherState, setOtherState] = useState(false); // 用于触发父组件重渲染

// 使用 useMemo 缓存昂贵的计算结果
// 只有当 count 改变时,expensiveCalculation 才会重新执行
const calculationResult = useMemo(() => expensiveCalculation(count), [count]);

// 使用 useCallback 缓存事件处理函数
// 只有当依赖项 (此处为空) 改变时,才会返回新的函数实例
// 这可以防止 MemoizedChild 因为 onClick prop 的引用变化而重新渲染
const handleClick = useCallback(() => {
console.log("Button clicked! Count is:", count); // 访问最新的 count 值
// setCount(c => c + 1); // 如果需要更新 count,需要将其加入依赖项,但这通常不是 useCallback 的主要用途
}, [count]); // 如果回调内部逻辑依赖 count,需要加入

// 另一个未使用 useCallback 的函数
const normalHandleClick = () => {
console.log("Normal click handler created on each render");
};

console.log("OptimizationDemo rendered");

return (
<div>
<h2>useCallback & useMemo Demo</h2>
<p>Count: {count}</p>
<p>Calculation Result: {calculationResult.toFixed(2)}</p>
<button onClick={() => setCount((c) => c + 1)}>Increment Count</button>
<button onClick={() => setOtherState(!otherState)}>
Toggle Other State
</button>
<br />
<MemoizedChild
onClick={handleClick}
text="Memoized Button (useCallback)"
/>
<MemoizedChild
onClick={normalHandleClick}
text="Memoized Button (Normal Func)"
/>
</div>
);
}

// --- Custom Hook Example ---
function useDocumentTitle(title) {
useEffect(() => {
// 组件挂载和 title 更新时设置文档标题
document.title = title;
// 组件卸载时可以恢复原标题 (可选)
// return () => { document.title = 'React App'; };
}, [title]); // 依赖项:仅当 title 变化时更新
}

function CustomHookDemo() {
const [name, setName] = useState("Alice");
useDocumentTitle(`Hello, ${name}`); // 使用自定义 Hook

return (
<div>
<h2>Custom Hook Demo</h2>
<input value={name} onChange={(e) => setName(e.target.value)} />
<p>Check the browser tab title!</p>
</div>
);
}

// --- Main App to combine examples ---
function App() {
return (
<div>
<TimerWithEffect />
<hr />
<CounterWithReducer />
<hr />
<OptimizationDemo />
<hr />
<CustomHookDemo />
</div>
);
}

export default App;

11. Context API (上下文)

说明

Context 提供了一种在组件树中共享数据的方式,而无需显式地通过每一层组件手动传递 props(称为 "prop drilling")。它主要用于那些被许多组件需要访问的“全局”数据,例如当前认证的用户信息、UI 主题、首选语言等。

使用 Context 的主要步骤:

  1. React.createContext(defaultValue): 创建一个 Context 对象。defaultValue 是在组件树中找不到匹配的 Provider 时使用的值。
  2. MyContext.Provider: 一个 React 组件,允许消费组件订阅 context 的变化。它接收一个 value prop,这个值会传递给树下所有消费该 context 的组件。一个 Provider 可以嵌套在另一个 Provider 内部,内部的值会覆盖外部的值。
  3. 消费 Context:
    • useContext(MyContext) Hook: 在函数组件中消费 context 值的推荐方式。它读取并订阅 context。
    • MyContext.Consumer Component: 使用 Render Props 模式的组件,接收一个函数作为 children,该函数接收当前的 context 值并返回一个 React 节点。(较少在新代码中使用)。
    • Class.contextType: 在类组件中,可以将 contextType 静态属性设置为 React.createContext() 创建的 Context 对象,然后通过 this.context 访问其值。只能订阅单一 context。

示例代码

import React, { useState, useContext, createContext } from "react";

// 1. 创建 Context 对象
const ThemeContext = createContext("light"); // 默认值 'light'
const UserContext = createContext({ name: "Guest", loggedIn: false });

// --- 组件使用 Context ---

// 函数组件使用 useContext Hook (推荐)
function Toolbar() {
const theme = useContext(ThemeContext); // 读取 ThemeContext
const user = useContext(UserContext); // 读取 UserContext

const style = {
background: theme === "dark" ? "#333" : "#eee",
color: theme === "dark" ? "#fff" : "#000",
padding: "10px",
border: `1px solid ${theme === "dark" ? "#fff" : "#333"}`,
marginBottom: "10px",
};

return (
<div style={style}>
Current theme: {theme} | User: {user.name} (
{user.loggedIn ? "Logged In" : "Logged Out"})
<ThemedButton />
</div>
);
}

// 嵌套组件
function ThemedButton() {
const theme = useContext(ThemeContext); // 同样可以访问 Context
const style = {
background: theme === "dark" ? "grey" : "lightblue",
color: "black",
padding: "5px",
border: "none",
marginLeft: "10px",
};
return <button style={style}>Theme Button</button>;
}

// 类组件使用 contextType (只能访问一个 Context)
class UserStatus extends React.Component {
// 指定要访问的 context
static contextType = UserContext;

render() {
// 通过 this.context 访问
const user = this.context;
return (
<p>
User Status (Class): {user.name} is{" "}
{user.loggedIn ? "logged in" : "logged out"}.
</p>
);
}
}

// --- App 组件提供 Context ---
function App() {
const [theme, setTheme] = useState("light");
const [user, setUser] = useState({ name: "Alice", loggedIn: true });

const toggleTheme = () => setTheme((t) => (t === "light" ? "dark" : "light"));
const toggleLogin = () => setUser((u) => ({ ...u, loggedIn: !u.loggedIn }));

// 2. 使用 Provider 将 value 传递给子孙组件
return (
<ThemeContext.Provider value={theme}>
<UserContext.Provider value={user}>
<h1>Context API Demo</h1>
<button onClick={toggleTheme}>Toggle Theme</button>
<button onClick={toggleLogin}>Toggle Login Status</button>
<Toolbar />
<UserStatus />
{/* 没有被 Provider 包裹的组件会使用默认值 */}
{/* <OutsideComponent /> */}
</UserContext.Provider>
</ThemeContext.Provider>
);
}

// // 组件在 Provider 外部,将使用默认 context 值
// function OutsideComponent() {
// const theme = useContext(ThemeContext); // 'light' (default)
// const user = useContext(UserContext); // { name: 'Guest', loggedIn: false } (default)
// return <p>Outside: Theme is {theme}, User is {user.name}</p>;
// }

export default App;

12. Refs (引用)

说明

Refs 提供了一种直接访问 DOM 节点或在 render 方法中创建的 React 元素实例的方式。虽然 React 的声明式模型通常让我们避免直接操作 DOM,但在某些情况下 Refs 是必要的:

  • 管理焦点、文本选择或媒体(如 video, audio)播放。
  • 触发强制性的动画。
  • 集成第三方 DOM 库。

创建和使用 Refs 的方式:

  • 函数组件: 使用 useRef Hook。useRef 返回一个可变的 ref 对象,其 .current 属性初始化为传入的参数。将 ref 对象通过 ref prop 附加到 JSX 元素上。
  • 类组件: 使用 React.createRef() 方法创建 ref 对象,同样通过 ref prop 附加。

注意: 避免过度使用 Refs。优先考虑通过 props 和 state 来实现功能。Refs 不应作为在组件树中传递数据的常规方式。

示例代码

函数组件 (使用 useRef)

import React, { useRef, useEffect, useState } from "react";

function TextInputWithFocus() {
// 1. 创建 ref 对象
const inputRef = useRef(null);
const counterRef = useRef(0); // useRef 也可以存储任何可变值,其变化不触发渲染
const [renderTrigger, setRenderTrigger] = useState(0); // 用于演示 counterRef

// 2. 在需要时访问 ref.current
const focusInput = () => {
// inputRef.current 指向 <input> DOM 元素
if (inputRef.current) {
inputRef.current.focus();
console.log("Input value via ref:", inputRef.current.value);
}
};

const incrementCounterRef = () => {
counterRef.current += 1;
console.log("Counter ref value:", counterRef.current);
// 注意:更新 ref 不会触发重新渲染
// setRenderTrigger(r => r + 1); // 手动触发渲染才能看到更新的 ref 值显示
};

useEffect(() => {
// 组件挂载后自动聚焦 (示例)
// focusInput();
}, []);

return (
<div>
<h2>useRef Demo</h2>
{/* 3. 将 ref 附加到 DOM 元素 */}
<input ref={inputRef} type="text" defaultValue="Hello" />
<button onClick={focusInput}>Focus the input</button>
<hr />
<p>Counter Ref (won't update UI automatically): {counterRef.current}</p>
<button onClick={incrementCounterRef}>Increment Counter Ref</button>
<button onClick={() => setRenderTrigger((r) => r + 1)}>
Trigger Render
</button>
</div>
);
}

export default TextInputWithFocus;

类组件 (使用 React.createRef)

import React, { Component, createRef } from "react";

class VideoPlayer extends Component {
constructor(props) {
super(props);
// 1. 创建 ref
this.videoRef = createRef();
}

playVideo = () => {
// 3. 通过 ref.current 访问 DOM API
if (this.videoRef.current) {
this.videoRef.current.play();
}
};

pauseVideo = () => {
if (this.videoRef.current) {
this.videoRef.current.pause();
}
};

render() {
return (
<div>
<h2>createRef Demo (Class Component)</h2>
{/* 2. 将 ref 附加到元素 */}
<video
ref={this.videoRef}
width="320"
height="240"
controls
src="https://www.w3schools.com/html/mov_bbb.mp4" // 示例视频
>
Your browser does not support the video tag.
</video>
<div>
<button onClick={this.playVideo}>Play</button>
<button onClick={this.pauseVideo}>Pause</button>
</div>
</div>
);
}
}

export default VideoPlayer;

13. Higher-Order Components (HOC) (高阶组件)

说明

高阶组件 (HOC) 是一种用于复用组件逻辑的高级技术模式。它不是 React API 的一部分,而是 React 组合特性形成的一种模式。

HOC 是一个函数,它接收一个组件作为参数,并返回一个新的增强型组件。

HOC 常用于:

  • 代码复用、逻辑抽象(如数据订阅、连接到外部 API)。
  • Props 代理:向被包裹组件注入额外的 props。
  • 渲染劫持(较少见,更复杂)。
  • 权限控制、日志记录等横切关注点。

注意: 随着 Hooks 的引入,许多 HOC 的用例可以通过自定义 Hooks 更简洁地实现,但 HOC 仍然是理解 React 生态和一些库(如旧版 Redux connect)的重要概念。

示例代码

import React, { useState, useEffect } from "react";

// HOC:为包裹的组件添加 loading 状态和 data prop
function withLoadingAndData(WrappedComponent, fetchData) {
// 返回一个新的组件 (函数组件或类组件)
return function WithLoadingAndDataComponent(props) {
const [isLoading, setIsLoading] = useState(true);
const [data, setData] = useState(null);
const [error, setError] = useState(null);

useEffect(() => {
setIsLoading(true);
setError(null);
console.log(`HOC: Fetching data for ${WrappedComponent.name}...`);
fetchData(props) // 使用传入的 fetchData 函数,可以依赖 props
.then((fetchedData) => {
setData(fetchedData);
setIsLoading(false);
console.log(`HOC: Data fetched for ${WrappedComponent.name}`);
})
.catch((err) => {
setError(err);
setIsLoading(false);
console.error(
`HOC: Error fetching data for ${WrappedComponent.name}`,
err
);
});
}, []); // 简单示例,只在挂载时获取,实际可能需要依赖 props

if (isLoading) {
return <p>Loading data...</p>;
}
if (error) {
return <p>Error loading data: {error.message}</p>;
}

// 渲染原始组件,注入 data 和 loading 状态相关的 props,并传递其他 props
return (
<WrappedComponent
{...props}
data={data}
isLoading={isLoading}
error={error}
/>
);
};
}

// --- 使用 HOC ---

// 基础组件 1: 显示用户信息
function UserProfile({ data, userId }) {
// 接收来自 HOC 的 data 和自身的 userId
if (!data) return null;
return (
<div>
<h3>User Profile (ID: {userId})</h3>
<p>Name: {data.name}</p>
<p>Email: {data.email}</p>
</div>
);
}

// 基础组件 2: 显示评论列表
function CommentList({ data }) {
// 接收来自 HOC 的 data
if (!data || data.length === 0) return <p>No comments found.</p>;
return (
<div>
<h3>Comments</h3>
<ul>
{data.map((comment) => (
<li key={comment.id}>
<strong>{comment.name}:</strong> {comment.body.substring(0, 50)}...
</li>
))}
</ul>
</div>
);
}

// 定义数据获取逻辑
const fetchUserData = (props) =>
fetch(`https://jsonplaceholder.typicode.com/users/${props.userId}`).then(
(res) => res.json()
);
const fetchComments = () =>
fetch("https://jsonplaceholder.typicode.com/comments?_limit=5").then((res) =>
res.json()
);

// 使用 HOC 创建增强型组件
const UserProfileWithData = withLoadingAndData(UserProfile, fetchUserData);
const CommentListWithData = withLoadingAndData(CommentList, fetchComments);

// App 组件使用增强后的组件
function HOCDemo() {
return (
<div>
<h2>Higher-Order Component Demo</h2>
{/* 传递 HOC 需要的 prop (userId) 和原始组件需要的 prop */}
<UserProfileWithData userId={1} />
<hr />
<CommentListWithData />
</div>
);
}

export default HOCDemo;

14. Render Props (渲染属性)

说明

Render Props 是一种在 React 组件之间使用一个值为函数的 prop 来共享代码的技术。拥有 Render Prop 的组件不实现自己的渲染逻辑,而是调用这个函数 prop,并将一些 state 或数据作为参数传递给它,由这个函数 prop 来决定最终渲染什么。

这提供了一种强大的组件间行为共享机制。常见的 prop 名称是 render,但任何值为函数的 prop 都可以实现此模式,包括 children prop(当 children 是一个函数时)。

注意: 与 HOC 类似,许多 Render Props 的用例现在可以通过自定义 Hooks 更简洁地实现。但它仍然是 React 中重要的模式之一,尤其是在一些库(如 Formik, React Router v4/v5 的某些部分)中。

示例代码

import React, { useState, useEffect } from "react";

// Render Prop 组件: 追踪鼠标位置
class MouseTracker extends React.Component {
constructor(props) {
super(props);
this.state = { x: 0, y: 0 };
}

handleMouseMove = (event) => {
this.setState({
x: event.clientX,
y: event.clientY,
});
};

componentDidMount() {
window.addEventListener("mousemove", this.handleMouseMove);
}

componentWillUnmount() {
window.removeEventListener("mousemove", this.handleMouseMove);
}

render() {
// 调用 render prop (或 children prop),将 state 作为参数传递
// return this.props.render(this.state);
return this.props.children(this.state); // 使用 children 作为 render prop
}
}

// --- 使用 Render Prop ---

// 组件 1: 显示坐标
function DisplayMousePosition(props) {
// 通过 render prop 接收 mouse 位置
return (
<p>
The mouse position is ({props.mouse.x}, {props.mouse.y})
</p>
);
}

// 组件 2: 让图片跟随鼠标
function CatImage({ mouse }) {
// 直接解构 mouse
return (
<img
src="https://placekitten.com/50/50" // Placeholder kitten
alt="Cat following mouse"
style={{ position: "absolute", left: mouse.x - 25, top: mouse.y - 25 }}
/>
);
}

// App 使用 MouseTracker 和 Render Prop
function RenderPropsDemo() {
return (
<div
style={{
height: "300px",
border: "1px dashed blue",
position: "relative",
}}
>
<h2>Render Props Demo</h2>
<p>Move your mouse over this area!</p>

{/* 使用 children 作为 render prop */}
<MouseTracker>
{(mousePosition) => (
// 这个函数决定了如何使用 mousePosition 数据进行渲染
<>
<DisplayMousePosition mouse={mousePosition} />
<CatImage mouse={mousePosition} />
</>
)}
</MouseTracker>

{/* 或者使用名为 render 的 prop */}
{/*
<MouseTracker render={mousePosition => (
<DisplayMousePosition mouse={mousePosition} />
)}/>
*/}
</div>
);
}

export default RenderPropsDemo;

15. Virtual DOM & Reconciliation (虚拟 DOM 与协调)

说明

这是 React 高效更新 UI 的核心机制,虽然不是直接使用的 API,但理解它很重要。

  • Virtual DOM (VDOM): React 在内存中维护一个轻量级的 UI 表示,称为虚拟 DOM。它是一个与真实 DOM 同步的 JavaScript 对象树。
  • Reconciliation (协调): 当组件的 state 或 props 改变时,React 会创建一个新的虚拟 DOM 树。然后,React 会比较新旧两棵虚拟 DOM 树,找出它们之间的差异(这个过程称为 "diffing" 算法)。
  • 更新真实 DOM: React 只会将计算出的差异部分更新到实际的浏览器 DOM 中,而不是重新渲染整个页面。这种最小化 DOM 操作的方式使得 React 应用性能很高。

key prop 在列表渲染中对于协调过程至关重要,它帮助 React 高效地识别列表项的移动、添加或删除,而不是仅仅基于它们在列表中的位置。

示例代码 (概念性,非直接 API)

// 概念演示,非实际 React 代码

// 初始状态 -> VDOM 1
const vdom1 = {
type: "div",
props: { className: "container" },
children: [
{ type: "h1", props: {}, children: ["Hello"] },
{ type: "p", props: { key: "p1" }, children: ["World"] },
],
};
// React 将 vdom1 渲染为真实 DOM

// 状态更新 -> VDOM 2
const vdom2 = {
type: "div",
props: { className: "container updated" }, // className 变化
children: [
{ type: "h1", props: {}, children: ["Hello React"] }, // children 变化
{ type: "p", props: { key: "p2" }, children: ["Awesome"] }, // 新的 p 元素 (key 不同)
// { type: 'p', props: { key: 'p1' }, children: ['World'] } // 旧 p 元素被移除
],
};

// React 的协调过程 (Diffing):
// 1. 比较根节点 'div' -> 类型相同,比较 props -> className 变了,更新真实 DOM 的 class。
// 2. 比较 children:
// a. 旧 h1 vs 新 h1 -> 类型相同,比较 props -> 相同,比较 children -> 'Hello' 变为 'Hello React',更新文本节点。
// b. 旧 p (key='p1') vs 新 p (key='p2') -> Keys 不同,React 认为旧 p 被移除,新 p 被添加。
// - 移除真实 DOM 中对应 key='p1' 的 <p>。
// - 创建新的真实 DOM <p> (key='p2') 并插入。

// 最终结果:React 只执行了必要的 DOM 操作(更新 class, 更新 h1 文本, 移除旧 p, 添加新 p)。