React 面试问题

50+ React 问答,采用测验形式,由前 FAANG 面试官解答
前面试官提供的问答和解答
涵盖关键主题

In real-world scenarios, mastering React goes far beyond just building components. It’s about creating efficient, reusable, and performant applications. React interviewers typically focus on key areas such as:

  • Component Lifecycle: Understanding how components mount, update, and unmount is crucial for managing UI and state.
  • State and Props Management: Knowing when and how to use props, state, and context to share data across components.
  • Hooks: Leveraging React hooks like useState, useEffect, and useReducer to simplify logic and manage side effects.
  • Performance Optimization: Efficient rendering, memoization, and handling large datasets.
  • Testing: Writing robust tests for React components using tools like Jest and React Testing Library.
  • Routing: Managing views and navigation in single-page applications with React Router.

Below, you’ll find 50+ expertly curated questions covering everything from component lifecycle and state management to hooks and performance optimization. Each question includes:

  • Quick answers (TL;DR): Clear, concise responses to help you answer confidently.
  • In-depth explanations: Detailed insights to ensure you fully understand each concept.

Best of all, our list is crafted by senior and staff engineers from top tech companies, not unverified or AI-generated content. Don’t waste time—prepare with real, experienced-backed React interview questions!

如果您正在寻找 React 编码问题 -我们也会为您提供:
Javascript 编码
  • 90+ React 编码面试问题
  • 一个模拟真实面试条件的浏览器内编码工作区
  • 来自 Big Tech 公司前面试官的参考解决方案
  • UI 问题的即时 UI 预览
开始使用
加入 50,000+ 工程师

什么是 React? 描述 React 的优点

主题
React

TL;DR

React 是一个由 Facebook 创建的 JavaScript 库,用于构建用户界面,主要用于单页应用程序。它允许开发人员创建管理自己状态的可重用组件。React 的主要优点包括基于组件的架构,用于模块化代码,虚拟 DOM 用于高效更新,声明式 UI 用于更易读的代码,单向数据绑定用于可预测的数据流,以及拥有丰富资源和工具的强大社区和生态系统。

React 的主要特点

  • 声明式:您根据数据描述 UI 的所需状态,React 处理高效更新实际 DOM。
  • 基于组件:构建管理自身状态和逻辑的可重用和模块化 UI 元素(组件)。
  • 虚拟 DOM:React 使用实际 DOM 的轻量级内存表示,允许它选择性地高效地执行更新。
  • JSX:虽然不是强制性的,但 JSX 提供了一个语法扩展,允许您在 JavaScript 代码中编写类似 HTML 的结构,使 UI 开发更直观。

什么是 React?

React 是一个由 Facebook 开发的开源 JavaScript 库,用于构建用户界面。它专注于应用程序的视图层,特别适用于创建单页应用程序,在这种应用程序中,无缝的用户体验至关重要。React 允许开发人员构建封装其自身状态的组件,并将它们组合起来创建复杂的 UI。

React 的优点

1. 基于组件的架构

React 鼓励将您的 UI 分解为独立的、可重用的组件。每个组件封装其自身的状态、逻辑和渲染,使您的代码:

  • 模块化和可重用:组件可以在应用程序的不同部分甚至其他项目中轻松重用。
  • 可维护:组件内的更改是隔离的,从而降低了意外副作用的风险。
  • 更易于测试:组件可以独立测试,确保其功能和可靠性。

2. 虚拟 DOM 和高效更新

React 使用虚拟 DOM,即实际 DOM 的轻量级内存表示。当数据更改时,React 首先更新虚拟 DOM,然后将其与之前的版本进行比较。这个过程称为 diffing,它允许 React 识别实际 DOM 中所需的最少更改集。通过仅更新必要的元素,React 最小化了昂贵的 DOM 操作,从而显着提高了性能。

3. 大型且活跃的社区

React 拥有庞大而活跃的全球开发人员社区。 这转化为:

  • 广泛的文档和资源:查找全面的文档、教程和社区驱动的资源,以帮助您的学习和开发过程。
  • 丰富的第三方库和工具:利用丰富的预构建组件、库和工具生态系统,这些生态系统扩展了 React 的功能并简化了开发。
  • 强大的社区支持:通过论坛、在线社区和聚会寻求帮助、分享知识并与同行开发人员互动。

4. 一次学习,随处编写

React 的多功能性超出了 Web 开发的范围。 使用 React Native,您可以使用您的 React 知识为 iOS 和 Android 构建原生移动应用程序。 这种“一次学习,随处编写”的方法允许您:

  • 在平台之间共享代码:在 Web 和移动设备之间重用组件和逻辑,从而减少开发时间和精力。
  • 利用现有技能:将您的 React 专业知识应用于移动开发,而无需学习全新的技术。
  • 使用单个代码库定位多个平台:通过为多个平台管理单个代码库来简化开发和维护。

延伸阅读

React Node、React Element 和 React Component 之间有什么区别?

主题
React

TL;DR

React Node 是 React 中的任何可渲染单元,例如元素、字符串、数字或 null。React Element 是一个描述要渲染内容的不可变对象,使用 JSX 或 React.createElement 创建。React Component 是一个返回 React Element 的函数或类,支持创建可重用的 UI 片段。


React node

一个 React Node 是 React 渲染系统中最基本的单元。它可以是一个 React 元素、一个字符串、一个数字、一个布尔值或 null。 基本上,任何可以在 React 中渲染的东西都是一个 React Node。

const stringNode = 'Hello, world!';
const numberNode = 123;
const booleanNode = true;
const nullNode = null;
const elementNode = <div>Hello, world!</div>;

React element

一个 React Element 是一个不可变的纯对象,表示您希望在屏幕上看到的内容。它包括类型(例如 HTML 标签的字符串或 React 组件)、props 和 children。React 元素是使用 JSX 语法或 React.createElement 创建的。

const element = <div className="greeting">Hello, world!</div>;
// 使用 React.createElement
const element = React.createElement(
'div',
{ className: 'greeting' },
'Hello, world!',
);

React component

一个 React Component 是一个可重用的 UI 片段,它可以接受输入(props)并返回描述 UI 的 React 元素。 有两种类型的组件:函数组件和类组件。

  • 函数组件:这些更简单,只是将 props 作为参数并返回 React 元素的函数。

    function Welcome(props) {
    return <h1>Hello, {props.name}</h1>;
    }
  • 类组件:这些是扩展 React.Component 的 ES6 类,并且必须具有返回 React 元素的 render 方法。

    class Welcome extends React.Component {
    render() {
    return <h1>Hello, {this.props.name}</h1>;
    }
    }

延伸阅读

什么是JSX以及它如何工作?

主题
React

TL;DR

JSX 代表 JavaScript XML。它是 JavaScript 的语法扩展,允许您在 JavaScript 中编写类似 HTML 的代码。JSX 通过允许您直接在 JavaScript 代码中编写类似 HTML 的内容,从而更容易创建 React 组件。在底层,JSX 被转换为 JavaScript 函数调用,通常使用 Babel 等工具。例如,JSX 中的 <div>Hello, world!</div> 被转换为 React.createElement('div', null, 'Hello, world!')


什么是JSX以及它如何工作?

什么是JSX?

JSX 代表 JavaScript XML。它是 JavaScript 的语法扩展,允许您在 JavaScript 中编写类似 HTML 的代码。JSX 主要与 React 一起使用,用于描述 UI 应该是什么样子。

JSX 如何工作?

JSX 本身不是有效的 JavaScript。它需要先转换为常规 JavaScript,然后才能由浏览器执行。这种转换通常由 Babel 等工具完成。

JSX 语法

JSX 允许您直接在 JavaScript 代码中编写类似 HTML 的标签。例如:

const element = <h1>Hello, world!</h1>;

转换过程

当您编写 JSX 时,它会被转换为 JavaScript 函数调用。例如,JSX 代码:

const element = <h1>Hello, world!</h1>;

被转换为:

const element = React.createElement('h1', null, 'Hello, world!');

嵌入表达式

您可以使用大括号 {} 将 JavaScript 表达式嵌入到 JSX 中。例如:

const name = 'John';
const element = <h1>Hello, {name}!</h1>;

JSX 中的属性

您可以使用引号指定字符串字面量作为属性,并使用大括号嵌入 JavaScript 表达式。例如:

const element = <img src={user.avatarUrl} alt="User Avatar" />;

JSX 是一个表达式

编译后,JSX 表达式会变成常规的 JavaScript 函数调用,并计算为 JavaScript 对象。这意味着您可以在 if 语句、for 循环中使用 JSX,并将其分配给变量。

JSX 阻止注入攻击

默认情况下,React DOM 会在渲染之前转义 JSX 中嵌入的任何值。这确保了您永远无法注入未在应用程序中明确编写的任何内容。

延伸阅读

React 中 state 和 props 的区别是什么?

主题
React

TL;DR

在 React 中,state 是在组件内部管理并可以随时间变化的数据存储,而 props 是从父组件传递给子组件的只读属性。State 用于组件内变化的数据,而 props 用于将数据和事件处理程序传递给子组件。


React 中 state 和 props 的区别是什么?

State

State 是 React 组件中的一个内置对象,用于保存关于组件的数据或信息。它在组件内部进行管理,并且可以随时间变化,通常是响应用户操作或网络响应。当 state 发生变化时,组件会重新渲染以反映新的 state。

  • State 是组件局部的,无法在组件外部访问或修改
  • State 可以在类组件的构造函数中初始化,或者在函数组件中使用 useState hook
  • State 的变化是异步的,应该使用类组件中的 setState 方法或函数组件中 useState 返回的更新函数

类组件中 state 的示例:

class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0,
};
}
increment = () => {
this.setState({ count: this.state.count + 1 });
};
render() {
return (
<div>
<p>Count: {this.state.count}</p>
<button onClick={this.increment}>Increment</button>
</div>
);
}
}

函数组件中 state 的示例:

import React, { useState } from 'react';
function MyComponent() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}

Props

Props(属性的简称)是从父组件传递给子组件的只读属性。它们用于将数据和事件处理程序传递到组件树的下方。Props 是不可变的,这意味着它们不能被子组件更改。

  • Props 作为参数传递给子组件函数,或者作为类组件中 this 对象的属性
  • Props 允许通过向组件传递不同的数据来复用组件
  • Props 也可以用来传递函数,使子组件能够与父组件通信

类组件中 props 的示例:

class ParentComponent extends React.Component {
render() {
return <ChildComponent message="Hello, World!" />;
}
}
class ChildComponent extends React.Component {
render() {
return <p>{this.props.message}</p>;
}
}

函数组件中 props 的示例:

function ParentComponent() {
return <ChildComponent message="Hello, World!" />;
}
function ChildComponent(props) {
return <p>{props.message}</p>;
}

关键区别

  • State 在组件内部管理,而 props 从父组件传递
  • State 可以随时间变化,而 props 是不可变的
  • State 用于组件内部变化的数据,而 props 用于向子组件传递数据和事件处理程序

延伸阅读

React 中 `key` prop 的作用是什么?

主题
React

TL;DR

React 中的 key prop 用于唯一标识列表中的元素。它通过有效地更新和重新排序元素来帮助 React 优化渲染。如果没有唯一的 key,React 可能会不必要地重新渲染元素,从而导致性能问题和错误。

{
items.map((item) => <ListItem key={item.id} value={item.value} />);
}

React 中 key prop 的作用是什么?

简介

key prop 是一个特殊的属性,当你在 React 中创建元素列表时,你需要包含它。它对于帮助 React 识别哪些项目已更改、已添加或已删除至关重要,从而优化渲染过程。

为什么 key 很重要

  1. 高效更新:React 使用 key prop 来跟踪元素。当列表的状态发生变化时,React 可以快速确定哪些项目需要重新渲染、添加或删除。
  2. 避免错误:如果没有唯一的键,React 可能会不必要地或错误地重新渲染元素,从而导致应用程序中出现潜在的错误。
  3. 性能优化:通过使用唯一的键,React 最小化了 DOM 操作的数量,使你的应用程序更快、更高效。

如何使用 key prop

当渲染元素列表时,你应该为每个元素提供一个唯一的 key。此键应该是稳定的,这意味着它不应在渲染之间更改。通常,你可以使用数据中的唯一标识符,例如 id

const items = [
{ id: 1, value: 'Item 1' },
{ id: 2, value: 'Item 2' },
{ id: 3, value: 'Item 3' },
];
function ItemList() {
return (
<ul>
{items.map((item) => (
<ListItem key={item.id} value={item.value} />
))}
</ul>
);
}
function ListItem({ value }) {
return <li>{value}</li>;
}

常见错误

  1. 使用数组索引作为键:虽然使用数组索引作为键可能很诱人,但我们不建议这样做,因为如果列表被重新排序或添加/删除项目,索引可能会发生变化。
  2. 非唯一键:确保键在整个列表中是唯一的。重复的键可能导致意外行为和错误。
