React19开发:从零到项目实战

序言

欢迎踏入React 19的璀璨星河。在这里,代码不仅是指令,更是构建灵动数字世界的诗篇。React,以其声明式的优雅与组件化的智慧,早已重塑前端疆域。而React 19,并非简单的迭代,它是一次轻盈的跃迁——Server Components如预制的星辰,在云端悄然点亮;Actions化繁为简,让数据流转如溪水潺潺;文档元数据触手可及,资源加载暗蕴锋芒。

本书将作你的罗盘,穿越从初识到精通的壮阔航程。我们不仅剖析API的脉络,更探寻设计哲学的幽微光芒;不仅搭建坚实的基石,更触碰前沿的革新浪潮。每一行代码,都将是对优雅逻辑的雕琢;每一次实践,都是对工程之美的礼赞。

摒弃芜杂,拥抱纯粹。React 19邀你以更直观的方式,编织更富表现力的未来。打开这本书,让键盘成为你的梭子,在虚拟与现实的经纬间,编织属于你的、独一无二的数字星空。


目录

第一部分:筑基篇 - 初识React的哲学与基石

第1章:React的星辰大海 - 引言与生态纵览

  • 1.1 React的诞生、演进与核心理念 (声明式、组件化、单向数据流)

  • 1.2 React 19:新纪元的开启 (主要目标:简化、性能、能力增强)

  • 1.3 现代前端开发格局中的React定位

  • 1.4 搭建开发环境:Node.js, npm/yarn/pnpm, 现代构建工具链(Vite等)初探

  • 1.5 创建第一个React 19项目 (create-react-app 或 Vite模板)

第2章:构建世界的积木 - JSX与组件核心

  • 2.1 JSX的本质:语法糖与JavaScript的融合之美

  • 2.2 深入理解React元素与虚拟DOM

  • 2.3 函数组件:现代React的基石

  • 2.4 Props:组件间通信的桥梁 (类型检查:PropTypes vs TypeScript)

  • 2.5 条件渲染与列表渲染的艺术

第3章:组件的生命力 - State与生命周期 (函数组件视角)

  • 3.1 State:组件内部的状态管理

  • 3.2 useState Hook:状态管理的核心武器 (深入理解其原理与闭包)

  • 3.3 副作用(Side Effects)的概念与 useEffect Hook (数据获取、订阅、DOM操作)

  • 3.4 清理函数的重要性:避免内存泄漏

  • 3.5 函数组件的“生命周期” (依赖项数组的奥秘)

  • 3.6 理解“纯函数”与“副作用”的边界

第二部分:核心篇 - 掌握React的引擎室

第4章:Hooks的魔法世界 - 复用逻辑与状态管理进阶

  • 4.1 Hooks规则与设计哲学 (为何在顶层调用?)

  • 4.2 useContext:跨越层级的优雅通信 (主题、用户信息等全局状态)

  • 4.3 useRef:访问DOM与持久化可变值的利器

  • 4.4 useMemo & useCallback:性能优化的精密工具 (深入理解记忆化与闭包陷阱)

  • 4.5 构建强大的自定义Hook:逻辑复用的艺术

  • 4.6 其他常用内置Hook (useReducer, useImperativeHandle, useLayoutEffect等) 精解

第5章:渲染的智慧 - 协调、Keys与性能调优基础

  • 5.1 协调(Reconciliation)过程揭秘:React如何高效更新UI?

  • 5.2 key属性的本质:列表项的身份标识与性能关键

  • 5.3 识别常见性能瓶颈:不必要的渲染及其成因

  • 5.4 利用React DevTools进行性能剖析

  • 5.5 优化策略初探:React.memo, 合理拆分组件

第6章:组件间的交响乐 - 高级组合模式

  • 6.1 组件组合(Composition) vs 继承(Inheritance):React的黄金法则

  • 6.2 容器组件与展示组件模式

  • 6.3 Render Props模式:灵活的代码复用

  • 6.4 高阶组件(HOC)模式:增强组件能力 (结合Hooks的现代实践)

  • 6.5 插槽(Slot)模式与children Prop的灵活运用

  • 6.6 设计可复用、可维护组件的原则

第三部分:进阶篇 - React19的新大陆与深水区

第7章:React19革命性特性 - Server Components (服务端组件)

  • 7.1 RSC的设计哲学:解决什么问题?(Bundle Size, 数据获取, 安全性)

  • 7.2 理解服务端组件与客户端组件的边界与协作

  • 7.3 服务端组件的编写规则与限制 (无状态、无Effect、无浏览器API)

  • 7.4 数据获取:在服务端组件中直接获取数据 (与useEffect对比)

  • 7.5 使用RSC实现部分渲染(Partial Hydration)与流式渲染(Streaming)

  • 7.6 实战:构建一个集成RSC的应用架构 (结合Next.js App Router最佳实践)

第8章:React19革命性特性 - Actions & 数据变更

  • 8.1 传统数据提交的痛点 (表单提交、异步状态管理)

  • 8.2 Actions API:声明式数据变更的革命

  • 8.3 在组件中使用Actions (action Prop, useActionState, useFormStatus, useOptimistic)

  • 8.4 处理异步状态、乐观更新(Optimistic Updates)、错误处理

  • 8.5 与表单深度集成 (, FormData)

  • 8.6 实战:用Actions重构复杂表单交互

第9章:React19增强特性 - 文档元数据与资源加载

  • 9.1 传统管理文档元数据(, , )的挑战

  • 9.2 内置, , 组件:在组件内声明式管理

  • 9.3 资源加载优化:新的资源加载API (preload, preinit)

  • 9.4 结合RSC:在服务端设置元数据

第10章:状态管理的星辰大海 - Context与外部库(Redux, Zustand, Recoil)

  • 10.1 useContext的适用场景与局限性 (性能考量)

  • 10.2 状态管理库选型指南:何时需要?选择哪个?

  • 10.3 Redux核心概念与现代实践 (Redux Toolkit, RTK Query)

  • 10.4 Zustand:轻量级状态管理的魅力

  • 10.5 Recoil:原子化状态管理探索

  • 10.6 将状态管理库与React 19新特性(如Actions)结合

第11章:路由的艺术 - React Router深入与Next.js集成

  • 11.1 React Router v6+ 核心概念 (, , , )

  • 11.2 动态路由、嵌套路由、数据加载(loader/action)

  • 11.3 Next.js App Router:基于文件的路由与React 19深度集成 (RSC, Actions)

  • 11.4 在Next.js中充分利用React 19特性构建全栈应用

第四部分:实战篇 - 打造健壮、高性能的现代应用

第12章:样式化的乐章 - CSS-in-JS, CSS Modules, Tailwind CSS

  • 12.1 样式方案选型:各有所长

  • 12.2 CSS Modules:局部作用域CSS实践

  • 12.3 主流CSS-in-JS库:Styled Components, Emotion (与React 19的兼容性)

  • 12.4 Tailwind CSS:实用优先的现代方案 (在React项目中的高效应用)

  • 12.5 服务端组件中的样式处理策略

第13章:质量保障的堡垒 - 测试策略与工具

  • 13.1 测试金字塔与React应用测试策略

  • 13.2 Jest:测试运行器与断言库

  • 13.3 React Testing Library:以用户为中心的组件测试哲学

  • 13.4 测试Hook:@testing-library/react-hooks 或自定义渲染

  • 13.5 端到端(E2E)测试:Cypress / Playwright

  • 13.6 测试React 19新特性 (Actions, RSC的测试策略探讨)

第14章:性能优化的精雕细琢 - 深入React渲染机制

  • 14.1 深入理解渲染与提交(Commit)阶段

  • 14.2 Profiler API与React DevTools Profiler高级用法

  • 14.3 代码分割(Code Splitting):React.lazy, Suspense 与动态导入

  • 14.4 虚拟化(Virtualization):长列表性能救星 (react-window, react-virtualized)

  • 14.5 React 19新优化点分析 (如RSC对性能的潜在影响与优化)

  • 14.6 使用生产模式构建与部署

第15章:工程化与未来之路

  • 15.1 TypeScript与React 19的深度整合 (类型安全最佳实践)

  • 15.2 项目结构与代码组织规范

  • 15.3 代码规范与格式化 (ESLint, Prettier)

  • 15.4 状态机与状态管理:XState探索

  • 15.5 React的未来展望 (Beyond React 19)

  • 15.6 持续学习资源与社区参与

第五部分:项目工坊 - 融会贯通

第16章:实战项目一 - 构建现代化电商平台核心功能

  • 16.1 应用React 19 RSC实现商品列表页 (服务端数据获取、SEO优化)

  • 16.2 使用Actions处理购物车添加、商品收藏等交互 (乐观更新)

  • 16.3 集成状态管理 (如Zustand) 管理购物车全局状态

  • 16.4 路由管理 (React Router 或 Next.js App Router)

  • 16.5 性能优化点实践 (图片懒加载、代码分割)

第17章:实战项目二 - 打造实时互动社交应用

  • 17.1 利用useOptimistic实现即时消息发送的流畅体验

  • 17.2 复杂表单处理与数据提交 (Actions API)

  • 17.3 集成WebSocket实现实时消息推送

  • 17.4 性能挑战:无限滚动列表与虚拟化应用

  • 17.5 响应式设计实践

附录:

  • A. React 核心API 速查手册

  • B. 常用Hook 速查手册

  • C. React 19 新API 详解

  • D. 调试技巧与常见问题解答 (React 19相关陷阱)

  • E. 生态工具链推荐 (构建、部署、监控等)

  • F. 从旧版本迁移到React 19的注意事项


引言

在瞬息万变的数字时代,前端开发领域犹如一片浩瀚的星辰大海,技术浪潮此起彼伏,创新之光璀璨夺目。在这片广袤的领域中,React 以其独特的魅力和强大的生命力,成为了无数开发者追逐的焦点。它不仅仅是一个JavaScript库,更是一种构建用户界面的哲学,一种引领前端范式变革的力量。本书将带领读者,从React的起源与核心理念出发,逐步深入其内部机制,直至掌握React 19的最新特性与实战应用,共同探索这片充满无限可能的星辰大海。

React,由Facebook(现Meta)于2013年开源,自问世以来便以其“声明式编程”和“组件化”的理念,彻底改变了前端开发的格局。它将复杂的UI拆解为独立、可复用的组件,极大地提升了开发效率和代码的可维护性。随着前端技术的飞速发展,React也在不断演进,从最初的类组件到Hook的引入,再到如今React 19带来的革命性更新,它始终走在技术前沿,为开发者提供了构建高性能、可扩展Web应用的强大工具。

React 19的发布,标志着React生态系统迈入了一个全新的纪元。它不仅在性能和开发体验上带来了显著提升,更引入了如Server Components、Actions等颠覆性特性,模糊了前后端的界限,为全栈开发带来了前所未有的机遇。本书将紧密围绕React 19的这些核心变化,结合丰富的代码示例和实战项目,帮助读者深入理解其设计思想,并将其应用于实际开发中。

无论您是初入前端领域的探索者,还是经验丰富的资深开发者,本书都将是您掌握React 19、驾驭现代前端开发的得力助手。让我们一同启程,在这片React的星辰大海中,乘风破浪,探索未知,共同铸就卓越的数字产品。


第一章:React的星辰大海 - 引言与生态纵览

1.1 React的诞生、演进与核心理念

React的诞生,源于Facebook在构建复杂用户界面时所面临的挑战。传统的命令式UI编程方式,使得代码难以维护和扩展,尤其是在数据频繁变化的场景下,手动操作DOM往往会导致性能问题和难以追踪的bug。为了解决这些痛点,Facebook的工程师们开始探索一种全新的UI构建方式,最终催生了React。

1.1.1 诞生与早期演进

2011年,Facebook的软件工程师Jordan Walke创造了FaxJS,这是React的早期原型。2012年,Instagram被Facebook收购后,其团队在开发移动应用时也遇到了类似的UI开发难题,于是FaxJS被引入并应用于Instagram的Web版本。2013年5月,在JSConf US大会上,React正式开源,并迅速引起了业界的广泛关注。早期React主要以类组件(Class Components)为主,通过setState来管理组件内部状态,并通过生命周期方法来处理组件的挂载、更新和卸载等。

1.1.2 核心理念

React之所以能够脱颖而出,并成为前端开发的主流框架之一,离不开其三大核心理念:声明式、组件化和单向数据流。

声明式 (Declarative)

声明式编程是React最显著的特点之一。在传统的命令式编程中,开发者需要一步步地指示计算机如何完成任务,例如手动操作DOM元素、改变它们的样式和内容。这种方式虽然灵活,但在面对复杂UI时,代码会变得冗长且难以理解和维护。React则采用了声明式的方式,开发者只需描述UI在给定状态下应该呈现的“样子”,而无需关心如何实现这些变化。React会根据状态的变化,自动高效地更新UI。

示例:命令式与声明式对比

假设我们要根据一个布尔值isVisible来显示或隐藏一个div元素。

命令式 (原生JavaScript):

const myDiv = document.getElementById("myDiv");
if (isVisible) {
  myDiv.style.display = "block";
} else {
  myDiv.style.display = "none";
}

声明式 (React JSX):

function MyComponent({ isVisible }) {
  return (
    
Hello, React!
); }

从上述示例可以看出,声明式代码更加简洁、直观,开发者可以更专注于“做什么”而不是“怎么做”,这大大降低了心智负担,提升了开发效率。

组件化 (Component-Based)

组件化是React的另一大核心理念。React鼓励开发者将UI拆分成独立、可复用、可组合的组件。每个组件都封装了自己的逻辑、状态和UI,形成一个独立的单元。这种模块化的开发方式带来了诸多优势:

  • 可复用性: 一旦组件被创建,就可以在应用程序的任何地方重复使用,避免了代码重复。
  • 可维护性: 每个组件都是独立的,修改一个组件不会影响其他组件,降低了维护成本。
  • 可测试性: 独立的组件更容易进行单元测试,确保其功能的正确性。
  • 协作性: 团队成员可以并行开发不同的组件,提高开发效率。

React中的组件可以是函数组件(Function Components)或类组件(Class Components)。随着Hook的引入,函数组件成为了现代React开发的主流。

单向数据流 (Unidirectional Data Flow)

React遵循严格的单向数据流原则,也被称为“自上而下”的数据流。这意味着数据总是从父组件流向子组件,子组件不能直接修改父组件传递的props。如果子组件需要与父组件通信或修改数据,它必须通过调用父组件传递的回调函数来实现。这种数据流模式使得数据变化可预测,更容易调试和理解应用程序的状态变化。

示例:单向数据流

function ParentComponent() {
  const [count, setCount] = React.useState(0);

  const increment = () => {
    setCount(count + 1);
  };

  return (
    

Parent Count: {count}

); } function ChildComponent({ count, onIncrement }) { return (

Child Count: {count}

); }

在上述示例中,count状态由ParentComponent管理,并通过props传递给ChildComponentChildComponent不能直接修改count,但可以通过调用onIncrement回调函数来请求ParentComponent更新count。这种清晰的数据流向,有效避免了复杂应用中数据混乱的问题。

这些核心理念共同构成了React强大而优雅的基石,使其能够高效地构建复杂且响应迅速的用户界面。理解并掌握这些理念,是深入学习React的关键。

1.2 React 19:新纪元的开启

React 19 的发布,不仅仅是版本号的简单迭代,它更像是一次深思熟虑的革新,旨在开启React开发的新纪元。此次更新的核心目标围绕着“简化”、“性能”和“能力增强”三个方面展开,旨在让开发者能够更轻松地构建高性能、可维护的现代Web应用。

1.2.1 简化开发体验

React 19 在简化开发体验方面做出了诸多努力,其中最引人注目的莫过于对异步操作和表单处理的优化。在以往的React开发中,处理数据提交、异步状态(如加载中、错误)以及乐观更新常常需要编写大量的样板代码,并手动管理复杂的逻辑。React 19 引入的 Actions 机制,彻底改变了这一现状。

通过 Actions,开发者可以直接将异步函数作为 form 元素的 action 属性或通过 useActionState Hook 进行管理。React 会自动处理请求的生命周期,包括:

  • 待定状态 (Pending State):自动跟踪异步操作的开始和结束,通过 isPending 等状态变量简化加载指示器的实现。
  • 错误处理 (Error Handling):提供统一的错误捕获机制,使得错误边界和错误提示的实现更加便捷。
  • 乐观更新 (Optimistic Updates):借助 useOptimistic Hook,开发者可以在数据实际更新前,提前更新UI,从而提供即时响应的用户体验,即使在网络延迟较高的情况下也能保持应用的流畅性。
  • 表单管理 (Form Management)
    元素与 Actions 的深度集成,使得表单提交和数据变更变得声明式且易于管理,大大减少了手动处理 FormData 和状态的复杂性。

这些改进使得开发者能够更专注于业务逻辑的实现,而无需过多关注底层异步操作的细节,从而显著提升了开发效率和代码的可读性。

1.2.2 性能提升

性能一直是React团队关注的重点,React 19 在此方面也带来了显著的提升,尤其是在服务端渲染(SSR)和静态站点生成(SSG)场景下。

  • 新的 use API:这个全新的Hook允许组件在渲染过程中直接读取Promise(例如数据请求的结果)和Context。这意味着开发者可以在组件内部更自然地处理异步数据,而无需依赖 useEffect 或其他生命周期方法。use API 与 Suspense 结合使用,可以实现更细粒度的加载状态管理和更流畅的用户体验,因为它允许React在数据准备好之前暂停渲染组件树的一部分,并在数据可用时恢复渲染。

  • 新的 React DOM Static APIs (prerender, prerenderToNodeStream):这些API旨在改进静态站点生成和SSR的性能。它们允许React在将HTML流发送到客户端之前,等待所有数据加载完成。这有助于确保客户端接收到完整的、可交互的HTML,减少了客户端水合(hydration)所需的时间,从而提升了首屏加载速度和用户感知的性能。

  • React 服务器组件 (Server Components):虽然Server Components在React 19之前就已经存在于Canary版本中,但它在React 19中得到了稳定支持。RSC允许开发者在服务器上渲染部分UI,并将渲染结果发送到客户端。这不仅可以减少客户端JavaScript包的大小,还可以利用服务器的计算能力进行数据获取和复杂逻辑处理,从而显著提升应用的性能和响应速度。RSC与客户端组件的无缝协作,为构建高性能的全栈应用提供了强大的支持。

1.2.3 能力增强

