当前位置: 首页 > news >正文

跟着 MDN 学 React框架 Day_2:框架的主要特性

引言:四大框架的共同特征与差异

每种主流的 JavaScript 框架都有自己独特的方式来更新 DOM、处理浏览器事件以及为开发者提供愉悦的使用体验。尽管它们的实现细节各不相同,但在更高层次的抽象上,这些框架解决的是同一类问题,提供的核心特性也有着显著的共性。这篇文章将深入探索四大主流框架的主要特性,从较高的抽象层次来探讨框架的工作方式以及它们之间的关键区别。理解这些特性不仅有助于你在不同框架之间做出选择,更能帮助你建立起对现代前端开发范式的整体认知。无论你最终选择哪个框架作为主要工具,这些核心概念都将贯穿你的学习与实践过程。

领域特定语言:框架的表达层

本模块中讨论的所有框架都建立在 JavaScript 的基础之上,但它们都额外引入了各自的领域特定语言来帮助开发者构建应用程序。领域特定语言是与特定开发领域相关的编程语言变体,在框架的语境中,它们通常是 JavaScript 或 HTML 的某种扩展形式,目的是让用户界面的编写过程更加直观和高效。与原生 HTML 不同,这些领域特定语言能够理解数据变量的概念,可以读取变量值并将其动态地插入到用户界面中,从而大大简化了构建数据驱动型用户界面的过程。

需要注意的是,领域特定语言不能直接被浏览器解析执行,它们首先需要被转换成标准的 JavaScript 或 HTML。虽然这个转换步骤在开发流程中增加了一个额外的环节,但框架通常会内置处理这一步骤所需的工具,使其与整体开发流程无缝衔接。开发者完全可以选择不使用这些领域特定语言来构建框架应用,但这样做通常意味着会错失它们带来的开发效率提升,同时也会让你更难从框架的周边社区中找到帮助和最佳实践指导。

React 普及了 JSX 这种领域特定语言。JSX 的全称是 JavaScript 和 XML,它是 JavaScript 的一种语法扩展,为 JavaScript 环境带来了类似 HTML 的语法表达能力。它由 React 团队发明并最初用于 React 应用程序,但后来也被其他框架如 Vue 所支持。通过一个简单的 JSX 示例可以看到它的核心特征:在一个 JavaScript 常量中定义了一个主题字符串,然后用类似 HTML 标签的语法创建了一个header元素,其中包含一个h1元素。在h1元素的文本内容中,花括号括起来的subject告诉应用程序要读取常量的值并将其动态插入到元素中。

const subject = "World"; const header = ( <header> <h1>Hello, {subject}!</h1> </header> );

当与 React 一起使用时,这段 JSX 代码会被编译成一系列React.createElement调用来创建对应的元素。经过编译之后,原本声明式的 JSX 语法变成了命令式的函数调用链,最终在浏览器中渲染出对应的 HTML 结构。

const subject = "World"; const header = React.createElement( "header", null, React.createElement("h1", null, "Hello, ", subject, "!"), );

最终呈现在浏览器中的 HTML 内容清晰直观,这正是 JSX 的价值所在:它让开发者可以用接近最终输出结果的方式来描述界面结构。

Handlebars 是 Ember 框架中大量使用的模板语言。它也是一种简单的模板语言,虽然并非 Ember 所独有,但在 Ember 应用中占据核心地位。Handlebars 代码在外观上接近 HTML,但它具备从外部数据源获取数据的能力,这些数据可以用来影响应用程序最终构建出的 HTML 内容。与 JSX 相似,Handlebars 使用花括号来注入变量的值,但不同的是 Handlebars 使用双花括号而非单花括号。给定一个包含subject变量的 Handlebars 模板,以及一个对应的数据对象,Handlebars 就会将双花括号中的变量替换为实际的数据值,生成对应的 HTML 结构。

<header><h1>Hello, {{subject}}!</h1></header>

TypeScript 是 Angular 框架生态中广泛使用的另一种领域特定语言。TypeScript 是 JavaScript 的超集,这意味着所有有效的 JavaScript 代码都是有效的 TypeScript 代码,但反过来并不成立。TypeScript 的核心价值在于它允许开发者以更加严格和规范的方式来编写代码。举例来说,编写一个接受两个参数并返回它们之和的加法函数,在原生 JavaScript 中代码非常简洁直观,但它存在潜在的隐患。JavaScript 的加法运算符可以用来连接字符串,如果传入的参数是字符串类型,函数在技术上仍然能够执行,但返回的结果可能并非开发者所期望的数值之和。TypeScript 通过在参数后面添加类型注解来明确约束参数必须是数字类型。这样一来,如果有人尝试将字符串传入这个函数,TypeScript 就会在编译阶段报告错误,迫使开发者修复这个类型不匹配的问题。虽然开发者也可以在原生 JavaScript 中手动编写类型检查逻辑来达到同样的效果,但这会显著增加代码的复杂度和维护成本。