// 错误做法:使用数组索引作为键
{
items.map((item, index) => <ListItem key={index} value={item.value} />);
}
// 好的做法:使用唯一标识符作为键
{
items.map((item) => <ListItem key={item.id} value={item.value} />);
}

延伸阅读

在 React 中使用数组索引作为 `key` 的值有什么后果?

主题
React

TL;DR

在 React 中使用数组索引作为 key 的值可能会导致性能问题和错误。当项目顺序发生变化时,React 可能无法正确识别哪些项目已更改,从而导致不必要的重新渲染或不正确的组件更新。最好使用唯一的标识符作为 key,以确保 React 可以有效地管理和更新 DOM。


使用数组索引作为 React 中 key 值的后果

性能问题

当数组索引用作 key 时,React 可能无法有效地更新 DOM。如果数组中项目的顺序发生变化,React 将无法正确识别已添加、删除或移动的项目。这可能导致不必要的重新渲染和性能下降。

不正确的组件更新

使用数组索引作为 key 可能会导致应用程序中的错误。例如,如果项目的顺序发生变化,React 可能会为不同的项目重用相同的组件实例,从而导致不正确的状态和 props 传递给组件。这可能导致意外行为和难以调试的问题。

例子

考虑以下使用数组索引作为 key 的示例:

const items = ['Item 1', 'Item 2', 'Item 3'];
const List = () => (
<ul>
{items.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
);

如果 items 的顺序发生变化,React 可能无法正确更新 DOM,从而导致性能问题和潜在的错误。

更好的方法

与其使用数组索引,不如使用唯一的标识符作为 key。这确保了 React 可以有效地管理和更新 DOM。

const items = [
{ id: 1, name: 'Item 1' },
{ id: 2, name: 'Item 2' },
{ id: 3, name: 'Item 3' },
];
const List = () => (
<ul>
{items.map((item) => (
<li key={item.id}>{item.name}</li>
))}
</ul>
);

在此示例中,使用 item.id 作为 key 可确保 React 在顺序更改时正确识别和更新项目。

延伸阅读

受控组件和非受控 React 组件有什么区别?

主题
React

TL;DR

React 中的受控组件是指表单数据由组件的状态处理的组件。状态是唯一的真理来源,对表单输入的任何更改都通过事件处理程序进行管理。另一方面,非受控组件在其内部存储自己的状态,并依赖于 refs 来访问表单值。受控组件提供更多控制,更容易测试,而非受控组件对于基本用例来说可能更易于实现。


受控组件和非受控 React 组件有什么区别?

受控组件

受控组件是指表单数据由组件的状态处理的组件。状态是唯一的真理来源,对表单输入的任何更改都通过事件处理程序进行管理。

示例
class ControlledComponent extends React.Component {
constructor(props) {
super(props);
this.state = { value: '' };
}
handleChange = (event) => {
this.setState({ value: event.target.value });
};
handleSubmit = (event) => {
alert('A name was submitted: ' + this.state.value);
event.preventDefault();
};
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Name:
<input
type="text"
value={this.state.value}
onChange={this.handleChange}
/>
</label>
<input type="submit" value="Submit" />
</form>
);
}
}

非受控组件

非受控组件在其内部存储自己的状态,并依赖于 refs 来访问表单值。这种方法更类似于传统的 HTML 表单元素。

示例
class UncontrolledComponent extends React.Component {
constructor(props) {
super(props);
this.inputRef = React.createRef();
}
handleSubmit = (event) => {
alert('A name was submitted: ' + this.inputRef.current.value);
event.preventDefault();
};
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Name:
<input type="text" ref={this.inputRef} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
}

主要区别

状态管理
  • 受控组件:状态由 React 组件管理。
  • 非受控组件:状态由 DOM 管理。
数据流
  • 受控组件:数据从组件的状态流向输入元素。
  • 非受控组件:数据通过 refs 从输入元素流向组件。
用例
  • 受控组件:当您需要强制验证、有条件地禁用/启用输入或执行其他复杂交互时,首选受控组件。
  • 非受控组件:适用于简单表单或需要与非 React 代码集成时。

延伸阅读

使用 React Context 时有哪些陷阱?

主题
React

TL;DR

如果管理不当,在 React 中使用 Context 可能会导致性能问题。它可能导致使用 Context 的组件不必要的重新渲染,即使它们使用的 Context 部分没有改变。此外,过度使用 Context 进行状态管理会使代码更难理解和维护。谨慎使用 Context 并考虑使用 Redux 或 Zustand 等其他状态管理解决方案来满足更复杂的状态需求非常重要。


使用 React Context 的陷阱

性能问题

在 React 中使用 Context 的主要陷阱之一是潜在的性能问题。当 Context 值更改时,所有使用 Context 的组件都将重新渲染,即使它们不使用已更改的 Context 部分。这可能导致不必要的重新渲染并降低应用程序的性能。

示例
const MyContext = React.createContext();
function ParentComponent() {
const [value, setValue] = React.useState(0);
return (
<MyContext.Provider value={value}>
<ChildComponent />
</MyContext.Provider>
);
}
function ChildComponent() {
const value = React.useContext(MyContext);
console.log('ChildComponent re-rendered');
return <div>{value}</div>;
}

在此示例中,ChildComponent 将在 ParentComponent 中的 value 每次更改时重新渲染,即使 ChildComponent 不需要更新。

过度使用 Context

使用 Context 进行状态管理会使代码更难理解和维护。Context 最适合于不经常更改的全局状态,例如主题设置或用户身份验证状态。过度使用 Context 进行更复杂的状态管理会导致代码库混乱且难以理解。

调试困难

调试与 Context 相关的问题可能具有挑战性。由于 Context 更新可能会在多个组件中触发重新渲染,因此很难追踪错误或性能问题的根源。对于具有许多 Context 提供程序和使用者的较大应用程序来说尤其如此。

缺乏细粒度控制

Context 提供了一种在组件树中传递数据的方法,而无需在每个级别手动传递 props。但是,它缺乏对 Context 值更改时应重新渲染哪些组件的细粒度控制。如果管理不当,这可能导致性能瓶颈。

Context 的替代方案

对于更复杂的状态管理需求,请考虑使用 Redux、Zustand 或 Recoil 等其他状态管理解决方案。这些库提供了对状态更新的更细粒度控制,并且可以帮助避免与使用 Context 相关的一些陷阱。

延伸阅读

使用 React Hooks 有什么好处?

主题
React

TL;DR

React 中的 Hooks 允许您使用状态和其他 React 功能,而无需编写类。它们使在组件之间重用有状态逻辑、提高代码可读性以及通过减少对生命周期方法的需求来简化代码库变得更容易。像 useStateuseEffect 这样的 Hooks 通常用于管理函数组件中的状态和副作用。


使用 React Hooks 的好处

简化的状态管理

useState 这样的 Hooks 允许您将状态添加到函数组件,而无需将它们转换为类组件。这使得代码更简洁,更易于阅读。

const [count, setCount] = useState(0);

提高代码可读性

Hooks 有助于将复杂的组件分解成更小、可重用的逻辑片段。这使得代码更模块化,更容易理解。

可重用逻辑

自定义 Hook 允许您跨多个组件提取和重用有状态逻辑。这促进了代码重用并减少了重复。

function useCustomHook() {
const [state, setState] = useState(initialState);
// Custom logic here
return [state, setState];
}

减少对生命周期方法的需求

useEffect 这样的 Hooks 可以替换生命周期方法,例如 componentDidMountcomponentDidUpdatecomponentWillUnmount。这简化了组件生命周期管理。

useEffect(() => {
// Side effect logic here
return () => {
// Cleanup logic here
};
}, [dependencies]);

更好地关注分离

Hooks 允许您通过将相关逻辑分组在一起来分离关注点。这使得代码库更易于维护和调试。

增强测试

与类组件相比,具有 Hooks 的函数组件通常更容易测试。Hooks 可以在隔离状态下进行测试,使单元测试更简单。

延伸阅读

React Hooks 的规则是什么?

主题
React

总结

React Hooks 有一些基本规则来确保它们正常工作。 始终在 React 函数的顶层调用 Hooks,切勿在循环、条件或嵌套函数内调用。 仅从 React 函数组件或自定义 Hooks 调用 Hooks。 这些规则确保 Hooks 保持正确的状态和生命周期行为。


React Hooks 的规则是什么?

始终在顶层调用 Hooks

Hooks 应该始终在 React 函数的顶层调用。 这意味着你不应该在循环、条件或嵌套函数内调用 Hooks。 此规则确保每次组件渲染时 Hooks 以相同的顺序调用,这对于维护正确的状态和生命周期行为至关重要。

// 正确
function MyComponent() {
const [count, setCount] = useState(0);
if (count > 0) {
// 做一些事情
}
}
// 不正确
function MyComponent() {
if (someCondition) {
const [count, setCount] = useState(0); // 这将破坏 Hooks 的规则
}
}

仅从 React 函数组件或自定义 Hooks 调用 Hooks

Hooks 应该仅从 React 函数组件或自定义 Hooks 调用。 此规则确保 Hooks 在 React 可以管理其状态和生命周期的适当上下文中被使用。

// 正确
function MyComponent() {
const [count, setCount] = useState(0);
return <div>{count}</div>;
}
// 正确(自定义 Hook)
function useCustomHook() {
const [state, setState] = useState(null);
return [state, setState];
}
// 不正确
function regularFunction() {
const [count, setCount] = useState(0); // 这将破坏 Hooks 的规则
}

使用 eslint-plugin-react-hooks linter

为了执行这些规则,你可以使用 eslint-plugin-react-hooks linter。 这个插件将帮助你识别和修复代码中 Hooks 规则的违规行为。

npm install eslint-plugin-react-hooks --save-dev

将插件添加到你的 ESLint 配置中:

{
"plugins": ["react-hooks"],
"rules": {
"react-hooks/rules-of-hooks": "error", // 检查 Hooks 的规则
"react-hooks/exhaustive-deps": "warn" // 检查 effect 依赖项
}
}

延伸阅读

React 中 `useEffect` 和 `useLayoutEffect` 有什么区别?

主题
React

TL;DR

useEffectuseLayoutEffect 是 React 钩子,用于处理函数组件中的副作用,但它们在时机和用例上有所不同:

  • useEffect:在 DOM 绘制后异步运行。它适用于数据获取、订阅或日志记录等任务。
  • useLayoutEffect:在 DOM 更改后但在浏览器绘制之前同步运行。将其用于测量 DOM 元素或将 UI 与 DOM 同步等任务。

代码示例:

import React, { useEffect, useLayoutEffect, useRef } from 'react';
function Example() {
const ref = useRef();
useEffect(() => {
console.log('useEffect: Runs after DOM paint');
});
useLayoutEffect(() => {
console.log('useLayoutEffect: Runs before DOM paint');
console.log('Element width:', ref.current.offsetWidth);
});
return <div ref={ref}>Hello</div>;
}

什么是 useEffect

useEffect 是一个 React 钩子,用于管理函数组件中的副作用。副作用包括获取数据、更新订阅或与浏览器的 DOM API 交互等操作。

  • 它在 DOM 更新和绘制后异步运行。
  • 它不会阻止浏览器更新 UI。
  • 默认情况下,它在每次渲染后运行,但依赖项可以控制其执行。

代码示例

import React, { useEffect } from 'react';
function Example() {
useEffect(() => {
console.log('Component mounted or updated');
return () => console.log('Cleanup on unmount or dependency change');
}, []); // Runs only on mount and unmount
return <div>Hello, World!</div>;
}

常见用例

  • 从 API 获取数据
  • 设置订阅(例如,WebSocket 连接)
  • 日志记录或分析跟踪
  • 添加和删除事件侦听器

什么是 useLayoutEffect

useLayoutEffect 是一个类似于 useEffect 的 React 钩子,但它在时机上有所不同。它在 DOM 更改后并在浏览器绘制屏幕之前同步运行。

  • 它适用于需要在绘制前访问 DOM 的任务。
  • 它可以阻止渲染,因此应谨慎使用。

代码示例

import React, { useLayoutEffect, useRef } from 'react';
function Example() {
const ref = useRef();
useLayoutEffect(() => {
console.log('Element dimensions:', ref.current.getBoundingClientRect());
});
return <div ref={ref}>Hello</div>;
}

常见用例

  • 测量 DOM 元素(例如,用于动画或布局)
  • 根据计算调整 DOM 属性或样式
  • 修复 UI 同步问题

useEffectuseLayoutEffect 之间的主要区别

时机

  • useEffect:在浏览器绘制 UI 后执行。
  • useLayoutEffect:在浏览器绘制之前,DOM 更改后立即执行。

阻塞行为

  • useEffect:非阻塞,异步运行。
  • useLayoutEffect:阻塞,同步运行。

用例示例

  • useEffect:获取数据、更新状态或添加事件监听器。
  • useLayoutEffect:测量 DOM 元素、管理动画或解决布局问题。

延伸阅读

React 中 `setState()` 的回调函数参数格式的目的是什么?应该在什么时候使用它?

