跳到主要内容

核心知识点

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.state 和 setState)​

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.PureComponent 或 React.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,清理函数在卸载时运行(类似 componentDidMount 和 componentWillUnmount)。
    • 有依赖项数组 [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)。