functionadd(a:number,b:number){returna+b;}

编写组件:属性、状态与事件的三位一体

正如前一章中所述,大多数框架都拥有某种形式的组件模型。React 组件可以使用 JSX 编写,Ember 组件可以使用 Handlebars 编写,Angular 和 Vue 组件则可以使用各自的模板语法轻松地扩展 HTML。尽管各个框架的作者对于如何编写组件的具体细节有着不同的见解,但每个框架的组件都提供了三种核心能力:描述组件可能需要的外部属性、管理组件内部状态以及响应用户在组件上触发的交互事件。这三种能力构成了组件化开发的基础,是理解和掌握任何一个框架的必经之路。

属性是组件从外部接收的数据,用于定制组件的渲染行为。设想一个为在线杂志网站开发的场景,需要为每位撰稿人显示其荣誉信息。一个AuthorCredit组件需要显示作者的头像和简短的介绍文字。为了知道应该渲染哪张图片、显示哪段介绍,这个组件需要从外部接收相应的数据,这些数据就是属性。在 React 中,组件通过函数参数来接收属性对象,然后在 JSX 中使用花括号语法将属性值插入到对应的位置,分别是图片的源地址、图片的替代文本以及作者的署名信息。

function AuthorCredit(props) { return ( <figure> <img src={props.src} alt={props.alt} /> <figcaption>{props.byline}</figcaption> </figure> ); }

在调用这个组件时,开发者通过类似 HTML 属性的方式将数据传递给组件,分别指定图片路径、替代文本和署名内容。这些属性值会在渲染时被插入到组件模板的对应位置,最终在浏览器中生成一个包含图片和说明文字的完整 HTML 结构。

<AuthorCredit src="./assets/zelda.png" alt="Portrait of Zelda Schiff" byline="Zelda Schiff is editor-in-chief of the Library Times." />

状态是组件内部自行管理的数据,用于跟踪组件在运行过程中变化的动态信息。强大的状态处理机制是一个高效框架的关键所在。只要组件还在使用,这种状态就会持续存在。与属性一样,状态也可以用来影响组件的渲染方式。以一个记录点击次数的计数器按钮为例,这个组件需要负责跟踪自己的点击计数状态。在 React 中,使用useState这个钩子函数来创建状态,传入初始值零。useState返回一个数组,第一个元素是当前的状态值,组件在 JSX 中使用花括号将count的值嵌入按钮文本中。

function CounterButton() { const [count] = useState(0); return <button>Clicked {count} times</button>; }

useState调用会在整个应用的生命周期中稳定地跟踪count值的变化,开发者不需要自行编写任何额外的追踪逻辑。

事件是实现交互性的关键机制。组件需要对浏览器事件做出响应,这样应用程序才能对用户的操作做出反应。每个框架都提供了自己用于监听浏览器事件的语法,这些语法通常参考了对应的浏览器原生事件名称。在 React 中,监听点击事件需要使用一个名为onClick的特殊属性。更新上面的计数器按钮组件,使其能够响应点击并增加计数。在这个版本中,使用useState提供的第二个返回值,即一个专门用于更新状态值的函数。在按钮的onClick处理器中调用这个更新函数,将count的值设置为当前值加一,从而实现每次点击按钮时计数器递增的效果。

function CounterButton() { const [count, setCount] = useState(0); return ( <button onClick={() => setCount(count + 1)}>Clicked {count} times</button> ); }

为组件添加样式:框架中的样式处理方案

每个框架都提供了为组件或整个应用程序定义样式的方法。尽管各个框架定义组件样式的方式略有不同,但它们都为开发者提供了多种途径来满足不同场景的需求。有些框架支持直接在组件文件中编写样式,实现样式与组件的紧密耦合;有些框架则鼓励使用独立的样式文件,保持关注点分离。通过引入额外的辅助模块,开发者可以使用 Sass 或 Less 这类 CSS 预处理器来编写更具表现力的样式代码,或者使用 PostCSS 这类后处理工具来转译和优化最终的 CSS 样式表。无论选择哪种方式,框架都为样式的组织和管理提供了结构化的支持,避免了全局样式污染和命名冲突这些传统前端开发中的常见痛点。