主题
React

TL;DR

React 中 setState() 的回调函数参数格式用于确保状态更新基于最新的状态和 props。当新状态依赖于之前的状态时,这一点尤其重要。您无需直接将对象传递给 setState(),而是传递一个函数,该函数将之前的状态和 props 作为参数,并返回新状态。

this.setState((prevState, props) => ({
counter: prevState.counter + props.increment,
}));

React 中 setState() 的回调函数参数格式的目的

确保状态更新基于最新的状态

React 的 setState() 是异步的,这意味着可以出于性能原因将对 setState() 的多个调用批处理在一起。如果依赖当前状态来计算下一个状态,使用回调函数格式可确保您使用最新的状态。

语法

setState() 的回调函数格式将一个函数作为参数。此函数接收两个参数:prevStateprops。它返回一个表示新状态的对象。

this.setState((prevState, props) => {
return {
counter: prevState.counter + props.increment,
};
});

什么时候使用它

  • 当新状态依赖于之前的状态时:如果需要根据当前状态更新状态,请使用回调函数格式以避免与异步状态更新相关的潜在问题。
  • 当多个状态更新被批处理时:在多个 setState() 调用可能被批处理在一起的情况下,使用回调函数可确保每次更新都基于最新的状态。

示例

考虑一个计数器组件,其中状态更新取决于之前的状态:

class Counter extends React.Component {
constructor(props) {
super(props);
this.state = { counter: 0 };
}
incrementCounter = () => {
this.setState((prevState, props) => ({
counter: prevState.counter + 1,
}));
};
render() {
return (
<div>
<p>Counter: {this.state.counter}</p>
<button onClick={this.incrementCounter}>Increment</button>
</div>
);
}
}

在此示例中,使用回调函数格式可确保正确递增 counter 状态,即使在短时间内多次调用 incrementCounter 也是如此。

延伸阅读

useEffect 的依赖项数组会影响什么?

主题
React

TL;DR

useEffect 的依赖项数组决定了 effect 应该何时重新运行。如果数组为空,则 effect 仅在初始渲染后运行一次。如果它包含变量,则 effect 在任何这些变量更改时运行。如果省略,则 effect 在每次渲染后运行。


useEffect 的依赖项数组会影响什么?

useEffect 简介

React 中的 useEffect 钩子用于在函数组件中执行副作用。这些副作用可以包括数据获取、订阅或手动更改 DOM。useEffect 钩子接受两个参数:一个包含副作用逻辑的函数和一个可选的依赖项数组。

依赖项数组

依赖项数组是 useEffect 钩子的第二个参数。它是一个 effect 所依赖的值的数组。React 使用此数组来确定何时重新运行 effect。

useEffect(() => {
// 在这里编写副作用逻辑
}, [dependency1, dependency2]);

依赖项数组如何影响 useEffect

  1. 空依赖项数组 ([]):

    • effect 仅在初始渲染后运行一次。
    • 这类似于类组件中 componentDidMount 的行为。
    useEffect(() => {
    // 此代码仅在初始渲染后运行一次
    }, []);
  2. 带有变量的依赖项数组:

    • effect 在初始渲染后运行,并在任何指定的依赖项更改时运行。
    • React 对依赖项进行浅层比较,以确定它们是否已更改。
    useEffect(() => {
    // 此代码在初始渲染后运行,并在 dependency1 或 dependency2 更改时运行
    }, [dependency1, dependency2]);
  3. 没有依赖项数组:

    • effect 在每次渲染后运行。
    • 如果 effect 代价高昂,这可能会导致性能问题。
    useEffect(() => {
    // 此代码在每次渲染后运行
    });

常见陷阱

  1. 过时的闭包:

    • 如果你在 effect 内部使用状态或 props,而没有将它们包含在依赖项数组中,你可能会得到过时的值。
    • 始终将 effect 所依赖的所有状态和 props 包含在依赖项数组中。
    const [count, setCount] = useState(0);
    useEffect(() => {
    const handle = setInterval(() => {
    console.log(count); // 如果 `count` 不在依赖项数组中,这可能会记录过时的值
    }, 1000);
    return () => clearInterval(handle);
    }, [count]); // 确保 `count` 包含在依赖项数组中
  2. 函数作为依赖项:

    • 函数在每次渲染时都会重新创建,因此将它们包含在依赖项数组中可能会导致 effect 运行的频率超过必要次数。
    • 如果需要将函数包含在依赖项数组中,请使用 useCallback 来记忆化函数。
    const handleClick = useCallback(() => {
    // 处理点击事件
    }, []);
    useEffect(() => {
    // 此 effect 不会不必要地重新运行,因为 `handleClick` 已被记忆化
    }, [handleClick]);

延伸阅读

React 中的 `useRef` 钩子是什么?应该在什么时候使用它?

主题
React

TL;DR

React 中的 useRef 钩子用于创建一个在渲染之间保持不变的可变对象。它可用于直接访问和操作 DOM 元素,存储在更新时不导致重新渲染的可变值,以及保留对值的引用而不触发重新渲染。例如,您可以使用 useRef 来聚焦输入元素:

import React, { useRef, useEffect } from 'react';
function TextInputWithFocusButton() {
const inputEl = useRef(null);
useEffect(() => {
inputEl.current.focus();
}, []);
return <input ref={inputEl} type="text" />;
}

React 中的 useRef 钩子是什么?应该在什么时候使用它?

useRef 简介

React 中的 useRef 钩子是一个函数,它返回一个可变的 ref 对象,其 .current 属性被初始化为传递的参数 (initialValue)。返回的对象将在组件的整个生命周期内保持不变。

useRef 的关键用例

访问和操作 DOM 元素

useRef 的主要用例之一是直接访问和操作 DOM 元素。当您需要以 React 的声明式方法无法轻松实现的方式与 DOM 交互时,这特别有用。

例子:

import React, { useRef, useEffect } from 'react';
function TextInputWithFocusButton() {
const inputEl = useRef(null);
useEffect(() => {
inputEl.current.focus();
}, []);
return <input ref={inputEl} type="text" />;
}

在此示例中,useRef 钩子用于创建对输入元素的引用,而 useEffect 钩子用于在组件挂载时聚焦输入元素。

存储可变值

useRef 也可以用于存储在更新时不会导致重新渲染的可变值。这对于跟踪随时间变化但不需要触发重新渲染的值很有用。

例子:

import React, { useRef } from 'react';
function Timer() {
const count = useRef(0);
const increment = () => {
count.current += 1;
console.log(count.current);
};
return <button onClick={increment}>Increment</button>;
}

在此示例中,count 变量存储在 useRef 对象中,并且它的值递增,而不会导致组件重新渲染。

保留对值的引用

useRef 可用于保留对值的引用,而不会触发重新渲染。这对于存储需要在渲染之间保持不变但不需要在更改时导致重新渲染的值很有用。

例子:

import React, { useRef, useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
const prevCountRef = useRef();
useEffect(() => {
prevCountRef.current = count;
}, [count]);
return (
<div>
<h1>Now: {count}</h1>
<h2>Before: {prevCountRef.current}</h2>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}

在这个例子中,prevCountRef 用于保持对 count 前一个值的引用,而不会导致重新渲染。

延伸阅读

React 中的 `useCallback` Hook 是什么?应该在什么时候使用?

主题
React

TL;DR

React 中的 useCallback Hook 用于记忆函数,防止它们在每次渲染时被重新创建。当将回调传递给依赖于引用相等性以防止不必要渲染的优化子组件时,这特别有用。当您有一个作为 prop 传递给子组件的函数,并且您希望避免子组件不必要地重新渲染时,应该使用 useCallback

const memoizedCallback = useCallback(() => {
doSomething(a, b);
}, [a, b]);

React 中的 useCallback Hook 是什么?应该在什么时候使用?

什么是 useCallback

useCallback Hook 是一个 React Hook,它返回一个记忆化的回调函数,该函数仅在其中一个依赖项发生更改时才会更改。它对于通过防止不必要的函数重新创建来优化性能非常有用。

语法

const memoizedCallback = useCallback(() => {
doSomething(a, b);
}, [a, b]);

什么时候应该使用 useCallback

防止不必要的重新渲染

当您将一个函数作为 prop 传递给子组件时,即使函数本身没有改变,子组件也可能在每次父组件重新渲染时重新渲染。使用 useCallback 可确保函数引用保持不变,只要其依赖项没有改变,从而防止不必要的重新渲染。

const ParentComponent = () => {
const [count, setCount] = useState(0);
const handleClick = useCallback(() => {
setCount(count + 1);
}, [count]);
return <ChildComponent onClick={handleClick} />;
};
const ChildComponent = React.memo(({ onClick }) => {
console.log('ChildComponent rendered');
return <button onClick={onClick}>Click me</button>;
});
优化性能

在复杂的应用程序中,在每次渲染时重新创建函数在性能方面可能代价高昂。通过使用 useCallback,您可以避免这种开销,并使您的应用程序更有效率。

注意事项

  • 过度使用:过度使用 useCallback 可能会导致代码更复杂,并且不一定总能提高性能。应谨慎使用。
  • 依赖项:确保所有依赖项都在依赖项数组中正确指定。缺少依赖项可能会导致陈旧的闭包和错误。

延伸阅读

React 中的 `useMemo` Hook 是什么?应该在什么时候使用它?

主题
React

TL;DR

React 中的 useMemo Hook 用于记忆昂贵的计算,以便仅在其中一个依赖项发生更改时才重新计算它们。 这可以通过避免不必要的重新计算来提高性能。 当您有一个计算量很大的函数,并且不需要在每次渲染时运行它时,您应该使用 useMemo

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

React 中的 useMemo Hook 是什么?应该在什么时候使用它?

什么是 useMemo

useMemo Hook 是一个内置的 React Hook,它允许您记忆函数的计算结果。 这意味着该函数仅在其依赖项之一发生更改时才会重新执行。 useMemo 的主要目的是通过防止不必要的重新计算来优化性能。

语法

useMemo 的语法如下:

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
  • 第一个参数是一个返回您要记忆的值的函数。
  • 第二个参数是一个依赖项数组。 仅当这些依赖项之一发生更改时,才会重新计算记忆值。

什么时候应该使用它?

昂贵的计算

如果您有一个执行计算量很大的计算的函数,则可以使用 useMemo 确保仅在必要时才执行此计算。

const expensiveCalculation = (num) => {
// Some expensive calculation
return num * 2;
};
const MyComponent = ({ number }) => {
const memoizedValue = useMemo(() => expensiveCalculation(number), [number]);
return <div>{memoizedValue}</div>;
};
避免不必要的渲染

useMemo 还可以用于避免子组件的不必要渲染。 如果子组件依赖于计算量很大的值,则可以使用 useMemo 确保仅在必要时才重新计算该值。

const MyComponent = ({ items }) => {
const sortedItems = useMemo(() => {
return items.sort((a, b) => a - b);
}, [items]);
return <ChildComponent sortedItems={sortedItems} />;
};

注意事项

  • 过度使用:过度使用 useMemo 可能会导致代码更复杂,而没有显着的性能优势。 应谨慎使用。
  • 依赖项:确保正确指定所有依赖项。 缺少依赖项可能会导致值过时,而额外的依赖项可能会导致不必要的重新计算。

延伸阅读

React 中的 `useReducer` Hook 是什么?应该在什么时候使用它?

主题
React

TL;DR

React 中的 useReducer Hook 用于在函数组件中管理复杂的 state 逻辑。它是 useState 的替代方案,当 state 具有多个子值或下一个 state 依赖于前一个 state 时,它特别有用。它接受一个 reducer 函数和一个初始 state 作为参数,并返回当前 state 和一个 dispatch 函数。

const [state, dispatch] = useReducer(reducer, initialState);

当您有涉及多个子值的复杂 state 逻辑,或者下一个 state 依赖于前一个 state 时,请使用 useReducer


React 中的 useReducer Hook 是什么?应该在什么时候使用它?

useReducer 简介

useReducer Hook 是一个 React Hook,用于在函数组件中管理 state。它是 useState Hook 的替代方案,特别适用于管理更复杂的 state 逻辑。useReducer Hook 类似于 JavaScript 数组中的 reduce 函数,您可以使用一个 reducer 函数来确定 state 应该如何响应操作而改变。

语法

useReducer Hook 接受两个参数:一个 reducer 函数和一个初始 state。它返回一个包含当前 state 和一个 dispatch 函数的数组。

const [state, dispatch] = useReducer(reducer, initialState);

Reducer 函数

reducer 函数是一个纯函数,它接受当前 state 和一个 action 作为参数,并返回新的 state。action 是一个对象,通常具有一个 type 属性和一个可选的 payload

function reducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
default:
throw new Error();
}
}

使用示例

这是一个使用 useReducer 管理计数器 state 的简单示例:

import React, { useReducer } from 'react';
const initialState = { count: 0 };
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
default:
throw new Error();
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<div>
<p>Count: {state.count}</p>
<button onClick={() => dispatch({ type: 'increment' })}>Increment</button>
<button onClick={() => dispatch({ type: 'decrement' })}>Decrement</button>
</div>
);
}
export default Counter;

什么时候使用 useReducer

  • 复杂的 state 逻辑:当您有涉及多个子值的复杂 state 逻辑,或者下一个 state 依赖于前一个 state 时,请使用 useReducer
  • 状态管理:当您需要更可预测的状态管理模式(类似于 Redux)时,它很有用。
  • 性能优化useReducer 可以通过避免不必要的重新渲染来帮助优化某些情况下的性能。

延伸阅读

React 中的 `useId` Hook 是什么?应该在什么时候使用?

主题
React

TL;DR

React 中的 useId Hook 用于为组件内的元素生成唯一的 ID。这对于可访问性目的特别有用,例如将表单输入与其标签链接起来。它确保 ID 在整个应用程序中是唯一的,即使组件被多次渲染。

import { useId } from 'react';
function MyComponent() {
const id = useId();
return (
<div>
<label htmlFor={id}>Name:</label>
<input id={id} type="text" />
</div>
);
}

React 中的 useId Hook 是什么?应该在什么时候使用?

useId 简介

useId Hook 是 React 18 中引入的内置 React Hook。它旨在生成可在组件内使用的唯一 ID。这对于确保 ID 在整个应用程序中是唯一的特别有用,即使组件被多次渲染。

什么时候使用 useId

可访问性

useId 的主要用例之一是改善可访问性。例如,在创建表单元素时,使用标签上的 htmlFor 属性和输入上的 id 属性将标签链接到其对应的输入非常重要。useId Hook 确保这些 ID 是唯一的,从而防止任何潜在的冲突。

import { useId } from 'react';
function MyComponent() {
const id = useId();
return (
<div>
<label htmlFor={id}>Name:</label>
<input id={id} type="text" />
</div>
);
}
动态组件

当您有动态渲染或多次渲染的组件时,使用 useId 可确保组件的每个实例都具有唯一的 ID。这可以防止多个元素最终具有相同的 ID,这可能会导致可访问性和 JavaScript 行为出现问题。

import { useId } from 'react';
function DynamicComponent() {
const id = useId();
return (
<div>
<label htmlFor={id}>Dynamic Input:</label>
<input id={id} type="text" />
</div>
);
}
function App() {
return (
<div>
<DynamicComponent />
<DynamicComponent />
</div>
);
}

useId 的工作原理

useId Hook 生成可用作 ID 的唯一字符串。此字符串在整个应用程序中是唯一的,即使组件被多次渲染,也能确保不会发生冲突。该 Hook 不接受任何参数并返回一个字符串。

import { useId } from 'react';
function ExampleComponent() {
const id = useId();
console.log(id); // Outputs a unique ID string
return <div id={id}>Unique ID Component</div>;
}

最佳实践

  • 用于可访问性:将标签链接到输入时,始终使用 useId 以确保可访问性。
  • 避免硬编码 ID:不要硬编码 ID,而是使用 useId 动态生成它们。
  • 一致使用:在整个应用程序中一致地使用 useId 以避免 ID 冲突。

延伸阅读

React 中重新渲染是什么意思?

主题
React

总结

在 React 中,重新渲染是指组件响应状态或 props 的变化而更新其在 DOM 中的输出的过程。当组件的状态或 props 发生变化时,React 会触发重新渲染,以确保 UI 反映最新的数据。此过程涉及再次调用组件的 render 方法以生成新的虚拟 DOM,然后将其与之前的虚拟 DOM 进行比较,以确定更新实际 DOM 所需的最小更改集。


React 中重新渲染是什么意思?

理解重新渲染

在 React 中,重新渲染是指组件响应其状态或 props 的变化而更新其在 DOM 中的输出的过程。这确保了 UI 始终与底层数据同步。

什么时候会发生重新渲染?

重新渲染发生在以下情况下:

  • 当组件使用 setState 更改状态时
  • 当组件从其父组件接收到新的 props 时
  • 当父组件重新渲染时,导致其子组件也重新渲染

重新渲染过程

  1. 状态或 props 更改:当组件的状态或 props 更改时,React 会为该组件安排重新渲染。
  2. render 方法:React 调用组件的 render 方法以生成新的虚拟 DOM 树。
  3. 虚拟 DOM 比较:React 使用 diffing 算法将新的虚拟 DOM 树与之前的虚拟 DOM 树进行比较。
  4. DOM 更新:React 计算所需的最小更改集并相应地更新实际 DOM。

示例

这是一个简单的示例,用于说明重新渲染:

import React, { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}

在此示例中:

  • Counter 组件有一个状态变量 count
  • 当单击按钮时,setCount 会更新状态,从而触发重新渲染。
  • 再次调用 render 方法,并将新的虚拟 DOM 与之前的虚拟 DOM 进行比较。
  • React 更新实际 DOM 以反映新的计数。

性能考虑

重新渲染可能很昂贵,尤其对于复杂的组件或大型应用程序而言。为了优化性能,React 提供了几种技术:

  • PureComponent:一个基类,它实现了 props 和 state 的浅层比较,以防止不必要的重新渲染。
  • React.memo:一个高阶组件,它会记忆组件 render 的结果,以避免在 props 未更改的情况下重新渲染。
  • useMemo 和 useCallback:用于记忆值和函数的 Hook,以防止不必要的重新渲染。

延伸阅读

React Fragments 有什么用?

主题
React

TL;DR

React Fragments 用于对多个元素进行分组,而无需向 DOM 添加额外的节点。当您希望从组件的 render 方法返回多个元素,而无需将它们包裹在额外的 HTML 元素中时,这非常有用。您可以使用简写语法 <>...</>React.Fragment 语法。

return (
<>
<ChildComponent1 />
<ChildComponent2 />
</>
);

React Fragments 有什么用?

对多个元素进行分组

React Fragments 允许您对多个元素进行分组,而无需向 DOM 添加额外的节点。当您希望从组件的 render 方法返回多个元素,但又不想引入不必要的包装元素时,这特别有用。

避免不必要的 DOM 节点

使用 React Fragments 有助于避免不必要的 DOM 节点,这对于性能和维护更清晰的 DOM 结构是有益的。这在复杂应用程序中尤为重要,因为额外的节点可能导致内存使用增加和渲染时间变慢。

语法

使用 React Fragments 有两种方法:

  1. 简写语法:这是使用 fragments 最简洁的方式。它使用空标签 <>...</>

    return (
    <>
    <ChildComponent1 />
    <ChildComponent2 />
    </>
    );
  2. 完整语法:这使用 React.Fragment,如果您需要向 fragment 添加键,这可能很有用。

    return (
    <React.Fragment>
    <ChildComponent1 />
    <ChildComponent2 />
    </React.Fragment>
    );

向 fragments 添加键

如果您需要向 fragment 中的元素添加键,则必须使用完整的 React.Fragment 语法。这在渲染元素列表时很有用。

return (
<>
{items.map((item) => (
<React.Fragment key={item.id}>
<ChildComponent />
</React.Fragment>
))}
</>
);

用例

  • 从组件返回多个元素:当组件需要返回多个兄弟元素时,使用 fragment 可以帮助避免不必要的包装元素。
  • 渲染列表:渲染元素列表时,可以使用 fragments 对列表项进行分组,而无需向 DOM 添加额外的节点。
  • 条件渲染:当有条件地渲染多个元素时,fragments 可以帮助保持 DOM 结构的整洁。

延伸阅读

React 中的 `forwardRef()` 有什么作用?

主题
React

TL;DR

React 中的 forwardRef() 用于通过组件将 ref 传递给其子组件之一。当您需要直接从父组件访问 DOM 元素或子组件的实例时,这非常有用。您使用 forwardRef() 包装您的函数式组件,并使用 ref 参数将 ref 转发到所需的子元素。

import React, { forwardRef } from 'react';
const MyComponent = forwardRef((props, ref) => <input ref={ref} {...props} />);

React 中的 forwardRef() 有什么作用?

简介

在 React 中,forwardRef() 是一个高阶函数,它允许您通过组件将 ref 转发给其子组件之一。当您需要直接从父组件访问 DOM 元素或子组件的实例时,这特别有用。

为什么使用 forwardRef()

在以下几种情况下,forwardRef() 很有用:

  • 访问 DOM 元素:当您需要直接操作 DOM 元素时,例如聚焦输入字段。
  • 与子组件交互:当您需要调用子组件实例的方法或访问其属性时。

如何使用 forwardRef()

要使用 forwardRef(),您需要使用它包装您的函数式组件,并使用 ref 参数将 ref 转发到所需的子元素。

示例

这是一个简单的示例,演示如何使用 forwardRef()

import React, { forwardRef, useRef } from 'react';
// 定义一个函数式组件并用 forwardRef 包装它
const MyInput = forwardRef((props, ref) => <input ref={ref} {...props} />);
const ParentComponent = () => {
const inputRef = useRef(null);
const focusInput = () => {
// 访问 input 元素并聚焦它
if (inputRef.current) {
inputRef.current.focus();
}
};
return (
<div>
<MyInput ref={inputRef} placeholder="Type here..." />
<button onClick={focusInput}>Focus Input</button>
</div>
);
};
export default ParentComponent;

在此示例中:

  1. MyInput 是一个用 forwardRef() 包装的函数式组件。
  2. ref 参数被转发到 MyInput 内部的 input 元素。
  3. ParentComponent 中,使用 useRef() 创建一个 ref (inputRef)。
  4. inputRef 被传递给 MyInput,允许父组件直接访问 input 元素。
  5. 当单击按钮时,focusInput 函数使用 ref 来聚焦 input 元素。

重要注意事项

  • 仅限函数式组件forwardRef() 与函数式组件一起使用。类组件可以直接使用 ref,而无需 forwardRef()
  • Ref 转发:确保 ref 被转发到 DOM 元素或类组件实例,而不是另一个函数式组件。

延伸阅读

如何在 React 中重置组件的状态?

主题
React

TL;DR

要在 React 中重置组件的状态,您可以将状态设置回其初始值。这可以通过定义初始状态,然后使用 setState 函数来重置它来实现。例如,如果您有一个像这样的状态对象:

const [state, setState] = useState(initialState);

您可以通过调用以下方法来重置它:

setState(initialState);

如何在 React 中重置组件的状态?

使用带有钩子的函数式组件

在函数式组件中,您可以使用 useState 钩子来管理状态。要重置状态,您可以简单地使用初始状态值调用 setState 函数。

示例
import React, { useState } from 'react';
const MyComponent = () => {
const initialState = { count: 0, text: '' };
const [state, setState] = useState(initialState);
const resetState = () => {
setState(initialState);
};
return (
<div>
<p>Count: {state.count}</p>
<p>Text: {state.text}</p>
<button onClick={resetState}>Reset</button>
</div>
);
};
export default MyComponent;

使用类组件

在类组件中,您可以通过使用初始状态值调用 this.setState 来重置状态。

示例
import React, { Component } from 'react';
class MyComponent extends Component {
constructor(props) {
super(props);
this.initialState = { count: 0, text: '' };
this.state = this.initialState;
}
resetState = () => {
this.setState(this.initialState);
};
render() {
return (
<div>
<p>Count: {this.state.count}</p>
<p>Text: {this.state.text}</p>
<button onClick={this.resetState}>Reset</button>
</div>
);
}
}
export default MyComponent;

使用函数生成初始状态

有时,初始状态可能源自 props 或其他动态源。在这种情况下,您可以使用一个函数来生成初始状态。

示例
import React, { useState } from 'react';
const MyComponent = (props) => {
const getInitialState = () => ({ count: props.initialCount, text: '' });
const [state, setState] = useState(getInitialState);
const resetState = () => {
setState(getInitialState());
};
return (
<div>
<p>Count: {state.count}</p>
<p>Text: {state.text}</p>
<button onClick={resetState}>Reset</button>
</div>
);
};
export default MyComponent;

延伸阅读

为什么 React 建议不要修改状态?

主题
React

TL;DR

React 建议不要修改状态,因为它可能导致意外行为和错误。React 依赖于状态不变性来有效地确定何时重新渲染组件。当状态被直接修改时,React 可能无法检测到更改,从而导致过时或不正确的 UI 更新。相反,请始终使用 setStateuseState 钩子等方法创建一个新的状态对象。


为什么 React 建议不要修改状态?

React 的渲染机制

React 使用虚拟 DOM 来优化渲染。当状态改变时,React 将新的虚拟 DOM 与之前的虚拟 DOM 进行比较,以确定更新实际 DOM 所需的最小更改集。此过程称为协调。

不变性和状态比较

React 依赖于不变性来有效地检测更改。当状态是不可变的,React 可以通过比较引用来快速确定组件是否需要重新渲染。如果引用不同,React 知道状态已更改。