除了简化开发和提升性能,React 19 还增强了React在处理文档元数据和ref方面的能力,使得开发者能够更灵活地控制应用的各个方面。

  • 内置文档元数据组件:在以往的React应用中,管理HTML文档的 部分(如 </code>, <code><meta></code>, <code><link></code> 标签)通常需要借助第三方库或手动操作DOM。React 19 引入了内置的 <code><title></code>, <code><meta></code>, <code><link></code> 组件,允许开发者在组件内部声明式地管理这些文档元数据。这意味着开发者可以在React组件中直接控制页面的标题、描述、图标等信息,这对于SEO(搜索引擎优化)和用户体验至关重要。</p> </li> <li> <p><strong><code>ref</code> 作为属性</strong>:从React 19开始,<code>ref</code> 不再仅仅是一个特殊的属性,它现在可以作为普通的 <code>prop</code> 传递给函数组件。这一改变使得在函数组件中转发 <code>ref</code> 变得更加直观和灵活,简化了组件间DOM操作的模式,尤其是在构建可复用组件库时,这一特性将大大提升开发便利性。</p> </li> </ul> <p>综上所述,React 19 的发布,是React生态系统发展中的一个重要里程碑。它通过引入Actions、<code>use</code> API、Server Components等一系列创新特性,以及对现有功能的优化,为开发者提供了更强大、更便捷的工具,以应对现代Web应用开发中的各种挑战。这些改变不仅提升了开发效率和应用性能,也为React的未来发展奠定了坚实的基础。</p> <h3>1.3 现代前端开发格局中的React定位</h3> <p>在当今瞬息万变的现代前端开发格局中,各种框架和库层出不穷,百花齐放。从老牌的Angular、Vue,到新兴的Svelte、SolidJS,再到各种构建工具和状态管理方案,前端生态系统呈现出前所未有的繁荣景象。然而,在这场技术竞赛中,React 始终占据着举足轻重的地位,并持续引领着行业的发展方向。</p> <h4>1.3.1 市场份额与社区生态</h4> <p>自开源以来,React凭借其卓越的性能、灵活的API和庞大的社区支持,迅速成为前端开发领域的主流选择。根据多项行业报告和开发者调查(例如Stack Overflow年度开发者调查、State of JS报告),React常年位居最受欢迎和使用率最高的前端框架之列。其庞大的用户基础和活跃的社区,为React生态系统注入了源源不断的活力。</p> <ul> <li><strong>丰富的第三方库和工具</strong>: React生态系统拥有海量的第三方库和工具,涵盖了从UI组件库(如Material-UI, Ant Design)、状态管理(如Redux, Zustand)、路由(如React Router)、数据请求(如React Query)到测试(如React Testing Library)等各个方面。这些成熟的解决方案极大地提升了开发效率,降低了项目风险。</li> <li><strong>强大的招聘市场需求</strong>: 鉴于React在业界的广泛应用,掌握React技能已成为前端工程师的必备条件之一。招聘市场上对React开发者的需求持续旺盛,为学习者提供了广阔的职业发展空间。</li> <li><strong>活跃的社区支持与学习资源</strong>: React拥有一个极其活跃的全球开发者社区。无论是官方文档、博客文章、在线教程,还是Stack Overflow上的问答、GitHub上的开源项目,都能为开发者提供及时、全面的帮助。这种强大的社区支持,使得学习和解决问题变得更加高效。</li> </ul> <h4>1.3.2 与其他主流框架的比较</h4> <p>尽管React在前端领域占据主导地位,但其他主流框架也各有千秋,适用于不同的项目需求和团队偏好。理解React的定位,需要将其置于与其他框架的比较中进行考量。</p> <ul> <li><strong>与Angular</strong>: Angular是一个由Google维护的全面(opinionated)的MVC框架,提供了从路由、状态管理到HTTP请求等一整套解决方案。它更适合大型企业级应用,强调规范和约定。相比之下,React更像一个“库”,它只关注UI层,开发者可以根据项目需求自由选择其他库来构建完整的应用。React的灵活性使其适用于各种规模的项目,但同时也要求开发者具备更强的技术选型能力。</li> <li><strong>与Vue</strong>: Vue.js以其渐进式框架的特性和友好的API,受到了许多开发者的喜爱。它在易用性和学习曲线上具有优势,尤其适合中小型项目或快速原型开发。Vue在某些方面借鉴了React的组件化思想,但在数据绑定和模板语法上有所不同。React的JSX提供了更强大的JavaScript表达能力,而Vue的模板语法则更接近传统HTML。</li> <li><strong>与Svelte/SolidJS</strong>: Svelte和SolidJS是近年来兴起的新一代前端框架,它们在编译时将组件转换为原生JavaScript代码,从而实现了极致的性能和更小的运行时体积。它们代表了前端性能优化的新方向。然而,与React相比,它们的社区生态和成熟度仍在发展中,适用于对性能有极高要求且愿意尝试新技术的项目。</li> </ul> <h4>1.3.3 React的独特优势</h4> <p>React之所以能够在激烈的竞争中脱颖而出,并保持其领先地位,主要得益于以下几个独特优势:</p> <ul> <li><strong>声明式UI</strong>: 如前所述,声明式编程使得UI开发更加直观和可预测,降低了心智负担。</li> <li><strong>虚拟DOM (Virtual DOM)</strong>: React通过引入虚拟DOM,极大地优化了UI更新的性能。当组件状态发生变化时,React会先在内存中构建一个新的虚拟DOM树,然后将其与旧的虚拟DOM树进行比较(Diff算法),找出最小的差异,最后只更新真实DOM中需要改变的部分。这种机制避免了直接操作真实DOM带来的性能开销,使得UI更新高效流畅。</li> <li><strong>组件化与可组合性</strong>: 强大的组件化能力使得UI开发模块化、可复用,提高了开发效率和代码质量。</li> <li><strong>跨平台能力</strong>: React不仅限于Web开发,通过React Native,开发者可以使用相同的React知识和JavaScript语言来构建原生移动应用(iOS和Android)。此外,还有React VR、React for Desktop等项目,进一步拓展了React的应用边界。</li> <li><strong>持续创新与前瞻性</strong>: React团队始终致力于推动前端技术的发展,不断引入新的概念和特性(如Hook、Suspense、Concurrent Mode、Server Components等),保持其在技术前沿的领导地位。React 19的发布,再次证明了其在解决现代Web应用复杂性方面的决心和能力。</li> </ul> <p>综上所述,React在现代前端开发格局中占据着核心地位。它不仅拥有庞大的社区支持和丰富的生态系统,更以其独特的设计理念和持续的创新能力,为开发者提供了构建高性能、可扩展、跨平台应用的强大工具。掌握React,意味着掌握了通往现代前端开发世界的一把金钥匙。</p> <h3>1.4 搭建开发环境:Node.js, npm/yarn/pnpm, 现代构建工具链(Vite等)初探</h3> <p>在深入React 19的奇妙世界之前,我们首先需要搭建一个稳定、高效的开发环境。这就像建造一座宏伟的建筑,地基的稳固至关重要。一个良好的开发环境将确保我们能够顺利地编写、运行和调试React应用。</p> <h4>1.4.1 Node.js与包管理器</h4> <p>React应用通常运行在浏览器环境中,但其开发过程离不开Node.js。Node.js是一个基于Chrome V8 JavaScript引擎的运行时,它允许JavaScript在服务器端运行。在前端开发中,Node.js主要用于:</p> <ul> <li><strong>运行构建工具</strong>: 像Webpack、Vite等构建工具都是基于Node.js运行的。</li> <li><strong>执行JavaScript脚本</strong>: 自动化任务、代码转换等。</li> <li><strong>管理项目依赖</strong>: 通过npm、yarn或pnpm等包管理器安装和管理第三方库。</li> </ul> <h5>1.4.1.1 安装Node.js</h5> <p>安装Node.js最推荐的方式是访问其官方网站 [1] 下载对应操作系统的安装包。Node.js的安装包通常会捆绑npm(Node Package Manager),这是Node.js的默认包管理器。</p> <p><strong>步骤:</strong></p> <ol> <li>访问Node.js官方网站:https://nodejs.org/zh-cn/download/</li> <li>下载LTS(长期支持)版本,该版本更为稳定,适合生产环境。</li> <li>按照安装向导的指示完成安装。</li> </ol> <p>安装完成后,打开终端或命令行工具,输入以下命令验证Node.js和npm是否安装成功:</p> <pre><code class="prism language-bash"><span class="token function">node</span> <span class="token parameter variable">-v</span> <span class="token function">npm</span> <span class="token parameter variable">-v</span> </code></pre> <p>如果能够正确显示版本号,则表示安装成功。</p> <h5>1.4.1.2 包管理器:npm, yarn, pnpm</h5> <p>包管理器是前端开发中不可或缺的工具,它们帮助我们管理项目所依赖的各种库和模块。目前主流的包管理器有npm、yarn和pnpm。</p> <ul> <li><strong>npm (Node Package Manager)</strong>: Node.js的默认包管理器,功能全面,社区庞大。使用<code>npm install</code>安装依赖,<code>npm run</code>执行脚本。</li> <li><strong>Yarn</strong>: 由Facebook(现Meta)推出,旨在解决npm早期版本的一些痛点,如安装速度慢、依赖管理不确定性等。Yarn在安装速度和离线模式方面表现出色。可以通过<code>npm install -g yarn</code>全局安装Yarn。</li> <li><strong>pnpm</strong>: 一个更高效的包管理器,它通过符号链接(symlinks)的方式管理依赖,避免了重复安装相同依赖的问题,从而节省了磁盘空间并提升了安装速度。pnpm的安装方式与Yarn类似,<code>npm install -g pnpm</code>。</li> </ul> <p>在本书中,我们将主要使用npm作为包管理器,但读者可以根据个人喜好选择Yarn或pnpm,它们的基本用法大同小异。</p> <h4>1.4.2 现代构建工具链初探:Vite</h4> <p>在React开发中,我们通常不会直接在浏览器中运行JSX代码或ES Module模块。相反,我们需要一个“构建工具”来将我们的源代码(包括JSX、TypeScript、CSS预处理器等)转换成浏览器可以理解和运行的JavaScript、CSS和HTML。传统的构建工具如Webpack功能强大但配置复杂,且启动和热更新速度较慢。随着前端工程化的发展,Vite等现代构建工具应运而生,它们以其极快的开发服务器启动速度和即时热模块更新(HMR)能力,成为了前端开发的新宠。</p> <h5>1.4.2.1 为什么选择Vite?</h5> <p>Vite(法语意为“快”)是由Vue.js的作者尤雨溪开发的下一代前端构建工具。它通过以下方式实现了极速的开发体验:</p> <ul> <li><strong>基于ESM的开发服务器</strong>: Vite利用浏览器原生支持ES模块的特性,在开发模式下,Vite的开发服务器直接提供ES模块给浏览器,无需打包。这大大减少了服务器启动时间,实现了真正的“按需编译”。</li> <li><strong>HMR (Hot Module Replacement)</strong>: Vite的HMR速度极快,当代码发生修改时,只有被修改的模块会被替换,而不会重新加载整个页面,极大地提升了开发效率。</li> <li><strong>开箱即用</strong>: Vite提供了对React、Vue、TypeScript等主流前端技术的开箱即用支持,无需复杂的配置。</li> <li><strong>Rollup打包</strong>: 在生产环境中,Vite使用Rollup进行打包,生成高度优化、体积更小的生产版本。</li> </ul> <p>相比于传统的Webpack,Vite在开发体验上具有压倒性优势,尤其是在大型项目中,其启动速度和热更新速度的提升将带来巨大的生产力收益。因此,本书将推荐使用Vite来创建和管理React项目。</p> <h4>1.4.3 编辑器与浏览器工具</h4> <p>除了Node.js和构建工具,一个趁手的代码编辑器和强大的浏览器开发者工具也是前端开发者的利器。</p> <ul> <li><strong>代码编辑器</strong>: 强烈推荐使用 <strong>Visual Studio Code (VS Code)</strong> [2]。VS Code是一款免费、开源、功能强大的代码编辑器,拥有丰富的插件生态系统,可以为React开发提供语法高亮、智能提示、代码格式化、调试等诸多便利功能。安装React相关的插件(如ES7+ React/Redux/GraphQL/React-Native snippets、Prettier、ESLint等)将进一步提升开发体验。</li> <li><strong>浏览器开发者工具</strong>: 现代浏览器(如Chrome、Firefox、Edge)都内置了强大的开发者工具。这些工具提供了元素检查、样式调试、JavaScript调试、网络请求监控、性能分析等功能,是前端开发和调试不可或缺的工具。特别是React DevTools [3],它是React官方提供的浏览器扩展,可以帮助我们检查React组件树、组件状态和props,以及进行性能分析,对于调试React应用至关重要。</li> </ul> <p>通过以上工具的安装和配置,我们就能够搭建一个完善的React开发环境,为后续的学习和实践打下坚实的基础。</p> <hr> <p><strong>参考资料:</strong></p> <p>[1] Node.js 官方网站. Retrieved from https://nodejs.org/zh-cn/<br> [2] Visual Studio Code 官方网站. Retrieved from https://code.visualstudio.com/<br> [3] React DevTools. Retrieved from https://react.dev/learn/react-developer-tools</p> <h3>1.5 创建第一个React 19项目</h3> <p>万丈高楼平地起,学习任何一门技术,最好的方式莫过于亲手实践。现在,我们将一起创建我们的第一个React 19项目。正如前文所述,我们将主要采用Vite作为构建工具,因为它提供了卓越的开发体验和性能。当然,我们也会简要提及传统的<code>create-react-app</code>。</p> <h4>1.5.1 使用Vite创建React项目</h4> <p>Vite以其闪电般的启动速度和即时热模块更新(HMR)功能,成为了现代React开发的优选。它提供了一套简洁的命令行工具,可以快速搭建起一个基于各种前端框架的项目。</p> <p><strong>步骤 1:创建Vite项目</strong></p> <p>打开你的终端或命令行工具,导航到你希望创建项目的目录,然后执行以下命令:</p> <pre><code class="prism language-bash"><span class="token function">npm</span> create vite@latest </code></pre> <p>执行此命令后,Vite会引导你完成项目创建过程,你需要依次选择:</p> <ol> <li><strong>项目名称 (Project name)</strong>: 输入你项目的名称,例如 <code>my-first-react-app</code>。</li> <li><strong>选择一个框架 (Select a framework)</strong>: 使用键盘的上下箭头选择 <code>React</code>。</li> <li><strong>选择一个变体 (Select a variant)</strong>: 选择 <code>TypeScript</code> 或 <code>JavaScript</code>。考虑到现代前端开发的趋势和本书的专业性,我们强烈推荐选择 <code>TypeScript</code>,它能为项目提供类型安全,提升代码质量和可维护性。</li> </ol> <p>整个交互过程大致如下:</p> <pre><code>bash $ npm create vite@latest Need to install the following packages: create-vite Ok to proceed? (y) y √ Project name: » my-first-react-app √ Select a framework: » React √ Select a variant: » TypeScript Scaffolding project in /path/to/your/directory/my-first-react-app... Done. Now run: cd my-first-react-app npm install npm run dev </code></pre> <p><strong>步骤 2:进入项目目录并安装依赖</strong></p> <p>根据Vite的提示,进入新创建的项目目录,并安装项目所需的依赖:</p> <pre><code class="prism language-bash"><span class="token builtin class-name">cd</span> my-first-react-app <span class="token function">npm</span> <span class="token function">install</span> </code></pre> <p><code>npm install</code> 命令会读取项目根目录下的 <code>package.json</code> 文件,并下载其中列出的所有依赖包到 <code>node_modules</code> 目录中。</p> <p><strong>步骤 3:运行开发服务器</strong></p> <p>依赖安装完成后,你就可以启动开发服务器了:</p> <pre><code class="prism language-bash"><span class="token function">npm</span> run dev </code></pre> <p>执行此命令后,Vite会启动一个本地开发服务器,并在终端中显示项目的访问地址(通常是 <code>http://localhost:5173</code> 或其他可用端口)。</p> <p>在浏览器中打开这个地址,你将看到Vite和React的欢迎页面,这标志着你的第一个React 19项目已经成功运行起来了!</p> <h4>1.5.2 项目结构概览</h4> <p>使用Vite创建的React项目,其初始结构简洁而清晰,便于开发者快速上手。</p> <pre><code>my-first-react-app/ ├── node_modules/ # 项目依赖包存放目录 ├── public/ # 静态资源目录,如favicon.ico ├── src/ │ ├── assets/ # 存放图片等静态资源 │ ├── App.css # 应用的样式文件 │ ├── App.tsx # 主应用组件 │ ├── index.css # 全局样式文件 │ ├── main.tsx # 应用的入口文件,负责渲染根组件 │ └── vite-env.d.ts # Vite的TypeScript环境声明文件 ├── .eslintrc.cjs # ESLint配置文件,用于代码规范检查 ├── .gitignore # Git忽略文件,指定不纳入版本控制的文件和目录 ├── index.html # 应用的HTML入口文件 ├── package.json # 项目配置文件,包含项目信息、依赖和脚本命令 ├── pnpm-lock.yaml # pnpm的依赖锁定文件(如果使用pnpm) ├── README.md # 项目说明文件 ├── tsconfig.json # TypeScript配置文件 └── vite.config.ts # Vite配置文件 </code></pre> <p><strong>核心文件说明:</strong></p> <ul> <li><code>index.html</code>: 这是应用的唯一HTML文件,React应用会挂载到这个文件中的一个DOM元素上(通常是<code><div id="root"></div></code>)。</li> <li><code>src/main.tsx</code>: 应用的入口文件。它负责导入React和ReactDOM,并将根组件(通常是<code>App</code>组件)渲染到<code>index.html</code>中的指定DOM元素上。</li> <li><code>src/App.tsx</code>: 你的主应用组件。你将在这里编写大部分的React代码,并引入其他子组件。</li> <li><code>vite.config.ts</code>: Vite的配置文件,你可以在这里配置Vite的各种行为,例如代理、插件等。</li> <li><code>package.json</code>: 包含了项目的元数据、依赖列表以及可执行的脚本命令(如<code>dev</code>、<code>build</code>)。</li> </ul> <h4>1.5.3 简述 <code>create-react-app</code> (CRA)</h4> <p>在Vite出现之前,<code>create-react-app</code> (CRA) 是官方推荐的创建React项目的工具。它提供了一个零配置的开发环境,集成了Webpack、Babel等工具,让开发者可以专注于代码编写而无需关心复杂的构建配置。</p> <p><strong>创建CRA项目:</strong></p> <pre><code class="prism language-bash">npx create-react-app my-cra-app <span class="token parameter variable">--template</span> typescript </code></pre> <p>尽管CRA在过去发挥了重要作用,但随着前端生态的发展,其在开发服务器启动速度和热更新效率方面逐渐显露出劣势。Vite等新一代构建工具的出现,提供了更快的开发体验。因此,在新的React 19项目中,我们更推荐使用Vite。然而,对于维护旧项目或对构建工具有特定偏好的开发者来说,CRA仍然是一个可行的选择。</p> <p>至此,我们已经成功搭建了React开发环境,并创建了第一个React项目。在下一章中,我们将深入探讨React的核心语法——JSX,以及组件的基本概念。</p> <p><strong>本章小结</strong></p> <p>在本章中,我们踏上了React 19的学习之旅,从宏观的视角审视了React在现代前端开发格局中的重要地位,深入理解了其核心理念,并亲手搭建了第一个React项目。这一章为我们后续深入学习React 19的各种特性和实战应用奠定了坚实的基础。</p> <p>我们首先回顾了React的诞生历程和演进轨迹,了解了它是如何从Facebook内部的一个解决方案,发展成为全球最受欢迎的前端框架之一。React的成功并非偶然,它凭借着三大核心理念——声明式、组件化和单向数据流——彻底改变了前端开发的范式。声明式编程让我们能够更直观地描述UI应该呈现的状态,而无需关心具体的DOM操作细节;组件化思想将复杂的UI拆解为独立、可复用的模块,极大地提升了代码的可维护性和开发效率;单向数据流则确保了数据变化的可预测性,使得应用状态的管理变得清晰明了。</p> <p>接着,我们深入探讨了React 19这一新纪元的开启。React 19不仅在简化开发体验、提升性能和增强能力方面带来了显著改进,更引入了诸如Actions、<code>use</code> API、Server Components等革命性特性。这些新特性不仅解决了传统React开发中的痛点,更为构建现代Web应用提供了更强大、更便捷的工具。Actions机制简化了异步操作和表单处理,<code>use</code> API让组件能够更自然地处理异步数据,而Server Components则模糊了前后端的界限,为全栈开发带来了新的可能性。</p> <p>在分析React在现代前端开发格局中的定位时,我们看到了React强大的生态系统和社区支持。与其他主流框架相比,React以其灵活性、跨平台能力和持续创新的特点,在激烈的技术竞争中保持着领先地位。无论是庞大的第三方库生态,还是活跃的开发者社区,都为React的持续发展提供了强有力的支撑。</p> <p>在实践环节,我们详细介绍了如何搭建React开发环境,从Node.js的安装到包管理器的选择,再到现代构建工具Vite的使用。我们特别强调了Vite在开发体验上的优势,它以其极快的启动速度和即时热模块更新能力,为React开发带来了前所未有的流畅体验。通过实际创建第一个React项目,我们不仅验证了环境搭建的正确性,更通过代码示例深入理解了React的核心理念。</p> <p>通过本章的学习,读者应该已经:</p> <ol> <li> <p><strong>理解了React的历史背景和设计哲学</strong>:掌握了声明式、组件化、单向数据流等核心概念,为后续深入学习打下了理论基础。</p> </li> <li> <p><strong>认识了React 19的重要性和新特性</strong>:了解了Actions、<code>use</code> API、Server Components等革命性功能,对React的发展方向有了清晰的认知。</p> </li> <li> <p><strong>掌握了React开发环境的搭建</strong>:能够独立安装Node.js、配置包管理器、使用Vite创建React项目,具备了开始React开发的基本条件。</p> </li> <li> <p><strong>获得了第一次React实践经验</strong>:通过创建和运行第一个React项目,对React的开发流程有了直观的认识。</p> </li> </ol> <p>在下一章中,我们将深入探讨React的核心语法——JSX,以及组件的基本概念和使用方法。我们将学习如何编写更复杂的React组件,理解JSX的本质和最佳实践,并掌握组件间通信的各种方式。这将是我们从React入门走向熟练的关键一步。</p> <p>React的学习之路虽然充满挑战,但也充满乐趣。每一个概念的掌握,每一行代码的编写,都将让我们更接近成为一名优秀的React开发者。让我们带着对知识的渴望和对技术的热情,继续在这片React的星辰大海中探索前行。</p> <hr> <h2>第二章:构建世界的积木 - JSX与组件核心</h2> <h3>2.1 JSX的本质:语法糖与JavaScript的融合之美</h3> <p>JSX,全称JavaScript XML,是React中用于描述用户界面(UI)的一种语法扩展。它允许我们在JavaScript代码中书写类似HTML的标签结构,使得UI的声明更加直观和富有表现力。初次接触JSX的开发者可能会觉得它像是一种模板语言,但其本质远不止于此,它拥有JavaScript的全部功能,并且最终会被Babel等编译器转换成普通的JavaScript对象。</p> <h4>2.1.1 JSX的起源与设计哲学</h4> <p>在React出现之前,前端开发通常将HTML(结构)、CSS(样式)和JavaScript(行为)分离到不同的文件中。这种“关注点分离”的模式在一定程度上提高了代码的可维护性。然而,随着Web应用的日益复杂,UI的逻辑变得越来越复杂,JavaScript开始更多地控制HTML的内容。React团队认为,渲染逻辑与UI的其他逻辑(如事件处理、状态变化时UI的更新、数据的展示等)是紧密耦合的。因此,React并没有采用将标记与逻辑分离到不同文件的方式,而是通过将二者共同存放在称之为“组件”的松散耦合单元之中,来实现“关注点分离”。JSX正是这种设计哲学的体现,它将UI的描述直接融入到JavaScript代码中,使得组件的创建、维护和删除变得更加容易。</p> <h4>2.1.2 JSX的语法特性</h4> <p>JSX的语法与HTML非常相似,但它有一些独特的规则和特性,以适应JavaScript的编程范式。</p> <h5>2.1.2.1 在JSX中嵌入JavaScript表达式</h5> <p>在JSX中,你可以使用大括号 <code>{}</code> 来嵌入任何有效的JavaScript表达式。这意味着你可以在标签内部插入变量、函数调用、算术运算等。例如:</p> <pre><code class="prism language-jsx">const name = 'React爱好者'; const element = <h1>Hello, {name}!</h1>; // 嵌入变量 function formatName(user) { return user.firstName + ' ' + user.lastName; } const user = { firstName: 'Harper', lastName: 'Perez' }; const greetingElement = ( <h1> Hello, {formatName(user)}! // 嵌入函数调用 </h1> ); const sum = <div>{2 + 2}</div>; // 嵌入算术运算 </code></pre> <p>需要注意的是,在大括号中嵌入JavaScript表达式时,不要在表达式外面再加引号。对于属性值,只能使用引号(字符串字面量)或大括号(JavaScript表达式)中的一种。</p> <h5>2.1.2.2 JSX也是一个表达式</h5> <p>JSX本身也是一个JavaScript表达式。这意味着你可以在<code>if</code>语句和<code>for</code>循环等控制流中使用JSX,将其赋值给变量,作为参数传递给函数,或者从函数中返回JSX。这为构建动态和可复用的UI提供了极大的灵活性。</p> <pre><code class="prism language-jsx">function getGreeting(user) { if (user) { return <h1>Hello, {user.name}!</h1>; } return <h1>Hello, Stranger.</h1>; } const welcomeMessage = getGreeting({ name: 'Alice' }); // JSX作为函数返回值 const items = ['Apple', 'Banana', 'Orange']; const listItems = ( <ul> {items.map((item, index) => ( <li key={index}>{item}</li> ))} </ul> ); // JSX在map函数中使用 </code></pre> <h5>2.1.2.3 JSX中指定属性</h5> <p>JSX中的属性与HTML属性类似,但遵循JavaScript的命名约定,即使用驼峰式命名法(camelCase)。例如,HTML中的<code>class</code>属性在JSX中变为<code>className</code>,<code>tabindex</code>变为<code>tabIndex</code>。这是因为JSX最终会被转换成JavaScript对象,而JavaScript对变量命名有特定限制。</p> <pre><code class="prism language-jsx">const element = <a href="https://react.dev" target="_blank" className="my-link">React官网</a>; const image = <img src="avatar.jpg" alt="User Avatar" style={{ width: '100px' }} />; </code></pre> <p>对于样式属性,JSX支持内联样式,其值是一个JavaScript对象,属性名同样使用驼峰式命名(如<code>backgroundColor</code>而不是<code>background-color</code>)。</p> <h5>2.1.2.4 JSX中指定子元素</h5> <p>JSX标签可以包含子元素,就像HTML一样。如果一个标签没有内容,可以使用自闭合标签的形式(如<code><img /></code>)。</p> <pre><code class="prism language-jsx">const container = ( <div> <h1>标题</h1> <p>这是一段文字。</p> <img src="example.jpg" alt="示例图片" /> </div> ); </code></pre> <p>当一个组件需要返回多个根元素时,必须用一个父标签(如<code><div></code>)或一个<code>Fragment</code>(<code><></></code>)包裹起来。<code>Fragment</code>不会在DOM中添加额外的节点,这对于保持DOM结构扁平化非常有用。</p> <pre><code class="prism language-jsx">// 使用div包裹 function MyComponentWithDiv() { return ( <div> <p>第一段</p> <p>第二段</p> </div> ); } // 使用Fragment包裹 function MyComponentWithFragment() { return ( <> <p>第一段</p> <p>第二段</p> </> ); } </code></pre> <h4>2.1.3 JSX防止注入攻击</h4> <p>JSX在设计时就考虑了安全性。React DOM在渲染所有用户输入内容之前,默认会进行转义。这意味着你可以安全地在JSX中插入用户输入内容,而不用担心跨站脚本(XSS)攻击。所有内容在渲染之前都会被转换成字符串,从而有效防止恶意代码的注入。</p> <pre><code class="prism language-jsx">const userInput = '<script>alert("You are hacked!")</script>'; const safeElement = <div>{userInput}</div>; // 会被转义为普通字符串显示 </code></pre> <h4>2.1.4 JSX的转换:<code>React.createElement()</code>的语法糖</h4> <p>JSX的强大之处在于它并非浏览器原生支持的语法。在构建过程中,JSX代码会被Babel等JavaScript编译器转换成普通的JavaScript函数调用,最常见的就是<code>React.createElement()</code>。例如,以下两种代码是完全等效的:</p> <pre><code class="prism language-jsx">// JSX语法 const elementJSX = ( <h1 className="greeting"> Hello, world! </h1> ); // 转换后的JavaScript (React.createElement调用) const elementJS = React.createElement( 'h1', { className: 'greeting' }, 'Hello, world!' ); </code></pre> <p><code>React.createElement()</code>函数会返回一个JavaScript对象,这个对象被称为“React元素”(React Element)。React元素是描述你希望在屏幕上看到的内容的轻量级对象。它们并不是真实的DOM节点,而是对真实DOM的一种抽象描述。React正是通过这些元素来构建和管理UI的。</p> <h3>2.2 深入理解React元素与虚拟DOM</h3> <p>在React的世界里,用户界面并不是直接操作浏览器中的真实DOM来更新的。相反,React引入了“React元素”和“虚拟DOM”(Virtual DOM)这两个核心概念,它们是React高效更新UI的关键。</p> <h4>2.2.1 React元素:UI的轻量级描述</h4> <p>如前所述,React元素是<code>React.createElement()</code>函数返回的普通JavaScript对象。它们是React应用中最小的构建块,用于描述UI的一部分应该是什么样子。一个React元素包含了以下信息:</p> <ul> <li><strong><code>type</code></strong>: 元素的类型,可以是HTML标签字符串(如<code>'div'</code>、<code>'h1'</code>)或React组件(如<code>MyComponent</code>)。</li> <li><strong><code>props</code></strong>: 一个JavaScript对象,包含了传递给元素的属性(如<code>className</code>、<code>src</code>、<code>onClick</code>等)以及子元素(通过<code>children</code>属性)。</li> </ul> <p>例如,<code><h1>Hello, world!</h1></code>这个JSX会被编译成一个React元素对象,大致如下:</p> <pre><code class="prism language-javascript"><span class="token punctuation">{</span> <span class="token literal-property property">type</span><span class="token operator">:</span> <span class="token string">'h1'</span><span class="token punctuation">,</span> <span class="token literal-property property">props</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token literal-property property">className</span><span class="token operator">:</span> <span class="token string">'greeting'</span><span class="token punctuation">,</span> <span class="token literal-property property">children</span><span class="token operator">:</span> <span class="token string">'Hello, world!'</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre> <p>React元素是不可变的(immutable)。一旦创建,你就不能修改它的属性或子元素。如果需要更新UI,你需要创建一个新的React元素来描述新的UI状态。React会负责比较新旧元素,并高效地更新真实DOM。</p> <h4>2.2.2 虚拟DOM:高效更新的幕后英雄</h4> <p>虚拟DOM是React中一个至关重要的概念,它并不是一个真实存在的“DOM”,而是一种编程思想,或者说是一个存在于内存中的JavaScript对象树,它模拟了真实DOM的结构。当React应用的状态发生变化时,React会执行以下步骤来更新UI:</p> <ol> <li> <p><strong>创建新的虚拟DOM树</strong>: 每当组件的状态或属性发生变化时,React会重新调用组件的<code>render</code>方法(对于函数组件,是重新执行函数),生成一个新的React元素树,也就是新的虚拟DOM树。</p> </li> <li> <p><strong>Diff算法比较</strong>: React会使用其内部的“Diff算法”(或称为“协调Reconciliation”过程)来比较新旧两棵虚拟DOM树。这个算法会找出两棵树之间最小的差异。Diff算法是高效的,它不会逐个比较每个节点,而是采用启发式算法,例如:</p> <ul> <li><strong>同层比较</strong>: 只比较同一层级的节点,如果节点类型不同,则直接销毁旧节点及其子树,创建新节点。</li> <li><strong>Key属性</strong>: 对于列表渲染,通过<code>key</code>属性来识别元素的唯一性,从而优化列表项的更新。</li> </ul> </li> <li> <p><strong>生成最小化DOM操作</strong>: Diff算法会计算出将旧虚拟DOM树转换为新虚拟DOM树所需的最少操作(如添加、删除、更新节点或属性)。</p> </li> <li> <p><strong>批量更新真实DOM</strong>: React会将这些最小化的DOM操作批量地应用到真实的浏览器DOM上。由于直接操作真实DOM的开销较大,批量更新可以显著提高性能。React会尽可能地减少对真实DOM的操作,只更新那些真正发生变化的部分。</p> </li> </ol> <p><strong>虚拟DOM的优势:</strong></p> <ul> <li><strong>性能优化</strong>: 虚拟DOM的主要优势在于其性能。通过在内存中进行比较和批量更新,React避免了频繁、昂贵的真实DOM操作,从而提升了UI的渲染效率,尤其是在数据频繁更新的复杂应用中。</li> <li><strong>跨平台能力</strong>: 虚拟DOM的抽象层使得React不仅可以渲染到浏览器DOM,还可以渲染到其他平台,如React Native(移动应用)、React VR(虚拟现实)等,实现了“一次学习,随处编写”。</li> <li><strong>简化开发</strong>: 开发者无需直接操作DOM,只需关注组件的状态和属性,声明式地描述UI应该是什么样子,React会处理底层的DOM操作,大大简化了开发流程。</li> </ul> <h4>2.2.3 虚拟DOM与真实DOM的关系</h4> <p>虚拟DOM是真实DOM在内存中的一个轻量级副本。它不是真实DOM的替代品,而是真实DOM和React组件之间的一个中间层。React通过虚拟DOM来管理和优化对真实DOM的更新。当虚拟DOM发生变化时,React会智能地决定如何高效地更新真实DOM,而不是简单地重新渲染整个页面。</p> <h3>2.3 函数组件:现代React的基石</h3> <p>在React的演进过程中,组件的编写方式经历了从类组件到函数组件的转变。自React 16.8引入Hooks以来,函数组件凭借其简洁性、可读性和强大的功能,成为了现代React开发的首选。</p> <h4>2.3.1 函数组件的定义与特点</h4> <p>函数组件(Functional Components)是接收一个<code>props</code>对象作为参数,并返回React元素的JavaScript函数。它们通常比类组件更简洁,更易于理解和测试。</p> <pre><code class="prism language-jsx">// 传统函数组件 function Welcome(props) { return <h1>Hello, {props.name}</h1>; } // 使用ES6箭头函数 const Greeting = (props) => { return <p>Greetings, {props.name}!</p>; }; // 使用解构赋值简化props const UserInfo = ({ name, age }) => { return ( <div> <p>Name: {name}</p> <p>Age: {age}</p> </div> ); }; </code></pre> <p><strong>函数组件的特点:</strong></p> <ul> <li><strong>简洁性</strong>: 相较于类组件,函数组件没有<code>this</code>的困扰,也无需编写<code>constructor</code>和<code>render</code>方法,代码量更少,逻辑更清晰。</li> <li><strong>易于理解</strong>: 它们就是普通的JavaScript函数,更容易理解其输入(props)和输出(React元素)。</li> <li><strong>性能</strong>: 在React 16.8之前,函数组件被称为“无状态组件”,因为它们不能拥有自己的状态和生命周期方法。但随着Hooks的引入,函数组件现在可以拥有状态和副作用,并且在某些情况下,其性能表现甚至优于类组件(例如,通过<code>React.memo</code>进行优化)。</li> <li><strong>Hooks的基石</strong>: Hooks是为函数组件设计的,它们使得函数组件能够使用状态(<code>useState</code>)、副作用(<code>useEffect</code>)以及其他React特性,从而完全取代了类组件的功能。</li> </ul> <h4>2.3.2 函数组件与类组件的对比</h4> <p>在Hooks出现之前,类组件是React中唯一能够拥有状态和生命周期的方法。然而,类组件存在一些问题:</p> <ul> <li><strong><code>this</code>的复杂性</strong>: 在JavaScript中,<code>this</code>的指向问题常常令人困惑,尤其是在事件处理函数中,需要手动绑定<code>this</code>。</li> <li><strong>逻辑复用困难</strong>: 在类组件中,复用有状态逻辑通常需要使用高阶组件(HOC)或Render Props模式,这会增加组件的嵌套层级,导致“Wrapper Hell”(包装器地狱)。</li> <li><strong>生命周期方法的复杂性</strong>: 类组件的生命周期方法(如<code>componentDidMount</code>、<code>componentDidUpdate</code>、<code>componentWillUnmount</code>)使得相关逻辑分散在不同的方法中,难以维护。</li> </ul> <p>函数组件结合Hooks解决了这些问题,使得组件逻辑更加内聚、可读性更高,并且更易于测试和复用。</p> <h3>2.4 Props:组件间通信的桥梁 (类型检查:PropTypes vs TypeScript)</h3> <p>在React应用中,组件之间需要相互通信才能协同工作。<code>props</code>(properties的缩写)是React中实现组件间通信的主要方式之一。它们允许父组件向子组件传递数据。</p> <h4>2.4.1 Props的基本概念与传递</h4> <p><code>props</code>是父组件传递给子组件的只读数据。子组件不能直接修改<code>props</code>,这保证了数据流的单向性,使得应用的状态变化更容易追踪和理解。当父组件的<code>props</code>发生变化时,子组件会重新渲染以反映这些变化。</p> <p><strong>传递Props:</strong></p> <p>你可以在JSX中像HTML属性一样将数据作为<code>props</code>传递给子组件:</p> <pre><code class="prism language-jsx">// ParentComponent.jsx import React from 'react'; import ChildComponent from './ChildComponent'; function ParentComponent() { const userName = "爱学习的你"; const userAge = 18; return ( <div> <h2>父组件</h2> <ChildComponent name={userName} age={userAge} /> </div> ); } export default ParentComponent; </code></pre> <p><strong>接收Props:</strong></p> <p>在函数组件中,<code>props</code>作为函数的第一个参数被接收:</p> <pre><code class="prism language-jsx">// ChildComponent.jsx import React from 'react'; function ChildComponent(props) { return ( <div> <h3>子组件</h3> <p>姓名: {props.name}</p> <p>年龄: {props.age}</p> </div> ); } export default ChildComponent; </code></pre> <p>为了方便,通常会使用ES6的解构赋值来直接获取<code>props</code>中的特定属性:</p> <pre><code class="prism language-jsx">// ChildComponent.jsx (使用解构赋值) import React from 'react'; function ChildComponent({ name, age }) { return ( <div> <h3>子组件</h3> <p>姓名: {name}</p> <p>年龄: {age}</p> </div> ); } export default ChildComponent; </code></pre> <h4>2.4.2 <code>children</code> Prop</h4> <p><code>children</code>是<code>props</code>中的一个特殊属性,它允许你将组件的子元素作为<code>props</code>传递。这使得组件可以像HTML标签一样嵌套内容。</p> <pre><code class="prism language-jsx">// Layout.jsx import React from 'react'; function Layout({ title, children }) { return ( <div style={{ border: '1px solid #eee', padding: '20px' }}> <h1>{title}</h1> <div>{children}</div> {/* 渲染子元素 */} </div> ); } export default Layout; // App.jsx import React from 'react'; import Layout from './Layout'; function App() { return ( <Layout title="我的应用"> <p>这是应用的主要内容。</p> <button>点击我</button> </Layout> ); } export default App; </code></pre> <h4>2.4.3 Props的类型检查:PropTypes vs TypeScript</h4> <p>随着应用规模的增长,确保组件接收到正确类型的<code>props</code>变得越来越重要。错误的<code>props</code>类型可能导致运行时错误,降低代码的健壮性。React提供了两种主要的<code>props</code>类型检查方式:<code>PropTypes</code>和<code>TypeScript</code>。</p> <h5>2.4.3.1 PropTypes</h5> <p><code>PropTypes</code>是React官方提供的一个库,用于在开发模式下对组件的<code>props</code>进行类型检查。当<code>props</code>的类型不匹配时,会在控制台输出警告信息。<code>PropTypes</code>在生产环境下会被移除,不会增加额外的代码体积。</p> <p><strong>使用PropTypes:</strong></p> <p>首先,你需要安装<code>prop-types</code>库:</p> <pre><code class="prism language-bash"><span class="token function">npm</span> <span class="token function">install</span> prop-types </code></pre> <p>然后在组件中导入并使用它:</p> <pre><code class="prism language-jsx">// ChildComponent.jsx import React from 'react'; import PropTypes from 'prop-types'; function ChildComponent({ name, age }) { return ( <div> <h3>子组件</h3> <p>姓名: {name}</p> <p>年龄: {age}</p> </div> ); } // 定义props的类型 ChildComponent.propTypes = { name: PropTypes.string.isRequired, // name必须是字符串且必传 age: PropTypes.number, // age必须是数字,可选 // 更多类型:array, bool, func, object, element, node, arrayOf, instanceOf, oneOf, oneOfType, shape等 }; // 定义props的默认值(可选) ChildComponent.defaultProps = { age: 0, }; export default ChildComponent; </code></pre> <p><code>PropTypes</code>的优点是简单易用,无需额外的编译配置。然而,它的缺点是只在开发模式下进行运行时检查,无法在编译时捕获类型错误,也无法提供IDE的智能提示。</p> <h5>2.4.3.2 TypeScript</h5> <p><code>TypeScript</code>是JavaScript的超集,它为JavaScript添加了静态类型。在大型和复杂的React项目中,<code>TypeScript</code>是更推荐的<code>props</code>类型检查方案。它能在开发阶段就捕获类型错误,提供强大的IDE支持(如自动补全、类型检查),从而大大提升开发效率和代码质量。</p> <p><strong>使用TypeScript定义Props类型:</strong></p> <pre><code class="prism language-typescript"><span class="token comment">// ChildComponent.tsx</span> <span class="token keyword">import</span> React <span class="token keyword">from</span> <span class="token string">'react'</span><span class="token punctuation">;</span> <span class="token comment">// 定义Props接口</span> <span class="token keyword">interface</span> <span class="token class-name">ChildComponentProps</span> <span class="token punctuation">{</span> name<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> <span class="token comment">// name必须是字符串</span> age<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">;</span> <span class="token comment">// age是可选的数字类型</span> <span class="token comment">// 更多类型定义...</span> <span class="token punctuation">}</span> <span class="token keyword">function</span> <span class="token function">ChildComponent</span><span class="token punctuation">(</span><span class="token punctuation">{</span> name<span class="token punctuation">,</span> age <span class="token operator">=</span> <span class="token number">0</span> <span class="token punctuation">}</span><span class="token operator">:</span> ChildComponentProps<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token punctuation">(</span> <span class="token operator"><</span>div<span class="token operator">></span> <span class="token operator"><</span>h3<span class="token operator">></span>子组件<span class="token operator"><</span><span class="token operator">/</span>h3<span class="token operator">></span> <span class="token operator"><</span>p<span class="token operator">></span>姓名<span class="token operator">:</span> <span class="token punctuation">{</span>name<span class="token punctuation">}</span><span class="token operator"><</span><span class="token operator">/</span>p<span class="token operator">></span> <span class="token operator"><</span>p<span class="token operator">></span>年龄<span class="token operator">:</span> <span class="token punctuation">{</span>age<span class="token punctuation">}</span><span class="token operator"><</span><span class="token operator">/</span>p<span class="token operator">></span> <span class="token operator"><</span><span class="token operator">/</span>div<span class="token operator">></span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">export</span> <span class="token keyword">default</span> ChildComponent<span class="token punctuation">;</span> </code></pre> <p><code>TypeScript</code>的优势在于其静态类型检查能力,它能在代码编写阶段就发现潜在的类型问题,减少运行时错误。虽然引入<code>TypeScript</code>会增加一些学习成本和配置工作,但对于构建健壮、可维护的React应用来说,这是非常值得的投入。本书在后续的代码示例中,将优先采用<code>TypeScript</code>来增强代码的严谨性。</p> <h3>2.5 条件渲染与列表渲染的艺术</h3> <p>在构建动态用户界面时,我们经常需要根据不同的条件显示不同的内容,或者渲染一个数据集合。React提供了直观的方式来实现条件渲染和列表渲染。</p> <h4>2.5.1 条件渲染</h4> <p>条件渲染允许你根据组件的<code>props</code>或<code>state</code>来决定哪些元素应该被渲染,哪些应该被隐藏。在React中,你可以使用标准的JavaScript控制流语句(如<code>if</code>、<code>&&</code>、三元运算符)来实现条件渲染。</p> <h5>2.5.1.1 <code>if</code>语句</h5> <p>你可以在组件内部使用<code>if</code>语句来有条件地返回不同的JSX:</p> <pre><code class="prism language-jsx">function UserGreeting(props) { if (props.isLoggedIn) { return <h1>欢迎回来!</h1>; } return <h1>请先登录。</h1>; } function LoginControl() { const [isLoggedIn, setIsLoggedIn] = React.useState(false); const handleLoginClick = () => { setIsLoggedIn(true); }; const handleLogoutClick = () => { setIsLoggedIn(false); }; let button; if (isLoggedIn) { button = <button onClick={handleLogoutClick}>退出</button>; } else { button = <button onClick={handleLoginClick}>登录</button>; } return ( <div> <UserGreeting isLoggedIn={isLoggedIn} /> {button} </div> ); } </code></pre> <h5>2.5.1.2 逻辑与运算符 <code>&&</code> (短路求值)</h5> <p>当你想在条件为真时才渲染某个元素,否则什么都不渲染时,可以使用JavaScript的逻辑与运算符<code>&&</code>。在JavaScript中,如果<code>&&</code>左侧的表达式为<code>true</code>,则返回右侧的表达式;如果为<code>false</code>,则返回左侧的表达式(通常是<code>false</code>或<code>null</code>,React会忽略这些值)。</p> <pre><code class="prism language-jsx">function Mailbox(props) { const unreadMessages = props.unreadMessages; return ( <div> <h1>Hello!</h1> {unreadMessages.length > 0 && <h2> 您有 {unreadMessages.length} 条未读消息。 </h2> } </div> ); } const messages = ['React', 'Re: React', 'Re:Re: React']; <Mailbox unreadMessages={messages} />; </code></pre> <h5>2.5.1.3 三元运算符 (条件运算符)</h5> <p>三元运算符<code>condition ? expression1 : expression2</code>可以在两种不同情况之间切换渲染内容时使用,它比<code>if/else</code>更简洁,尤其是在行内使用时。</p> <pre><code class="prism language-jsx">function Greeting(props) { return ( <div> {props.isLoggedIn ? ( <h1>欢迎回来!</h1> ) : ( <h1>请先登录。</h1> )} </div> ); } </code></pre> <h5>2.5.1.4 阻止组件渲染</h5> <p>在某些情况下,你可能希望组件完全不渲染任何内容。你可以让组件的<code>render</code>方法(或函数组件的返回值)返回<code>null</code>。返回<code>null</code>并不会影响组件的生命周期方法(或Hooks),它们仍然会被调用。</p> <pre><code class="prism language-jsx">function WarningBanner(props) { if (!props.warn) { return null; // 不渲染任何内容 } return ( <div className="warning"> 警告! </div> ); } function Page() { const [showWarning, setShowWarning] = React.useState(true); const handleToggleClick = () => { setShowWarning(prevShowWarning => !prevShowWarning); }; return ( <div> <WarningBanner warn={showWarning} /> <button onClick={handleToggleClick}> {showWarning ? '隐藏' : '显示'} 警告 </button> </div> ); } </code></pre> <h4>2.5.2 列表渲染</h4> <p>在React中渲染列表通常使用JavaScript数组的<code>map()</code>方法。<code>map()</code>方法会遍历数组中的每个元素,并返回一个新的数组,其中包含对每个元素进行操作后的结果。在React中,这个结果就是一系列的React元素。</p> <pre><code class="prism language-jsx">function NumberList(props) { const numbers = props.numbers; const listItems = numbers.map((number) => <li key={number.toString()}> {number} </li> ); return ( <ul>{listItems}</ul> ); } const numbers = [1, 2, 3, 4, 5]; <NumberList numbers={numbers} />; </code></pre> <h5>2.5.2.1 <code>key</code>属性的重要性</h5> <p>在渲染列表时,React要求为列表中的每个元素添加一个唯一的<code>key</code>属性。<code>key</code>是React用于识别列表中每个元素的特殊字符串属性。当列表项的顺序发生变化或者列表项被添加/删除时,<code>key</code>能够帮助React高效地更新UI,避免不必要的DOM操作。</p> <p><strong>为什么需要<code>key</code>?</strong></p> <p>当列表项发生变化时,React需要知道哪些项被添加、删除或重新排序了。如果没有<code>key</code>,React会默认按照顺序比较新旧列表项,这可能导致性能问题和不正确的UI更新。例如,如果列表项的顺序发生变化,没有<code>key</code>会导致React重新渲染所有列表项,而有了<code>key</code>,React可以根据<code>key</code>来识别哪些项是相同的,从而只移动或更新发生变化的项。</p> <p><strong>如何选择<code>key</code>?</strong></p> <ul> <li><strong>唯一且稳定</strong>: <code>key</code>必须是唯一的,并且在列表的整个生命周期中保持稳定。理想情况下,<code>key</code>应该来自数据源中的唯一ID,例如数据库ID。</li> <li><strong>避免使用索引作为<code>key</code></strong>: 除非列表项是静态的,永不改变顺序,并且没有增删操作,否则不建议使用数组索引作为<code>key</code>。因为当列表项的顺序发生变化时,索引也会随之变化,这会混淆React的Diff算法,导致性能下降和潜在的bug。</li> </ul> <pre><code class="prism language-jsx">// 错误示例:使用索引作为key(如果列表项会变动) function TodoList({ todos }) { return ( <ul> {todos.map((todo, index) => ( <li key={index}>{todo.text}</li> ))} </ul> ); } // 正确示例:使用唯一ID作为key function TodoListCorrect({ todos }) { return ( <ul> {todos.map((todo) => ( <li key={todo.id}>{todo.text}</li> ))} </ul> ); } </code></pre> <p><strong>总结:</strong> <code>key</code>是React列表渲染中一个非常重要的优化手段,它能够帮助React高效地识别和更新列表项,从而提升应用的性能和稳定性。始终为列表项提供一个稳定且唯一的<code>key</code>是最佳实践。</p> <hr> <h2>第三章:组件的生命力 - State与生命周期 (函数组件视角)</h2> <h3>3.1 State:组件内部的状态管理</h3> <p>在React中,<code>state</code>是组件内部用来存储和管理自身数据的机制。与<code>props</code>不同,<code>state</code>是组件私有的,只能在组件内部被修改。当组件的<code>state</code>发生变化时,React会自动重新渲染该组件及其子组件,以反映最新的数据状态。<code>state</code>使得组件能够响应用户交互、网络请求或其他事件,从而实现动态和交互式的用户界面。</p> <h4>3.1.1 什么是State?</h4> <p>简单来说,<code>state</code>就是一个普通的JavaScript对象,它包含了组件在特定时刻的数据快照。这些数据可以是用户输入、服务器响应、UI元素的状态(如是否展开、是否选中)等等。<code>state</code>赋予了组件“记忆”的能力,使其能够记住信息并在需要时更新UI。</p> <p>在函数组件中,我们使用<code>useState</code> Hook来声明和管理<code>state</code>。<code>useState</code>返回一个包含两个元素的数组:当前状态值和一个用于更新该状态的函数。</p> <pre><code class="prism language-jsx">import React, { useState } from 'react'; function Counter() { // 声明一个名为count的state变量,初始值为0 const [count, setCount] = useState(0); return ( <div> <p>你点击了 {count} 次</p> <button onClick={() => setCount(count + 1)}> 点击我 </button> </div> ); } </code></pre> <p>在这个例子中,<code>count</code>是我们的<code>state</code>变量,<code>setCount</code>是更新<code>count</code>的函数。每次点击按钮时,<code>setCount(count + 1)</code>会被调用,<code>count</code>的值会增加,React会重新渲染<code>Counter</code>组件,显示最新的点击次数。</p> <h4>3.1.2 State与Props的区别</h4> <p><code>state</code>和<code>props</code>是React中两个核心的数据概念,理解它们的区别至关重要:</p> <table> <thead> <tr> <th align="left">特性</th> <th align="left">Props (属性)</th> <th align="left">State (状态)</th> </tr> </thead> <tbody> <tr> <td align="left"><strong>来源</strong></td> <td align="left">由父组件传递给子组件</td> <td align="left">在组件内部定义和管理</td> </tr> <tr> <td align="left"><strong>可变性</strong></td> <td align="left">只读 (子组件不能直接修改props)</td> <td align="left">可变 (组件可以通过特定的更新函数修改state)</td> </tr> <tr> <td align="left"><strong>所有权</strong></td> <td align="left">父组件拥有并控制</td> <td align="left">组件自身拥有并控制</td> </tr> <tr> <td align="left"><strong>用途</strong></td> <td align="left">用于配置和定制子组件,实现父子组件间的数据传递</td> <td align="left">用于存储和管理组件内部的动态数据,驱动组件的更新</td> </tr> </tbody> </table> <p>可以将<code>props</code>看作是函数的参数,而<code>state</code>则像是函数内部声明的变量。一个组件接收<code>props</code>并根据其内部的<code>state</code>来渲染UI。</p> <h4>3.1.3 何时使用State?</h4> <p>并非所有的数据都应该存储在<code>state</code>中。通常,只有那些会随着时间变化并且会影响组件渲染的数据才应该作为<code>state</code>。以下是一些判断是否应该使用<code>state</code>的准则:</p> <ul> <li><strong>数据是否由父组件通过<code>props</code>传递?</strong> 如果是,那么它可能不应该是<code>state</code>。</li> <li><strong>数据是否在组件的整个生命周期中保持不变?</strong> 如果是,那么它可能不应该是<code>state</code>,可以考虑将其定义为组件外部的常量或组件内部的普通变量(如果它不影响渲染)。</li> <li><strong>能否根据其他<code>state</code>或<code>props</code>计算出该数据?</strong> 如果是,那么它可能不应该是<code>state</code>,以避免数据冗余和不一致。</li> </ul> <p><strong>经验法则:保持<code>state</code>的最小化。</strong> 只将那些真正代表组件“状态”并且需要被组件自身管理的数据放入<code>state</code>中。如果一个数据可以从<code>props</code>派生,或者可以从其他<code>state</code>计算得到,那么通常不需要将其设为独立的<code>state</code>。</p> <h3>3.2 <code>useState</code> Hook:状态管理的核心武器 (深入理解其原理与闭包)</h3> <p><code>useState</code>是React Hooks中最基础也是最重要的一个Hook。它允许函数组件拥有自己的状态,从而打破了以往只有类组件才能管理状态的限制。</p> <h4>3.2.1 <code>useState</code>的基本用法</h4> <p><code>useState</code>接收一个可选的参数作为初始状态(<code>initialState</code>),并返回一个包含两个元素的数组:</p> <ol> <li><strong>当前状态值 (<code>state</code>)</strong>: 在组件的第一次渲染时,它等于你传入的<code>initialState</code>。在后续的渲染中,它会是最后一次通过<code>setState</code>函数更新后的值。</li> <li><strong>状态更新函数 (<code>setState</code>)</strong>: 一个函数,用于更新对应的状态值并触发组件的重新渲染。</li> </ol> <pre><code class="prism language-jsx">const [state, setState] = useState(initialState); </code></pre> <p><strong>命名约定:</strong></p> <p>通常,我们会使用数组解构来获取这两个值,并将状态变量命名为描述其含义的名称(如<code>count</code>、<code>name</code>、<code>isActive</code>),状态更新函数则以<code>set</code>开头,后跟状态变量的驼峰式名称(如<code>setCount</code>、<code>setName</code>、<code>setIsActive</code>)。这是一种广泛遵循的社区约定,有助于提高代码的可读性。</p> <p><strong>示例:一个简单的开关组件</strong></p> <pre><code class="prism language-jsx">import React, { useState } from 'react'; function ToggleSwitch() { const [isOn, setIsOn] = useState(false); // 初始状态为关闭 (false) const handleToggle = () => { setIsOn(!isOn); // 点击时切换状态 }; return ( <div> <p>开关当前状态: {isOn ? '开启' : '关闭'}</p> <button onClick={handleToggle}> {isOn ? '关闭开关' : '开启开关'} </button> </div> ); } </code></pre> <h4>3.2.2 初始状态 (<code>initialState</code>)</h4> <p><code>initialState</code>参数只在组件的<strong>首次渲染</strong>时被使用。在后续的重新渲染中,React会忽略这个参数,并使用当前的状态值。</p> <p><strong>惰性初始状态 (Lazy initial state):</strong></p> <p>如果初始状态的计算比较昂贵(例如,需要执行复杂的计算或读取<code>localStorage</code>),你可以向<code>useState</code>传递一个函数作为<code>initialState</code>。这个函数只会在组件首次渲染时执行一次,其返回值将作为初始状态。</p> <pre><code class="prism language-jsx">import React, { useState } from 'react'; function HeavyComputationComponent() { // 假设expensiveInitialValue是一个计算成本很高的函数 const expensiveInitialValue = () => { console.log('执行昂贵的初始值计算...'); let sum = 0; for (let i = 0; i < 1000000000; i++) { sum += i; } return sum % 100; // 返回一个计算结果 }; // 传递一个函数作为初始状态,这个函数只会在首次渲染时执行 const [value, setValue] = useState(expensiveInitialValue); // 或者更简洁的写法: // const [value, setValue] = useState(() => expensiveInitialValue()); return ( <div> <p>计算得到的初始值: {value}</p> <button onClick={() => setValue(value + 1)}>增加值</button> </div> ); } </code></pre> <p>这种方式可以避免在每次组件重新渲染时不必要地重复执行昂贵的初始状态计算。</p> <h4>3.2.3 状态更新函数 (<code>setState</code>)</h4> <p><code>setState</code>函数用于更新状态并触发组件的重新渲染。它可以接收两种类型的参数:</p> <ol> <li> <p><strong>新的状态值</strong>: 直接传递新的状态值。</p> <pre><code class="prism language-jsx">setCount(10); // 将count设置为10 setName('新的名字'); // 将name设置为'新的名字' </code></pre> </li> <li> <p><strong>一个函数 (updater function)</strong>: 传递一个函数,该函数接收前一个状态(pending state)作为参数,并返回新的状态。这种方式在基于前一个状态计算新状态时非常有用,可以避免因状态更新的异步性而导致的问题。</p> <pre><code class="prism language-jsx">setCount(prevCount => prevCount + 1); // 基于前一个count值加1 setItems(prevItems => [...prevItems, newItem]); // 向数组中添加新项 </code></pre> </li> </ol> <p><strong>状态更新的异步性与批量处理:</strong></p> <p>调用<code>setState</code>并不会立即改变<code>state</code>的值。React会将状态更新操作放入一个队列中,并在适当的时候(通常是在当前事件处理函数执行完毕后)批量处理这些更新,然后触发一次重新渲染。这意味着在同一个事件处理函数中多次调用<code>setState</code>,组件通常只会重新渲染一次。</p> <pre><code class="prism language-jsx">function handleClick() { setCount(count + 1); // 假设此时count为0 setCount(count + 1); // 这里的count仍然是0 console.log(count); // 输出0,因为状态更新是异步的 } // 最终count会是1,而不是2 </code></pre> <p>如果需要基于前一个状态进行多次更新,或者确保获取到最新的状态值进行计算,<strong>务必使用函数式更新</strong>:</p> <pre><code class="prism language-jsx">function handleClickMultipleUpdates() { setCount(prevCount => prevCount + 1); setCount(prevCount => prevCount + 1); // 这样,最终count会是2 (假设初始为0) } </code></pre> <p><strong>对象和数组的更新:</strong></p> <p>当状态是对象或数组时,直接修改它们是无效的,因为React通过比较新旧状态的引用来判断是否需要重新渲染。你需要创建一个新的对象或数组副本,并在副本上进行修改,然后将新的副本传递给<code>setState</code>。</p> <pre><code class="prism language-jsx">// 更新对象状态 const [user, setUser] = useState({ name: '张三', age: 20 }); function handleAgeIncrement() { setUser(prevUser => ({ ...prevUser, // 展开旧的user对象 age: prevUser.age + 1 // 更新age属性 })); } // 更新数组状态 const [items, setItems] = useState(['苹果', '香蕉']); function addItem(newItem) { setItems(prevItems => [ ...prevItems, // 展开旧的items数组 newItem // 添加新项 ]); } </code></pre> <h4>3.2.4 <code>useState</code>与闭包</h4> <p>理解<code>useState</code>和闭包的关系对于深入掌握React Hooks至关重要。在函数组件的每次渲染中,组件函数都会重新执行。这意味着在每次渲染中,<code>useState</code>返回的<code>state</code>变量和<code>setState</code>函数都是“新”的(尽管<code>setState</code>函数的引用通常是稳定的)。</p> <p><strong>闭包陷阱:</strong></p> <p>当你在<code>useEffect</code>、事件处理函数或其他异步回调中使用<code>state</code>变量时,需要特别注意闭包问题。这些函数会“捕获”它们被创建时所在作用域的变量值。如果这些函数是在某次渲染中创建的,它们会记住那次渲染时的<code>state</code>值,即使后续<code>state</code>已经更新,这些函数内部的<code>state</code>值也不会自动更新。</p> <pre><code class="prism language-jsx">import React, { useState, useEffect } from 'react'; function DelayedCount() { const [count, setCount] = useState(0); useEffect(() => { const intervalId = setInterval(() => { // 这个回调函数是在组件首次渲染时创建的,它捕获了当时的count值 (0) // 即使后续count通过setCount更新了,这个回调函数内部的count仍然是0 console.log('Interval count:', count); // 始终输出0 }, 1000); return () => clearInterval(intervalId); }, []); // 空依赖数组,effect只在挂载和卸载时运行 return ( <div> <p>当前 Count: {count}</p> <button onClick={() => setCount(count + 1)}>增加 Count</button> </div> ); } </code></pre> <p><strong>解决闭包陷阱的方法:</strong></p> <ol> <li> <p><strong>使用函数式更新 <code>setState</code></strong>: 如果更新逻辑依赖于前一个状态,使用函数式更新可以确保获取到最新的状态值。</p> <pre><code class="prism language-jsx">// 在上面的例子中,如果想在interval中更新count // setCount(prevCount => prevCount + 1); </code></pre> </li> <li> <p><strong>将<code>state</code>变量添加到<code>useEffect</code>的依赖数组中</strong>: 如果<code>useEffect</code>的逻辑依赖于某个<code>state</code>变量,应该将其添加到依赖数组中。这样,当该<code>state</code>变量变化时,<code>useEffect</code>会重新执行,其内部的回调函数会捕获到最新的<code>state</code>值。但这可能会导致<code>useEffect</code>频繁执行,需要谨慎处理。</p> <pre><code class="prism language-jsx">useEffect(() => { // ... 逻辑依赖于 count ... }, [count]); // 当count变化时,effect重新执行 </code></pre> </li> <li> <p><strong>使用<code>useRef</code></strong>: 对于某些不需要触发重新渲染,但需要在多次渲染之间保持一致引用的值,可以使用<code>useRef</code>。可以将最新的<code>state</code>值存储在<code>ref.current</code>中,并在回调函数中读取它。但这通常不是处理<code>state</code>闭包的首选方案。</p> </li> </ol> <p>理解闭包是正确使用React Hooks,特别是<code>useState</code>和<code>useEffect</code>的关键。在编写涉及异步操作或回调函数的代码时,务必考虑到变量捕获的问题。</p> <h3>3.3 副作用(Side Effects)的概念与 <code>useEffect</code> Hook</h3> <p>在React组件中,除了渲染UI之外,我们经常需要执行一些与外部系统交互的操作,例如:数据获取、订阅、手动更改DOM等。这些操作被称为“副作用”(Side Effects),因为它们会影响组件外部的环境,或者被外部环境影响。</p> <p><code>useEffect</code> Hook允许你在函数组件中执行副作用操作。它告诉React你的组件需要在渲染完成后执行某些操作。</p> <h4>3.3.1 <code>useEffect</code>的基本用法</h4> <p><code>useEffect</code>接收两个参数:</p> <ol> <li><strong>一个设置函数 (<code>setup</code> function)</strong>: 包含副作用逻辑的函数。这个函数会在React完成DOM更新后异步执行。</li> <li><strong>一个可选的依赖项数组 (<code>dependencies</code> array)</strong>: 一个数组,包含了<code>setup</code>函数所依赖的<code>props</code>或<code>state</code>。React会比较依赖项数组中的值,只有当依赖项发生变化时,才会重新执行<code>setup</code>函数。</li> </ol> <pre><code class="prism language-jsx">useEffect(() => { // 副作用逻辑 console.log('组件已渲染或依赖项已更新'); // 可选的清理函数 return () => { console.log('组件将卸载或依赖项将更新,执行清理'); // 清理逻辑 }; }, [dependency1, dependency2]); // 依赖项数组 </code></pre> <p><strong>执行时机:</strong></p> <ul> <li><strong>首次渲染后</strong>: <code>setup</code>函数会在组件首次挂载到DOM并完成渲染后执行。</li> <li><strong>依赖项更新后</strong>: 如果提供了依赖项数组,并且在后续的重新渲染中,依赖项数组中的任何一个值发生了变化,React会首先执行上一次<code>useEffect</code>返回的清理函数(如果存在),然后再执行新的<code>setup</code>函数。</li> <li><strong>组件卸载前</strong>: 当组件从DOM中移除时,React会执行最后一次<code>useEffect</code>返回的清理函数。</li> </ul> <h4>3.3.2 依赖项数组 (<code>dependencies</code>)</h4> <p>依赖项数组是<code>useEffect</code>中非常关键的一部分,它控制着副作用函数的执行时机。</p> <ul> <li><strong>不提供依赖项数组</strong>: 如果省略第二个参数,<code>useEffect</code>会在<strong>每次组件渲染完成后</strong>都执行。这通常不是我们期望的行为,因为它可能导致不必要的副作用执行和性能问题。</li> <li><strong>提供空数组 <code>[]</code></strong>: 如果传递一个空数组作为依赖项,<code>useEffect</code>的<code>setup</code>函数只会在组件<strong>首次挂载后执行一次</strong>,其返回的清理函数只会在组件<strong>卸载前执行一次</strong>。这模拟了类组件中<code>componentDidMount</code>和<code>componentWillUnmount</code>的行为。</li> <li><strong>提供包含依赖项的数组 <code>[dep1, dep2, ...]</code></strong>: 这是最常见的用法。<code>useEffect</code>会在首次渲染后执行,并且在后续的渲染中,只有当数组中的<strong>任何一个依赖项发生变化</strong>时,才会重新执行(先清理,后设置)。</li> </ul> <p>正确指定依赖项至关重要。如果你遗漏了某个依赖项,副作用函数可能不会在期望的时候执行,导致bug。React的ESLint插件(<code>eslint-plugin-react-hooks</code>)通常会帮助你检查并提示遗漏的依赖项。</p> <h3>3.4 清理函数的重要性:避免内存泄漏</h3> <p>在<code>useEffect</code>中,返回一个函数是可选的,这个返回的函数被称为“清理函数”(cleanup function)。清理函数在以下情况下会被执行:</p> <ol> <li><strong>组件卸载前</strong>: 当组件从DOM中移除时。</li> <li><strong>下一次<code>useEffect</code>执行前</strong>: 如果<code>useEffect</code>的依赖项发生了变化,导致副作用函数需要重新执行,那么在执行新的副作用函数之前,会先执行上一次副作用函数返回的清理函数。</li> </ol> <p><strong>为什么需要清理函数?</strong></p> <p>清理函数的主要目的是<strong>防止内存泄漏</strong>和<strong>避免不必要的行为</strong>。当组件执行的副作用涉及到订阅、定时器、全局事件监听或创建了需要手动释放的资源时,如果不在组件卸载或副作用不再需要时进行清理,这些资源可能会持续存在于内存中,或者继续执行,导致应用性能下降甚至崩溃。</p> <p><strong>常见的需要清理的场景:</strong></p> <ul> <li><strong>取消订阅</strong>: 如果你在<code>useEffect</code>中订阅了某个事件源,必须在清理函数中取消订阅。</li> <li><strong>清除定时器</strong>: 如果你使用了<code>setInterval</code>或<code>setTimeout</code>,必须在清理函数中使用<code>clearInterval</code>或<code>clearTimeout</code>来清除它们。</li> <li><strong>取消网络请求</strong>: 对于长时间运行的网络请求,如果组件在请求完成前卸载,可能会尝试更新一个已卸载组件的状态,导致React警告。可以使用<code>AbortController</code>来取消<code>fetch</code>请求,并在清理函数中调用<code>abort()</code>。</li> <li><strong>释放其他资源</strong>: 任何在副作用中创建的、需要手动释放的资源。</li> </ul> <p><strong>示例:使用<code>setInterval</code>并进行清理</strong></p> <pre><code class="prism language-jsx">import React, { useState, useEffect } from 'react'; function Timer() { const [seconds, setSeconds] = useState(0); useEffect(() => { console.log('定时器启动'); const intervalId = setInterval(() => { setSeconds(prevSeconds => prevSeconds + 1); }, 1000); // 清理函数:在组件卸载时清除定时器 return () => { clearInterval(intervalId); console.log('定时器已清除'); }; }, []); // 空依赖数组,只在挂载和卸载时执行 return <p>计时: {seconds} 秒</p>; } </code></pre> <p>在这个例子中,如果忘记在清理函数中调用<code>clearInterval</code>,即使<code>Timer</code>组件已经卸载,<code>setInterval</code>创建的定时器仍然会继续在后台运行,尝试更新一个不存在的组件的状态,从而导致内存泄漏和潜在的错误。</p> <p><strong>总结:</strong> 只要你的<code>useEffect</code>执行了任何需要“撤销”或“清理”的操作,就<strong>必须</strong>提供一个清理函数。这是编写健壮、无内存泄漏的React应用的关键实践。</p> <h3>3.5 函数组件的“生命周期” (依赖项数组的奥秘)</h3> <p>虽然函数组件本身没有像类组件那样显式的生命周期方法(如<code>componentDidMount</code>、<code>componentDidUpdate</code>、<code>componentWillUnmount</code>),但通过巧妙地使用<code>useEffect</code>及其依赖项数组,我们可以模拟出类似的行为,从而在函数组件的不同“阶段”执行逻辑。</p> <p><strong>将<code>useEffect</code>视为与外部系统同步的机制,而不是严格的生命周期方法,有助于更好地理解其行为。</strong></p> <h4>3.5.1 模拟 <code>componentDidMount</code> (挂载后执行)</h4> <p>当<code>useEffect</code>的依赖项数组为空<code>[]</code>时,其<code>setup</code>函数只会在组件首次挂载到DOM并完成渲染后执行一次。这与类组件的<code>componentDidMount</code>行为类似。</p> <pre><code class="prism language-jsx">useEffect(() => { // 这里的代码只在组件挂载后执行一次 console.log('组件已挂载 (类似 componentDidMount)'); // 例如:进行初始数据获取、设置订阅 }, []); </code></pre> <h4>3.5.2 模拟 <code>componentWillUnmount</code> (卸载前执行)</h4> <p>当<code>useEffect</code>的依赖项数组为空<code>[]</code>时,其返回的清理函数只会在组件从DOM中移除(卸载)前执行一次。这与类组件的<code>componentWillUnmount</code>行为类似。</p> <pre><code class="prism language-jsx">useEffect(() => { // ... 挂载逻辑 ... return () => { // 这里的代码只在组件卸载前执行一次 console.log('组件将卸载 (类似 componentWillUnmount)'); // 例如:取消订阅、清除定时器、移除全局事件监听器 }; }, []); </code></pre> <h4>3.5.3 模拟 <code>componentDidUpdate</code> (更新后执行)</h4> <p>当<code>useEffect</code>的依赖项数组包含特定的<code>props</code>或<code>state</code>时,副作用函数会在这些依赖项发生变化导致组件重新渲染后执行。这在某种程度上模拟了<code>componentDidUpdate</code>的行为,但更精确地控制了执行时机。</p> <ul> <li> <p><strong>每次渲染后都执行 (不推荐,除非特定场景)</strong>: 如果不提供依赖项数组,<code>useEffect</code>会在每次渲染后都执行,类似于<code>componentDidMount</code>和<code>componentDidUpdate</code>的组合,但通常会导致不必要的执行。</p> <pre><code class="prism language-jsx">useEffect(() => { console.log('每次渲染后都会执行 (类似 componentDidMount + componentDidUpdate)'); }); </code></pre> </li> <li> <p><strong>特定依赖项更新后执行</strong>: 这是更常见的用法。当依赖项数组中的某个值发生变化时,先执行清理函数(如果上一次有),再执行<code>setup</code>函数。</p> <pre><code class="prism language-jsx">const [count, setCount] = useState(0); const [name, setName] = useState('初始名称'); useEffect(() => { console.log(`count 或 name 更新了: count=${count}, name=${name}`); // 可以在这里根据count或name的变化执行逻辑 document.title = `计数: ${count} | 名称: ${name}`; return () => { console.log(`清理旧的 count 或 name 相关的副作用: 旧count=${count}, 旧name=${name}`); // 注意:这里的count和name是上一次渲染时的值 }; }, [count, name]); // 当count或name变化时执行 </code></pre> <p><strong>与<code>componentDidUpdate(prevProps, prevState)</code>的对比:</strong></p> <p>在类组件的<code>componentDidUpdate</code>中,我们可以通过比较<code>prevProps</code>、<code>prevState</code>与当前的<code>this.props</code>、<code>this.state</code>来判断是否需要执行某些逻辑。在<code>useEffect</code>中,依赖项数组隐式地完成了这个比较。如果依赖项没有变化,React会跳过<code>useEffect</code>的执行。</p> <p>如果需要在<code>useEffect</code>中访问变化前的<code>props</code>或<code>state</code>,通常需要通过<code>useRef</code>来手动存储它们,或者在清理函数中访问(清理函数捕获的是上一次渲染时的值)。</p> </li> </ul> <h4>3.5.4 理解依赖项数组的“奥秘”</h4> <p>依赖项数组是<code>useEffect</code>的核心,它决定了副作用何时以及为何重新运行。React通过浅比较(<code>Object.is</code>)依赖项数组中的每一个值与上一次渲染时的对应值来判断是否发生了变化。</p> <p><strong>关键点:</strong></p> <ul> <li> <p><strong>引用类型</strong>: 对于对象和数组等引用类型,即使它们的内容没有改变,如果它们的引用地址发生了变化(例如,在每次渲染时都创建了一个新的对象或数组),React也会认为依赖项发生了变化,从而重新执行<code>useEffect</code>。这是常见的导致<code>useEffect</code>意外频繁执行的原因。</p> <pre><code class="prism language-jsx">// 错误示例:options对象在每次渲染时都是新的引用 function MyComponent({ propValue }) { const options = { value: propValue }; // 每次渲染都创建新对象 useEffect(() => { console.log('Effect执行,因为options引用变了'); // ... }, [options]); // 依赖于一个每次都新的对象 return <div>...</div>; } </code></pre> <p><strong>解决方法:</strong></p> <ul> <li>如果对象或数组的内容是稳定的,可以将它们移到组件外部,或者使用<code>useMemo</code>来记忆化它们。</li> <li>如果只需要对象或数组中的某些原始类型值作为依赖,可以将这些原始值直接放入依赖数组。</li> </ul> </li> <li> <p><strong>函数作为依赖项</strong>: 如果<code>useEffect</code>依赖于在组件内部定义的函数,并且这个函数在每次渲染时都会重新创建(因为函数也是对象,引用会变),那么也可能导致<code>useEffect</code>频繁执行。可以使用<code>useCallback</code>来记忆化这个函数,或者将函数移到<code>useEffect</code>内部(如果它只被这个effect使用)。</p> </li> </ul> <p><strong>“生命周期”的思维转变:</strong></p> <p>与其将<code>useEffect</code>严格地映射到类组件的生命周期方法,不如将其理解为一种<strong>声明副作用的方式</strong>,并根据<strong>数据的变化</strong>来驱动这些副作用的执行和清理。依赖项数组正是连接数据变化和副作用执行的桥梁。</p> <p>思考“当这些数据(依赖项)发生变化时,我需要执行什么操作,以及如何清理上一次的操作?”可以帮助你更有效地使用<code>useEffect</code>。</p> <h3>3.6 理解“纯函数”与“副作用”的边界</h3> <p>在React和函数式编程的语境中,“纯函数”(Pure Functions)和“副作用”(Side Effects)是两个核心概念。理解它们的边界对于编写可预测、可维护和易于测试的React组件至关重要。</p> <h4>3.6.1 纯函数 (Pure Functions)</h4> <p>纯函数具有以下两个主要特征:</p> <ol> <li><strong>相同的输入总是产生相同的输出</strong>: 给定相同的参数,纯函数总是返回相同的结果,不受任何外部状态或时间的影响。</li> <li><strong>没有副作用</strong>: 纯函数不会修改其作用域之外的任何状态,也不会与外部世界进行任何可观察的交互(如修改全局变量、写入文件、进行网络请求、操作DOM等)。</li> </ol> <p><strong>示例:</strong></p> <pre><code class="prism language-javascript"><span class="token comment">// 纯函数:计算两个数的和</span> <span class="token keyword">function</span> <span class="token function">sum</span><span class="token punctuation">(</span><span class="token parameter">a<span class="token punctuation">,</span> b</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> a <span class="token operator">+</span> b<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment">// 纯函数:将字符串转换为大写</span> <span class="token keyword">function</span> <span class="token function">toUpperCase</span><span class="token punctuation">(</span><span class="token parameter">str</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> str<span class="token punctuation">.</span><span class="token function">toUpperCase</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment">// 非纯函数:因为它修改了外部变量</span> <span class="token keyword">let</span> globalCounter <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> <span class="token keyword">function</span> <span class="token function">incrementGlobalCounter</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> globalCounter<span class="token operator">++</span><span class="token punctuation">;</span> <span class="token keyword">return</span> globalCounter<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment">// 非纯函数:因为它依赖于外部状态 (Date.now())</span> <span class="token keyword">function</span> <span class="token function">getCurrentTimestamp</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> Date<span class="token punctuation">.</span><span class="token function">now</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p><strong>React组件的渲染部分应该是纯粹的:</strong></p> <p>在React中,组件的渲染逻辑(即函数组件本身或类组件的<code>render</code>方法)应该尽可能地像纯函数一样工作。给定相同的<code>props</code>和<code>state</code>,它应该总是渲染出相同的UI(React元素树)。不应该在渲染过程中执行副作用操作。</p> <pre><code class="prism language-jsx">// 理想情况下,这是一个纯粹的渲染函数 function Greeting({ name }) { return <h1>Hello, {name}!</h1>; // 给定相同的name,总是返回相同的JSX } </code></pre> <h4>3.6.2 副作用 (Side Effects)</h4> <p>副作用是指函数或表达式在执行过程中,除了返回一个值之外,还与外部世界发生了交互,或者修改了其作用域之外的状态。这些交互或修改使得函数的行为不再仅仅取决于其输入参数。</p> <p><strong>常见的副作用类型:</strong></p> <ul> <li><strong>DOM操作</strong>: 直接修改浏览器DOM(如添加、删除、更新元素属性)。</li> <li><strong>网络请求</strong>: 发起HTTP请求(如<code>fetch</code>、<code>axios</code>)。</li> <li><strong>定时器</strong>: 使用<code>setTimeout</code>或<code>setInterval</code>。</li> <li><strong>本地存储</strong>: 读取或写入<code>localStorage</code>或<code>sessionStorage</code>。</li> <li><strong>日志记录</strong>: 向控制台输出日志(<code>console.log</code>在严格意义上也算副作用,但通常在开发中被接受)。</li> <li><strong>订阅与取消订阅</strong>: 监听事件、订阅数据流等。</li> <li><strong>修改全局变量或外部状态</strong>。</li> </ul> <p><strong>在React中处理副作用:</strong></p> <p>React组件的渲染过程应该是纯粹的,不应该包含副作用。那么,副作用应该在哪里执行呢?答案是使用<code>useEffect</code> Hook。</p> <p><code>useEffect</code>提供了一个专门的地方来处理副作用,它会在React完成渲染和DOM更新之后异步执行,从而将副作用与纯粹的渲染逻辑分离开来。</p> <pre><code class="prism language-jsx">import React, { useState, useEffect } from 'react'; function DocumentTitleUpdater({ title }) { // 渲染逻辑是纯粹的:给定相同的title, 返回相同的JSX // return null; // 假设这个组件只负责更新文档标题,不渲染任何UI // 副作用:更新文档标题 useEffect(() => { document.title = title; console.log(`文档标题已更新为: ${title}`); // 可选的清理函数 (如果需要) // return () => { document.title = '默认标题'; }; }, [title]); // 当title变化时执行副作用 return <p>当前文档标题会根据传入的title prop动态更新。</p>; } </code></pre> <h4>3.6.3 为什么区分纯函数和副作用很重要?</h4> <ol> <li><strong>可预测性</strong>: 纯函数更容易理解和预测其行为,因为它们的输出只依赖于输入。</li> <li><strong>可测试性</strong>: 纯函数更容易测试,因为不需要模拟复杂的外部环境或状态。</li> <li><strong>可缓存性/记忆化</strong>: 纯函数的输出可以被缓存(记忆化),因为相同的输入总是产生相同的结果。React的<code>useMemo</code>和<code>React.memo</code>就利用了这个特性来优化性能。</li> <li><strong>并发与并行</strong>: 纯函数更容易进行并发或并行处理,因为它们之间没有依赖关系或共享状态的修改。</li> <li><strong>React的渲染优化</strong>: React的协调算法依赖于组件渲染的纯粹性。如果渲染过程中有副作用,可能会导致不可预测的UI更新和性能问题。</li> </ol> <p><strong>在React中,努力将组件的渲染逻辑保持纯粹,并将所有副作用操作移至<code>useEffect</code>中,是编写高质量、可维护React应用的核心原则之一。</strong></p> <p>通过清晰地划分纯函数和副作用的边界,我们可以构建出更健壮、更易于推理的组件和应用。</p> <hr> <p><strong>参考资料:</strong></p> <p>[1] useState – React 中文文档. Retrieved from https://zh-hans.react.dev/reference/react/useState<br> [2] useEffect – React 中文文档. Retrieved from https://zh-hans.react.dev/reference/react/useEffect</p> </div> </div> </div> </div> </div> <!--PC和WAP自适应版--> <div id="SOHUCS" sid="1935139983673323520"></div> <script type="text/javascript" src="/views/front/js/chanyan.js"></script> <!-- 文章页-底部 动态广告位 --> <div class="youdao-fixed-ad" id="detail_ad_bottom"></div> </div> <div class="col-md-3"> <div class="row" id="ad"> <!-- 文章页-右侧1 动态广告位 --> <div id="right-1" class="col-lg-12 col-md-12 col-sm-4 col-xs-4 ad"> <div class="youdao-fixed-ad" id="detail_ad_1"> </div> </div> <!-- 文章页-右侧2 动态广告位 --> <div id="right-2" class="col-lg-12 col-md-12 col-sm-4 col-xs-4 ad"> <div class="youdao-fixed-ad" id="detail_ad_2"></div> </div> <!-- 文章页-右侧3 动态广告位 --> <div id="right-3" class="col-lg-12 col-md-12 col-sm-4 col-xs-4 ad"> <div class="youdao-fixed-ad" id="detail_ad_3"></div> </div> </div> </div> </div> </div> </div> <div class="container"> <h4 class="pt20 mb15 mt0 border-top">你可能感兴趣的:(前端权威教程合集,前端,TypeScript,React)</h4> <div id="paradigm-article-related"> <div class="recommend-post mb30"> <ul class="widget-links"> <li><a href="/article/1950232190038110208.htm" title="day15|前端框架学习和算法" target="_blank">day15|前端框架学习和算法</a> <span class="text-muted">universe_01</span> <a class="tag" taget="_blank" href="/search/%E5%89%8D%E7%AB%AF/1.htm">前端</a><a class="tag" taget="_blank" href="/search/%E7%AE%97%E6%B3%95/1.htm">算法</a><a class="tag" taget="_blank" href="/search/%E7%AC%94%E8%AE%B0/1.htm">笔记</a> <div>T22括号生成先把所有情况都画出来,然后(在满足什么情况下)把不符合条件的删除。T78子集要画树状图,把思路清晰。可以用暴力法、回溯法和DFS做这个题DFS深度搜索:每个边都走完,再回溯应用:二叉树搜索,图搜索回溯算法=DFS+剪枝T200岛屿数量(非常经典BFS宽度把树状转化成队列形式,lambda匿名函数“一次性的小函数,没有名字”setup语法糖:让代码更简洁好写的语法ref创建:基本类型的</div> </li> <li><a href="/article/1950213818848178176.htm" title="D124:如何训练独立思考力?" target="_blank">D124:如何训练独立思考力?</a> <span class="text-muted">大栗子_</span> <div>当我们要判断一个理论或者思想是否正确,需要有三个层次,分别是体验、解释和分析。首先看体验。很多时候,我们会相信“听上去、感觉是对的”的事情。我们之前讲的太空笔的故事之所以大多数人都认为是对的,就是有一些看似真实的关键词,比如美国,NASA,设计等,这些词看起来非常权威,但是离我们又遥远,这时候我们的大脑就会放松警惕了。于是,我们毫不犹豫就接受了。说到这里,你有没有发现之前的电视广告中的各种高让我们</div> </li> <li><a href="/article/1950193455162519552.htm" title="19.0-《超越感觉》-说服他人" target="_blank">19.0-《超越感觉》-说服他人</a> <span class="text-muted">SAM52</span> <div>Becausethoughtfuljudgmentsdeservetobeshared,andthewaytheyarepresentedcanstronglyinfluencethewayothersreacttothem.因为经过深思熟虑的判断值得分享,而这些判断的呈现方式会强烈影响其他人对它们的反应。Bylearningtheprinciplesofpersuasionandapplying</div> </li> <li><a href="/article/1950191208873652224.htm" title="vue element 封装表单" target="_blank">vue element 封装表单</a> <span class="text-muted">影子信息</span> <a class="tag" taget="_blank" href="/search/vue/1.htm">vue</a><a class="tag" taget="_blank" href="/search/vue.js/1.htm">vue.js</a><a class="tag" taget="_blank" href="/search/javascript/1.htm">javascript</a><a class="tag" taget="_blank" href="/search/%E5%89%8D%E7%AB%AF/1.htm">前端</a> <div>背景:在前端系统开发中,系统页面涉及到的表单组件比较多,所以进行了简单的封装。封装的包括一些Form表单组件,如下:input输入框、select下拉框、等实现效果:理论知识:表单组件官方链接:点击跳转封装组件:封装组件的思路:不封装element组件,每一个input组件绑定一个form对象,例如官网。简单封装element组件,利用for循环生成form表单的每一项el-form-item。进</div> </li> <li><a href="/article/1950191165710069760.htm" title="前端面试每日 3+1 —— 第39天" target="_blank">前端面试每日 3+1 —— 第39天</a> <span class="text-muted">浪子神剑</span> <div>今天的面试题(2019.05.25)——第39天[html]title与h1、b与strong、i与em的区别分别是什么?[css]写出你知道的CSS水平和垂直居中的方法[js]说说你对模块化的理解[软技能]公钥加密和私钥加密是什么?《论语》,曾子曰:“吾日三省吾身”(我每天多次反省自己)。前端面试每日3+1题,以面试题来驱动学习,每天进步一点!让努力成为一种习惯,让奋斗成为一种享受!欢迎在Iss</div> </li> <li><a href="/article/1950178477592342528.htm" title="前端数据库:IndexedDB从基础到高级使用指南" target="_blank">前端数据库:IndexedDB从基础到高级使用指南</a> <span class="text-muted"></span> <div>文章目录前端数据库:IndexedDB从基础到高级使用指南引言一、IndexedDB概述1.1什么是IndexedDB1.2与其他存储方案的比较二、基础使用2.1打开/创建数据库2.2基本CRUD操作添加数据读取数据更新数据删除数据三、高级特性3.1复杂查询与游标3.2事务高级用法3.3性能优化技巧四、实战案例:构建离线优先的待办事项应用4.1数据库设计4.2同步策略实现五、常见问题与解决方案5.</div> </li> <li><a href="/article/1950178478011772928.htm" title="全面解析:Spring Gateway如何优雅处理微服务的路由转发?" target="_blank">全面解析:Spring Gateway如何优雅处理微服务的路由转发?</a> <span class="text-muted">万猫学社</span> <a class="tag" taget="_blank" href="/search/gateway/1.htm">gateway</a><a class="tag" taget="_blank" href="/search/java/1.htm">java</a><a class="tag" taget="_blank" href="/search/spring/1.htm">spring</a> <div>SpringGateway简介SpringGateway,这是一个基于Spring5、SpringBoot2和ProjectReactor的API网关。它旨在为微服务架构提供一个简单、有效的统一的API路由、限流、熔断等功能。在微服务的世界里,SpringGateway就像一个交通警察,负责指挥和引导各个微服务之间的交通。相较于其他的网关技术,比如Nginx、Zuul等,SpringGateway</div> </li> <li><a href="/article/1950169524384886784.htm" title="【Java Web实战】从零到一打造企业级网上购书网站系统 | 完整开发实录(三)" target="_blank">【Java Web实战】从零到一打造企业级网上购书网站系统 | 完整开发实录(三)</a> <span class="text-muted">笙囧同学</span> <a class="tag" taget="_blank" href="/search/java/1.htm">java</a><a class="tag" taget="_blank" href="/search/%E5%89%8D%E7%AB%AF/1.htm">前端</a><a class="tag" taget="_blank" href="/search/%E7%8A%B6%E6%80%81%E6%A8%A1%E5%BC%8F/1.htm">状态模式</a> <div>核心功能设计用户管理系统用户管理是整个系统的基础,我设计了完整的用户生命周期管理:用户注册流程验证失败验证通过验证失败验证通过用户名已存在用户名可用失败成功用户访问注册页面填写注册信息前端表单验证显示错误提示提交到后端后端数据验证返回错误信息用户名唯一性检查提示用户名重复密码加密处理保存用户信息保存成功?显示系统错误注册成功跳转登录页面登录认证机制深度解析我实现了一套企业级的多层次安全认证机制:认</div> </li> <li><a href="/article/1950169526440095744.htm" title="从零到一:打造基于GigaChat AI的艺术创作平台 | 笙囧同学的全栈开发实战" target="_blank">从零到一:打造基于GigaChat AI的艺术创作平台 | 笙囧同学的全栈开发实战</a> <span class="text-muted"></span> <div>作者简介:笙囧同学,中科院计算机大模型方向硕士,全栈开发爱好者联系方式:3251736703@qq.com各大平台账号:笙囧同学座右铭:偷懒是人生进步的阶梯前言在AI技术飞速发展的今天,如何将前沿的大模型技术与实际应用相结合,一直是我们开发者关注的焦点。今天,笙囧同学将带大家从零开始,构建一个基于GigaChatAI的艺术创作平台,实现React前端+Django后端的完整全栈解决方案。这不仅仅是</div> </li> <li><a href="/article/1950164483057971200.htm" title="14.tornado操作之应用Websocket协议实现聊天室功能" target="_blank">14.tornado操作之应用Websocket协议实现聊天室功能</a> <span class="text-muted">孤寒者</span> <a class="tag" taget="_blank" href="/search/Tornado%E6%A1%86%E6%9E%B6%E4%BB%8E%E5%85%A5%E9%97%A8%E5%88%B0%E5%AE%9E%E6%88%98/1.htm">Tornado框架从入门到实战</a><a class="tag" taget="_blank" href="/search/websocket/1.htm">websocket</a><a class="tag" taget="_blank" href="/search/tornado/1.htm">tornado</a><a class="tag" taget="_blank" href="/search/%E8%81%8A%E5%A4%A9%E5%AE%A4%E5%8A%9F%E8%83%BD%E5%AE%9E%E7%8E%B0/1.htm">聊天室功能实现</a><a class="tag" taget="_blank" href="/search/python/1.htm">python</a> <div>目录:每篇前言:1.什么是WebSocket(1)定义(2)优点(3)和HTTP对比(4)适用场景2.WebSocket关键方法3.本tornado项目中使用WebSocket(1)准备一个聊天室的页面:第一步:编写视图:第二步:编写接口:(app.py中加入以下接口!)第三步:编写前端页面:测试接口——响应OK!(2)使用WebSocket:(3)聊天室的聊天功能的最终实现:第一步:战前准备第二</div> </li> <li><a href="/article/1950144218282389504.htm" title="为什么学习Web前端一定要掌握JavaScript?" target="_blank">为什么学习Web前端一定要掌握JavaScript?</a> <span class="text-muted">web前端学习指南</span> <div>为什么学习Web前端一定要掌握JavaScript?在前端的世界里,没有什么是JavaScript实现不了的,关于JS有一句话:凡是可以用JavaScript来写的应用,最终都会用JavaScript,JavaScript可运行在所有主要平台的所有主流浏览器上,也可运行在每一个主流操作系统的服务器端上。现如今我们在为网站写任何一个主要功能的时候都需要有懂能够用JavaScript写前端的开发人员。</div> </li> <li><a href="/article/1950143305194991616.htm" title="小架构step系列25:错误码" target="_blank">小架构step系列25:错误码</a> <span class="text-muted">秋千码途</span> <a class="tag" taget="_blank" href="/search/%E6%9E%B6%E6%9E%84/1.htm">架构</a><a class="tag" taget="_blank" href="/search/java/1.htm">java</a> <div>1概述一个系统中,可能产生各种各样的错误,对这些错误进行编码。当错误发生时,通过这个错误码就有可能快速判断是什么错误,不一定需要查看代码就可以进行处理,提高问题处理效率。有了统一的错误码,还可以标准化错误信息,方便把错误信息纳入文档管理和对错误信息进行国际化等。没有错误码的管理,开发人员就会按自己的理解处理这些错误。有些直接把堆栈直接反馈到前端页面上,使用看不懂这些信息体验很差,也暴露了堆栈信息有</div> </li> <li><a href="/article/1950140903616212992.htm" title="Java朴实无华按天计划从入门到实战(强化速战版-66天)" target="_blank">Java朴实无华按天计划从入门到实战(强化速战版-66天)</a> <span class="text-muted">岫珩</span> <a class="tag" taget="_blank" href="/search/Java/1.htm">Java</a><a class="tag" taget="_blank" href="/search/%E5%90%8E%E7%AB%AF/1.htm">后端</a><a class="tag" taget="_blank" href="/search/java/1.htm">java</a><a class="tag" taget="_blank" href="/search/%E5%BC%80%E5%8F%91%E8%AF%AD%E8%A8%80/1.htm">开发语言</a><a class="tag" taget="_blank" href="/search/%E5%AD%A6%E4%B9%A0/1.htm">学习</a><a class="tag" taget="_blank" href="/search/Java/1.htm">Java</a><a class="tag" taget="_blank" href="/search/%E6%97%B6%E9%97%B4%E5%AE%89%E6%8E%92/1.htm">时间安排</a><a class="tag" taget="_blank" href="/search/%E5%AD%A6%E4%B9%A0%E8%AE%A1%E5%88%92/1.htm">学习计划</a> <div>致敬读者感谢阅读笑口常开生日快乐⬛早点睡觉博主相关博主信息博客首页专栏推荐活动信息文章目录Java朴实无华按天计划从入门到实战(强化速战版-66天)1.基础(18)1.1JavaSE核心(5天)1.2数据库与SQL(5天)1.3前端基础(8天)2.进阶(17天)2.1JavaWeb核心(5天)2.2Mybatis与Spring全家桶(6天)2.3中间件入门(4天)2.4实践项目(2天)3.高阶(1</div> </li> <li><a href="/article/1950132204336115712.htm" title="《跨域资源共享CORS的深层逻辑与前端实践精要》" target="_blank">《跨域资源共享CORS的深层逻辑与前端实践精要》</a> <span class="text-muted"></span> <div>不同源头的资源交互已成为常态,而跨域资源共享(CORS)正是支撑这种交互的隐形架构。现代Web安全体系中平衡开放与防护的精妙设计。理解CORS的深层逻辑,不仅能解决实际开发中的跨域难题,更能触及网络安全与资源流通的核心矛盾,为前端工程师构建稳健的应用提供底层认知支撑。跨域资源共享的诞生,源于网络安全与应用发展的必然冲突。浏览器的同源策略,作为早期网络安全的基石,通过限制不同源文档的交互,有效阻挡了</div> </li> <li><a href="/article/1950131321980383232.htm" title="深入了解 Kubernetes(k8s):从概念到实践" target="_blank">深入了解 Kubernetes(k8s):从概念到实践</a> <span class="text-muted"></span> <div>目录一、k8s核心概念二、k8s的优势三、k8s架构组件控制平面组件节点组件四、k8s+docker运行前后端分离项目的例子1.准备前端项目2.准备后端项目3.创建k8s部署配置文件4.部署应用到k8s集群在当今云计算和容器化技术飞速发展的时代,Kubernetes(简称k8s)已成为容器编排领域的事实标准。无论是互联网巨头、传统企业还是初创公司,都在广泛采用k8s来管理和部署容器化应用。本文将带</div> </li> <li><a href="/article/1950119224630374400.htm" title="大厂都在用的前端缓存策略,你掌握了吗?" target="_blank">大厂都在用的前端缓存策略,你掌握了吗?</a> <span class="text-muted">AI架构全栈开发实战笔记</span> <a class="tag" taget="_blank" href="/search/%E5%89%8D%E7%AB%AF/1.htm">前端</a><a class="tag" taget="_blank" href="/search/%E7%BC%93%E5%AD%98/1.htm">缓存</a><a class="tag" taget="_blank" href="/search/ai/1.htm">ai</a> <div>大厂都在用的前端缓存策略,你掌握了吗?关键词:前端缓存、HTTP缓存、ServiceWorker、CDN缓存、缓存策略、性能优化、浏览器缓存摘要:本文将深入探讨前端开发中常用的缓存策略,从浏览器缓存到ServiceWorker,从HTTP缓存头到CDN缓存,全面解析大厂都在使用的高效缓存技术。通过生动的比喻和实际代码示例,帮助开发者理解并掌握这些提升Web应用性能的关键技术。背景介绍目的和范围本文</div> </li> <li><a href="/article/1950115062781898752.htm" title="26. 什么是雪碧图,作用和原理了解吗" target="_blank">26. 什么是雪碧图,作用和原理了解吗</a> <span class="text-muted">yqcoder</span> <a class="tag" taget="_blank" href="/search/%E5%89%8D%E7%AB%AF%E9%9D%A2%E8%AF%95-CSS/1.htm">前端面试-CSS</a><a class="tag" taget="_blank" href="/search/css/1.htm">css</a><a class="tag" taget="_blank" href="/search/%E5%89%8D%E7%AB%AF/1.htm">前端</a><a class="tag" taget="_blank" href="/search/html/1.htm">html</a> <div>总结小图合成一张,使用background来使用,减少资源请求一、什么是雪碧图?雪碧图(CSSSprite)是一种前端优化技术,指的是将多个小图标合并成一张大图,通过CSS控制显示其中的某一部分。它常用于网站中图标、按钮等小图较多的场景。二、雪碧图的作用作用说明✅减少HTTP请求次数合并多个图片为一张图,减少请求资源数✅提升页面加载速度尤其在图片较多时效果明显✅避免图片加载闪烁鼠标悬停切换图片时不</div> </li> <li><a href="/article/1950112039502409728.htm" title="H5UI微信小程序前端框架实战指南" target="_blank">H5UI微信小程序前端框架实战指南</a> <span class="text-muted">ai</span> <div>本文还有配套的精品资源,点击获取简介:H5UI是一个为微信小程序开发设计的前端框架,基于H5技术,提供简洁高效的组件库。框架集成了丰富的UI元素,如按钮、表格、导航栏等,简化了界面布局和交互的实现。通过安装、引入、使用组件和事件绑定四个步骤,开发者可以轻松构建功能齐全的应用。了解性能优化等注意事项对于高效开发同样重要。1.微信小程序前端开发框架介绍微信小程序概述微信小程序是微信官方推出的一种无需下</div> </li> <li><a href="/article/1950104854819041280.htm" title="Ubuntu安装LAMP" target="_blank">Ubuntu安装LAMP</a> <span class="text-muted">L_h1</span> <a class="tag" taget="_blank" href="/search/%E6%B5%8B%E8%AF%95/1.htm">测试</a><a class="tag" taget="_blank" href="/search/ubuntu/1.htm">ubuntu</a><a class="tag" taget="_blank" href="/search/linux/1.htm">linux</a> <div>在安装vim时遇到了一个问题:E:无法获得锁/var/lib/dpkg/lock-frontend-open(11:资源暂时不可用)E:无法获取dpkg前端锁(/var/lib/dpkg/lock-frontend),是否有其他进程正占用它?解决办法:强制解锁sudorm/var/lib/dpkg/lock-frontendsudorm/var/cache/apt/archives/locksud</div> </li> <li><a href="/article/1950104727928762368.htm" title="震惊!DOM变化监控神器MutationObserver,前端开发必知的隐藏武器!" target="_blank">震惊!DOM变化监控神器MutationObserver,前端开发必知的隐藏武器!</a> <span class="text-muted">coding随想</span> <a class="tag" taget="_blank" href="/search/JavaScript/1.htm">JavaScript</a><a class="tag" taget="_blank" href="/search/%E5%89%8D%E7%AB%AF/1.htm">前端</a><a class="tag" taget="_blank" href="/search/javascript/1.htm">javascript</a><a class="tag" taget="_blank" href="/search/html5/1.htm">html5</a> <div>一、什么是MutationObserver?如果你是一个前端开发者,一定会遇到这样的场景:页面动态加载内容后,某些操作失效了。比如,你写了一个监听按钮点击的代码,但按钮是通过AJAX动态加载的,你的代码根本无法触发。这个时候,你就需要一个“监控哨兵”——MutationObserver,它能实时监听DOM树的变化,帮你捕获那些“暗中作祟”的节点变动。MutationObserver是HTML5引入</div> </li> <li><a href="/article/1950102582793924608.htm" title="分布式IO选型指南:2025年分布式无线远程IO品牌及采集控制方案详解" target="_blank">分布式IO选型指南:2025年分布式无线远程IO品牌及采集控制方案详解</a> <span class="text-muted">2501_91398178</span> <a class="tag" taget="_blank" href="/search/%E5%88%86%E5%B8%83%E5%BC%8F/1.htm">分布式</a><a class="tag" taget="_blank" href="/search/%E5%88%86%E5%B8%83%E5%BC%8FIO%E6%A8%A1%E5%9D%97/1.htm">分布式IO模块</a><a class="tag" taget="_blank" href="/search/%E8%BF%9C%E7%A8%8BIO%E6%A8%A1%E5%9D%97/1.htm">远程IO模块</a> <div>近年来,随着工业物联网(IIoT)、智能制造和工业4.0的深入发展,分布式无线远程IO模块在工业控制领域的应用愈发广泛。这种模块通过无线方式实现远程数据采集与控制,极大地提高了工业设施的灵活性和效率。2025年,分布式IO市场呈现出技术革新与品牌竞争加剧的态势。本文基于权威数据平台(如Statista、MarketsandMarkets、GrandViewResearch)的市场分析,全面解读分布</div> </li> <li><a href="/article/1950102582307385344.htm" title="胶棒天线选购指南:2025十大通信天线品牌盘点与应用方案解析" target="_blank">胶棒天线选购指南:2025十大通信天线品牌盘点与应用方案解析</a> <span class="text-muted">2501_91398178</span> <a class="tag" taget="_blank" href="/search/%E8%83%B6%E6%A3%92%E5%A4%A9%E7%BA%BF/1.htm">胶棒天线</a> <div>胶棒天线选购指南:2025十大品牌盘点与应用方案解析随着无线通信技术的迅猛发展,胶棒天线(RubberDuckAntenna)作为一种轻便、经济且高效的天线解决方案,广泛应用于物联网(IoT)、无线局域网(WLAN)、工业自动化、无人机以及各类通信设备中。2025年的市场竞争更加激烈,新兴技术与经典产品并存,行业用户在选购时需要全面了解品牌、产品性能与应用方案。本文将结合权威性数据平台的分析,详细</div> </li> <li><a href="/article/1950099176436068352.htm" title="分布式IO详解:2025年分布式无线远程IO采集控制方案选型指南" target="_blank">分布式IO详解:2025年分布式无线远程IO采集控制方案选型指南</a> <span class="text-muted">2501_91398178</span> <a class="tag" taget="_blank" href="/search/%E5%88%86%E5%B8%83%E5%BC%8F/1.htm">分布式</a><a class="tag" taget="_blank" href="/search/%E5%88%86%E5%B8%83%E5%BC%8FIO/1.htm">分布式IO</a> <div>随着工业物联网(IIoT)和智能制造的快速发展,分布式远程IO(输入/输出)采集控制技术作为工业自动化系统的重要组成部分,正逐步取代传统集中式控制架构。这种技术广泛应用于工厂自动化、能源管理、智慧城市、过程控制等领域。2025年,分布式无线远程IO系统凭借其灵活性、低功耗和高可靠性,成为工业控制领域的核心解决方案。本文基于权威数据平台分析,详细解读分布式无线远程IO技术,盘点全球领先厂商及其产品优</div> </li> <li><a href="/article/1950095581212897280.htm" title="什么值得买网站官网?什么值得买网站靠谱吗?" target="_blank">什么值得买网站官网?什么值得买网站靠谱吗?</a> <span class="text-muted">高省APP珊珊</span> <div>什么值得买网站的官网是。这是一个专注于消费决策的平台,致力于为消费者提供全面、实时的购物信息和优惠活动,帮助用户做出更明智的购买决策。关于什么值得买网站的靠谱性,可以从以下几个方面进行评估:平台背景与信誉:什么值得买由北京值得买科技股份有限公司运营,该公司成立于2011年,并于2019年在深交所上市,具有较高的信誉和权威性。平台作为国内知名的消费导购平台之一,其用户群体广泛,口碑良好。内容质量与审</div> </li> <li><a href="/article/1950095270544994304.htm" title="从零入门:云迁移原理详解与华为Rainbow实战指南" target="_blank">从零入门:云迁移原理详解与华为Rainbow实战指南</a> <span class="text-muted">来自于狂人</span> <a class="tag" taget="_blank" href="/search/%E7%BD%91%E7%BB%9C/1.htm">网络</a><a class="tag" taget="_blank" href="/search/%E4%BA%91%E8%AE%A1%E7%AE%97/1.htm">云计算</a> <div>(全文较长,建议收藏后分段阅读)一、云迁移基础:新手必懂的10个核心概念1.云迁移的定义与战略价值权威定义:Gartner将云迁移定义为"将企业IT资产、应用和工作负载从传统本地环境(物理服务器、私有数据中心)迁移到云平台(公有云/混合云)的过程"。其本质是通过云技术实现资源弹性扩展、成本优化和业务创新。典型应用场景:业务系统上云:ERP(如SAP)、CRM(如Salesforce)等核心系统迁移</div> </li> <li><a href="/article/1950094764498022400.htm" title="Coze Studio 架构拆解:AI Agent 开发平台项目结构全分析" target="_blank">Coze Studio 架构拆解:AI Agent 开发平台项目结构全分析</a> <span class="text-muted">代码简单说</span> <a class="tag" taget="_blank" href="/search/2025%E5%BC%80%E5%8F%91%E5%BF%85%E5%A4%87%28%E9%99%90%E6%97%B6%E7%89%B9%E6%83%A0%29/1.htm">2025开发必备(限时特惠)</a><a class="tag" taget="_blank" href="/search/%E6%9E%B6%E6%9E%84/1.htm">架构</a><a class="tag" taget="_blank" href="/search/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/1.htm">人工智能</a><a class="tag" taget="_blank" href="/search/Coze/1.htm">Coze</a><a class="tag" taget="_blank" href="/search/Studio/1.htm">Studio</a><a class="tag" taget="_blank" href="/search/%E6%9E%B6%E6%9E%84/1.htm">架构</a><a class="tag" taget="_blank" href="/search/AI/1.htm">AI</a><a class="tag" taget="_blank" href="/search/Agent/1.htm">Agent</a><a class="tag" taget="_blank" href="/search/%E5%BC%80%E5%8F%91%E5%B9%B3%E5%8F%B0/1.htm">开发平台</a><a class="tag" taget="_blank" href="/search/%E5%85%A8%E6%A0%88/1.htm">全栈</a><a class="tag" taget="_blank" href="/search/AI/1.htm">AI</a><a class="tag" taget="_blank" href="/search/%E5%B7%A5%E7%A8%8B%E5%8C%96/1.htm">工程化</a><a class="tag" taget="_blank" href="/search/%E5%9B%BE%E8%A7%A3%E6%9E%B6%E6%9E%84/1.htm">图解架构</a> <div>CozeStudio架构拆解:AIAgent开发平台项目结构全分析标签:CozeStudio项目架构、领域驱动设计DDD、全栈开发规范、Hertz框架、前后端协作、云原生容器、前端测试、IDL接口设计、微服务解耦、AI开发平台源码分析在最近研究AIAgent开发平台的过程中,我深入分析了刚刚开源的CozeStudio项目。这套系统是国内少有的开源全栈AI工程化项目,代码整洁、架构先进,特别是它基于</div> </li> <li><a href="/article/1950075480849838080.htm" title="关于前端的性能优化" target="_blank">关于前端的性能优化</a> <span class="text-muted"></span> <div>性能优化主要涵盖了以下四个方面:(tip:仅代表个人总结,如有不当,还希望看到的大佬多多指示)减少网络请求:合并文件、使用CDN、启用缓存。优化资源加载:代码分割、懒加载、图片压缩。提升渲染性能:减少重绘回流、防抖节流、使用WebWorker。监控和迭代:定期使用工具检测性能,持续优化。一、网络层面优化减少HTTP请求合并文件:将多个CSS或JavaScript文件合并成一个,减少请求次数。使用C</div> </li> <li><a href="/article/1950059723604684800.htm" title="linux网卡显示未知未托管,linux有线网络显示设备未托管" target="_blank">linux网卡显示未知未托管,linux有线网络显示设备未托管</a> <span class="text-muted"></span> <div>NetworkManagerNetworkManager是为了使网络配置尽可能简单而开发的网络管理软件包,如果使用DHCP,NetworkManager会替换默认的路由表、从DHCP服务器获取IP地址并根据情况设置域名服务器,NetworkManager的目标是使网络能够开箱即用。NetworkManager由两部分组成:一个以超级用户运行的守护进程(network-manager);一个前端管理</div> </li> <li><a href="/article/1950055310613868544.htm" title="Vue3组合API初体验" target="_blank">Vue3组合API初体验</a> <span class="text-muted">DTcode7</span> <a class="tag" taget="_blank" href="/search/Vue%E5%AE%9E%E6%88%98%E6%8C%87%E5%8D%97/1.htm">Vue实战指南</a><a class="tag" taget="_blank" href="/search/VUE/1.htm">VUE</a><a class="tag" taget="_blank" href="/search/HTML/1.htm">HTML</a><a class="tag" taget="_blank" href="/search/web/1.htm">web</a><a class="tag" taget="_blank" href="/search/vue%E6%A1%86%E6%9E%B6/1.htm">vue框架</a><a class="tag" taget="_blank" href="/search/%E5%89%8D%E7%AB%AF/1.htm">前端</a> <div>Vue3组合API初体验基本概念与作用说明示例一:使用ref创建响应式数据示例二:使用reactive创建响应式对象示例三:使用computed计算属性示例四:使用watch监听数据变化示例五:使用provide/inject进行父子组件间通信功能使用思路与实际开发技巧1.何时使用ref与reactive?2.如何在组合式API中保持逻辑的清晰?3.如何处理异步操作?随着Vue3的发布,组合式AP</div> </li> <li><a href="/article/1950053798433058816.htm" title="页面开发样式和布局入门:Vite + Vue 3 + Less" target="_blank">页面开发样式和布局入门:Vite + Vue 3 + Less</a> <span class="text-muted"></span> <div>页面开发样式和布局入门:Vite+Vue3+Less引言在现代前端开发中,样式和布局是页面开发的核心部分。随着技术的不断发展,Vite、Vue3和Less等工具和框架的出现,使得前端开发变得更加高效和灵活。然而,尽管这些工具和框架提供了强大的功能,但在实际开发中仍然会遇到各种样式和布局的问题。本文将结合Vite、Vue3和Less,详细介绍在页面开发中常见的样式和布局问题,并提供解决方案和最佳实践</div> </li> <li><a href="/article/73.htm" title="Hadoop(一)" target="_blank">Hadoop(一)</a> <span class="text-muted">朱辉辉33</span> <a class="tag" taget="_blank" href="/search/hadoop/1.htm">hadoop</a><a class="tag" taget="_blank" href="/search/linux/1.htm">linux</a> <div>今天在诺基亚第一天开始培训大数据,因为之前没接触过Linux,所以这次一起学了,任务量还是蛮大的。 首先下载安装了Xshell软件,然后公司给了账号密码连接上了河南郑州那边的服务器,接下来开始按照给的资料学习,全英文的,头也不讲解,说锻炼我们的学习能力,然后就开始跌跌撞撞的自学。这里写部分已经运行成功的代码吧.    在hdfs下,运行hadoop fs -mkdir /u</div> </li> <li><a href="/article/200.htm" title="maven An error occurred while filtering resources" target="_blank">maven An error occurred while filtering resources</a> <span class="text-muted">blackproof</span> <a class="tag" taget="_blank" href="/search/maven/1.htm">maven</a><a class="tag" taget="_blank" href="/search/%E6%8A%A5%E9%94%99/1.htm">报错</a> <div>转:http://stackoverflow.com/questions/18145774/eclipse-an-error-occurred-while-filtering-resources   maven报错: maven An error occurred while filtering resources   Maven -> Update Proje</div> </li> <li><a href="/article/327.htm" title="jdk常用故障排查命令" target="_blank">jdk常用故障排查命令</a> <span class="text-muted">daysinsun</span> <a class="tag" taget="_blank" href="/search/jvm/1.htm">jvm</a> <div>linux下常见定位命令: 1、jps      输出Java进程       -q       只输出进程ID的名称,省略主类的名称;       -m      输出进程启动时传递给main函数的参数;     &nb</div> </li> <li><a href="/article/454.htm" title="java 位移运算与乘法运算" target="_blank">java 位移运算与乘法运算</a> <span class="text-muted">周凡杨</span> <a class="tag" taget="_blank" href="/search/java/1.htm">java</a><a class="tag" taget="_blank" href="/search/%E4%BD%8D%E7%A7%BB/1.htm">位移</a><a class="tag" taget="_blank" href="/search/%E8%BF%90%E7%AE%97/1.htm">运算</a><a class="tag" taget="_blank" href="/search/%E4%B9%98%E6%B3%95/1.htm">乘法</a> <div>  对于 JAVA 编程中,适当的采用位移运算,会减少代码的运行时间,提高项目的运行效率。这个可以从一道面试题说起:     问题: 用最有效率的方法算出2 乘以8 等於几?” 答案:2 << 3 由此就引发了我的思考,为什么位移运算会比乘法运算更快呢?其实简单的想想,计算机的内存是用由 0 和 1 组成的二</div> </li> <li><a href="/article/581.htm" title="java中的枚举(enmu)" target="_blank">java中的枚举(enmu)</a> <span class="text-muted">g21121</span> <a class="tag" taget="_blank" href="/search/java/1.htm">java</a> <div>从jdk1.5开始,java增加了enum(枚举)这个类型,但是大家在平时运用中还是比较少用到枚举的,而且很多人和我一样对枚举一知半解,下面就跟大家一起学习下enmu枚举。先看一个最简单的枚举类型,一个返回类型的枚举: public enum ResultType { /** * 成功 */ SUCCESS, /** * 失败 */ FAIL, </div> </li> <li><a href="/article/708.htm" title="MQ初级学习" target="_blank">MQ初级学习</a> <span class="text-muted">510888780</span> <a class="tag" taget="_blank" href="/search/activemq/1.htm">activemq</a> <div>1.下载ActiveMQ 去官方网站下载:http://activemq.apache.org/ 2.运行ActiveMQ 解压缩apache-activemq-5.9.0-bin.zip到C盘,然后双击apache-activemq-5.9.0-\bin\activemq-admin.bat运行ActiveMQ程序。 启动ActiveMQ以后,登陆:http://localhos</div> </li> <li><a href="/article/835.htm" title="Spring_Transactional_Propagation" target="_blank">Spring_Transactional_Propagation</a> <span class="text-muted">布衣凌宇</span> <a class="tag" taget="_blank" href="/search/spring/1.htm">spring</a><a class="tag" taget="_blank" href="/search/transactional/1.htm">transactional</a> <div>//事务传播属性 @Transactional(propagation=Propagation.REQUIRED)//如果有事务,那么加入事务,没有的话新创建一个 @Transactional(propagation=Propagation.NOT_SUPPORTED)//这个方法不开启事务 @Transactional(propagation=Propagation.REQUIREDS_N</div> </li> <li><a href="/article/962.htm" title="我的spring学习笔记12-idref与ref的区别" target="_blank">我的spring学习笔记12-idref与ref的区别</a> <span class="text-muted">aijuans</span> <a class="tag" taget="_blank" href="/search/spring/1.htm">spring</a> <div>idref用来将容器内其他bean的id传给<constructor-arg>/<property>元素,同时提供错误验证功能。例如: <bean id ="theTargetBean" class="..." /> <bean id ="theClientBean" class=&quo</div> </li> <li><a href="/article/1089.htm" title="Jqplot之折线图" target="_blank">Jqplot之折线图</a> <span class="text-muted">antlove</span> <a class="tag" taget="_blank" href="/search/js/1.htm">js</a><a class="tag" taget="_blank" href="/search/jquery/1.htm">jquery</a><a class="tag" taget="_blank" href="/search/Web/1.htm">Web</a><a class="tag" taget="_blank" href="/search/timeseries/1.htm">timeseries</a><a class="tag" taget="_blank" href="/search/jqplot/1.htm">jqplot</a> <div>timeseriesChart.html <script type="text/javascript" src="jslib/jquery.min.js"></script> <script type="text/javascript" src="jslib/excanvas.min.js&</div> </li> <li><a href="/article/1216.htm" title="JDBC中事务处理应用" target="_blank">JDBC中事务处理应用</a> <span class="text-muted">百合不是茶</span> <a class="tag" taget="_blank" href="/search/java/1.htm">java</a><a class="tag" taget="_blank" href="/search/JDBC%E7%BC%96%E7%A8%8B/1.htm">JDBC编程</a><a class="tag" taget="_blank" href="/search/%E4%BA%8B%E5%8A%A1%E6%8E%A7%E5%88%B6%E8%AF%AD%E5%8F%A5/1.htm">事务控制语句</a> <div>  解释事务的概念; 事务控制是sql语句中的核心之一;事务控制的作用就是保证数据的正常执行与异常之后可以恢复   事务常用命令:             Commit提交         </div> </li> <li><a href="/article/1343.htm" title="[转]ConcurrentHashMap Collections.synchronizedMap和Hashtable讨论" target="_blank">[转]ConcurrentHashMap Collections.synchronizedMap和Hashtable讨论</a> <span class="text-muted">bijian1013</span> <a class="tag" taget="_blank" href="/search/java/1.htm">java</a><a class="tag" taget="_blank" href="/search/%E5%A4%9A%E7%BA%BF%E7%A8%8B/1.htm">多线程</a><a class="tag" taget="_blank" href="/search/%E7%BA%BF%E7%A8%8B%E5%AE%89%E5%85%A8/1.htm">线程安全</a><a class="tag" taget="_blank" href="/search/HashMap/1.htm">HashMap</a> <div>在Java类库中出现的第一个关联的集合类是Hashtable,它是JDK1.0的一部分。 Hashtable提供了一种易于使用的、线程安全的、关联的map功能,这当然也是方便的。然而,线程安全性是凭代价换来的――Hashtable的所有方法都是同步的。此时,无竞争的同步会导致可观的性能代价。Hashtable的后继者HashMap是作为JDK1.2中的集合框架的一部分出现的,它通过提供一个不同步的</div> </li> <li><a href="/article/1470.htm" title="ng-if与ng-show、ng-hide指令的区别和注意事项" target="_blank">ng-if与ng-show、ng-hide指令的区别和注意事项</a> <span class="text-muted">bijian1013</span> <a class="tag" taget="_blank" href="/search/JavaScript/1.htm">JavaScript</a><a class="tag" taget="_blank" href="/search/AngularJS/1.htm">AngularJS</a> <div>        angularJS中的ng-show、ng-hide、ng-if指令都可以用来控制dom元素的显示或隐藏。ng-show和ng-hide根据所给表达式的值来显示或隐藏HTML元素。当赋值给ng-show指令的值为false时元素会被隐藏,值为true时元素会显示。ng-hide功能类似,使用方式相反。元素的显示或</div> </li> <li><a href="/article/1597.htm" title="【持久化框架MyBatis3七】MyBatis3定义typeHandler" target="_blank">【持久化框架MyBatis3七】MyBatis3定义typeHandler</a> <span class="text-muted">bit1129</span> <a class="tag" taget="_blank" href="/search/TypeHandler/1.htm">TypeHandler</a> <div>什么是typeHandler? typeHandler用于将某个类型的数据映射到表的某一列上,以完成MyBatis列跟某个属性的映射   内置typeHandler MyBatis内置了很多typeHandler,这写typeHandler通过org.apache.ibatis.type.TypeHandlerRegistry进行注册,比如对于日期型数据的typeHandler, </div> </li> <li><a href="/article/1724.htm" title="上传下载文件rz,sz命令" target="_blank">上传下载文件rz,sz命令</a> <span class="text-muted">bitcarter</span> <a class="tag" taget="_blank" href="/search/linux%E5%91%BD%E4%BB%A4rz/1.htm">linux命令rz</a> <div>刚开始使用rz上传和sz下载命令: 因为我们是通过secureCRT终端工具进行使用的所以会有上传下载这样的需求: 我遇到的问题: sz下载A文件10M左右,没有问题 但是将这个文件A再传到另一天服务器上时就出现传不上去,甚至出现乱码,死掉现象,具体问题 解决方法: 上传命令改为;rz -ybe 下载命令改为:sz -be filename 如果还是有问题: 那就是文</div> </li> <li><a href="/article/1851.htm" title="通过ngx-lua来统计nginx上的虚拟主机性能数据" target="_blank">通过ngx-lua来统计nginx上的虚拟主机性能数据</a> <span class="text-muted">ronin47</span> <a class="tag" taget="_blank" href="/search/ngx-lua%E3%80%80%E7%BB%9F%E8%AE%A1+%E8%A7%A3%E7%A6%81ip/1.htm">ngx-lua 统计 解禁ip</a> <div>介绍 以前我们为nginx做统计,都是通过对日志的分析来完成.比较麻烦,现在基于ngx_lua插件,开发了实时统计站点状态的脚本,解放生产力.项目主页: https://github.com/skyeydemon/ngx-lua-stats 功能 支持分不同虚拟主机统计, 同一个虚拟主机下可以分不同的location统计. 可以统计与query-times request-time </div> </li> <li><a href="/article/1978.htm" title="java-68-把数组排成最小的数。一个正整数数组,将它们连接起来排成一个数,输出能排出的所有数字中最小的。例如输入数组{32, 321},则输出32132" target="_blank">java-68-把数组排成最小的数。一个正整数数组,将它们连接起来排成一个数,输出能排出的所有数字中最小的。例如输入数组{32, 321},则输出32132</a> <span class="text-muted">bylijinnan</span> <a class="tag" taget="_blank" href="/search/java/1.htm">java</a> <div> import java.util.Arrays; import java.util.Comparator; public class MinNumFromIntArray { /** * Q68输入一个正整数数组,将它们连接起来排成一个数,输出能排出的所有数字中最小的一个。 * 例如输入数组{32, 321},则输出这两个能排成的最小数字32132。请给出解决问题</div> </li> <li><a href="/article/2105.htm" title="Oracle基本操作" target="_blank">Oracle基本操作</a> <span class="text-muted">ccii</span> <a class="tag" taget="_blank" href="/search/Oracle+SQL%E6%80%BB%E7%BB%93/1.htm">Oracle SQL总结</a><a class="tag" taget="_blank" href="/search/Oracle+SQL%E8%AF%AD%E6%B3%95/1.htm">Oracle SQL语法</a><a class="tag" taget="_blank" href="/search/Oracle%E5%9F%BA%E6%9C%AC%E6%93%8D%E4%BD%9C/1.htm">Oracle基本操作</a><a class="tag" taget="_blank" href="/search/Oracle+SQL/1.htm">Oracle SQL</a> <div>一、表操作 1. 常用数据类型 NUMBER(p,s):可变长度的数字。p表示整数加小数的最大位数,s为最大小数位数。支持最大精度为38位 NVARCHAR2(size):变长字符串,最大长度为4000字节(以字符数为单位) VARCHAR2(size):变长字符串,最大长度为4000字节(以字节数为单位) CHAR(size):定长字符串,最大长度为2000字节,最小为1字节,默认</div> </li> <li><a href="/article/2232.htm" title="[强人工智能]实现强人工智能的路线图" target="_blank">[强人工智能]实现强人工智能的路线图</a> <span class="text-muted">comsci</span> <a class="tag" taget="_blank" href="/search/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/1.htm">人工智能</a> <div>     1:创建一个用于记录拓扑网络连接的矩阵数据表      2:自动构造或者人工复制一个包含10万个连接(1000*1000)的流程图      3:将这个流程图导入到矩阵数据表中      4:在矩阵的每个有意义的节点中嵌入一段简单的</div> </li> <li><a href="/article/2359.htm" title="给Tomcat,Apache配置gzip压缩(HTTP压缩)功能" target="_blank">给Tomcat,Apache配置gzip压缩(HTTP压缩)功能</a> <span class="text-muted">cwqcwqmax9</span> <a class="tag" taget="_blank" href="/search/apache/1.htm">apache</a> <div>背景: HTTP 压缩可以大大提高浏览网站的速度,它的原理是,在客户端请求网页后,从服务器端将网页文件压缩,再下载到客户端,由客户端的浏览器负责解压缩并浏览。相对于普通的浏览过程HTML ,CSS,Javascript , Text ,它可以节省40%左右的流量。更为重要的是,它可以对动态生成的,包括CGI、PHP , JSP , ASP , Servlet,SHTML等输出的网页也能进行压缩,</div> </li> <li><a href="/article/2486.htm" title="SpringMVC and Struts2" target="_blank">SpringMVC and Struts2</a> <span class="text-muted">dashuaifu</span> <a class="tag" taget="_blank" href="/search/struts2/1.htm">struts2</a><a class="tag" taget="_blank" href="/search/springMVC/1.htm">springMVC</a> <div>SpringMVC VS Struts2 1: spring3开发效率高于struts 2: spring3 mvc可以认为已经100%零配置 3: struts2是类级别的拦截, 一个类对应一个request上下文, springmvc是方法级别的拦截,一个方法对应一个request上下文,而方法同时又跟一个url对应 所以说从架构本身上 spring3 mvc就容易实现r</div> </li> <li><a href="/article/2613.htm" title="windows常用命令行命令" target="_blank">windows常用命令行命令</a> <span class="text-muted">dcj3sjt126com</span> <a class="tag" taget="_blank" href="/search/windows/1.htm">windows</a><a class="tag" taget="_blank" href="/search/cmd/1.htm">cmd</a><a class="tag" taget="_blank" href="/search/command/1.htm">command</a> <div>在windows系统中,点击开始-运行,可以直接输入命令行,快速打开一些原本需要多次点击图标才能打开的界面,如常用的输入cmd打开dos命令行,输入taskmgr打开任务管理器。此处列出了网上搜集到的一些常用命令。winver 检查windows版本 wmimgmt.msc 打开windows管理体系结构(wmi) wupdmgr windows更新程序 wscrip</div> </li> <li><a href="/article/2740.htm" title="再看知名应用背后的第三方开源项目" target="_blank">再看知名应用背后的第三方开源项目</a> <span class="text-muted">dcj3sjt126com</span> <a class="tag" taget="_blank" href="/search/ios/1.htm">ios</a> <div>知名应用程序的设计和技术一直都是开发者需要学习的,同样这些应用所使用的开源框架也是不可忽视的一部分。此前《 iOS第三方开源库的吐槽和备忘》中作者ibireme列举了国内多款知名应用所使用的开源框架,并对其中一些框架进行了分析,同样国外开发者 @iOSCowboy也在博客中给我们列出了国外多款知名应用使用的开源框架。另外txx's blog中详细介绍了 Facebook Paper使用的第三</div> </li> <li><a href="/article/2867.htm" title="Objective-c单例模式的正确写法" target="_blank">Objective-c单例模式的正确写法</a> <span class="text-muted">jsntghf</span> <a class="tag" taget="_blank" href="/search/%E5%8D%95%E4%BE%8B/1.htm">单例</a><a class="tag" taget="_blank" href="/search/ios/1.htm">ios</a><a class="tag" taget="_blank" href="/search/iPhone/1.htm">iPhone</a> <div>一般情况下,可能我们写的单例模式是这样的: #import <Foundation/Foundation.h> @interface Downloader : NSObject + (instancetype)sharedDownloader; @end #import "Downloader.h" @implementation</div> </li> <li><a href="/article/2994.htm" title="jquery easyui datagrid 加载成功,选中某一行" target="_blank">jquery easyui datagrid 加载成功,选中某一行</a> <span class="text-muted">hae</span> <a class="tag" taget="_blank" href="/search/jquery/1.htm">jquery</a><a class="tag" taget="_blank" href="/search/easyui/1.htm">easyui</a><a class="tag" taget="_blank" href="/search/datagrid/1.htm">datagrid</a><a class="tag" taget="_blank" href="/search/%E6%95%B0%E6%8D%AE%E5%8A%A0%E8%BD%BD/1.htm">数据加载</a> <div>1.首先你需要设置datagrid的onLoadSuccess $( '#dg' ).datagrid({onLoadSuccess :  function (data){      $( '#dg' ).datagrid( 'selectRow' ,3); }});   2.onL</div> </li> <li><a href="/article/3121.htm" title="jQuery用户数字打分评价效果" target="_blank">jQuery用户数字打分评价效果</a> <span class="text-muted">ini</span> <a class="tag" taget="_blank" href="/search/JavaScript/1.htm">JavaScript</a><a class="tag" taget="_blank" href="/search/html/1.htm">html</a><a class="tag" taget="_blank" href="/search/jquery/1.htm">jquery</a><a class="tag" taget="_blank" href="/search/Web/1.htm">Web</a><a class="tag" taget="_blank" href="/search/css/1.htm">css</a> <div>效果体验:http://hovertree.com/texiao/jquery/5.htmHTML文件代码: <!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title>jQuery用户数字打分评分代码 - HoverTree</</div> </li> <li><a href="/article/3248.htm" title="mybatis的paramType" target="_blank">mybatis的paramType</a> <span class="text-muted">kerryg</span> <a class="tag" taget="_blank" href="/search/DAO/1.htm">DAO</a><a class="tag" taget="_blank" href="/search/sql/1.htm">sql</a> <div>MyBatis传多个参数: 1、采用#{0},#{1}获得参数:    Dao层函数方法:     public User selectUser(String name,String area); 对应的Mapper.xml    <select id="selectUser" result</div> </li> <li><a href="/article/3375.htm" title="centos 7安装mysql5.5" target="_blank">centos 7安装mysql5.5</a> <span class="text-muted">MrLee23</span> <a class="tag" taget="_blank" href="/search/centos/1.htm">centos</a> <div>首先centos7 已经不支持mysql,因为收费了你懂得,所以内部集成了mariadb,而安装mysql的话会和mariadb的文件冲突,所以需要先卸载掉mariadb,以下为卸载mariadb,安装mysql的步骤。   #列出所有被安装的rpm package rpm -qa | grep mariadb   #卸载 rpm -e mariadb-libs-5.</div> </li> <li><a href="/article/3502.htm" title="利用thrift来实现消息群发" target="_blank">利用thrift来实现消息群发</a> <span class="text-muted">qifeifei</span> <a class="tag" taget="_blank" href="/search/thrift/1.htm">thrift</a> <div>           Thrift项目一般用来做内部项目接偶用的,还有能跨不同语言的功能,非常方便,一般前端系统和后台server线上都是3个节点,然后前端通过获取client来访问后台server,那么如果是多太server,就是有一个负载均衡的方法,然后最后访问其中一个节点。那么换个思路,能不能发送给所有节点的server呢,如果能就</div> </li> <li><a href="/article/3629.htm" title="实现一个sizeof获取Java对象大小" target="_blank">实现一个sizeof获取Java对象大小</a> <span class="text-muted">teasp</span> <a class="tag" taget="_blank" href="/search/java/1.htm">java</a><a class="tag" taget="_blank" href="/search/HotSpot/1.htm">HotSpot</a><a class="tag" taget="_blank" href="/search/%E5%86%85%E5%AD%98/1.htm">内存</a><a class="tag" taget="_blank" href="/search/%E5%AF%B9%E8%B1%A1%E5%A4%A7%E5%B0%8F/1.htm">对象大小</a><a class="tag" taget="_blank" href="/search/sizeof/1.htm">sizeof</a> <div>   由于Java的设计者不想让程序员管理和了解内存的使用,我们想要知道一个对象在内存中的大小变得比较困难了。本文提供了可以获取对象的大小的方法,但是由于各个虚拟机在内存使用上可能存在不同,因此该方法不能在各虚拟机上都适用,而是仅在hotspot 32位虚拟机上,或者其它内存管理方式与hotspot 32位虚拟机相同的虚拟机上 适用。     </div> </li> <li><a href="/article/3756.htm" title="SVN错误及处理" target="_blank">SVN错误及处理</a> <span class="text-muted">xiangqian0505</span> <a class="tag" taget="_blank" href="/search/SVN%E6%8F%90%E4%BA%A4%E6%96%87%E4%BB%B6%E6%97%B6%E6%9C%8D%E5%8A%A1%E5%99%A8%E5%BC%BA%E8%A1%8C%E5%85%B3%E9%97%AD/1.htm">SVN提交文件时服务器强行关闭</a> <div>在SVN服务控制台打开资源库“SVN无法读取current” ---摘自网络 写道 SVN无法读取current修复方法 Can't read file : End of file found 文件:repository/db/txn_current、repository/db/current   其中current记录当前最新版本号,txn_current记录版本库中版本</div> </li> </ul> </div> </div> </div> <div> <div class="container"> <div class="indexes"> <strong>按字母分类:</strong> <a href="/tags/A/1.htm" target="_blank">A</a><a href="/tags/B/1.htm" target="_blank">B</a><a href="/tags/C/1.htm" target="_blank">C</a><a href="/tags/D/1.htm" target="_blank">D</a><a href="/tags/E/1.htm" target="_blank">E</a><a href="/tags/F/1.htm" target="_blank">F</a><a href="/tags/G/1.htm" target="_blank">G</a><a href="/tags/H/1.htm" target="_blank">H</a><a href="/tags/I/1.htm" target="_blank">I</a><a href="/tags/J/1.htm" target="_blank">J</a><a href="/tags/K/1.htm" target="_blank">K</a><a href="/tags/L/1.htm" target="_blank">L</a><a href="/tags/M/1.htm" target="_blank">M</a><a href="/tags/N/1.htm" target="_blank">N</a><a href="/tags/O/1.htm" target="_blank">O</a><a href="/tags/P/1.htm" target="_blank">P</a><a href="/tags/Q/1.htm" target="_blank">Q</a><a href="/tags/R/1.htm" target="_blank">R</a><a href="/tags/S/1.htm" target="_blank">S</a><a href="/tags/T/1.htm" target="_blank">T</a><a href="/tags/U/1.htm" target="_blank">U</a><a href="/tags/V/1.htm" target="_blank">V</a><a href="/tags/W/1.htm" target="_blank">W</a><a href="/tags/X/1.htm" target="_blank">X</a><a href="/tags/Y/1.htm" target="_blank">Y</a><a href="/tags/Z/1.htm" target="_blank">Z</a><a href="/tags/0/1.htm" target="_blank">其他</a> </div> </div> </div> <footer id="footer" class="mb30 mt30"> <div class="container"> <div class="footBglm"> <a target="_blank" href="/">首页</a> - <a target="_blank" href="/custom/about.htm">关于我们</a> - <a target="_blank" href="/search/Java/1.htm">站内搜索</a> - <a target="_blank" href="/sitemap.txt">Sitemap</a> - <a target="_blank" href="/custom/delete.htm">侵权投诉</a> </div> <div class="copyright">版权所有 IT知识库 CopyRight © 2000-2050 E-COM-NET.COM , All Rights Reserved. <!-- <a href="https://beian.miit.gov.cn/" rel="nofollow" target="_blank">京ICP备09083238号</a><br>--> </div> </div> </footer> <!-- 代码高亮 --> <script type="text/javascript" src="/static/syntaxhighlighter/scripts/shCore.js"></script> <script type="text/javascript" src="/static/syntaxhighlighter/scripts/shLegacy.js"></script> <script type="text/javascript" src="/static/syntaxhighlighter/scripts/shAutoloader.js"></script> <link type="text/css" rel="stylesheet" href="/static/syntaxhighlighter/styles/shCoreDefault.css"/> <script type="text/javascript" src="/static/syntaxhighlighter/src/my_start_1.js"></script> </body> </html>