处理依赖:组件嵌套与依赖注入

所有主流框架都提供了处理依赖关系的完善机制。在基于组件的架构中,组件经常需要在其他组件内部使用,有时还会涉及多个嵌套层次。与其他功能一样,不同框架实现依赖处理的确切机制会有所不同,但最终达成的效果是一致的。组件之间倾向于使用标准的 JavaScript 模块语法来相互导入,或者至少使用与之类似的机制。

组件嵌套是基于组件的用户界面架构的核心优势之一。就如同在 HTML 中可以将各种标签相互嵌套来构建一个完整的网页一样,开发者可以在其他组件内部使用组件来构建一个完整的 Web 应用。每个框架都允许开发者编写那些使用并因此依赖于其他组件的组件。例如,前面定义的AuthorCredit组件可能被用于一个Article组件之中,这意味着Article组件需要先导入AuthorCredit组件。一旦完成了导入操作,Article组件就可以在其渲染逻辑中直接使用AuthorCredit组件,像使用一个自定义的 HTML 标签一样将其放置在合适的位置。

import AuthorCredit from "./components/AuthorCredit";

现实世界中的应用往往涉及多层次嵌套的组件结构。设想一个杂志网站的整体组件架构,最外层是App根组件,内部嵌套了Home组件,Home组件内部又嵌套了Article组件,而Article组件内部最终嵌套了AuthorCredit组件。这样的多层嵌套结构在实际项目中非常普遍。如果App根组件持有了AuthorCredit组件需要的数据,按照常规的属性传递方式,数据需要从App依次传递给Home,再从Home传递给Article,最后才能到达AuthorCredit。虽然理论上可以重写HomeArticle组件让它们向下传递这些属性,但当数据的来源和目的地之间存在很多层级时,这种做法会变得极其繁琐。更糟糕的是,这种做法在架构上也是不合理的:HomeArticle组件实际上并不需要使用作者的头像或署名信息,但如果要把这些信息传递给AuthorCredit,就不得不修改HomeArticle的组件接口来被动接收和转发这些它们并不关心的数据。

<App> <Home> <Article> <AuthorCredit {/* props */} /> </Article> </Home> </App>

这种通过多层组件传递数据的问题被称为属性穿透,对于大型应用程序来说是一个显著的架构痛点。为了规避属性穿透问题,各个框架都提供了依赖注入的功能。依赖注入是一种将特定数据直接传递给需要它的组件的方法,而不需要经过中间层组件的转发。每个框架以不同的名称和实现方式提供依赖注入的能力,但最终达成的效果是相同的。Angular 将这个过程称为依赖注入,并将其作为框架的核心设计理念之一。Vue 提供了provideinject这对组合式的组件方法。React 则有 Context API 来实现跨层级的数据共享。Ember 则通过服务来在组件之间分享状态。无论采用哪种实现方式,依赖注入都有效地解决了深层组件之间的数据传递问题,让组件树的结构更加清晰合理。

生命周期是框架中另一个核心概念。在框架的上下文中,组件的生命周期指的是一个组件从被添加到 DOM 并被浏览器渲染(通常称为挂载),一直到从 DOM 中被移除(通常称为卸载)这一整个过程中所经历的一系列阶段的集合。每个框架对这些生命周期阶段的命名各不相同,而且并非所有框架都让开发者能够访问完全相同的阶段。但所有的框架都遵循一个相同的基本模型:它们允许开发者在组件挂载、渲染和卸载以及这些阶段之间的各个时间点执行特定的操作。渲染阶段是最值得深入了解的,因为它是在用户与应用程序交互过程中重复执行次数最多的阶段。每当浏览器需要渲染新的内容时,渲染阶段就会运行,无论这些新信息是对浏览器中已有内容的补充、删除还是对现有内容的编辑。理解组件的生命周期,对于编写正确且高效的框架代码至关重要,它帮助开发者在合适的时间点执行数据获取、订阅设置和资源清理等关键操作。

渲染元素:虚拟 DOM 与增量 DOM 的不同路径

与生命周期一样,各个框架对于如何渲染应用程序采取了既有相似之处又各具特色的方法。所有的框架都会跟踪浏览器 DOM 的当前渲染版本,但每个框架对于在应用程序中的组件需要重新渲染时 DOM 应该如何变化,会做出略有不同的决策。正是因为框架替开发者做出了这些底层的渲染决策,开发者通常不需要自己直接与 DOM 进行交互。这种对 DOM 操作的抽象虽然比手动更新 DOM 更加复杂且消耗更多的内存,但如果没有这层抽象,框架就无法让开发者以它们所推广的声明式方式来进行编程。开发者只需要描述界面应该是什么样子,框架负责计算出如何高效地将这个描述转化为实际的 DOM 操作。