修改状态的问题

  1. 过时的 UI 更新:直接修改状态可能导致 React 无法检测到更改,从而导致 UI 未按预期更新。
  2. 调试困难:直接修改状态会使跟踪状态更改和调试问题变得更加困难。
  3. 意外行为:修改状态可能导致不可预测的行为,尤其是在多个组件依赖于同一状态时。

如何正确更新状态

不要直接修改状态,而是始终创建一个新的状态对象。例如,当使用 useState 钩子时:

const [state, setState] = useState(initialState);
// 错误:直接修改状态
state.value = newValue;
// 正确:创建一个新的状态对象
setState({ ...state, value: newValue });

当使用类组件和 setState 时:

this.setState((prevState) => ({
...prevState,
value: newValue,
}));

延伸阅读

React 中的错误边界是做什么的?

主题
React

总结

React 中的错误边界是捕获其子组件树中任何位置的 JavaScript 错误的组件,记录这些错误,并显示备用 UI,而不是崩溃整个应用程序。它们使用 componentDidCatch 生命周期方法和 static getDerivedStateFromError 方法实现。错误边界不会捕获事件处理程序、异步代码或服务器端渲染中的错误。


React 中的错误边界是做什么的?

简介

错误边界是 React 中的一项功能,有助于以更优雅的方式管理和处理错误。它们允许开发人员捕获其组件树中任何位置的 JavaScript 错误,记录这些错误,并显示备用 UI,而不是崩溃整个应用程序。

如何实现错误边界

要创建错误边界,您需要定义一个类组件,该组件实现以下一个或两个生命周期方法:

  • static getDerivedStateFromError(error):此方法用于更新状态,以便下一次渲染将显示备用 UI。
  • componentDidCatch(error, info):此方法用于记录错误信息。

这是一个错误边界组件的示例:

import React, { Component } from 'react';
class ErrorBoundary extends Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
// Update state so the next render shows the fallback UI
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
// You can also log the error to an error reporting service
console.error('Error caught by ErrorBoundary: ', error, errorInfo);
}
render() {
if (this.state.hasError) {
// You can render any custom fallback UI
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
export default ErrorBoundary;

用法

要使用错误边界,请将其包装在您要监视错误的任何组件周围:

<ErrorBoundary>
<MyComponent />
</ErrorBoundary>

限制

错误边界有一些限制:

  • 它们不会捕获事件处理程序中的错误。对于事件处理程序,您需要使用常规的 JavaScript try/catch 块。
  • 它们不会捕获异步代码中的错误(例如,setTimeoutrequestAnimationFrame 回调)。
  • 它们不会捕获服务器端渲染期间的错误。
  • 它们不会捕获在错误边界本身中抛出的错误。

最佳实践

  • 使用错误边界来包装高级组件,例如路由处理程序或应用程序的主要部分。
  • 将错误记录到错误报告服务,以跟踪生产中的问题。
  • 提供用户友好的备用 UI,以改善发生错误时的用户体验。

延伸阅读

如何测试 React 应用程序?

主题
React

TL;DR

要测试 React 应用程序,您可以使用 Jest 和 React Testing Library 等工具。Jest 是一个与 React 配合良好的 JavaScript 测试框架,而 React Testing Library 提供了以类似于用户与组件交互的方式测试 React 组件的实用程序。您可以使用 Cypress 等工具为单个组件编写单元测试、为组件交互编写集成测试以及编写端到端测试。


如何测试 React 应用程序?

单元测试

单元测试涉及单独测试各个组件。Jest 是一个用于单元测试 React 应用程序的常用选择。它提供了一个测试运行器、断言库和模拟功能。

示例
import React from 'react';
import { render, screen } from '@testing-library/react';
import '@testing-library/jest-dom/extend-expect';
import MyComponent from './MyComponent';
test('renders the component with the correct text', () => {
render(<MyComponent />);
expect(screen.getByText('Hello, World!')).toBeInTheDocument();
});

集成测试

集成测试涉及测试多个组件之间的交互。React Testing Library 对此很有用,因为它允许您渲染组件并像用户一样与它们交互。

示例
import React from 'react';
import { render, fireEvent, screen } from '@testing-library/react';
import '@testing-library/jest-dom/extend-expect';
import ParentComponent from './ParentComponent';
test('updates child component when parent state changes', () => {
render(<ParentComponent />);
fireEvent.click(screen.getByText('Update Child'));
expect(screen.getByText('Child Updated')).toBeInTheDocument();
});

端到端测试

端到端 (E2E) 测试涉及从用户的角度测试整个应用程序流程。Cypress 是一个用于 React 应用程序中 E2E 测试的常用工具。

示例
describe('My Application', () => {
it('should allow a user to log in', () => {
cy.visit('/login');
cy.get('input[name="username"]').type('user');
cy.get('input[name="password"]').type('password');
cy.get('button[type="submit"]').click();
cy.url().should('include', '/dashboard');
});
});

快照测试

快照测试涉及捕获组件的渲染输出并将其与已保存的快照进行比较。Jest 提供了对快照测试的内置支持。

示例
import React from 'react';
import renderer from 'react-test-renderer';
import MyComponent from './MyComponent';
test('matches the snapshot', () => {
const tree = renderer.create(<MyComponent />).toJSON();
expect(tree).toMatchSnapshot();
});

延伸阅读

解释 React 水合是什么

主题
React

TL;DR

React 水合是在客户端附加事件监听器并使服务器渲染的 HTML 页面具有交互性的过程。当 React 应用程序进行服务器端渲染时,HTML 会发送到客户端,React 会接管并通过附加事件处理程序和初始化状态来使其具有动态性。此过程称为水合。


什么是 React 水合?

服务器端渲染 (SSR)

服务器端渲染 (SSR) 是一种在服务器上生成网页 HTML 并将其发送到客户端的技术。这允许更快的初始页面加载和更好的 SEO,因为内容在加载页面时已经可用。

水合过程

水合是在将服务器端渲染的 HTML 发送到客户端后发生的过程。React 获取静态 HTML 并通过附加事件监听器和初始化状态来“水合”它,从而使页面具有交互性。此过程涉及:

  1. 重用现有的 HTML:React 使用服务器生成的 HTML,而不是从头开始重新渲染它。
  2. 附加事件监听器:React 将必要的事件监听器附加到现有的 HTML 元素。
  3. 初始化状态:React 初始化组件状态和属性以使页面具有动态性。

示例

这是一个简单的例子,说明这个概念:

  1. 服务器端渲染:服务器生成以下 HTML:

    <div id="root">
    <button>Click me</button>
    </div>
  2. 客户端水合:当 HTML 发送到客户端时,React 使用以下代码对其进行水合:

    import React from 'react';
    import ReactDOM from 'react-dom';
    function App() {
    const handleClick = () => {
    alert('Button clicked!');
    };
    return <button onClick={handleClick}>Click me</button>;
    }
    ReactDOM.hydrate(<App />, document.getElementById('root'));

在此示例中,服务器将带有按钮的静态 HTML 发送到客户端。然后,React 通过附加 onClick 事件监听器来对按钮进行水合,使其具有交互性。

水合的好处

  1. 更快的初始加载:由于 HTML 已经可用,因此初始页面加载速度更快。
  2. SEO 优势:搜索引擎可以抓取服务器渲染的 HTML,从而改善 SEO。
  3. 改进的用户体验:用户可以立即看到内容,甚至在 React 完全接管之前。

水合的挑战

  1. 不匹配问题:如果服务器渲染的 HTML 与客户端 React 组件不匹配,则可能导致错误和警告。
  2. 性能开销:水合可能占用大量资源,尤其对于大型应用程序而言。

延伸阅读

React Portals 有什么用?

主题
React

TL;DR

React Portals 用于将子项渲染到存在于父组件层级结构之外的 DOM 节点中。这对于模态框、工具提示和下拉菜单等需要突破父组件的 overflow 或 z-index 限制的场景非常有用。您可以使用 ReactDOM.createPortal(child, container) 创建一个 portal。


React Portals 有什么用?

简介

React Portals 提供了一种将子项渲染到存在于父组件 DOM 结构之外的 DOM 节点中的方法。这对于某些需要突破正常父子 DOM 结构的 UI 模式特别有用。

用例

模态框

模态框通常需要在父组件之外渲染,以避免 z-index 和 overflow 问题。通过使用 portal,您可以确保模态框在 DOM 的顶层渲染,从而更容易管理其可见性和定位。

import React from 'react';
import ReactDOM from 'react-dom';
const Modal = ({ isOpen, children }) => {
if (!isOpen) return null;
return ReactDOM.createPortal(
<div className="modal">{children}</div>,
document.getElementById('modal-root'),
);
};
工具提示

工具提示需要在父组件之外渲染,以避免被 overflow 设置剪切。使用 portal 允许工具提示正确地定位,而不会受到父组件样式的约束。

import React from 'react';
import ReactDOM from 'react-dom';
const Tooltip = ({ text, targetRef }) => {
const tooltipStyle = {
position: 'absolute',
top: targetRef.current.offsetTop,
left: targetRef.current.offsetLeft,
};
return ReactDOM.createPortal(
<div className="tooltip" style={tooltipStyle}>
{text}
</div>,
document.body,
);
};
下拉菜单

下拉菜单通常需要在父组件之外渲染,以避免被 overflow 设置剪切。使用 portal 允许下拉菜单正确地定位,而不会受到父组件样式的约束。

import React from 'react';
import ReactDOM from 'react-dom';
const Dropdown = ({ isOpen, children }) => {
if (!isOpen) return null;
return ReactDOM.createPortal(
<div className="dropdown">{children}</div>,
document.body,
);
};

如何创建 portal

要创建一个 portal,您可以使用 ReactDOM.createPortal 方法。此方法接受两个参数:要渲染的子元素和要将其渲染到的 DOM 节点。

ReactDOM.createPortal(child, container);

优点

  • 突破父级约束:Portals 允许您在父组件的 DOM 层次结构之外渲染组件,这对于避免 z-index 和 overflow 问题很有用。
  • 改进的可访问性:通过在 DOM 的顶层渲染模态框和工具提示等组件,您可以确保它们更容易被屏幕阅读器和其他辅助技术访问。
  • 简化样式:通过在父组件之外渲染组件,您可以避免复杂的 CSS 规则,并确保组件的样式正确。

延伸阅读

如何调试 React 应用程序?

主题
React

TL;DR

要调试 React 应用程序,您可以使用 React Developer Tools 浏览器扩展程序来检查组件层次结构和状态。此外,您可以使用 console.log 语句来记录数据和错误,并利用浏览器开发者工具在代码中设置断点。对于更高级的调试,您可以使用错误边界来捕获和处理组件中的错误。


如何调试 React 应用程序?

使用 React Developer Tools

React Developer Tools 浏览器扩展程序是一个用于检查和调试 React 应用程序的强大工具。它允许您:

  • 检查组件层次结构
  • 查看和编辑组件状态和属性
  • 跟踪组件重新渲染

您可以从 Chrome Web StoreFirefox Add-ons 安装该扩展程序。

使用 console.log 语句

在代码中添加 console.log 语句可以帮助您了解应用程序的流程并识别问题。例如:

function MyComponent(props) {
console.log('Rendering MyComponent with props:', props);
return <div>{props.message}</div>;
}

使用断点

浏览器开发者工具(例如 Chrome DevTools)允许您在代码中设置断点。这可以帮助您暂停执行并检查应用程序的当前状态。要设置断点:

  1. 打开浏览器的开发者工具(通常按 F12Ctrl+Shift+I)。
  2. 导航到“Sources”选项卡。
  3. 找到相关文件和代码行。
  4. 单击行号以设置断点。

使用错误边界

错误边界是 React 组件,用于捕获其子组件树中的 JavaScript 错误。您可以通过两种方式实现错误边界:

使用 React 的内置类组件
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
// Update state so the next render will show the fallback UI.
return { hasError: true };
}
componentDidCatch(error, info) {
// You can also log the error to an error reporting service
console.error('Error caught by error boundary:', error, info);
}
render() {
if (this.state.hasError) {
// You can render any custom fallback UI
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
// Usage
function App() {
return (
<ErrorBoundary>
<MyComponent />
</ErrorBoundary>
);
}
使用 react-error-boundary 包

或者,您可以使用 react-error-boundary 包来获得更方便的方法:

import { ErrorBoundary } from 'react-error-boundary';
function ErrorFallback({ error, resetErrorBoundary }) {
return (
<div role="alert">
<p>出错了:</p>
<pre style={{ color: 'red' }}>{error.message}</pre>
<button onClick={resetErrorBoundary}>重试</button>
</div>
);
}
function App() {
return (
<ErrorBoundary
FallbackComponent={ErrorFallback}
onReset={() => {
// 重置您的应用程序的状态
}}
onError={(error, info) => {
// 将错误记录到错误报告服务
}}>
<MyComponent />
</ErrorBoundary>
);
}

对于处理事件处理程序或异步代码中的错误,您可以使用 useErrorBoundary 钩子:

import { useErrorBoundary } from 'react-error-boundary';
function MyComponent() {
const { showBoundary } = useErrorBoundary();
const handleAsyncError = async () => {
try {
await someAsyncOperation();
} catch (error) {
showBoundary(error);
}
};
return <button onClick={handleAsyncError}>执行操作</button>;
}

延伸阅读

什么是 React 严格模式以及它有什么好处?

主题
React

TL;DR

React 严格模式是一个开发工具,可帮助识别应用程序中的潜在问题。它为其后代激活额外的检查和警告。它不呈现任何可见的 UI,也不会影响生产构建。其好处包括识别不安全的生命周期方法、警告旧的字符串 ref API 的使用、检测意外的副作用以及确保组件能够适应未来的变化。


什么是 React 严格模式以及它有什么好处?

什么是 React 严格模式?

React 严格模式是 React 中的一个功能,可帮助开发人员识别其应用程序中的潜在问题。它是一个包装组件,您可以使用它来包装应用程序的某些部分以启用额外的检查和警告。它不呈现任何可见的 UI,也不会影响生产构建。

要使用 React 严格模式,您可以使用 StrictMode 组件包装您的组件树:

import React from 'react';
import ReactDOM from 'react-dom';
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root'),
);

React 严格模式的好处

识别不安全的生命周期方法

React 严格模式有助于识别使用不安全的生命周期方法的组件,例如 componentWillMountcomponentWillReceivePropscomponentWillUpdate。这些方法被认为是不安全的,因为它们可能导致错误和意外行为。如果您的任何组件使用这些方法,React 严格模式将向您发出警告。

警告旧的字符串 ref API 的使用

如果您正在使用旧的字符串 ref API,React 严格模式会向您发出警告。字符串 ref API 被认为是旧的,不建议在新代码中使用。相反,您应该使用回调 ref API 或 React.createRef API。

检测意外的副作用

React 严格模式通过有意地双重调用某些生命周期方法和函数来帮助检测意外的副作用。这有助于确保您的组件能够适应未来的变化,并且它们不依赖于可能不总是执行的副作用。

确保组件能够适应未来的变化

通过启用 React 严格模式,您可以确保您的组件更能适应 React 的未来变化。额外的检查和警告可帮助您尽早识别潜在问题,从而更容易维护和更新您的代码库。

延伸阅读

如何本地化 React 应用程序?

主题
React国际化

TL;DR

要本地化 React 应用程序,您通常使用像 react-i18nextreact-intl 这样的库。首先,您为不同的语言设置翻译文件。然后,您在 React 应用程序中配置本地化库。最后,您使用提供的钩子或组件在组件中显示本地化文本。

// 使用 react-i18next 的示例
import { useTranslation } from 'react-i18next';
const MyComponent = () => {
const { t } = useTranslation();
return <p>{t('welcome_message')}</p>;
};

在 React 中设置本地化

选择本地化库

有几个库可用于本地化 React 应用程序,其中 react-i18nextreact-intl 是最受欢迎的。在本指南中,我们将重点介绍 react-i18next

安装库

首先,安装必要的软件包:

npm install i18next react-i18next

设置翻译文件

为要支持的每种语言创建 JSON 文件。例如,在 locales 目录中创建 en.jsonfr.json

// locales/en.json
{
"welcome_message": "Welcome to our application!"
}
// locales/fr.json
{
"welcome_message": "Bienvenue dans notre application!"
}

配置本地化库

在您的应用程序中设置 i18nextreact-i18next。创建一个 i18n.js 文件用于配置:

// i18n.js
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import en from './locales/en.json';
import fr from './locales/fr.json';
i18n.use(initReactI18next).init({
resources: {
en: { translation: en },
fr: { translation: fr },
},
lng: 'en', // default language
fallbackLng: 'en',
interpolation: {
escapeValue: false, // react already safes from xss
},
});
export default i18n;

与您的 React 应用程序集成

使用 I18nextProvider 包装您的应用程序并导入 i18n 配置:

// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import { I18nextProvider } from 'react-i18next';
import i18n from './i18n';
ReactDOM.render(
<I18nextProvider i18n={i18n}>
<App />
</I18nextProvider>,
document.getElementById('root'),
);

在组件中使用翻译

使用 useTranslation 钩子来访问用于翻译文本的 t 函数:

// MyComponent.js
import React from 'react';
import { useTranslation } from 'react-i18next';
const MyComponent = () => {
const { t } = useTranslation();
return <p>{t('welcome_message')}</p>;
};
export default MyComponent;

切换语言

要切换语言,请使用 i18n.changeLanguage 方法:

// LanguageSwitcher.js
import React from 'react';
import { useTranslation } from 'react-i18next';
const LanguageSwitcher = () => {
const { i18n } = useTranslation();
const changeLanguage = (lng) => {
i18n.changeLanguage(lng);
};
return (
<div>
<button onClick={() => changeLanguage('en')}>English</button>
<button onClick={() => changeLanguage('fr')}>Français</button>
</div>
);
};
export default LanguageSwitcher;

延伸阅读

React 应用程序中的代码拆分是什么?

主题
React

总结

React 应用程序中的代码拆分是一种通过将代码拆分成更小的块(可以按需加载)来提高性能的技术。这有助于减少应用程序的初始加载时间。您可以使用动态 import() 语句或 React 的 React.lazySuspense 来实现代码拆分。

// 使用 React.lazy 和 Suspense
const LazyComponent = React.lazy(() => import('./LazyComponent'));
function App() {
return (
<React.Suspense fallback={<div>Loading...</div>}>
<LazyComponent />
</React.Suspense>
);
}

React 应用程序中的代码拆分是什么?

简介

代码拆分是一种性能优化技术,它涉及将应用程序的代码分解成更小、更易于管理的块。这允许应用程序最初只加载必要的代码,并将其他部分的加载推迟到需要时。这可以显着减少初始加载时间并改善整体用户体验。

如何实现代码拆分

使用动态 import()

动态 import() 是一个 JavaScript 特性,允许您异步加载模块。这可以用于将代码拆分成单独的块。

// 动态导入示例
import('./module').then((module) => {
// 使用该模块
});
使用 React.lazy 和 Suspense

React 通过 React.lazySuspense 提供对代码拆分的内置支持。React.lazy 允许您将动态导入呈现为常规组件,而 Suspense 允许您在加载组件时指定加载回退。

// 延迟加载组件
const LazyComponent = React.lazy(() => import('./LazyComponent'));
function App() {
return (
<React.Suspense fallback={<div>Loading...</div>}>
<LazyComponent />
</React.Suspense>
);
}

代码拆分的好处

  • 提高性能:通过最初只加载必要的代码,您可以减少应用程序的初始加载时间。
  • 更好的用户体验:更快的加载时间带来更流畅、响应更快的用户体验。
  • 有效的资源使用:代码拆分确保仅在需要时加载代码,从而更有效地使用资源。

工具和库

  • Webpack:Webpack 是一个流行的模块打包器,它开箱即用地支持代码拆分。您可以将其配置为自动将代码拆分成块。
  • React Loadable:虽然 React.lazySuspense 是在 React 中实现代码拆分的推荐方法,但 React Loadable 是一个较旧的库,也提供了类似的功能。

延伸阅读

如何优化 React Context 的性能以减少重新渲染?

主题
React性能

总结

要优化 React Context 的性能并减少重新渲染,可以使用诸如记忆上下文值、拆分上下文和使用选择器等技术。使用 useMemo 记忆上下文值可确保上下文值仅在其依赖项更改时才更改。拆分上下文允许您将状态更改隔离到应用程序的特定部分。使用像 use-context-selector 这样的库与选择器可以帮助您仅重新渲染实际需要更新的上下文值的组件。

const value = useMemo(() => ({ state, dispatch }), [state, dispatch]);

如何优化 React Context 的性能以减少重新渲染

记忆上下文值

减少不必要的重新渲染的最有效方法之一是记忆上下文值。通过使用 useMemo,您可以确保上下文值仅在其依赖项更改时才更改。

import React, { createContext, useMemo, useState } from 'react';
const MyContext = createContext();
const MyProvider = ({ children }) => {
const [state, setState] = useState(initialState);
const value = useMemo(() => ({ state, setState }), [state]);
return <MyContext.Provider value={value}>{children}</MyContext.Provider>;
};

拆分上下文

另一种技术是将您的上下文拆分为多个较小的上下文。通过这种方式,您可以将状态更改隔离到应用程序的特定部分,从而减少需要重新渲染的组件数量。

const UserContext = createContext();
const ThemeContext = createContext();
const UserProvider = ({ children }) => {
const [user, setUser] = useState(null);
return (
<UserContext.Provider value={{ user, setUser }}>
{children}
</UserContext.Provider>
);
};
const ThemeProvider = ({ children }) => {
const [theme, setTheme] = useState('light');
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
{children}
</ThemeContext.Provider>
);
};

使用选择器

使用选择器可以帮助您仅重新渲染实际需要更新的上下文值的组件。像 use-context-selector 这样的库对此目的非常有用。

import { createContext, useContextSelector } from 'use-context-selector';
const MyContext = createContext();
const MyComponent = () => {
const state = useContextSelector(MyContext, (v) => v.state);
return <div>{state}</div>;
};

延伸阅读

React 中什么是高阶组件?

主题
React

TL;DR

React 中的高阶组件 (HOC) 是接受一个组件并返回一个新组件的函数,该新组件具有额外的 props 或行为。它们用于重用组件逻辑。例如,如果您有一个组件 MyComponent,您可以像这样创建一个 HOC:

const withExtraProps = (WrappedComponent) => {
return (props) => <WrappedComponent {...props} extraProp="value" />;
};
const EnhancedComponent = withExtraProps(MyComponent);

React 中什么是高阶组件?

定义

高阶组件 (HOC) 是 React 中接受一个组件作为参数并返回一个新组件的函数。新组件通常会包装原始组件并添加额外的 props、state 或行为。HOC 是一种重用组件逻辑的模式。

目的

HOC 用于:

  • 在组件之间共享通用功能
  • 抽象和重用组件逻辑
  • 使用额外的 props 或 state 增强组件

示例

这是一个简单的 HOC 示例,它向包装的组件添加一个 extraProp

import React from 'react';
// 定义 HOC
const withExtraProps = (WrappedComponent) => {
return (props) => {
return <WrappedComponent {...props} extraProp="value" />;
};
};
// 定义要包装的组件
const MyComponent = (props) => {
return <div>{props.extraProp}</div>;
};
// 使用 HOC 包装组件
const EnhancedComponent = withExtraProps(MyComponent);
// 使用增强组件
const App = () => {
return <EnhancedComponent />;
};
export default App;

在此示例中,withExtraProps 是一个 HOC,它向 MyComponent 添加一个 extraPropEnhancedComponent 现在可以访问 extraProp

常见用例

  • 身份验证:在渲染之前包装组件以检查用户是否已通过身份验证。
  • 日志记录:向组件添加日志记录功能。
  • 主题:将与主题相关的 props 注入到组件中。
  • 数据获取:获取数据并将其作为 props 传递给组件。

最佳实践

  • 不要改变原始组件:始终返回一个新组件。
  • 谨慎使用 HOC:过度使用 HOC 会使代码更难理解。
  • 正确命名 HOC:使用一个描述性名称来指示 HOC 的作用。

替代方案

  • Render props: 一种模式,组件使用函数作为 prop 来确定要渲染的内容。
  • Hooks: 自定义 hooks 可用于在函数组件之间共享逻辑。

延伸阅读

什么是 Flux 模式以及它的好处?

主题
React

总结

Flux 模式是一种用于管理应用程序状态(尤其是在 React 生态系统中)的架构设计。它强制执行单向数据流,从而更容易管理和调试应用程序状态。

  • 核心组件
    • 调度器:管理操作并将它们分派到存储。
    • 存储:保存应用程序的状态和逻辑。
    • 操作:从应用程序发送到调度器的信息负载。
    • 视图:当存储更新时重新渲染的 React 组件。
  • 好处
    • 由于单向数据流,状态管理可预测。
    • 改进的调试和测试。
    • 关注点清晰分离。

示例流程:

  1. 用户与 视图 交互。
  2. 操作 被触发并由 调度器 分派。
  3. 存储 处理操作并更新其状态。
  4. 视图 根据更新后的状态重新渲染。

什么是 Flux 模式?

概述

Flux 是 Facebook 引入的一种设计模式,用于管理 React 应用程序中的数据流。它强制执行单向数据流,其中数据通过特定组件在一个方向上流动:

  1. 调度器:充当中央枢纽,将操作分派到存储。
  2. 存储:管理应用程序的状态并包含业务逻辑。
  3. 操作:表示发送到调度器的信息负载。
  4. 视图:由 React 组件组成,这些组件监听存储的变化并相应地重新渲染。

这种结构简化了状态管理,特别是对于复杂的应用程序,通过确保数据以可预测和可追踪的方式流动。

单向数据流

与数据可以双向流动的传统 MVC 模式不同,Flux 的单向流确保了一致性:

  1. 用户交互触发 操作
  2. 操作 被发送到 调度器,后者将它们转发到 存储
  3. 存储 更新其状态并通知 视图 重新渲染。

代码示例

const Dispatcher = require('flux').Dispatcher;
const dispatcher = new Dispatcher();
// Action
const action = {
type: 'INCREMENT',
payload: { amount: 1 },
};
dispatcher.dispatch(action);
// Store
class CounterStore {
constructor() {
this.count = 0;
dispatcher.register((action) => {
if (action.type === 'INCREMENT') {
this.count += action.payload.amount;
console.log(`Count: ${this.count}`);
}
});
}
}
const store = new CounterStore();
// Dispatching an action
dispatcher.dispatch({ type: 'INCREMENT', payload: { amount: 1 } });

Flux 模式的优点

可预测的状态管理

单向数据流确保了应用程序的状态转换清晰且可预测,从而更容易理解和调试。

改进调试和测试

  • 每个动作代表一个离散事件,更容易追踪应用程序中的变化。
  • 存储包含纯逻辑,可以独立于视图进行单元测试。

可扩展性

  • 随着应用程序的增长,Flux 模式有助于保持清晰的结构。
  • 解耦组件允许模块化开发。

关注点清晰分离

  • 动作封装事件和有效载荷。
  • 存储处理状态和业务逻辑。
  • 视图侧重于渲染 UI。

与 React 的兼容性

Flux 的单向数据流与 React 的声明式组件模型完美契合,实现无缝集成。

延伸阅读

解释 React 的单向数据流及其优势

主题
React

TL;DR

在 React 中,单向数据流意味着 React 应用程序中的数据以单个方向流动,从父组件到子组件。这使得数据流可预测且更易于调试。主要优点包括提高可维护性、更易于调试和更好的性能。


React 的单向数据流及其优势

什么是单向数据流?

在 React 中,单向数据流是指数据以单个方向流动,从父组件到子组件的概念。这是通过使用 props 实现的。父组件通过 props 将数据传递给子组件,子组件只能读取这些 props,但不能修改它们。如果子组件需要反馈给父组件,它会通过调用从父组件作为 prop 传递下来的函数来实现。

示例

这是一个简单的示例,用于说明单向数据流:

// ParentComponent.jsx
import React, { useState } from 'react';
import ChildComponent from './ChildComponent';
const ParentComponent = () => {
const [data, setData] = useState('Hello from Parent');
const handleChange = (newData) => {
setData(newData);
};
return (
<div>
<h1>{data}</h1>
<ChildComponent data={data} onChange={handleChange} />
</div>
);
};
export default ParentComponent;
// ChildComponent.jsx
import React from 'react';
const ChildComponent = ({ data, onChange }) => {
return (
<div>
<p>{data}</p>
<button onClick={() => onChange('Hello from Child')}>Change Data</button>
</div>
);
};
export default ChildComponent;

在此示例中,ParentComponent 通过 propsdatahandleChange 函数传递给 ChildComponentChildComponent 可以读取 data 并调用 onChange 以反馈给父组件。

单向数据流的优势

提高可维护性

单向数据流使应用程序结构更具可预测性,更易于理解。由于数据以单个方向流动,因此更容易跟踪数据随时间的变化,从而使代码库更易于维护。

更易于调试

使用单向数据流,更容易查明可能发生错误的地点。由于数据只能从父级流向子级,因此您可以跟踪数据流并更快地确定问题来源。

更好的性能

单向数据流可以带来更好的性能,因为它降低了数据管理的复杂性。React 的协调算法可以通过比较当前状态和先前状态来有效地更新 DOM,从而最大限度地减少所需的更新次数。

延伸阅读

如何在 React 应用程序中处理异步数据加载?

主题
React异步

TL;DR

在 React 应用程序中,异步数据加载通常使用 useEffectuseState 钩子来处理。您在 useEffect 内部启动数据获取,并使用获取的数据更新状态。这确保了组件使用新数据重新渲染。这是一个简单的例子:

import React, { useState, useEffect } from 'react';
function DataFetchingComponent() {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
async function fetchData() {
const response = await fetch('https://api.example.com/data');
const result = await response.json();
setData(result);
setLoading(false);
}
fetchData();
}, []);
if (loading) {
return <div>Loading...</div>;
}
return <div>{JSON.stringify(data)}</div>;
}

在 React 应用程序中处理异步数据加载

使用 useEffectuseState

处理 React 中异步数据加载最常见的方法是使用 useEffectuseState 钩子。useEffect 允许您执行副作用,例如数据获取,而 useState 有助于管理组件的状态。

  1. 初始化状态:使用 useState 创建状态变量来存储获取的数据和加载状态。
  2. 获取数据:使用 useEffect 在组件挂载时执行数据获取。
  3. 更新状态:获取数据后,更新状态以触发重新渲染。

这是一个详细的例子:

import React, { useState, useEffect } from 'react';
function DataFetchingComponent() {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
async function fetchData() {
try {
const response = await fetch('https://api.example.com/data');
const result = await response.json();
setData(result);
} catch (error) {
console.error('Error fetching data:', error);
} finally {
setLoading(false);
}
}
fetchData();
}, []);
if (loading) {
return <div>Loading...</div>;
}
return <div>{JSON.stringify(data)}</div>;
}

处理错误

处理数据获取期间可能发生的错误非常重要。您可以在 useEffect 中使用 try-catch 块来捕获和处理错误。

useEffect(() => {
async function fetchData() {
try {
const response = await fetch('https://api.example.com/data');
const result = await response.json();
setData(result);
} catch (error) {
console.error('Error fetching data:', error);
} finally {
setLoading(false);
}
}
fetchData();
}, []);

使用自定义钩子

为了更好地重用代码,您可以创建自定义钩子来处理数据获取。这允许您封装数据获取逻辑并在多个组件中重用它。

import { useState, useEffect } from 'react';
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
async function fetchData() {
try {
const response = await fetch(url);
const result = await response.json();
setData(result);
} catch (error) {
setError(error);
} finally {
setLoading(false);
}
}
fetchData();
}, [url]);
return { data, loading, error };
}
export default useFetch;

然后,您可以在组件中使用此自定义钩子:

import React from 'react';
import useFetch from './useFetch';
function DataFetchingComponent() {
const { data, loading, error } = useFetch('https://api.example.com/data');
if (loading) {
return <div>Loading...</div>;
}
if (error) {
return <div>Error: {error.message}</div>;
}
return <div>{JSON.stringify(data)}</div>;
}

延伸阅读

解释 React 应用程序的服务器端渲染及其优势?

主题
React

总结

React 中的服务器端渲染 (SSR) 涉及在服务器上渲染 React 组件,并将完全渲染的 HTML 发送到客户端。这种方法可以改善初始加载时间和 SEO。服务器处理初始渲染,客户端通过 React 的水合过程接管。好处包括更快的初始页面加载、更好的 SEO 以及在较慢设备上的改进性能。


什么是 React 应用程序的服务器端渲染?

定义

服务器端渲染 (SSR) 是一种技术,服务器渲染 React 应用程序的初始 HTML 并将其发送到客户端。这与客户端渲染 (CSR) 形成对比,在客户端渲染中,浏览器下载一个最小的 HTML 页面,并使用 JavaScript 渲染内容。

工作原理

  1. 初始请求:当用户请求页面时,服务器处理此请求。
  2. 在服务器上渲染:服务器使用 React 将组件渲染成 HTML。
  3. 将 HTML 发送到客户端:服务器将完全渲染的 HTML 发送到客户端。
  4. 水合:加载 HTML 后,React 接管并绑定事件处理程序,使页面具有交互性。

代码示例

这是一个使用 Next.js 的基本示例,这是一个支持开箱即用的 SSR 的流行 React 框架:

import React from 'react';
const Home = ({ data }) => (
<div>
<h1>欢迎来到我的 SSR React 应用程序</h1>
<p>来自服务器的数据:{data}</p>
</div>
);
export async function getServerSideProps() {
// 从 API 或数据库中获取数据
const data = await fetchDataFromAPI();
return { props: { data } };
}
export default Home;

服务器端渲染的优势

改进的初始加载时间

  • 更快的内容显示:由于服务器发送完全渲染的 HTML,用户看到内容的显示速度比 CSR 快,在 CSR 中,浏览器必须先下载并执行 JavaScript 才能渲染。

更好的 SEO

  • 搜索引擎索引:搜索引擎可以轻松地索引完全渲染的 HTML,从而改善应用程序的 SEO。这对于内容丰富的网站尤其重要。

在较慢设备上的性能

  • 减少客户端处理:SSR 减少了需要在客户端处理的 JavaScript 量,这对于使用较慢设备或网络状况不佳的用户来说是有益的。

增强的用户体验

  • 感知性能:用户会觉得应用程序更快,因为他们可以更快地看到内容,即使 JavaScript 仍在后台加载。

延伸阅读

解释 React 应用程序的静态生成及其优势?

主题
React

TL;DR

React 应用程序中的静态生成涉及在构建时预渲染 HTML,而不是在每个请求时。这可以加快加载时间并提高性能,因为 HTML 已经生成,可以直接从 CDN 提供。它还可以改善 SEO 并减少服务器负载。像 Next.js 这样的工具通过允许开发人员轻松生成静态页面来促进静态生成。


React 应用程序的静态生成及其优势

什么是静态生成?

静态生成是一种预渲染方法,其中页面的 HTML 在构建时生成。这意味着 HTML 在构建过程中创建一次,然后在每个请求中重复使用。在 React 应用程序的上下文中,这通常使用 Next.js 等框架来实现。

静态生成如何工作?

  1. 构建时渲染:在构建过程中,框架根据 React 组件和数据为每个页面生成 HTML。
  2. 静态文件:然后,生成的 HTML、CSS 和 JavaScript 文件将存储为静态文件。
  3. 提供文件:这些静态文件可以直接从 CDN 或 Web 服务器提供,而无需在每个请求上进行服务器端渲染。

静态生成的优势

提高性能
  • 更快的加载时间:由于 HTML 是预生成的,因此可以立即提供,而无需等待服务器端渲染。
  • 减少服务器负载:静态文件可以从 CDN 提供,从而减少了源服务器的负载。
更好的 SEO
  • 搜索引擎索引:预渲染的 HTML 更容易被搜索引擎索引,从而改善 SEO。
  • 一致的内容:内容在请求之间保持一致,确保搜索引擎看到与用户相同的内容。
增强安全性
  • 没有服务器端代码执行:由于没有服务器端渲染,服务器端漏洞的风险较小。
  • 静态文件:提供静态文件减少了与动态内容生成相比的攻击面。
可扩展性
  • CDN 分发:静态文件可以在多个 CDN 节点之间分发,从而提高可扩展性并减少延迟。
  • 高效缓存:静态文件可以轻松缓存,从而进一步提高性能并减少服务器负载。

Next.js 示例

Next.js 是一个流行的 React 框架,支持静态生成。这是一个在 Next.js 中使用静态生成的简单示例:

// pages/index.js
import React from 'react';
const HomePage = ({ data }) => {
return (
<div>
<h1>欢迎来到我的静态网站!</h1>
<p>{data.message}</p>
</div>
);
};
export async function getStaticProps() {
// 在构建时获取数据
const data = { message: '你好,世界!' };
return {
props: {
data,
},
};
}
export default HomePage;

在这个例子中,getStaticProps 函数在构建时获取数据,HomePage 组件使用这些数据来渲染 HTML。

延伸阅读

解释 React 中的展示组件与容器组件模式

主题
React

TL;DR

在 React 中,展示组件与容器组件模式是一种设计方法,其中展示组件侧重于外观,而容器组件侧重于工作方式。展示组件负责渲染 HTML 和 CSS,而容器组件处理逻辑和状态管理。这种分离有助于维护干净和有组织的 codebase。


React 中的展示组件与容器组件模式

展示组件

展示组件主要关注 UI。它们仅通过 props 接收数据和回调,并且很少有自己的状态(除了 UI 状态,如 hoveractive)。它们通常是无状态函数组件,但也可以是类组件。

特点
  • 关注外观
  • 通过 props 接收数据和回调
  • 很少有自己的状态
  • 通常写成函数组件
  • 不直接使用 Redux 或其他状态管理库
例子
const Button = ({ onClick, label }) => (
<button onClick={onClick}>{label}</button>
);

容器组件

容器组件关注于工作方式。它们管理状态并处理业务逻辑。它们通常获取数据、处理用户交互,并将数据作为 props 传递给展示组件。

特点
  • 关注工作方式
  • 管理状态并处理业务逻辑
  • 获取数据并处理用户交互
  • 将数据和回调传递给展示组件
  • 经常使用 Redux 或其他状态管理库
例子
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { fetchData } from './actions';
import Button from './Button';
class ButtonContainer extends Component {
componentDidMount() {
this.props.fetchData();
}
handleClick = () => {
// Handle button click
};
render() {
return <Button onClick={this.handleClick} label="Click me" />;
}
}
const mapDispatchToProps = {
fetchData,
};
export default connect(null, mapDispatchToProps)(ButtonContainer);