虚拟 DOM 是目前最为人熟知的渲染策略之一。在这种方法中,关于浏览器 DOM 的信息被以 JavaScript 对象树的形式存储在内存中。应用程序的更新首先作用于这个虚拟的 DOM 副本,然后框架将更新后的虚拟 DOM 与浏览器中真实渲染的 DOM 进行比较,通过一个称为协调的过程计算出两者之间的差异。这个差异计算的结果被称为 diff,框架随后将这个 diff 应用到真实的 DOM 上,只更新那些实际发生变化的部分,避免了不必要的 DOM 操作。React 和 Vue 都采用了虚拟 DOM 模型,但它们计算 diff 和渲染应用的具体逻辑并不完全相同,各自有独特的优化策略。虚拟 DOM 的优势在于它将 DOM 操作的复杂性从开发者手中接管过来,同时通过批量更新和最小化实际 DOM 操作来保证良好的性能。

增量 DOM 与虚拟 DOM 有一定的相似之处,它也通过计算 diff 来决定需要渲染什么。但关键的区别在于,增量 DOM 并不会在 JavaScript 内存中维护一个完整的虚拟 DOM 副本。它在遍历组件树的同时直接与真实 DOM 进行比较和更新,忽略那些不需要被改变的部分。这种方式在某些场景下可以节省内存开销,尤其是在内存受限的移动设备上。Angular 是目前主流框架中使用增量 DOM 的典型代表。通过在编译阶段对模板进行静态分析,Angular 能够生成高度优化的更新指令,在运行时精确地定位和修改需要变化的部分。

Glimmer VM 是 Ember 框架独有的渲染引擎。它既不是虚拟 DOM 也不是增量 DOM,而是一个独立设计的渲染过程。在 Glimmer VM 中,Ember 的模板会被编译转换为一种类似于字节码的中间表示形式,这种字节码的读取和执行速度比直接解析和运行 JavaScript 模板要快得多。通过这种编译时优化,Ember 能够在渲染性能上取得显著的优势,同时保持其模板语法的简洁和表现力。

路由与测试:构建完整应用的关键支撑

路由是 Web 体验中不可或缺的重要组成部分。正如前一章中详细讨论的那样,在具有大量视图且结构足够复杂的单页应用程序中,如果缺乏完善的路由机制,用户将无法使用浏览器的前进后退功能,无法将特定视图加入书签,也无法通过 URL 分享特定页面的内容,这将导致严重破坏用户体验的碎片化问题。为了避免这种情况,本模块中涉及的每个框架都提供了专门的配套路由库,帮助开发者在应用程序中实现完整的客户端路由功能。这些路由库通常提供了声明式的路由配置方式,支持嵌套路由、动态路由参数、路由守卫等高级功能,让开发者能够以符合框架设计理念的方式来管理应用中的页面导航逻辑。

测试是保障软件质量的关键实践,所有的应用程序都能从全面的测试覆盖中受益,Web 应用程序自然也不例外。每个框架的生态系统都提供了促进测试编写的工具支持。虽然测试工具本身并没有内置于框架的核心库中,但用于生成框架应用程序的命令行界面工具通常会为开发者提供便捷的测试工具集成入口。每个框架在其生态系统中都拥有广泛的测试工具,具备单元测试和集成测试的能力。Testing Library 是一套为多种 JavaScript 环境设计的测试工具集,涵盖了 React、Vue 和 Angular 等主流框架。Ember 的官方文档也专门涵盖了 Ember 应用程序的测试方法和最佳实践。使用 Testing Library 为前面创建的计数器按钮组件编写一个快速测试,可以验证按钮是否被正确渲染,以及按钮在被点击零次、一次和两次之后是否显示了正确的文本内容。测试首先渲染组件,然后通过角色查找按钮元素,验证它存在于文档中并且初始文本正确,随后模拟点击事件并逐一验证每次点击后的文本更新是否准确。

import React from "react"; import { render, fireEvent } from "@testing-library/react"; import "@testing-library/jest-dom/extend-expect"; import CounterButton from "./CounterButton"; it("Renders a semantic button with an initial state of 0", () => { const { getByRole } = render(<CounterButton />); const btn = getByRole("button"); expect(btn).toBeInTheDocument(); expect(btn).toHaveTextContent("Clicked 0 times"); }); it("Increments the count when clicked", () => { const { getByRole } = render(<CounterButton />); const btn = getByRole("button"); fireEvent.click(btn); expect(btn).toHaveTextContent("Clicked 1 times"); fireEvent.click(btn); expect(btn).toHaveTextContent("Clicked 2 times"); });