优点

  • 关注点分离:通过将 UI 与逻辑分离,代码库变得更加模块化,更易于维护。
  • 可重用性:展示组件可以在应用程序的不同部分重复使用,因为它们不与特定逻辑相关联。
  • 可测试性:展示组件更容易测试,因为它们是无状态的,并且仅依赖于 props。

延伸阅读

在 React 中进行数据获取时,有哪些常见的陷阱?

主题
React

TL;DR

在 React 中进行数据获取时,常见的陷阱包括不处理加载和错误状态、通过不清理订阅导致内存泄漏,以及不使用正确的生命周期方法或钩子。 始终确保正确处理这些状态,清理组件后,并在函数式组件中使用 useEffect 进行副作用处理。


在 React 中进行数据获取时常见的陷阱

不处理加载和错误状态

在获取数据时,管理请求的不同状态(加载、成功和错误)至关重要。 否则,可能会导致糟糕的用户体验。

const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
fetch('https://api.example.com/data')
.then((response) => response.json())
.then((data) => {
setData(data);
setLoading(false);
})
.catch((error) => {
setError(error);
setLoading(false);
});
}, []);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return <div>{JSON.stringify(data)}</div>;

通过不清理订阅导致内存泄漏

当组件在获取请求完成之前卸载时,它可能导致内存泄漏。 为了防止这种情况,您应该清理任何正在进行的请求。

useEffect(() => {
let isMounted = true;
fetch('https://api.example.com/data')
.then((response) => response.json())
.then((data) => {
if (isMounted) {
setData(data);
setLoading(false);
}
})
.catch((error) => {
if (isMounted) {
setError(error);
setLoading(false);
}
});
return () => {
isMounted = false;
};
}, []);

不使用正确的生命周期方法或钩子

在类组件中,数据获取应在 componentDidMount 中完成。 在函数式组件中,使用 useEffect 钩子。

// Class component
class MyComponent extends React.Component {
componentDidMount() {
fetch('https://api.example.com/data')
.then((response) => response.json())
.then((data) => this.setState({ data, loading: false }))
.catch((error) => this.setState({ error, loading: false }));
}
}
// Functional component
const MyComponent = () => {
useEffect(() => {
fetch('https://api.example.com/data')
.then((response) => response.json())
.then((data) => setData(data))
.catch((error) => setError(error));
}, []);
};

忽略 useEffect 中的依赖项数组

useEffect 中的依赖项数组决定了 effect 的运行时间。 忽略它可能导致不必要的重新渲染或错过的更新。

useEffect(() => {
fetch('https://api.example.com/data')
.then((response) => response.json())
.then((data) => setData(data))
.catch((error) => setError(error));
}, []); // 空数组意味着此 effect 在初始渲染后运行一次

在 render 方法中获取数据

直接在 render 方法中获取数据可能导致无限循环和性能问题。 始终使用生命周期方法或钩子。

// Incorrect
const MyComponent = () => {
const data = fetch('https://api.example.com/data').then((response) =>
response.json(),
);
return <div>{JSON.stringify(data)}</div>;
};
// Correct
const MyComponent = () => {
useEffect(() => {
fetch('https://api.example.com/data')
.then((response) => response.json())
.then((data) => setData(data))
.catch((error) => setError(error));
}, []);
};

延伸阅读

React 中的 render props 是什么,它们有什么用?

主题
React

TL;DR

React 中的 Render props 是一种在组件之间共享代码的技术,它使用一个值为函数的 prop。此函数返回一个 React 元素,并允许您将数据传递给子组件。它有助于重用组件逻辑,而无需使用高阶组件或钩子。

class DataFetcher extends React.Component {
state = { data: null };
componentDidMount() {
fetch(this.props.url)
.then((response) => response.json())
.then((data) => this.setState({ data }));
}
render() {
return this.props.render(this.state.data);
}
}
// Usage
<DataFetcher
url="/api/data"
render={(data) => <div>{data ? data.name : 'Loading...'}</div>}
/>;

React 中的 render props 是什么,它们有什么用?

定义

Render props 是 React 中一种在组件之间共享代码的模式,它使用一个值为函数的 prop。此函数被称为“render prop”,因为它用于确定要渲染的内容。

目的

Render props 用于:

  • 在组件之间共享逻辑,而无需使用高阶组件 (HOC) 或钩子
  • 使组件更具可重用性和可组合性
  • 提高代码可读性和可维护性

工作原理

使用 render prop 的组件将一个函数作为 prop。此函数在组件的 render 方法中被调用,以生成所需的输出。

示例

这是一个简单的示例,用于说明这个概念:

class MouseTracker extends React.Component {
state = { x: 0, y: 0 };
handleMouseMove = (event) => {
this.setState({
x: event.clientX,
y: event.clientY,
});
};
render() {
return (
<div style={{ height: '100vh' }} onMouseMove={this.handleMouseMove}>
{this.props.render(this.state)}
</div>
);
}
}
// Usage
<MouseTracker
render={({ x, y }) => (
<h1>
The mouse position is ({x}, {y})
</h1>
)}
/>;

在此示例中,MouseTracker 是一个跟踪鼠标位置并将坐标传递给 render prop 函数的组件。然后,render prop 函数确定如何显示坐标。

优点

  • 可重用性:跟踪鼠标位置的逻辑被封装在 MouseTracker 中,使其可在应用程序的不同部分重复使用。
  • 关注点分离MouseTracker 组件负责跟踪鼠标位置,而 render prop 函数负责渲染 UI。
  • 灵活性:通过将不同的 render prop 函数传递给同一个 MouseTracker 组件,可以创建不同的 UI 表示。

延伸阅读

React 的一些反模式是什么?

主题
React

TL;DR

React 反模式是指可能导致代码效率低下、难以维护或出现错误的实践。一些常见的反模式包括:

  • 直接改变状态,而不是使用 setState
  • 使用 componentWillMount 进行数据获取
  • 过度使用 componentWillReceiveProps
  • 在列表中不使用键
  • 在 render 中过度使用内联函数
  • 状态嵌套过深

常见的 React 反模式

直接改变状态

直接改变状态可能导致意外的行为和错误。 始终使用 setState 来更新状态。

// 反模式
this.state.count = 1;
// 正确方法
this.setState({ count: 1 });

使用 componentWillMount 进行数据获取

componentWillMount 已被弃用,不应用于数据获取。 请改用 componentDidMount

// 反模式
componentWillMount() {
fetchData();
}
// 正确方法
componentDidMount() {
fetchData();
}

过度使用 componentWillReceiveProps

componentWillReceiveProps 已被弃用。 请改用 getDerivedStateFromPropscomponentDidUpdate

// 反模式
componentWillReceiveProps(nextProps) {
if (nextProps.value !== this.props.value) {
this.setState({ value: nextProps.value });
}
}
// 正确方法
static getDerivedStateFromProps(nextProps, prevState) {
if (nextProps.value !== prevState.value) {
return { value: nextProps.value };
}
return null;
}

在列表中不使用键

键可帮助 React 识别哪些项目已更改、已添加或已删除。 不使用键可能导致渲染效率低下。

// 反模式
const listItems = items.map((item) => <li>{item}</li>);
// 正确方法
const listItems = items.map((item) => <li key={item.id}>{item}</li>);

在 render 中过度使用内联函数

render 方法中定义函数可能导致性能问题,因为每次渲染都会创建一个新函数。

// 反模式
render() {
return <button onClick={() => this.handleClick()}>点击我</button>;
}
// 正确方法
render() {
return <button onClick={this.handleClick}>点击我</button>;
}

深度嵌套状态

深度嵌套状态会使状态管理变得复杂且容易出错。 尽可能扁平化状态结构。

// 反模式
this.state = {
user: {
profile: {
name: 'John',
age: 30,
},
},
};
// 正确方法
this.state = {
userName: 'John',
userAge: 30,
};

延伸阅读

您如何决定使用 React 状态、上下文和外部状态管理器?

主题
React

TL;DR

在 React 状态、上下文和外部状态管理器之间进行选择取决于您的应用程序状态管理需求的复杂性和范围。将 React 状态用于本地组件状态,将 React 上下文用于需要在多个组件之间共享的全局状态,将 Redux 或 MobX 等外部状态管理器用于需要高级功能(如中间件、时间旅行调试)或需要在大型应用程序之间共享状态的复杂状态管理。


决定使用 React 状态、上下文和外部状态管理器

React 状态

React 状态最适合用于管理单个组件中的本地状态。它易于使用,并提供了一种直接的方式来处理不需要在多个组件之间共享的状态。

何时使用 React 状态
  • 当状态仅与单个组件相关时
  • 当状态不需要被其他组件访问或修改时
  • 当您希望保持组件自包含且易于理解时
示例
import React, { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}

React 上下文

React 上下文对于在多个组件之间共享状态非常有用,而无需通过组件树的每一层传递 props。它非常适合需要被许多组件访问的全局状态。

何时使用 React 上下文
  • 当您需要在多个组件之间共享状态时
  • 当您想避免 prop 钻取(通过多层组件传递 props)时
  • 当状态相对简单且不需要高级状态管理功能时
示例
import React, { createContext, useContext, useState } from 'react';
const ThemeContext = createContext();
function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
{children}
</ThemeContext.Provider>
);
}
function ThemedComponent() {
const { theme, setTheme } = useContext(ThemeContext);
return (
<div>
<p>Current theme: {theme}</p>
<button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
切换主题
</button>
</div>
);
}
function App() {
return (
<ThemeProvider>
<ThemedComponent />
</ThemeProvider>
);
}

外部状态管理器

Redux 或 MobX 等外部状态管理器专为复杂的状态管理需求而设计。它们提供高级功能,例如中间件、时间旅行调试以及在大型应用程序中管理状态的能力。

何时使用外部状态管理器
  • 当状态管理需求复杂并涉及许多相互关联的状态时
  • 当您需要高级功能(如中间件、时间旅行调试或开发工具)时
  • 当需要在具有许多组件的大型应用程序之间共享状态时
Redux 示例
// actions.js
export const increment = () => ({ type: 'INCREMENT' });
// reducer.js
const initialState = { count: 0 };
function counterReducer(state = initialState, action) {
switch (action.type) {
case 'INCREMENT':
return { ...state, count: state.count + 1 };
default:
return state;
}
}
export default counterReducer;
// store.js
import { createStore } from 'redux';
import counterReducer from './reducer';
const store = createStore(counterReducer);
export default store;
// Counter.js
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { increment } from './actions';
function Counter() {
const count = useSelector((state) => state.count);
const dispatch = useDispatch();
return (
<div>
<p>Count: {count}</p>
<button onClick={() => dispatch(increment())}>Increment</button>
</div>
);
}
// App.js
import React from 'react';
import { Provider } from 'react-redux';
import store from './store';
import Counter from './Counter';
function App() {
return (
<Provider store={store}>
<Counter />
</Provider>
);
}

延伸阅读

解释 React 中的组合模式

主题
React

TL;DR

React 中的组合模式是一种通过组合更小、可复用的组件来构建组件的方式。React 鼓励使用组合来创建复杂的 UI,而不是使用继承。你可以将组件作为子组件或 props 传递给其他组件来实现这一点。例如:

function WelcomeDialog() {
return (
<Dialog>
<h1>Welcome</h1>
<p>Thank you for visiting our spacecraft!</p>
</Dialog>
);
}
function Dialog(props) {
return <div className="dialog">{props.children}</div>;
}

React 中的组合模式

什么是组合?

组合是一种设计原则,它涉及组合更小、可复用的组件来构建更复杂的组件。在 React 中,这比继承更适合创建复杂的 UI。

如何在 React 中使用组合

将组件作为子组件传递

使用组合的一种常见方法是将组件作为子组件传递给其他组件。这允许你嵌套组件并创建一个层次结构。

function Dialog(props) {
return <div className="dialog">{props.children}</div>;
}
function WelcomeDialog() {
return (
<Dialog>
<h1>Welcome</h1>
<p>Thank you for visiting our spacecraft!</p>
</Dialog>
);
}
将组件作为 props 传递

实现组合的另一种方法是将组件作为 props 传递。这允许更大的灵活性和自定义。

function SplitPane(props) {
return (
<div className="split-pane">
<div className="split-pane-left">{props.left}</div>
<div className="split-pane-right">{props.right}</div>
</div>
);
}
function App() {
return <SplitPane left={<Contacts />} right={<Chat />} />;
}

组合的优点

  • 可重用性:较小的组件可以在应用程序的不同部分重复使用。
  • 可维护性:更容易管理和更新较小的组件。
  • 灵活性:组件可以以不同的方式轻松组合,以创建复杂的 UI。

什么时候使用组合

  • 当你需要从更小、可重用的组件创建复杂的 UI 时。
  • 当你想避免继承的缺陷时,例如紧密耦合和难以管理状态。

延伸阅读

什么是 React? 描述 React 的优点