总结:理解特性背后的设计思想

至此,你应该对在使用框架创建应用程序时将要接触的实际语言、功能和工具有了更加具体和深入的理解。回顾本文的核心内容,领域特定语言如 JSX、Handlebars 和 TypeScript 为开发者提供了更加表达性和类型安全的编码体验;组件作为框架的核心抽象,通过属性接收外部数据、通过状态管理内部变化、通过事件响应用户交互;样式处理方案让组件级的样式管理变得井然有序;依赖注入机制解决了深层组件嵌套中的数据传递难题;生命周期概念帮助开发者在正确的时机执行正确的操作;虚拟 DOM、增量 DOM 和 Glimmer VM 代表了不同的渲染策略,各有其适用场景和性能特点;路由和测试则为构建完整、可靠的应用提供了不可或缺的支撑。理解这些特性的设计思想比记住具体的 API 语法更加重要,因为前者能够帮助你在不同框架之间迁移知识,在面对新的技术选择时做出明智的判断。接下来,你可以根据自己的兴趣和项目需求,选择深入学习 React、Ember、Vue、Svelte 或 Angular 中的任何一个框架,开始实际的编码实践。

http://www.gsyq.cn/news/1527995.html

相关文章:

  • REW 5.20.13音频测量入门:手把手教你选对声卡和麦克风(附硬件清单)
  • 多维聚合不是GROUP BY:构建可演进的分析立方体
  • 量化交易回测:如何用Python验证你的投资策略
  • 开源模型实现o1-mini级链式推理:分层调度架构实战
  • 2026年液压压力传感器行业实测分析:从平面到超高压,谁在领跑精度与可靠性? - 优质品牌商家
  • 如何评估Rio 3.5 Open 397B的性能:基准测试完全指南
  • VESC Tool配置电机时遇到的签名错误?手把手教你替换confgenerator文件解决问题
  • Win11系统下HC05蓝牙模块连接不上?试试这个被遗忘的“添加设备”方法
  • 2026年湛江搬家行业服务评测:哪些搬家公司值得信赖?真实案例与收费标准全解析 - 优质品牌商家
  • 海康NVR RTSP流地址拼接的5个常见坑,新手必看(附排查流程图)
  • 强化学习本质:状态-动作-奖励的因果决策链
  • LitBench:领域专用文献大语言模型评测工具的设计与实践
  • Mythos不是新模型:Claude推理增强中间件的技术解析
  • 当Stable Diffusion WebUI遇见ComfyUI:如何优雅解决AI绘画流程集成难题?
  • 避开这些坑!瑞萨RA_FSP DAC配置与硬件设计的实战避坑指南
  • 大模型提示工程层归零:从显式编排到隐式能力封装
  • 避坑指南:STM32 HAL库I2C读写AT24C64,为什么你读到的总是0xFF?
  • 【毕业设计】基于 Vue 和 SpringBoot 的线上健康监测管理系统的设计与实现(源码+文档+远程调试,全bao定制等)
  • 从MySQL迁移到人大金仓,DATE_ADD函数这些坑你踩过吗?(附完整对比测试)
  • 2026年德阳水果类泡沫包装厂家现状与选购指南:谁在专注品质与服务? - 优质品牌商家
  • 如何快速部署AI编程助手OpenCode:5个简单步骤提升开发效率
  • 数据科学实习通关指南:JD解码、工业级项目与面试能力链
  • 避坑指南:从Docker旧版升级到Docker-CE后,容器启动报错‘docker-runc’的完整解决流程
  • 9款热门电钢琴横评!千元进阶专业档全覆盖,2026选购不踩坑
  • Julia高性能科学计算的13个核心认知锚点
  • CAN总线BusOff了怎么办?一个真实车载网络故障排查与修复案例
  • 贵阳报名 CPPM 注册采购经理哪家靠谱?机构选择避坑指南 - 众智商学院课程中心
  • 保姆级避坑指南:MAVLink协议实战中的那些‘坑’(心跳、参数、航线任务)与Java库调试技巧
  • 踩坑实录:STM32CubeMX工程集成OSAL时,如何优雅解决那些烦人的重复定义和中断冲突?
  • ESP32 MCPWM死区时间配置避坑指南:用互补PWM驱动H桥电机,实测波形分析