Meta-前端开发笔记-全-
Meta 前端开发笔记(全)
1:React 基础课程简介
在本节课中,我们将要学习 React 基础课程的整体介绍,了解课程的核心目标与主要内容。
欢迎来到 React 基础课程。本课程将向你介绍使用 React 的基础知识。在本模块中,你将学习如何熟悉 React 库的基本结构和使用方法,探索状态和状态管理的概念与实际应用,并了解如何在你的 React 应用中设置导航和使用资源。
构建用户界面
上一节我们介绍了课程的整体目标,本节中我们来看看 React 的核心应用场景。
你将首先学习如何使用 React 在网站前端构建现代用户界面(UI)。


这涉及到用户界面(UI)中独立部分的概念。这些独立的 UI 部分通常被称为组件。你将在本模块的后面更详细地探索组件。目前,你只需要知道每个网站的 UI 都是建立在组件和可组合性的基础之上。

简单的组件组合成更复杂的组件,最终合并形成一个网站。因此,一个网站本质上可以被看作是一个高度复杂的组件。


网站 UI 中的组件并不局限于 React。但 React 如此受欢迎的部分原因在于它简化了构建和组合组件的过程。React 高效地做到了这一点,并且不会对你的浏览器资源产生重大影响。
理解与应用状态
了解了组件是构建 UI 的基础后,接下来我们将探讨 React 应用中的另一个核心概念。

本课程将讨论的另一个主要主题是在 React 应用中处理状态。随着课程的深入,你将学到更多关于状态的知识。但现在,这里有一个关于状态的简单定义。

状态,简单来说,就是你的应用在任意给定时间点正在处理的所有变量的所有值。用公式可以表示为:
应用状态 = { 变量1: 值1, 变量2: 值2, ... }
课程涵盖的其他主题
除了组件和状态,本课程还将涵盖一系列构建完整应用所需的关键技能。
随着课程的进展,你还将学习如何为你的 React 应用添加样式,这包括复用通用样式;设置你的应用以响应诸如点击和用户提交数据等事件;创建导航路由和使用资源。

以下是本课程将涵盖的主要技能点列表:
- 为 React 应用添加样式。
- 处理用户交互事件。
- 创建应用内的导航。
- 在应用中使用图片等资源。
课程项目与实践
最后,你将通过一个实践项目来巩固所学知识。
你将以一个作品集项目结束本课程,在那里你将应用你新学到的知识。


我希望你和我一样对开始这门 React 基础课程感到兴奋。让我们开始吧。
本节课中我们一起学习了 React 基础课程的概览,明确了我们将要学习的核心内容包括:使用组件构建用户界面、理解和应用状态管理、样式设计、事件处理、导航与资源使用,并最终通过一个项目来实践所有技能。
2:React 在现实世界中的应用 🚀
在本节课中,我们将了解 React 在大型科技公司(如 Meta)中的实际应用,探索其优势、社区生态以及如何从零开始参与其中。
概述
React 是一个用于构建用户界面的流行 JavaScript 库。它不仅被众多知名公司采用,还拥有一个活跃的开源社区。本节内容基于 Meta 工程师的分享,将揭示 React 在重写 Facebook.com 等大型项目中的关键作用,并说明其为何能成为现代 Web 开发的首选工具之一。
React 在 Meta 的应用
我的团队使用 React,并且我们大量使用了 React 团队正在构建的许多新的实验性功能。Facebook.com 就像是 React 工程师正在开发的所有新酷功能的展示平台。当我们重写网站时,实际上使用了许多尚未发布的功能,并尝试了 React 团队正在构建的许多新事物,能够率先使用这些功能非常酷。
我的名字是 Katie,我是 Meta React 应用团队的软件工程师。我们致力于为 Facebook.com 构建各种新功能。
React 的广泛应用
有许多基于 React 的应用你可能以前就使用过。Facebook 和 Instagram 是两个例子,但 Netflix、Airbnb、纽约时报等众多公司也在其网站中使用 React,因此你很可能已经接触过它了。
那些倾向于拥有高度交互式用户界面的网站,更有可能使用像 React 这样的技术。
重写 Facebook.com 的动机
在大约使用了十年之后,Facebook.com 变得性能不佳且外观过时。我们已经从 Facebook.com 中榨取了所有可能的性能优势。因此,当时唯一的选择是在一个不同的技术栈上重写它,这个新栈将更快且更易于构建。
基本上,当时需要一个非常快速和响应迅速的用户界面,而 React 满足了所有这些需求。这就像是一种思维方式的转变,我们开始以不同的方式思考如何构建 Web 应用。
重写过程与团队协作
几年前,我们实际上重写了我们的网站,使其完全基于 React。因此,在 Meta 为 Facebook.com 编写的每位工程师基本上都在使用 React。
我经历了从项目中期到最终发布的整个过程。很酷的一点是,部分代码已经写好,我们正准备将其展示给真实用户。我们团队大约有 40 名工程师都在致力于构建这个重新设计的 Facebook.com 的最小可行产品。我们会与各种产品团队合作,让他们也在我们的新技术栈上重写他们的产品。

这是一项非常巨大的努力,也是一个非常大的风险。并非所有我们交谈过的团队都认为我们可以发布这个新版本的 Facebook.com。因此,我们必须真正证明它更快,显然看起来更现代,但我们也必须证明它确实能帮助这些产品团队在 Web 上交付他们所能做到的最佳版本的产品。
React 的开源生态与社区
React 是开源的,这意味着任何人都可以为其做出贡献。那些工程师全职从事 React 工作,但我们也有 Meta 内部的开发者在为 React 做贡献。这意味着 React 周围有一个非常强大的社区,很多人对基于它进行构建和改进感到非常兴奋。
因此,如果你有任何问题,或者对某个功能、优化或改进有想法,实际上有一个很好的机会可以直接为 React 库本身做出贡献。Meta 有一个 React 团队,这个团队由全职从事 React 工作的工程师组成,但 Meta 外部也有一群开发者在为 React 做贡献。
我认为它的开源部分非常酷,正是因为 React 周围有这样一个社区。它不断被更新和维护,总会有人可以回答问题或更新文档,它非常与时俱进。
给初学者的建议

如果你是第一次使用 React,一定要看看我们的官方文档。网上有很多关于 React 的讨论,我们每年都会举办 React 大会,在会上讨论 React 的新功能,你可以与许多 React 开发者建立联系。
因此,有很多机会可以参与到 React 社区中。我认为,如果你也能与社区建立联系,那么使用 React 进行构建会更有收获。
总结

本节课中,我们一起学习了 React 在现实世界,尤其是在 Meta 这样的大型公司中的关键应用。我们了解到 React 如何驱动像 Facebook.com 这样的大型重写项目,其高性能和组件化特性如何满足现代 Web 应用的需求。同时,我们也看到了 React 强大的开源社区和丰富的学习资源,这为开发者提供了持续学习和贡献的绝佳平台。掌握 React 不仅能帮助你构建交互式界面,还能让你融入一个充满活力的技术生态。
3:为什么选择React 🎯
在本节课中,我们将探讨React在技术领域被广泛采用的原因,并了解它为何成为构建现代用户界面的强大工具。我们将从开发者的实际经验出发,分析React的核心优势、适用场景以及它如何解决前端开发中的关键问题。
无处不在的React
React在技术世界中无处不在,在你的职业生涯中你将有机会持续使用它。因此,掌握React是一项非常有价值的技能。

我的名字是凯蒂,我是Meta公司React应用团队的一名软件工程师。我们的工作是使用React为facebook.com构建新功能。
我第一次接触React是在大学的一个高级设计项目中。当时我们正在为一个正在构建的应用程序寻找一个优秀的客户端库,而React似乎是最容易使用和构建速度最快的选择。

那时我已经获得了Meta的工作,但我并不知道我将在Meta从事什么工作。因此,能够提前获得使用React的经验是很酷的。我意识到我真的很喜欢用它工作,并且后来在日常工作中也能继续使用它。
从面向对象到组合思想
在学校里,你往往会做很多使用继承的面向对象编程。而React完全不使用那种方式,它使用一种叫做组合的概念。
起初,这种思维方式有点难以理解。但React拥有大量的文档和强大的社区,因此很容易上手,并且你可以从许多不同的地方获得支持。你可以去YouTube看视频、阅读文档,或者查看其他使用React的开源项目。有如此多的资源可供利用。
我认为Meta之外的开发者选择使用React的原因与我们在Meta内部的原因相同:它超级容易学习和上手。
构建高度自定义的UI
使用React,你能够创建真正自定义的用户界面。我们每天都在facebook.com上构建许多不同的功能,这些功能需要大量的自定义组件和UI。因此,拥有React提供的灵活性是非常好的。


如果你的用户界面非常自定义,并且你希望在选择为应用或网站集成的其他库方面拥有很大的灵活性,那么React将是最佳选择。
React只是一个前端库,你还需要与其他第三方库进行交互。如果你希望拥有与Redux或其他第三方库集成的灵活性,React是你的完美选择。
卓越的代码复用性
如果你的UI很复杂,并且希望在许多不同的页面中复用代码,React也非常出色。因此,如果你正在构建一个拥有大量功能、跨越众多不同页面的复杂网站,我认为它是一个绝佳的选择。
我认为React很好地解决了代码复用性的问题。在Meta,我们有一套核心的UI组件,实际上可以在整个网站中复用。这意味着我们能够为这些核心UI组件构建很多功能,比如无障碍访问。
我认为我们能够在整个网站保持高质量标准,正是因为我们到处复用组件,并且我们甚至能够在Facebook、Instagram和Messenger之间共享代码。我认为这是React一个非常独特的部分。
这真的很有趣,因为如果我在一个核心UI组件中发现了一个bug并修复了它,我不仅为我的一个用例修复了它,而是为整个网站的所有用例修复了它。所以我认为这是一种非常聪明的利用工程努力的方式:你修复一次bug,就解决了所有地方的问题。
React vs. Angular
可以说,React最大的竞争对手是Angular。Angular与React的不同之处在于,它是一个用于应用或网站的完整解决方案。

使用Angular时,你基本上不需要集成第三方库。而React只是一个客户端库,所以你需要自己解决如何进行路由和服务器-客户端通信。
我认为React在创建这些复杂的自定义UI方面提供了更多的灵活性,而Angular则通过开箱即用的解决方案,让创建单页面Web应用变得更加容易。
给初学者的建议
通过实践来学习。尝试制作一个示例应用,但不要一开始就去尝试创建你听说过的最复杂的应用。我建议从简单的开始,确保你使用了最佳实践。
持续参考文档,检查你是否以正确的方式使用Hooks等等。学习过程应该会相当顺利。我认为React的一大好处就是它非常容易上手和学习。
React有很多不同的部分和概念需要学习,但最终这一切都非常值得,因为你将在日常工作中(希望在你的整个职业生涯中)持续使用React,并且有一个庞大的社区在你身后,随时准备帮助你解答关于React的问题。
总结

本节课中,我们一起学习了选择React作为前端开发库的核心原因。我们了解到React因其易学性、灵活性、强大的社区支持和卓越的代码复用能力而受到广泛青睐。它特别适合构建需要高度自定义UI和复杂交互的现代Web应用。通过组合而非继承的思想,以及丰富的生态系统,React帮助开发者高效地构建和维护高质量的应用程序。对于初学者而言,从简单的项目开始,遵循最佳实践,并充分利用丰富的学习资源,是掌握React的有效途径。
4:3:React.js 概述 🚀
在本节课中,我们将要学习 React.js 的基础知识。我们将探讨单页应用的概念,并深入了解构成 React 核心的组件化架构和虚拟 DOM。这些概念是理解现代前端开发如何构建高效、可维护用户界面的关键。
单页应用(SPA)概念
上一节我们介绍了现代前端开发的背景,本节中我们来看看一种流行的网站构建方式:单页应用。


单页应用(Single Page Application,简称 SPA)是一种网站,其页面内容会根据用户交互而动态变化,而无需重新加载整个页面。这与传统网站不同,传统网站中用户每次导航到新页面都需要加载完整的 HTML 文档。
SPA 避免了频繁的页面重载,这被认为是更高效的,因为一些重复的内容(如 Logo、导航栏和页脚)无需每次都重新加载。

传统多页应用:每次导航都加载新页面

单页应用:仅动态更新部分内容
构建 SPA 最流行的方法之一是使用 Meta 开发的 React 库。React 被用于运行世界上许多最受欢迎的网站。
在这些网站上,你可能会在输入框中键入搜索词,然后网站返回相关内容或结果。你可能会注意到,即使内容已更新,网站的 URL 也可能不会改变。由于 SPA 只按需加载内容,它们对于需要提供丰富用户界面、速度、可扩展性和灵活性的企业和网络应用来说是理想的选择。


SPA 交互示例:搜索


SPA 交互示例:内容更新
React 核心概念:组件化架构
作为一名有抱负的开发者,你可能会觉得在使用 React 时需要理解许多新概念和流程。但请放心,你将有机会熟悉并使用它们。
在 React 中,一切构建的基础是组件。


当 Meta 发布 React 库时,它引入了一种称为组件化架构的设计理念。这本质上是一种基于可重用代码组件来构建软件的设计哲学。


以下是组件化架构的关键特点:

- 每个组件都包含定义明确的功能。
- 组件可以插入到应用程序中,而无需修改其他组件。


独立组件


组合成应用
因为组件是可重用的,它们可以被多次使用,并轻松插入到任何需要的地方。

组件复用
这导致组件可以存在于同一空间内,却能彼此独立交互。使用组件进行开发的优势之一是,许多开发者可以在同一个项目上工作,而不会干扰其他开发者的代码。



团队协作开发组件


组件组合

最终应用

组件:UI 的构建块
你可能还记得,现代前端 Web 开发围绕着创建用户界面(UI)的独立部分这一概念。在 React 中,这些独立的部分就是使用组件创建的,组件构成了所有 UI 设计的基础。

重要的是要明白,所有 UI 都是由简单的组件组成的,这些组件可以组合成更复杂的组件。事实上,你可以将整个网站视为一个组件的集合。
例如,考虑一个电子商务网站的产品结账页面。该页面由三个部分组成:页眉、支付区域和侧边栏。
以下是这些部分的组件分解:
- 页眉组件:包含公司 Logo、导航菜单和查看购物车的按钮。
- 支付组件:包含一个表单,用户在其中输入支付信息。
- 侧边栏组件:包含订单摘要信息。


电子商务页面组件分解
由于组件是自包含的,它们拥有自己的 HTML、CSS 和实现功能的 JavaScript 逻辑。例如,支付组件可能有一个 JavaScript 函数,在按钮被点击时提交支付。


组件包含结构、样式与逻辑
虚拟 DOM:高效的更新机制
需要知道的是,在网站 UI 设计中使用组件并不局限于 React。许多网站的前端或 UI 都建立在组件和可组合性的基础之上。但 React 是一个强大的工具,可以简化构建和组合组件的过程。
它高效地执行这些操作,因为组件被渲染到 DOM 时,不会显著影响浏览器的资源。这被称为组件渲染。
在深入渲染之前,我们需要理解 React 如何管理对实际浏览器 DOM 的更新。你可能还记得,DOM 是一个表示 HTML 文档的逻辑树状结构,它使用节点来描述文档的各个部分。


文档对象模型(DOM)
在 React 出现之前,你仍然可以构建组件和样式布局,但这涉及更复杂的 DOM 操作和代码,使得布局更复杂、更难处理。这导致了所谓的“面条式代码”——开发者用来描述那些复杂、混乱且难以理解的代码的术语。

React 通过避免直接操作真实 DOM 来防止这种面条式代码。

“面条式代码”与清晰组件代码的对比
相反,React 提供了一种称为虚拟 DOM 的机制。

你可能还记得,这是真实 DOM 的一个内存中的表示或克隆。React 使用虚拟 DOM 来最小化对真实 DOM 的更新,只在需要时才更新浏览器 DOM。

其工作流程可以概括为以下步骤:

- 状态改变时,React 在虚拟 DOM 上重新渲染 UI。
- React 将新的虚拟 DOM 与之前的快照进行比较(这个过程称为 “Diffing”)。
- React 计算出需要更新的最小变更集。
- React 仅将这些变更应用到真实浏览器 DOM 上。

虚拟 DOM 更新流程
这确保了更新尽可能最小化,从而提高了应用程序的速度和性能。


高效更新带来的性能优势

总结
本节课中我们一起学习了 React.js 的核心基础。我们首先回顾了单页应用(SPA)的概念及其优势。然后,我们深入探讨了 React 的基石——组件化架构,了解了组件如何作为可重用、独立的构建块来创建用户界面。最后,我们介绍了虚拟 DOM 的概念,理解了 React 如何通过这种内存中的表示来高效、最小化地更新真实浏览器 DOM,从而提升应用性能。这些概念为后续深入学习 React 开发奠定了坚实的基础。
5:函数组件简介 🧩
概述
在本节课中,我们将要学习React架构中的一个核心概念:函数组件。我们将了解什么是函数组件、它与JavaScript函数的关系,以及如何使用JSX语法来创建动态的UI内容。

函数组件与JavaScript函数
回想一下你在JavaScript中学到的函数知识。函数是可重用的代码块,可以接收输入,执行某些过程或计算,然后返回输出。React组件的行为与传统的JavaScript函数非常相似。

React组件类型
React提供了两种类型的组件:函数组件和类组件。它们在React中的行为与JavaScript中的传统函数和类非常相似。
现在不必担心类组件,你将在以后学到更多相关知识。😊
我们只关注函数组件,它的行为类似于JavaScript函数。
根组件与渲染
在默认的React应用程序中,只有一个组件被渲染,那就是位于src文件夹内index.js文件中的App组件。需要知道的是,每个React应用必须包含至少一个组件,它被称为根组件。

这个组件通过import语句加载。你将在以后学到更多关于import语句和React的知识。现在只需知道,它用于导入React工作所需的代码,例如import React和import ReactDOM语句。


渲染组件的语法与HTML中的自闭合标签非常相似。你只需将组件名称放在左右尖括号内,并且不要忘记正斜杠。
语法示例:
<ComponentName />

根组件可以包含开发者创建的其他组件,这些组件代表应用程序的各个UI部分,就像你之前学到的电子商务示例一样。

回想一下,这个组件最终会被转换成一个DOM片段,并作为ID为root的HTML div元素的子元素插入到现有的DOM中。

然后,这个div元素被渲染到浏览器中。
分析App组件
如果你分析App组件,你会注意到它看起来非常像一个内部包含一些HTML代码的JavaScript函数。
你可能还会注意到一个export default语句。你很快就会学到更多关于它的知识。现在只需知道你需要它来使你的组件可用。
创建函数组件
既然你已经熟悉了函数组件的概念,让我们来探索Web开发者如何在React中创建它们。
React使用一种特殊的语法进行脚本编写,称为JavaScript XML,简称JSX。对于许多React开发者来说,这被称为JavaScript的语法扩展。
那么JSX语法是什么样的呢?让我们回到默认的React App组件来了解一下。
回想一下,在App函数的return语句中,似乎返回了一些HTML内容。实际上,这些内容并不完全是HTML,它是JSX。
JSX语法看起来非常像HTML,它的优点之一是允许你在看起来像HTML元素的内部编写JavaScript代码。
事实上,你可以把JSX看作是自定义HTML和JavaScript的结合。😊
这允许你使你的网站变得动态。你将在以后学到更多关于HTML和JSX之间的区别。现在只需知道,你可以将这种语法放在函数组件的return语句中。
同样重要的是要知道,React组件在被用作JSX元素之前不会渲染,就像JavaScript函数声明在被调用或执行之前不会运行一样。

创建包含JSX的组件
现在你知道了什么是JSX,让我们探索创建一个React组件的步骤,该组件将在<h1> HTML元素中包含一些JSX代码,以在网页上显示一些文本。

以下是创建步骤:
首先,你创建组件,它基本上就是一个JavaScript文件。由于它的目的是返回一些标题文本,你将文件命名为Heading.js。注意组件名称的首字母是大写的。
这是因为React对待大写和非大写组件名称的方式不同,所以记住React中所有组件名称必须大写非常重要。
为什么这样呢?因为React将小写组件视为常规的HTML元素。大写组件名称有助于React区分JSX元素和HTML元素。
现在让我们继续我们的组件。
接下来,在App.js文件中,创建一个名为Heading的函数。函数名的第一个字母也必须用大写字母声明。
然后,在函数体内,创建一个名为title的变量,并为其分配字符串值"这是一些标题文本"。
现在你准备好创建函数的return语句了。在return的括号内,插入一个<h1>标签,并在其中放置名为title的变量。
为了让React评估title变量,你需要将它放在花括号{}内。如果你不使用花括号,你会得到单词"title",而不是"这是一些标题文本"。
在这一点上,值得记住的是,虽然你正在创建类似HTML的语法,但实际上你是在一个JavaScript文件中进行编码。
正因为如此,你可以在JSX代码中输出一个变量,这是你在编写静态HTML时无法做到的。
整体语法指示React渲染<h1> HTML元素,并显示存储在名为title的变量中的任何文本值。
这种渲染在幕后发生,是因为一个叫做转译的过程。
你可以把转译看作是将JSX转换为HTML的过程。你将在本视频的后面学到更多关于这方面的知识。
总结

在本节课中,我们一起学习了函数组件以及如何在React中创建它们。我们还学习了JSX,它就像是HTML、CSS和JavaScript的组合,你可以在函数组件中使用它来生成动态内容。

最后,我们探讨了渲染和转译的概念。如果你想更详细地了解这些概念,本课末尾有一个附加阅读材料的链接。😊
6:创建React组件 🧩
在本节课中,我们将深入学习React中的组件概念,并从头开始创建一个组件。你将了解export语句的概念,以及如何使用组件来创建可复用的代码块。
概述
我们将通过一个简单的例子,演示如何在React应用中创建和渲染一个功能组件。首先,我们会初始化一个新的React应用,然后清理默认代码,最后创建并组合两个组件。
创建React应用
首先,我打开了VS Code,并在内置终端中打开了目标文件夹。为了创建一个新的React应用,我执行了以下命令:
npx create-react-app .

这个命令中的点号(.)指示VS Code在当前文件夹中运行此命令。换句话说,我使用create-react-app在当前文件夹内为我构建一个新的应用。按下回车键执行命令。

等待应用构建完成。这个过程可能需要几分钟。构建完成后,我可以通过输入以下命令来启动应用:

npm start

很好,我的React应用已经启动,并在浏览器中加载了localhost:3000,这是本地服务器。现在,在VS Code的左侧窗格中,我看到了所有生成的文件和文件夹,例如node_modules、public、src以及package.json等文件。你将在后续课程中了解更多关于这些文件的信息。目前,我们只需要在src文件夹中工作,因此不必担心其他文件和文件夹。
清理初始代码
为了专注于学习如何构建组件,我们需要一个干净的起点。最简单的方法是移除app.js文件中函数内的所有代码。
以下是清理后的App组件代码:
function App() {
return (
<div>
</div>
);
}
export default App;
你可以说这是最简单的组件。我声明了一个App函数,并将其作为默认模块导出。


保存文件后,我注意到应用页面现在是空白的。
创建新组件
现在,让我创建另一个组件,它包含一些我想在浏览器中显示的文本。为此,我创建了一个名为Header的函数。
在函数体内,我返回一个包含问候文本的JSX元素。代码如下:
function Header() {
return (
<h1>Hello world</h1>
);
}
我的代码看起来不错,但屏幕仍然是白色的。这是因为我还未从App函数中渲染任何内容。
渲染组件
为了在页面上显示内容,我需要返回到App函数并调用Header函数。我使用JSX元素语法来渲染一个组件,即我的函数名。
在App函数的return语句中,我输入函数名Header,并用尖括号括起来,记得在右尖括号前添加斜杠。
function App() {
return (
<div>
<Header />
</div>
);
}
请注意,渲染组件的语法与HTML中的自闭合标签非常相似。按Ctrl+S(Mac上是Command+S)再次保存所有内容。
很好,我的代码现在可以工作了。我注意到浏览器中显示了一个带有“Hello world”文本的HTML标题。

组件复用性
恭喜!在这个视频中,你学会了如何创建一个功能组件。这个名为App的组件调用了另一个名为Header的组件,后者显示了一个带有文本的HTML标题。
目前,Header组件的代码与App组件存在于同一个文件中。为了使Header组件独立且可复用,我需要将其放置在自己的文件中。这样,我就可以在应用中的任何需要显示带文本的标题元素时,多次复用这个组件。你很快将学习如何做到这一点。


总结
在本节课中,我们一起学习了如何在React中创建功能组件并将其渲染到根组件App.js中。我们掌握了以下核心步骤:
- 使用
create-react-app初始化项目。 - 清理默认代码以获得干净的起点。
- 创建新的功能组件(如
Header)。 - 在主组件(
App)中使用JSX语法渲染子组件。

通过将组件分离到不同文件,我们可以提高代码的模块化和可复用性,这是构建复杂React应用的基础。
7:6_React项目结构 📁
在本节课中,我们将学习React项目的默认文件夹结构,了解各个文件和文件夹的作用,并探讨如何根据项目需求自定义结构。

概述
一个组织良好的项目结构对于React应用的开发至关重要。它能使组件和资源易于访问和维护。本节将带你了解使用create-react-app命令生成的默认项目结构,并解释其核心组成部分。
默认项目结构剖析
当你使用 npx create-react-app 命令创建一个新的React应用时,项目会自动生成一个特定的文件和文件夹结构。让我们来逐一探索。
核心文件夹
项目根目录下通常包含三个主要文件夹:node_modules、public 和 src。
1. node_modules 文件夹
你可以将这个文件夹视为你React应用中所有模块的仓库。当你安装特定的NPM包时,node_modules文件夹会自动添加。
- 作用:存放项目依赖的所有第三方包(库或模块)。
- 注意:你通常不需要手动修改此文件夹,它是项目正常运行所必需的。
2. public 文件夹
此文件夹包含将直接展示给用户的静态资源。
以下是public文件夹内常见的文件:
index.html:这是最重要的文件。React应用的内容会被注入到这个HTML文件body内的一个特定div元素中。应用的所有更新都会反映在这个div里。favicon.ico:显示在浏览器标签页上的网站图标。logo192.png/logo512.png:用于不同场景的应用图标。manifest.json:当你的React Web应用被安装到设备(如手机主屏幕)时,此文件提供应用的元数据(如名称、图标)。robots.txt:用于指导搜索引擎如何抓取你的网站页面。
3. src (Source) 文件夹
这是你作为React开发者将花费最多时间的文件夹。它包含了确保React应用运行所需的所有核心组件和逻辑文件。
以下是src文件夹内默认生成的一些关键文件:
index.js:这是整个src文件夹中最重要的文件。它导入并渲染React应用的根组件(通常是App组件),是应用的入口点。import React from 'react'; import ReactDOM from 'react-dom/client'; import './index.css'; import App from './App'; const root = ReactDOM.createRoot(document.getElementById('root')); root.render(<App />);App.js:这是应用的根组件。你将从这里开始构建你的应用界面。App.css:包含App.js组件的样式。index.css:包含应用于整个应用的全局样式。App.test.js/setupTests.js/reportWebVitals.js:这些文件与应用的测试和性能报告相关。
重要提示:除了index.js,src文件夹中的其他默认文件(如logo.svg、测试文件)都可以安全删除,只要你同时移除引用它们的代码。React对如何在src文件夹内组织文件没有强制要求,这给了开发者很大的灵活性。
根目录下的其他文件
除了上述文件夹,项目根目录下还有一些重要的配置文件。
以下是这些文件的说明:
.gitignore:用于版本控制(如Git),指定哪些文件或文件夹不应被提交到代码仓库(例如node_modules)。此文件并非React特有。README.md:一个Markdown文件,用于描述项目信息。在GitHub等代码托管平台上非常有用。package.json:列出了项目的信息、依赖项和可运行的脚本(如npm start、npm run build)。NPM依赖此文件来管理项目。{ "name": "my-react-app", "scripts": { "start": "react-scripts start", "build": "react-scripts build" }, "dependencies": { "react": "^18.2.0" } }package-lock.json:锁定所有依赖包的确切版本,确保在不同机器上重建项目时依赖版本一致。通常不应手动修改。

自定义项目结构
上一节我们介绍了默认结构,本节中我们来看看如何根据项目规模进行自定义。一个常见的做法是在src文件夹内创建新的子文件夹来组织代码。
以下是一种常见的自定义文件夹结构示例:
components/:存放可复用的UI组件(如Button.jsx、Header.jsx)。pages/或views/:存放与路由对应的页面级组件(如HomePage.js、AboutPage.js)。assets/:存放静态资源,如图片、字体、样式文件。utils/或helpers/:存放工具函数和辅助代码。hooks/:存放自定义的React Hooks。context/或store/:存放状态管理相关的代码(如React Context, Redux store)。services/或api/:存放与后端API交互的代码。
规划文件夹结构的好处:
- 提高可维护性:相关文件集中存放,易于查找和修改。
- 增强可读性:项目结构清晰,新团队成员能快速理解。
- 促进代码复用:将通用组件分离,便于在不同地方调用。
- 便于团队协作:明确的规范减少冲突和 confusion。
总结
本节课中我们一起学习了React项目的核心结构。我们首先剖析了由create-react-app生成的默认文件夹(node_modules, public, src)和根目录配置文件的作用。然后,我们探讨了如何根据实际需求在src文件夹内自定义结构,例如创建components、pages等子文件夹,并理解了提前规划文件夹结构对应用开发带来的诸多好处。记住,虽然React提供了灵活性,但一个清晰、一致的项目结构是构建可维护大型应用的基础。
8:导入组件 📦
在本节课中,我们将要学习 React 中组件化架构的一个核心优势,即如何通过模块化的方式组织和管理组件。我们将重点探讨模块的概念,以及如何使用 import 和 export 语句来实现组件间的通信与复用。
基于组件的架构优势之一,是你的应用被分割成独立的、自包含的组件。
正如你已经了解到的,这些组件可以用来构建基于可复用代码块的强大用户界面。

为了创建一个功能完整的 React 应用,你需要创建一系列组件。但是,当应用被分解成多个不同的组件时,你可能会想知道如何定位并将它们全部集成到你的应用中。在本视频中,你将学习模块的概念,以及如何通过将组件放入一个 components 文件夹来管理你的 React 组件。
最后,你将探索 import 和 export 语句的结构。作为一名开发者,你经常需要一种方法来使用和复用那些可能在其他地方定义或由他人创建的组件。例如,你是否还记得 JavaScript 中的模块概念?模块是独立的代码单元,你可以反复复用。独立性意味着你可以将它们添加到你的程序中,移除它们,或用其他模块替换它们,而一切仍能正常工作。在 React 中,你可以利用这个 JavaScript 特性,通过将组件放在它们自己的文件中来分离它们。然后,你可以使用 import 和 export 语句让这些文件彼此通信。
理解导出(Export)语句 📤
export 语句用于使一个模块对另一个模块可用。

将每个 JavaScript 文件视为一个模块会很有帮助。

为了让函数和变量对其他文件可用,你需要导出它们,这使得它们可以通过 JavaScript 中的 import 语句被访问。在 JavaScript 中,有两种类型的导出:默认导出和命名导出。
- 默认导出:当函数名与文件名相同时使用。
- 命名导出:当你希望函数名与文件名不同时使用。
此时,你可能想知道模块和组件之间有什么区别,因为它们本质上都是 JavaScript 文件。你的想法是对的。虽然它们有相似之处,但可以这样理解:组件是一个单一的部分或小块功能,比如一个按钮;而模块则可以看作是比单个组件更大的东西,比如一系列组件。
这种将代码分割成多个模块的技术被称为模块化编程,它补充了 React 基于组件的架构。
导入(Import)语句的应用 📥
为了帮助你更好地理解,让我们探索以下场景。


假设你是一名开发者,目前正在使用 React 构建一个应用程序,并且有几个组件需要被包含在应用中。
一些必需的组件已经由你的同事创建好了,因此你需要一种方法将它们导入到你的应用中。为此,你需要使用称为“导入”的操作。
在 React 中,你使用 import 语句将组件导入到你的应用程序中。
你可能已经在默认的 index.js 文件中注意到了 import 语句,在那里 App 组件被渲染。
在 React 中导入一个组件,你需要使用关键字 import,后跟你想要导入的组件名。

然后使用关键字 from 来指定组件所在的位置。

你需要在文件名前使用一个文件路径序列,例如一个点加斜杠(./),但文件扩展名不是必需的。
现在你了解了 import 和 export 语句的语法。
组织你的 React 组件结构 🗂️

让我们探索一下如何在 React 中构建你的组件结构。
请记住,组件本质上就是一个 JavaScript 文件。React 对于如何将文件放入文件夹没有严格的规定,但是,有一些常见的方法你可能需要考虑。
一种方法是将所有组件放在一个名为 components 的文件夹中。这允许你通过将相似的文件分组在一起来构建你的项目。
例如,假设你正在为一个电子商务应用构建一个支付页面,该页面包含三个部分,每个部分在 React 中都将由一个组件表示:
以下是这些组件的示例:
- 一个标题部分,使用一个名为
Header的组件。 - 一个支付部分,使用一个名为
Main的组件。 - 最后,一个侧边栏,使用一个名为
Sidebar的组件。
每个组件将被调用,并将其内容返回到我们应用的根组件,即 App.js。


在本视频中,你探索了模块的概念以及 import 和 export 语句的结构。
你还学习了如何通过将 React 组件放入一个 components 文件夹来管理它们。
9:组件 Props 原则
在本节课中,我们将要学习 React 中一个核心概念:Props。Props 是组件之间传递数据的主要方式,理解它们的工作原理对于构建动态和可复用的 React 应用至关重要。
概述:什么是 Props?
现在,您应该已经熟悉 React 中函数组件的概念。它们类似于 JavaScript 函数,是可复用的代码块。
回想一下,在 JavaScript 中,您可以通过声明参数来使函数更加灵活,这样在调用函数时就可以传入值作为参数。

在 React 中,您可以使用一种称为“属性”的特性来执行类似的操作,它被表示为 props。

在本视频中,您将了解 props 对象,以及开发者如何使用它在组件之间传递数据。然后,您将探索组件层次结构,并了解为什么组件被称为具有父子结构。
预备知识:JavaScript 对象
在开始学习 props 之前,让我们回顾一下另一个 JavaScript 功能,它将帮助您理解 props 的工作原理。它叫做 JavaScript 对象。
在 JavaScript 中,对象是一种特殊的变量,可以包含多个值。当您需要存储不同类型但相关的数据组时,可以使用对象,每种数据类型被称为对象的属性。
例如,假设您创建一个名为 fruit 的对象,它包含 type、quantity 和 colour 属性。请记住,这些属性由键值对组成,您可以使用点表示法访问对象的属性。
const fruit = {
type: 'apple',
quantity: 5,
colour: 'red'
};
console.log(fruit.type); // 输出:apple
在 React 中,您可以使用类似的技术,通过属性对象(简称 props)将数据从一个组件传递到另一个组件。
rops 的核心概念
Props 允许您将数据从一个组件传递到另一个组件。将 props 视为组件可以接受的参数会很有帮助,它们使用 JSX 语法传递,很像 HTML 属性。在函数内部,您使用关键字 props。


实践:传递和接收 Props
现在您已经熟悉了 props 的概念,让我们通过一个例子来探索如何向组件发送一些 props 并在 React 应用中打印它们。
假设您在 index.js 文件中打开了 React 应用的默认代码。您调用了 App 组件。在 App 组件中,您返回一个带有静态标题文本的 H1 标题。虽然这段代码可以工作,但您可以通过使用 props 使这个标题变得动态。
现在让我们探索创建此功能的语法。在根组件 index.js 中,您将要传递给 App 组件的值以 HTML 属性的形式作为参数发送。
接下来,在 App 组件中,您使用 props 对象来接受这个参数。为此,您需要在函数声明的括号内添加关键字 props。
最后,要访问此对象的属性,您使用点表示法来引用作为 HTML 属性参数传递的对象属性名称。
再次提醒,请务必将您的代码用花括号括起来,这样 React 就知道您要处理的是 props 对象,而不是静态文本。
以下是代码示例:
// 在 index.js 中(父组件)
ReactDOM.render(
<App title="我的动态应用" />,
document.getElementById('root')
);

// 在 App.js 中(子组件)
function App(props) {
return (
<h1>{props.title}</h1>
);
}

rops 的数据类型
因为 props 本质上是一个 JavaScript 对象,所以它可以接受多种数据类型,从简单的类型(如字符串和整数)到更复杂的类型(如函数、数组和对象)。

因此,props 允许开发者在创建和使用组件时具有更大的灵活性,特别是当您希望应用中的数据流是动态的时候。


虽然我们刚刚探索了一个动态打印标题的基本示例,但稍后您将有机会使用 props 练习更复杂的数据结构。

组件层次结构与数据流
现在您已经熟悉了 props 如何在组件之间发送数据。让我们更详细地探索这种数据流。
当两个组件相互通信时,发送 props 数据的组件称为父组件,接收 props 数据的组件称为子组件。
正如您从之前的例子中学到的,这种父子关系允许父组件使用 props 将数据向下传递给子组件。一个父组件也可以将相同的数据发送给多个子组件。

然而,重要的是要知道这种通信是单向数据流。

rops 的局限性

使用 props 无法从子组件向父组件进行通信。相反,开发者会使用其他方法。现在不用担心这个,您将在以后了解更多。

尽管 props 在 React 中是一个非常强大的工具,但它们确实有一些限制。例如,您刚刚了解到无法使用 props 将数据从子组件发送回父组件。
另一个重要的限制与所谓的纯函数有关。在编程中,纯函数是指对于传入的相同参数值,总是返回相同输出的函数。
现在不必过于担心纯函数,只需记住:在 React 中,当您使用 props 声明一个组件时,它绝不能修改自己的 props。




总结
在本节课中,我们一起学习了 props 如何用于向组件传递数据。
您发现 props 是一个特殊的 React 对象,其工作方式类似于 JavaScript 对象,并且可以通过点表示法访问其属性。
您还了解了开发者为什么使用 props,以使他们的应用更加动态和灵活。
最后,您探讨了使用 props 的一些限制:不能使用它们将数据发送回父组件,并且使用 props 的函数绝不能修改自己的 props。
10:在组件中使用Props 🧩
在本节课中,我们将学习如何在React组件中有效地使用Props。Props是组件之间传递数据的主要方式,它使得应用的数据流变得动态和灵活。我们将通过一个简单的博客布局示例,来探索如何定义、传递和使用Props。
概述
上一节我们介绍了组件的基本概念。本节中,我们来看看如何让组件之间“交流”数据。Props(属性)允许你将数据从一个组件(父组件)传递到另一个组件(子组件)。这使得组件可以复用,并能根据接收到的不同数据渲染不同的内容。
理解Props与属性
Props的传递方式类似于为HTML元素设置属性。要成功使用Props,你需要熟悉“属性”这一概念。理解属性的最佳方式是通过一个实际的例子来构建一个使用Props的组件。
实践:从父组件传递Props

假设我们有一个应用,包含App、Header、Main和Sidebar组件。App组件是父组件,其他三个是它的子组件。我们将从App组件向每个子组件传递数据。
以下是具体步骤:
-
在父组件中传递Props:在
App组件的JSX中,为子组件元素添加属性,这些属性就是传递给子组件的Props。// 在 App.js 的 return 语句中 return ( <div> <Header name="Anna" color="purple" /> <Main greet="Howdy" /> <Sidebar greet="Hi" /> </div> );这里,
Header组件接收了两个Props:name和color。Main和Sidebar组件各接收了一个greetProp。 -
在子组件中接收Props:子组件通过一个名为
props的参数来接收这些数据。这个props是一个对象,其属性名就是在父组件中定义的属性名。// 在 Header.js 中 function Header(props) { console.log(props); // 查看props对象的内容 return ( <header> <h1 style={{ color: props.color }}>Welcome, {props.name}!</h1> </header> ); }首先,我们通过
console.log(props)来验证接收到的数据。控制台会输出一个类似{name: “Anna”, color: “purple”}的对象。 -
在JSX中使用Props:要在JSX中显示Prop的值,必须用花括号
{}将其包裹起来,这样JavaScript表达式才会被求值。例如,{props.name}会被渲染为“Anna”。
更新所有子组件
按照同样的逻辑,我们可以更新Main和Sidebar组件来使用它们接收到的Props。
以下是更新后的组件代码:
// Main.js
function Main(props) {
return <main>{props.greet} from the Main section!</main>;
}
// Sidebar.js
function Sidebar(props) {
return <aside>{props.greet} from the Sidebar!</aside>;
}
保存所有文件后,应用会重新编译。现在,每个组件都根据从父组件App接收到的Props数据,动态地显示内容。
总结
本节课中我们一起学习了Props的核心用法。我们了解到,Props是父组件向子组件传递数据的只读对象。通过以下步骤使用它们:
- 在父组件的JSX中,像添加HTML属性一样为子组件标签添加Props。
- 在子组件函数中,通过
props参数接收数据。 - 在子组件的JSX中,使用
{props.propName}的语法来渲染数据。

你现在应该能够有效地演示如何在函数组件中传递和使用Props了。掌握Props是构建动态、可复用React组件的基础。在接下来的课程中,我们将探索更复杂的数据流和状态管理。
11:JSX 简介 🧩
在本节课中,我们将要深入学习 JSX 的核心概念。我们将探讨 JSX 如何让开发者在一个组件内混合使用 JavaScript、HTML 和 CSS,并了解其独特的语法规则。通过本教程,你将掌握 JSX 的基本用法和关键注意事项。



在课程的这个阶段,你已经接触了 JSX 的概念,并了解了它如何与组件交互。



本节中,我们将在组件和样式的背景下更详细地探讨 JSX。你还将学习如何在一个组件内混合使用 JavaScript、HTML 和 CSS。


那么,是什么让 JSX 如此特别?
一言以蔽之,是表现力。开发者可以使用一种表现力极强的语法来表达他们希望 React 渲染的内容,这种语法几乎与 HTML 或 XML 相同。



换句话说,JSX 允许开发者直接在 JavaScript 代码中编写 HTML。例如,你可以通过创建一个导航组件来为网站添加导航功能。

然后,在其中放置 HTML 语义化的 nav 元素和一个无序列表。
并且,因为这是 React,列表的值可以通过传递 props 来变得动态。




重要的是要知道,一个普通的 JavaScript 函数被用来定义 React 应如何渲染组件,无论它在哪里通过 Nav 这个 JSX 元素被引用。这正是 JSX 如此强大的原因,比如你可以插入特定的 JSX 表达式,如变量和 props。

这种方式之所以非常棒,是因为任何放在代码花括号 {} 内的内容本质上都是常规的 JavaScript 代码。例如,你可以使用 li(列表项)标签来计算一系列简单的表达式。这意味着你可以将花括号视为一个特殊区域,可以在其中编写任何你喜欢的 JavaScript 代码,而其余代码则写在 return 语句内部。
上一节我们介绍了 JSX 表达式,现在让我们聚焦于函数 return 语句内部的一些 JSX 规则。

return 语句可以被视为表达性语法的区域,它允许你按照希望在渲染的应用中呈现的样子来编写常规的 HTML 代码。但是,如果你需要返回跨越多行的 HTML 代码,则必须将其放在括号 () 内。





这允许开发者在常规 HTML 标签和用花括号 {} 表示的动态 JSX 表达式之间交替使用。


同样重要的是要记住,HTML 代码必须包裹在一个顶级元素中,例如 div 标签。如果你不想向 DOM 添加额外的 div 元素,可以使用一种叫做 Fragment 的东西来代替。
这就像一个没有标签名的 HTML 开闭标签。虽然你可以将 return 语句内的代码视为常规 HTML,但值得记住的是,你是在一个 JavaScript 文件中,因此存在一些差异。


例如,你不能在 HTML 元素中使用关键字 class 来处理 CSS 类。这是因为 class 是 JavaScript 中的保留关键字。你必须使用一个稍有不同的关键字 className 来代替。


同时请注意,className 采用驼峰命名法,即通过单个大写字母来分隔单词。


你可以使用 className 来列出任意数量的 CSS 类,以样式化组件内的特定 JSX 元素。


这就像你在常规 HTML 中使用 class 属性一样,并且这只是你可以为 React 应用添加样式的方式之一。你将在以后学习更多关于如何做到这一点的方法。目前,只需知道这是最接近你在处理 HTML 和 CSS 网站布局时会做的事情。
还存在一些其他差异,你将在以后学习它们。但在实际应用中,你可以认为这些代码几乎就是常规的 HTML。


在本节课中,我们一起深入探讨了 JSX 的概念、样式以及它在 React 中的使用。你现在进一步理解了如何使用 JSX 在一个组件内混合 JavaScript、HTML 和 CSS。做得好。
12:实际样式
在本节课中,我们将学习如何将外部CSS文件中的样式规则,转换为React组件内部的样式。我们将通过一个具体的例子,演示如何将CSS代码从外部文件移动到组件内部,并将其转换为JavaScript对象格式,以便在JSX中使用。
概述
在HTML文档中包含CSS有三种主要技术。第一种是内联样式,通过在HTML元素内部使用style属性实现。第二种是内部样式,通过在文档的<head>部分使用<style>元素实现。第三种是外部样式,通过使用<link>元素链接到外部的CSS文件实现。
上一节我们介绍了CSS的三种引入方式,本节中我们来看看如何在React组件内部实现类似“内部样式”的效果。

从外部样式到组件内部样式
在本视频示例中,我们将从一个名为index.css的外部文件中提取CSS样式规则,并将其添加到一个组件内部作为内部样式。这些样式规则随后可以在组件的return语句中被代码引用。

请注意,当前组件是由外部样式表index.css进行样式设置的。在本视频中,我将继续在一个由Header、Main和Sidebar组件组成的应用上工作。这次的重点是在Sidebar组件内部使用内部样式。
为了演示这个过程,我不需要移动index.css文件中的所有代码。相反,我将只关注与Sidebar组件相关的样式。

以下是具体的操作步骤:
-
提取相关CSS代码:首先,在
index.css文件中,选中应用于侧边栏(sidebar)的CSS代码,然后剪切它(在Windows上按Ctrl+X,在Mac上按Command+X)。保存文件后,你会注意到浏览器中显示的侧边栏组件失去了原有的样式。 -
将代码粘贴到组件文件:接下来,打开
Sidebar.js文件,将剪切的CSS代码粘贴到return语句之前。

-
将CSS转换为JavaScript对象:直接将CSS代码粘贴到JavaScript文件中不会生效。我们需要将其转换为JavaScript对象。为此,需要声明一个常量变量(例如
sidebarStyle)来存储样式对象。然后,对代码进行以下修改:- 将每个CSS声明末尾的分号(
;)替换为逗号(,)。 - 将CSS属性名中的连字符命名法(kebab-case,如
background-color)转换为小驼峰命名法(camelCase,如backgroundColor)。 - 因为CSS声明现在变成了对象属性,所以需要将它们的值用双引号(
")包裹起来,使其成为字符串。
- 将每个CSS声明末尾的分号(
-
在JSX中应用样式:最后,在组件的
return语句中,找到对应的HTML标签(例如<aside>),为其添加style属性,并通过JSX表达式将我们定义的样式对象赋值给它,代码格式为:style={sidebarStyle}。
完成上述步骤后,选择文件菜单中的“全部保存”来保存更改。此时,你会发现浏览器中的侧边栏组件恢复了编辑index.css文件之前的样式。这就是直接在组件内部使用内联样式的一个例子。
总结
本节课中我们一起学习了如何将外部CSS文件中的样式规则迁移到React组件内部。关键步骤包括提取CSS代码、将其转换为JavaScript对象格式(涉及命名法转换和语法调整),以及通过style属性在JSX中应用该样式对象。这种方法使得组件的样式可以更紧密地与组件逻辑封装在一起。


13:嵌入JSX表达式 🧩
在本节课中,我们将学习JSX的一个核心特性:嵌入表达式。我们将了解JSX如何允许开发者在HTML代码中直接使用JavaScript变量、函数调用和表达式,从而动态地构建React元素。
JSX基础回顾
上一节我们介绍了JSX是JavaScript的语法扩展,它允许开发者在React组件代码中编写HTML。本节中,我们来看看JSX如何将HTML代码自动转换为React元素。
一个基本的JSX示例如下,它会在网页上输出“Hello world”文本:
const result = <p>Hello world</p>;
这段代码将一个包含短语“Hello world”的段落HTML元素,赋值给名为result的常量变量。当此JSX代码执行时,result变量将包含一个可以插入到网页中的React元素。这是JSX的关键特性之一:自动从HTML代码构建React元素。
嵌入表达式

现在,让我们探索JSX的另一个重要特性:嵌入表达式。

嵌入表达式允许开发者将JavaScript变量的值插入到最终React元素的HTML中。表达式被包裹在花括号{}中。
以下是嵌入表达式的几种常见用法:
1. 嵌入变量值
你可以直接在JSX中输出一个变量的值。
const name = 'John';
const greeting = <p>Hello, {name}</p>;
2. 嵌入函数调用
嵌入表达式也可以嵌入函数的输出结果。假设你需要以特定格式输出人名,可以创建一个格式化函数,然后在JSX的花括号内调用它。
function formatName(firstName, lastName) {
return firstName + ' ' + lastName;
}
const user = { firstName: 'Jane', lastName: 'Doe' };
const element = <h1>Hello, {formatName(user.firstName, user.lastName)}!</h1>;
与之前的例子一样,函数为JavaScript变量生成的值将被输出到HTML中。


3. 在HTML属性中使用表达式

表达式同样可以用于HTML属性。这在需要动态插入内容时非常有用,例如插入个人资料图片的地址。
以下是具体步骤:
首先,将图片地址存储在一个名为url的变量中。图片将使用<img>元素显示,因此你需要将url变量嵌入到src属性中。
const url = 'https://example.com/profile.jpg';
const profileImg = <img src={url} alt="Profile" />;
请注意,属性值不需要双引号,因为JSX会自动处理这些。

总结
本节课中,我们一起学习了JSX嵌入表达式的强大功能。我们了解到,通过使用花括号{},可以在JSX中:
- 直接插入JavaScript变量的值。
- 执行并插入函数调用的结果。
- 动态设置HTML元素的属性(如
src)。
这只是JSX作为一种高效输出包含JavaScript变量内容的HTML元素方式的其中一个例子。随着学习的深入,你将会遇到更多应用场景。掌握嵌入表达式是编写动态、交互式React组件的基础。
14:在属性中嵌入JS表达式 🖼️
在本节课中,我们将学习如何在React应用的HTML属性中嵌入JavaScript表达式。具体来说,我们将通过一个向应用添加并渲染图像的实例,演示如何操作<img>标签的src属性。
概述
我们将从一个简单的React应用开始,它目前只渲染一个标题。我们的目标是导入一张图片,并将其作为一个组件的属性进行渲染。这个过程涉及导入资源、创建组件以及在JSX属性中使用JavaScript表达式。
项目初始状态
首先,我们位于新项目的App.js文件中。初始的App组件仅返回一个包含“Hello World”文本的<h1>标题。
function App() {
return <h1>Hello World</h1>;
}

准备图像资源


我已经从Coursera的GitHub账户(通过公开的GitHub API获取)复制了一张头像图片,并将其粘贴到项目src文件夹的根目录下,命名为avatar.png。
为了在组件中使用这张图片,我需要先将其导入。
导入图像
在App.js文件中,使用import语句导入图像资源。
import avatar from './avatar.png';
创建Logo组件
接下来,我在App.js文件中添加一个名为Logo的新函数。虽然Logo本质上是一个独立的组件,但为了本示例的简洁性,我将代码保存在App组件内部,而非单独的文件中。
Logo函数接受一个props对象作为参数。在函数内部,我声明了一个userPic常量,并为其分配一个JSX元素。
以下是Logo组件的代码:
function Logo(props) {
const userPic = <img src={avatar} alt="User Avatar" />;
return userPic;
}
在这个<img>元素中,我将导入的avatar.png图像作为src属性的值进行传递。
渲染Logo组件
现在,回到App组件中,我通过在return语句中添加<Logo />元素来渲染Logo组件。
更新后的App组件代码如下:
function App() {
return (
<div>
<h1>Hello World</h1>
<Logo />
</div>
);
}
结果预览
在浏览器中预览我的应用。效果很好,它同时显示了标题文本和图像。
需要记住的是,如果继续构建包含更多组件的应用,最佳实践是将Logo组件提取到它自己的文件中,然后在需要时导入并渲染它。

总结

本节课我们一起完成了一个完整的演示,展示了如何在属性中嵌入JSX表达式。在这个案例中,我们操作的是HTML图像标签的src属性。我们学习了导入本地资源、在组件内封装逻辑以及最终在应用中渲染组件的基本流程。
15:模块总结 🎉
在本节课中,我们将回顾并总结整个关于React组件的模块内容。我们将梳理从React基础概念到实际组件创建与应用的完整学习路径,帮助你巩固所学知识。
模块概述

本模块旨在让你掌握React.js的基本结构与使用方法,目标是使你能够使用React构建单页应用程序。现在,让我们回顾一下各节课程是如何实现这一目标的。
课程内容回顾
以下是本模块涵盖的核心课程内容。
第一课:React与现实世界及基础准备
首先,你从Meta软件工程师Katie那里了解了React在现实世界中的应用。接着,为了给动手实验打下基础,课程快速回顾了HTML、CSS和JavaScript的基础知识。然后,你学习了如何在VS Code中设置项目以及如何使用JavaScript模块。
第二课:React组件与JSX 🧩

上一节我们介绍了开发环境设置,本节中我们来看看React的核心——组件。

在这一课中,你被引入了基于组件的架构。这是一种基于可复用代码组件(如React库)构建软件的设计哲学。你学习了组件类型、使用虚拟DOM的组件样式布局,以及如何创建构成UI设计基础的组件。

此外,你还深入了解了JSX。JSX是一种特殊的语法,类似于JavaScript的扩展。
// JSX示例:在JavaScript中编写类似HTML的代码
const element = <h1>Hello, world!</h1>;
你学习了如何转译它,换句话说,就是将JSX转换为HTML,这帮助你理解了组件是如何构建的。
在未评分的实验中,你学习了如何在App组件内部构建一个新组件并使其在屏幕上渲染,以及如何将组件保存到自己的文件中,并将其导入到父组件中以便在屏幕上渲染。
第三课:组件的使用与样式 🎨
上一节我们探讨了组件的创建,本节中我们来看看如何使用和美化它们。

你学习了如何使用属性将数据从一个组件传递到另一个组件,这项能力在未评分的实验中得到了测试。
在另一个未评分的实验中,你学习了如何使用不同的props多次渲染同一个组件。你还进一步学习了如何使用JSX及其嵌入表达式,以及如何以功能完善且外观良好的方式为JSX元素添加样式。
作为本课的一部分,你学习了:
- 使用和操作组件中的props。
- 使用函数表达式和箭头函数定义组件。
// 函数表达式组件 const MyComponent = function(props) { return <div>{props.content}</div>; }; // 箭头函数组件 const MyArrowComponent = (props) => <div>{props.content}</div>; - 在JSX表达式中调用函数。
- 使用表达式作为props。
- 声明需要props的组件,并在属性中使用表达式和嵌入表达式。

学习成果总结
完成本模块后,你现在能够:
- 解释React及其组件架构背后的概念。
- 创建服务于特定目的的组件。
- 创建组件文件夹,并演示如何在该文件夹内创建和导入文件。
- 使用和操作组件中的props以影响视觉结果。
- 描述如何在应用程序中使用资源来为功能组件应用样式。
结语

这是你React之旅一个良好的开端。恭喜你完成本模块的学习,请准备好迎接下一个模块的挑战。
16:事件类型 🎯

在本节课中,我们将要学习React中的事件类型。你将了解React支持哪些事件、它们与HTML事件的异同,以及如何从宏观层面理解事件处理过程。

现在你可能已经熟悉JavaScript中的事件了。回想一下,事件是JavaScript与HTML交互的过程,当用户或浏览器操作页面时,事件就会发生。它们提供了增强的交互体验,例如响应鼠标点击、移动或键盘命令。

因为事件通常依赖于某种交互,所以它们需要在后台等待并监听,直到交互发生才能被触发。
每个HTML元素都包含一组事件,开发者可以通过使用通常称为事件监听器的HTML属性来访问它们。例如,网站或应用程序中一个常见的功能是有一个按钮,当点击它时,会导致某些事情发生。
这个动作就是一个事件的例子,它可以通过事件监听器方法或定义特定的JavaScript函数来实现。开发者可以使用事件来执行JavaScript代码,以响应用户交互(如点击按钮)的操作。HTML按钮与JavaScript事件处理程序通信以执行某些代码来响应事件动作的这个过程,被称为触发。


例如,你可能想监听“添加到购物车”按钮上的点击事件。一旦捕获到这样的事件,你可能想运行一些JavaScript代码。在这个例子中,在购物车图标旁添加一个带有数字“1”的圆圈,表示已添加一件商品。
如果同一事件再次被触发,我们的事件处理代码就会通过更新购物车图标旁圆圈内的计数来处理该事件。圆圈随后会显示数字“2”,表示购物车中有两件商品。

事件是一个强大的工具,也是文档对象模型(DOM)的组成部分。作为一名有抱负的React开发者,你需要知道如何处理事件,因为它们在React中的处理方式略有不同。
在本视频结束时,你将能够识别React中可用的事件大类,并描述一些最常用的事件。你还将知道如何从高层次解释React中的事件处理过程。


在React代码中,事件是使用JSX事件属性来处理的,这些属性与你可能熟悉的HTML事件属性非常相似。例如,HTML中的点击处理属性是onclick,所有字母都是小写。

在React JSX中,等效的点击处理属性是驼峰命名法的onClick属性。请记住,驼峰命名法意味着首字母小写,单词之间的分隔用大写字母表示,而不是空格。

React支持许多事件,可以分为几组。这些组包括:

- 剪贴板事件
- 复合事件
- 键盘事件
- 以及更多其他事件。


每组通常包含多个事件。例如,支持的鼠标事件包括onClick、onContextMenu、onDoubleClick等。你还会发现剪贴板组有有用的事件onCopy、onCut和onPaste。这里要涵盖的事件太多,但你可以在补充阅读材料中找到完整列表。

React中可访问的大量事件类型起初可能看起来令人不知所措,但请注意,实际上是浏览器提供了这些功能,因为我们用来访问互联网的各种设备催生了用户与网站交互的多种方式。这意味着这些事件并非React特有,可能没有必要学习所有这些事件。
同时,请记住,其中许多事件都与特定的用例相关。例如,有几个鼠标事件仅限于拖放API。换句话说,在你当前的学习阶段,你的重点应该是理解整体的事件处理过程,以及事件可以为你开启哪些能力。


在本节课中,我们一起学习了React中可用的事件类型及其来源。接下来,你将探索具体的例子,并培养在各种情况下自信使用事件的技能。
17:16_常见事件处理

概述
在本节课中,我们将学习如何在React组件中处理多种事件。我们将从创建一个简单的按钮组件开始,逐步介绍如何为其添加点击和鼠标悬停事件处理功能。
创建基础按钮组件
首先,我在src文件夹中添加了一个名为Btn的新组件。目前,它只是一个带有默认导出的空函数。

为了简化演示,我清理了App.js文件的return语句:删除了logo导入语句,改为导入Btn组件,并移除了return语句中的原有内容,最后添加了Btn JSX元素以便渲染。
回到Btn组件,我在return语句中添加了一个按钮,按钮文字为“click me”。保存文件后,按钮成功渲染在屏幕上。
处理点击事件
现在,我想处理这个按钮的点击事件。为此,我添加了onClick合成事件,其语法为:一个等号,一对花括号,花括号内是名为clickHandler的表达式。
在return语句中,我将代码分布在多行以提高可读性。
这样设置后,每当用户点击按钮,都会执行名为clickHandler的表达式。接下来我需要定义这个clickHandler。
我将其设置为一个函数表达式:使用const变量关键字将其命名为clickHandler,然后为其分配一个箭头函数。
至此,我设置了一个点击处理器,它接收从按钮触发的点击事件,并通过在控制台输出单词“clicked”来处理它。
保存更改后,在浏览器中打开开发者工具,定位并激活控制台选项卡,同时将视图聚焦在按钮上。
当我点击按钮时,控制台会为每次点击事件显示“clicked”一词。
处理其他类型事件
上一节我们介绍了如何处理点击事件,本节中我们来看看如何处理其他类型的事件,例如鼠标悬停事件。
在Btn.js文件中,我选中组件内的所有代码,右键点击选择“复制”命令。接着,使用快捷键Ctrl + K, C(Mac上是Command + K, C)注释掉所有高亮代码。
在这段被注释的代码下方,我按下Ctrl + V粘贴之前复制的代码。
现在,我将展示如何处理不同的事件。让我将onClick JSX事件处理属性替换为另一个属性,从而处理不同的事件。
例如,我可以将onClick属性替换为onMouseOver属性,并相应地将事件处理函数更新为输出“mouse over”。
保存更改并等待应用重新编译。这次,当我将鼠标悬停在按钮上时,控制台会显示“mouse over”一词。
核心概念与代码示例
以下是事件处理的核心步骤:
-
在JSX中添加事件属性:在元素上使用如
onClick或onMouseOver等属性。<button onClick={clickHandler}>Click me</button> -
定义事件处理函数:通常使用箭头函数来定义处理逻辑。
const clickHandler = () => { console.log('clicked'); };
总结


本节课中,我们一起学习了如何在React中处理事件。你学会了如何为JSX元素添加onClick和onMouseOver等事件处理属性,并定义对应的函数来处理这些事件。记住,React中的事件命名采用驼峰式,并且你直接将函数引用(而不是函数调用)传递给事件属性。
18:处理程序语法 🎬
在本节课中,我们将要学习事件处理程序在HTML、原生JavaScript以及React中的不同语法和实现方式。理解这些差异对于编写高效、可维护的React应用至关重要。
事件处理程序概述
每次你点击或轻触一个按钮、滚动页面或关闭一个无聊的通知时,你都在浏览器中产生事件。为了让这些事件产生实际效果,你需要使用事件处理程序来执行相应的操作。例如,假设你使用一个按钮来打开菜单:点击按钮是事件,onclick是事件处理程序,而打开菜单则是事件触发的动作。


在React代码中添加事件处理程序有几种不同的方法,各有优势,因此你需要熟悉每一种。
HTML中的事件处理
假设你是一名正在开发React应用的开发者,需要创建一个在用户点击时触发事件的按钮。让我们先看看在纯HTML中如何实现此功能。

以下是实现此功能的HTML代码示例:
<button id="jsBtn" onclick="clickHandler()">Click Me</button>
这段代码包含一个ID为jsBtn的HTML属性,以及一个事件处理属性onclick。虽然这个HTML代码看起来相当直接,但对于此类场景,更推荐使用JavaScript来处理。原因我们稍后会探讨。
JavaScript中的事件处理
在原生JavaScript中实现等效功能主要包含两个步骤。
首先,你需要使用JavaScript连接到你想监听事件的特定HTML元素。在上一个例子中,HTML元素是jsBtn,这标志着它是允许JavaScript控制HTML结构的目标元素。
其次,一旦你通过JavaScript获得了对HTML元素的访问权,你就可以在document对象上使用内置的addEventListener方法来附加特定的事件监听器。


将这个方法应用到之前的例子中,HTML部分被移除了,但代码本身变得稍微复杂一些。
以下是具体的JavaScript代码:
const jsBtn = document.getElementById('jsBtn');
jsBtn.addEventListener('click', clickHandler);

具体来说,你需要先声明一个名为jsBtn的常量,并为其分配从DOM获取的值。然后,你需要添加点击监听器事件以及要运行的函数。

React中的事件处理


回到React,语法上最大的区别在于不使用addEventListener方法。

在React中,规则是尽可能避免直接操作DOM。你应该以声明式的方式设置一切,这意味着你向React描述更新,并让它处理其余的事情。
这最好通过使用事件属性来完成。幸运的是,HTML事件属性和JSX事件属性之间存在一一映射关系,这使得学习React事件处理变得更容易。
React中的事件处理总体上与HTML非常相似,但请注意,在React的事件处理属性中没有函数调用语法。换句话说,在纯JavaScript中,你需要将一个事件处理函数的调用作为值传递给onclick事件;而在React中,你不应该调用函数,而是只传递对事件处理函数的引用。
为了说明这一点,让我们比较一下HTML点击处理事件与其React JSX等效项的语法。


在HTML中,你提供以on开头的事件处理属性,并附加事件名称(全小写)。在等号后面,你使用一对双引号,在双引号分隔符内,你调用将要运行的函数。
<button onclick="clickHandler()">Click</button>
与HTML相反,在React中,你提供以on开头的事件处理属性,并附加事件名称,其中每个单词的首字母大写。在等号后面,你使用JSX表达式分隔符(即开头和结尾的花括号{}),在花括号分隔符内,你添加要运行的函数的名称,确保不要调用它。
<button onClick={clickHandler}>Click</button>
将函数声明作为Props传递

React独有的另一个特性是能够将函数声明作为Props传递。
例如,在一个App组件中,假设你想要渲染一个名为Counter的子组件。你可以使用一个Prop将一些数据从App组件传递到Counter组件。在这种情况下,让我们使用一个onClick Prop来传递你希望Counter组件接收的数据。
function App() {
const handleClick = () => {
console.log('Button clicked in App');
};
return <Counter onClick={handleClick} />;
}
总结

本节课中我们一起学习了如何区分HTML、JavaScript和React中的事件处理语法。你了解了在HTML中直接使用onclick属性,在原生JavaScript中使用addEventListener方法,以及在React中使用JSX事件属性(如onClick)并传递函数引用的方式。记住,React鼓励声明式编程,避免直接操作DOM,这是其事件处理模型的核心思想。下次当你在网页上点击按钮、关闭通知或浏览内容时,你将理解这些事件背后都有某种形式的事件处理程序在支持。
19:用户事件处理 🎯
在本节课中,我们将学习如何通过用户触发的事件来切换布尔状态变量的值,以及如何在单个JSX元素上处理多个事件。我们将通过一个具体的组件示例,结合状态管理、样式设置和三目运算符的使用,来演示事件处理的核心概念。
概述
接下来我们将探索的代码示例,与课程中之前接触的组件有所不同。这个示例展示了事件处理、状态管理、样式应用以及三目运算符如何协同工作。假设我们有一个组件,它使用状态来记录暗黑模式是否开启的布尔值。根据这个变量的值是true还是false,组件将渲染一个包含文本的H1标题,主题可能是暗色或亮色。此外,我们还可以通过点击按钮来切换主题。
现在,让我们深入演示事件处理的具体过程。

构建ModeToggler组件 🛠️
我将通过一个示例来演示事件处理,以便你能实际了解如何使用事件为应用添加额外功能。我将构建一个名为ModeToggler的组件。
在VS Code的侧边资源管理器中,我右键点击src文件夹,选择“新建文件”命令。我将文件命名为ModeToggler.js。目前,它只是一个空的函数声明和默认导出。
我按下Ctrl+S(在Mac上是Cmd+S)保存更新。回到App组件,我更新其return语句以渲染这个新的ModeToggler组件。我同时需要在App组件的第一行导入它,并保存App.js的更改。
现在,我为ModeToggler组件返回一些JSX代码,并添加一个return语句。在这个语句中,一个JSX表达式包裹着一个三目运算符,用于检查darkModeOn的值是true还是false。如果是true,它将返回存储在darkMode变量中的内容;如果是false,则返回存储在lightMode变量中的内容。
然而,我还没有定义要评估的这些值,所以如果现在保存代码,会抛出错误。
定义状态与变量 📝
我通过在return语句上方声明三个变量来定义这些值:
darkModeOn,其值为true。darkMode,其值为包裹在H1标签中的文本“dark mode is on”。lightMode,其值为包裹在H1标签中的文本“light mode is on”。
保存更改后,我在浏览器中看到了句子“dark mode is on”。
让我解释一下发生了什么。darkModeOn变量被设置为true。为了快速测试,我可以将三目运算符中darkModeOn变量名直接替换为值true。由于这个值是true,所以存储在darkMode中的值将被渲染。如果我将true改为false,那么存储在lightMode中的值将被显示。现在,我将测试词false替换回我们的变量darkModeOn,保存并再次测试。现在屏幕上显示的是“light mode is on”。
添加按钮与点击事件 🖱️
我添加一个带有onClick事件的按钮,用于处理将darkModeOn变量的值从true切换到false。

在这个三目运算符语句下方,我添加一个带有onClick事件处理器的按钮。同时,我将定义handleClick函数。
实现事件处理函数 ⚙️
我的函数从获取darkModeOn的值开始,并使用感叹号(即逻辑非运算符)将其更改为相反的布尔值。然后,我将这个值赋给darkModeOn变量作为新值。
为了更清楚地解释:例如,如果darkModeOn的值是true,那么!darkModeOn将被求值为!true(即false)。这个false将被赋值给darkModeOn变量,从而使其变为false。
现在,我为handleClick函数添加其余代码,这是一个if语句。逻辑是:如果darkModeOn被设置为true,则在控制台记录“dark mode is on”;否则记录“light mode is on”。我本可以用不同的方式编写这段代码,但我选择了一种能清晰展示正在发生什么的方式。这对于任何技能水平的开发者来说都是良好的实践,便于自己和他人日后轻松检查代码。
保存后,一旦应用重新编译,如果我点击“click me”按钮,我将在控制台看到相应的字符串输出。
发现问题与思考 🤔
这引出了一个有趣的结论:虽然控制台日志在更新,但屏幕上的实际H1标题并没有任何变化。当然,我可以通过手动将false改为true,然后保存应用并等待重新渲染来更新它,以确认我的更改确实发生了,因为之前显示“light mode is on”的标题现在变成了“dark mode is on”。但一旦我点击按钮,控制台日志会改变,然而网页应用中的标题并没有反映这个变化。
为什么会这样?要理解这一点,你需要更深入地了解React中的数据流,并观察它如何在组件间移动。幸运的是,你很快将会学到这些。
总结
干得不错!现在你应该能够演示如何通过用户触发的事件来切换布尔状态变量的值,以及如何在单个JSX元素上处理多个事件了。我们通过构建一个主题切换组件,实践了状态定义、条件渲染和事件处理函数的编写。虽然目前UI还不会随状态自动更新,但这为我们接下来学习React的核心概念——状态(State)与数据流——奠定了重要的基础。
20:父子数据流 👨👦

在本节课中,我们将要学习 React 中一个核心概念:父子组件之间的数据流。我们将探讨如何通过父组件向子组件传递数据,从而避免代码重复,并实现数据的高效更新。
想象一下,你正在为一家在线零售商工作,该零售商经常打折以保持库存流动。促销信息会在网站的多个位置公布。那么,保持这些信息更新的最佳方式是什么?
例如,单独更新每个项目将是繁琐且耗时的。幸运的是,我们可以在一个单一位置更改信息,并让其他所有内容自动更新以匹配。这个想法说明了父子关系。在本视频中,你将探索这个概念在 React 中的应用。
理解父子组件关系
让我们从两个将在同一个应用中使用的组件示例开始。
首先是 Promo 组件,它将返回你稍后将创建的 PromoHeading 组件的内容。
以下是构建 Promo 组件的代码:
function Promo() {
return (
<div>
<PromoHeading />
</div>
);
}
export default Promo;
接下来,让我们编写 PromoHeading 组件:
function PromoHeading() {
return (
<h1>80% off sale</h1>
);
}
export default PromoHeading;
现在,你已经创建了 Promo 组件,它调用 PromoHeading 组件中的函数来返回文本“80% off sale”。在这个例子中,Promo 组件被称为父组件,而它渲染的 PromoHeading 组件被称为子组件。
应对更复杂的需求
现在,假设折扣增加到 99%,你需要更新代码来反映这一点。一种方法是更新 PromoHeading 组件中 <h1> 标签内的文本。这是一个快速的修复,因为只需要处理一个更改。
然而,让我们探索一个更复杂的情况。这次,你的经理要求你在网站的侧边栏和页脚组件中也调用 PromoHeading 组件,而不仅仅是在 Promo 组件中。他们还希望显示两条消息:“99% off all items”和“Everything must go”。
这些新要求意味着,仅仅更新子组件的方法将不再那么有效。为什么呢?因为这意味着你现在必须用相同的数据更新多个组件。这不符合“DRY”(不要重复自己)的通用编程原则,该原则旨在减少不必要的代码重复。
此外,请考虑以下可能性:在多个组件中输入相同文本时,你可能会犯打字错误。另外,如果你的老板决定再次更改折扣呢?这意味着你将不得不再次更改所有相关组件中的文本。

建立单一数据源
那么,与其一遍又一遍地编写相同的代码,不如改变你的方法。你可以建立一个单一数据源,其中包含存储文本值的两个字符串:“99% off all items”和“Everything must go”。这将包含在父组件中,以便任何需要的数据都可以通过 props 传递给子组件。
现在,让我们使用这种方法来更新 Promo 组件。首先,你创建一个单一数据源:一个名为 data 的 JavaScript 对象。

const data = {
heading: "99% off all items",
callToAction: "Everything must go"
};
接下来,你更新 Promo 组件,将 data 对象的 heading 和 callToAction 值传递给 PromoHeading 组件。这被称为从父组件向子组件传递数据。
function Promo() {
return (
<div>
<PromoHeading
heading={data.heading}
callToAction={data.callToAction}
/>
</div>
);
}
回到 PromoHeading 组件内部,你更新它以接受来自其父组件的数据。为此,你需要首先删除 return 语句中现有的 <h1>,然后添加一个新的 <h1> 用于显示 props.heading,以及一个 <h2> 用于显示 props.callToAction。
function PromoHeading(props) {
return (
<div>
<h1>{props.heading}</h1>
<h2>{props.callToAction}</h2>
</div>
);
}
现在,这个组件接受一个 props 对象,具体来说是它的两个属性:heading 和 callToAction。props 对象的值是在父组件中确定的,当你添加到应该渲染的特定 JSX 元素时。这是在你于 Promo 组件内部渲染 PromoHeading 时实现的,在这里你使用普通 JavaScript 的点表示法语法访问 data 对象上的属性。
了解了这一点,你现在可以以类似 PromoHeading 组件的方式编写 Sidebar 组件和 Footer 组件。

数据流的单向性

请记住,在 React 中,props 数据总是从父组件流向子组件。这种单向数据流确保了数据的可预测性和可维护性。
使用 props 可以帮助你避免在多个地方更改数据。相反,你只需在数据源(即父组件)处进行更改,更新将自动应用于子组件。

总结
本节课中,我们一起学习了如何建立父子组件关系,以便数据从父组件流向子组件。通过将数据存储在父组件中,你可以动态地将其传递给子组件,而无需单独更新每个子组件。这种方法遵循了 DRY 原则,提高了代码的复用性和可维护性,是构建高效 React 应用的关键。
21:子组件与数据
在本节课中,我们将要学习 React JS 中的数据流。具体来说,我们将了解数据在 React 组件中如何单向流动,以及如何通过 props 和 state 来展示无状态和有状态组件的使用。
概述:什么是单向数据流?
数据流在 React 中是单向的。这意味着数据只能从一个方向流动。
一个自然的问题是:为什么 React 中的单向数据流如此重要?
这种数据流确保了数据通过组件层级结构从上到下地移动。它也确保了变更能够在整个系统中传递。你将在后续课程中更详细地了解这一点。在本视频中,你还会学习如何通过关注数据流来展示无状态和有状态组件的使用。
数据流的核心概念
想象数据是金钱,而金钱由你的雇主控制。这笔钱可以被视为 props。这笔作为 props 的钱传递给你,就变成了你的 state。
props 总是从你的雇主流向你,而绝不会反向流动。在 React 中,数据通过 props 从父组件向下传递到子组件。
子组件不能改变或修改它接收到的 props,它只能读取它们并重新渲染。这意味着数据来源于父组件,并仅在子组件中被消费。
然而,如果情况总是如此,那么你在 React 应用中拥有的将只是 DOM 的独立片段,它们作为组件模板,仅用于填充接收到的数据。
虽然这很有效,但几乎没有任何交互性。
数据管理的两种方式:Props 与 State
你已经学习了如何使用 props 向子组件传递数据。然而,在 React 组件中处理数据还有另一种方式,这种数据被称为 state。
React 中的所有数据都可以分为 props 数据和 state 数据。
props数据:是组件外部接收并使用的数据,但组件不能改变它。state数据:是组件内部的数据,组件可以控制并改变它。
也可以这样理解:
props 数据属于渲染该组件的父组件。
state 数据属于组件自身。

为了演示这一点,让我们打开 VS Code 并通过一个例子来实践。
实践示例:从父组件 State 到子组件 Props
我已经使用 create-react-app 构建了一个新应用。我创建了两个文件:App.js 和 Child.js。
App.js 文件使用类定义(而不是函数)来定义 App 组件。当它被创建时,它会用一个当前日期来初始化其 state。
// App.js
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
currentDate: new Date()
};
}
// ... 其他代码
}
render 函数随后渲染一个名为 Child 的组件。Child 组件定义了一个名为 message 的 prop,其值被设置为从组件 state 中获取的当前日期,并转换为包含日期、小时、分钟和秒的字符串格式。
// App.js 的 render 方法内
render() {
return (
<div className="App">
<Child message={this.state.currentDate.toString()} />
</div>
);
}
在 Child.js 文件中,该组件在一个 H1 元素中渲染 message 这个 prop。
// Child.js
function Child(props) {
return <h1>{props.message}</h1>;
}

现在,当我运行这个应用时,App 组件的 state 将其数据向下流动到子组件的 props 中,H1 元素将显示当前的日期和时间。
总结
本节课中我们一起学习了 React JS 中数据如何在子组件间流动。你现在应该已经理解了单向数据流的重要性,即数据通过 props 从父组件传递到子组件。同时,你也应该能够通过关注数据流来展示无状态组件(主要依赖 props)和有状态组件(拥有并管理自己的 state)的使用。记住,props 是只读的,属于父组件;而 state 是可变的,属于组件自身。
22:什么是钩子(Hooks)🔧
在本节课中,我们将要学习React中一个非常重要的概念——钩子(Hooks)。你将了解钩子是什么、它们如何被使用,以及为什么它们对构建交互式React应用如此有用。
到目前为止,你可能已经学习了一些React中重要且有用的核心概念。现在,你已经准备好学习如何为组件添加交互性、在React组件内维护状态,并探索钩子的世界了。
钩子解决的问题 🤔
上一节我们介绍了React组件的基础。本节中我们来看看,随着你作为React开发者的成长,你很快会使用具有状态逻辑的复杂组件。跨组件跟踪状态可能变得相当繁琐,而这正是React钩子可以大显身手的地方。
钩子的一个关键好处是,它们解决了组件间不必要的代码重复问题。让我们看看它们是如何做到的。
钩子是什么?🔗
钩子是React 16.8版本中引入的函数。它们让你能够从函数组件中“钩入”React的状态(state)和生命周期(lifecycle)特性。
以下是钩子的核心定义:
// 钩子是让你在函数组件中使用React状态和生命周期特性的函数。


探索useState钩子 📝

让我们观察一个具体的钩子示例。你将仔细研究useState钩子的一个实例,因为它是最常用的钩子之一。这个钩子用于管理组件内部的状态并跟踪它,并且它直接内置于React中。
要使用它,你需要做的第一件事是从react中导入useState,以便它可供使用。
import { useState } from 'react';
下一步是在组件内声明一个状态变量。你可以为状态变量和设置状态的函数提供任何名称。对于这个例子,让我们将状态变量称为showMenu,将设置状态的函数称为setShowMenu。
const [showMenu, setShowMenu] = useState(false);
理解useState语法 🧩
如果你学过JavaScript,这种语法可能会让你感到有些熟悉。你可能想知道这段代码到底做了什么。实际上,它做的事情你可能以前就遇到过。
请注意,约定是使用数组解构来命名状态变量和设置函数。当你使用useState声明一个状态变量时,它会返回一个包含两个项的数组(一对值)。如果没有数组解构,代码会变得冗长而繁琐,因为通过索引访问数组项会更令人困惑和乏味。数组解构是首选,它显著简化了代码。
现在你有了一个名为showMenu的新状态变量。useState然后将showMenu的初始值设置为false。
useState的作用总结 📋
以下是调用useState钩子所完成的两件事:
- 创建一个状态变量:它创建一个具有初始值的状态变量,代表当前状态。在这个例子中就是
showMenu。 - 创建一个设置函数:它创建一个函数来设置该状态变量的值。在这个例子中就是
setShowMenu。
函数setShowMenu用于通过向其传递布尔值来更新showMenu的值。你为状态变量使用什么名称并不重要,你可以根据你的组件和用例来定义它们。useState钩子应该在组件的顶层被调用。
状态变量的数据类型 📊
在这个例子中,注意到useState钩子被用来跟踪布尔状态值。你可以使用useState钩子来跟踪任何类型的数据。它可以是字符串、数字、数组、布尔值或对象。

例如,你甚至可以跟踪一个按钮被按下的次数。
const [clickCount, setClickCount] = useState(0);
自定义钩子 🛠️
除了React开箱即用的钩子之外,你还可以构建自己的钩子,这将允许你将自定义的组件逻辑提取到可重用的函数中。

这是使用钩子的一个极佳特性和好处。钩子最大的好处在于它们为代码提供的可读性和简洁性。

课程总结 🎯
本节课中我们一起学习了React钩子的基础知识,并探索了useState钩子。你现在理解了使用钩子的好处,以及如何在你的React应用中使用它们。钩子通过提供一种清晰、简洁的方式来管理状态和副作用,极大地简化了函数组件的开发。
23:什么是状态 🧠
在本节课中,我们将要学习React中一个核心概念——状态。我们将探讨状态是什么,它与Props有何不同,以及如何在组件中使用它来控制UI的显示和行为。通过理解状态,你将能够构建更具交互性和动态响应的React应用。

状态的概念与类比
考虑闹钟的不同模式以及你会在何种情况下使用它们。


通常,闹钟有几种模式:闹钟开启用于设置起床时间,闹钟关闭用于不需要此功能时,以及贪睡用于再偷睡几分钟。

设置这些模式并不需要给你的闹钟添加任何额外的东西,它们是内置功能,只需按一下按钮即可设置。
在React中实现类似功能
如果你在React应用中创建这个功能,可以创建一个名为 Clock 的组件,然后通过Props传入状态值。
回忆一下,Props是React的一个特性,本质上允许你在浏览器中保存关于UI的信息。


然而,在React中,你还有另一种方式来实现这个功能,那就是使用一个类似的概念——状态。
状态同样允许你轻松改变组件的行为以适应特定需求。通过本视频的学习,你将能够描述React中的状态是什么,以及为什么开发者使用它来控制组件在浏览器中的显示内容。

状态的定义与重要性

将状态理解为组件的内部数据会很有帮助,它决定了组件的当前行为。状态通常用于存储影响组件行为的数据。

状态之所以重要,是因为它允许组件之间保持同步,并确保你的应用按预期运行。


例如,如果一个组件更新了它的状态,所有依赖于该状态的其他组件也会自动更新。
状态与Props的关系

这意味着一个组件通过使用Props将其状态传递给它的子组件。


如果子组件还有自己的孙组件,那么子组件可能也有一些状态,它们会通过Props传递给那些孙组件。


状态的存储与初始化
在React中,状态保存在状态变量中。改变状态的主要方式就是修改这些变量。


当一个组件被创建时,它会获得一个初始状态。这个状态用于初始化组件的属性。


有状态组件与无状态组件
组件可以分为有状态或无状态,但这到底意味着什么?为了更好地理解,让我们来探讨每种类型的一个例子。

首先是一个没有定义状态的App组件。它执行一个单一动作:渲染文本“一个无状态组件”。
function App() {
return <div>一个无状态组件</div>;
}

然后,我们有一个有状态的函数组件。


这个组件也渲染一些文本,但它引用了一个变量来实现。

function App() {
const [greet, setGreet] = React.useState("Hello");
return <div>{greet}</div>;
}


理解useState Hook
你稍后会探索它是如何工作的。现在,请注意App函数体第一行使用的语法。
如果你熟悉普通JavaScript中的数组解构,这行代码可能对你来说已经很清楚了。但为了更清晰,让我们看一个例子。
考虑一个名为 fruits 的数组,它包含三个字符串:apple、pear 和 plum。
const fruits = ["apple", "pear", "plum"];


在ES6版本中,JavaScript引入了数组解构的概念,允许你使用一行代码从数组中分配多个变量。
换句话说,你可以快速地将 apple、pear 和 plum 分配给 fruitOne、fruitTwo 和 fruitThree 变量,而不是一次分配一个。
const [fruitOne, fruitTwo, fruitThree] = fruits;
// fruitOne = "apple", fruitTwo = "pear", fruitThree = "plum"


记住这一点,让我们回到有状态组件中的那一行代码。
注意,使用的语法与你刚刚检查的数组解构示例类似,但包含一个有趣的代码片段:React.useState。
Hook 允许开发者“钩入”一些原本无法访问的功能。

例如,要访问状态对象,你会使用 useState Hook。


解构useState的返回值

为了更好地理解在App组件中被解构的是什么,让我们对 useState Hook 调用一个 console.log。
console.log(React.useState("Hello"));


输出显示一个包含两样东西的数组:字符串 "Hello" 和一个函数。


在这个例子中,"Hello" 是分配给 greet 状态变量的状态值。那个函数是一个内置的、未声明的函数。


这个函数可以用任何你喜欢的名字来解构,但有一个约定需要遵循。


如果你将状态变量命名为 greet,那么解构出来的状态更新函数应该命名为 setGreet。这是因为第二个解构出来的变量是一个用于更新状态变量的函数。



状态更新函数

让我们检查这个有状态函数组件的一个更新版本。
注意,setGreet 变量实际上并没有被运行,这是在别处完成的事情。
在本课程后面,你将学习如何通过一个可点击的按钮来扩展这段代码,以更新状态。


总结
本节课中我们一起学习了React中的状态。具体来说,我们探讨了:
- 状态是决定组件当前行为的内部数据。
- 状态与Props的关系:状态可以通过Props传递给子组件。
- 组件分为有状态组件和无状态组件。
- 如何使用
useStateHook 在函数组件中创建和管理状态,包括状态初始化和解构状态值及其更新函数。
理解状态是掌握React交互式UI开发的关键一步。在接下来的课程中,你将学习如何通过事件(如点击)来触发状态更新,从而使你的应用真正“活”起来。
24:观察状态 🧐
在本节课中,我们将要学习React中状态(State)的核心概念,理解为何使用状态,并掌握如何使用useState钩子来观察和更新组件的状态。
概述
React中使用状态是为了处理应用程序中可能变化的数据。状态是React中一个强大的工具,开发者用它来管理应用程序内部可能改变的数据。需要记住,状态数据是组件内部的。这使得组件能够根据状态数据的变化重新渲染,并向用户展示最新的更新。
使用useState钩子更新组件
上一节我们介绍了状态的基本概念,本节中我们来看看如何使用之前遇到的useState钩子来更新组件。
useState钩子允许组件定义和追踪状态。它通过两个参数实现此功能:第一个参数用于访问状态,第二个参数是一个用于更新状态的函数。
例如,你可以使用date变量来访问日期状态,然后使用setDate函数来更新该状态。

const [date, setDate] = useState();
为了帮助你理解useState钩子的实用性,接下来我们将探索一个示例,演示如何使用它来观察和操纵组件的状态。
示例:观察与操纵状态

让我们观察一个为名为“Little Lemon”的地中海餐厅制作的应用程序。它有一个子组件Header,该组件接收一个包含props的对象。
function Header(props) {
return <h1>{props.message}</h1>;
}
该组件在返回并渲染为H1元素之前,会访问message属性。
在父组件App.js中,我导入了Heading组件,并将word设置为一个状态变量,其初始值为字符串“eat”。(目前,我忽略“eat”字符串后的注释。)
在return语句中,我将Heading组件包裹在一个单独的div中。我传递了message属性,其值为word加上用双引号括起来的“at Little Lemon”,所有这些都包裹在一对花括号中。
function App() {
const [word, setWord] = useState('eat');
return (
<div>
<Heading message={word + " at Little Lemon"} />
</div>
);
}
你已经知道,一对花括号表示一个JSX表达式,这意味着花括号内的所有代码都将作为常规JavaScript进行计算。JavaScript引擎获取单词“eat”并将其与“at Little Lemon”连接起来。因此,在浏览器窗口中,我得到“eat at Little Lemon”。
通过事件更新状态
如果我想将word状态变量的值更新为其他内容,例如“drink”,我可以直接使用setWord函数来实现。然而,当我保存更改并运行代码时,应用程序无法工作。这是因为不能直接从你的状态中调用状态设置函数。
相反,我需要基于一个事件(如点击)来更新它。😊
因此,我添加了另一个名为button的元素,并设置onClick属性等于handleClick。
我现在定义另一个名为handleClick的函数。在handleClick函数定义内部,我调用setWord('drink')。我保存所有文件并等待应用程序编译。
function App() {
const [word, setWord] = useState('eat');
function handleClick() {
setWord('drink');
}
return (
<div>
<Heading message={word + " at Little Lemon"} />
<button onClick={handleClick}>点击这里</button>
</div>
);
}
现在,当我点击“点击这里”按钮时,我得到“drink at Little Lemon”。
关键要点
以下是观察和更新状态的核心要点:
- 要观察和更新状态,你可以使用状态钩子提供的这些状态设置函数和状态变量。
- 但你必须确保在JSX语法中使用事件处理属性,或者使用你将在以后学到的其他方法。
总结
本节课中我们一起学习了React中状态变化的基础知识,包括如何应用useState语法来观察和操纵组件中的状态。做得好。
25:管理状态 🧠
在本节课中,我们将要学习如何在复杂的React应用中管理状态。随着应用规模的增长,跨组件管理状态会变得复杂。我们将探讨状态管理的概念,并介绍一些在React应用中管理状态的解决方案。
状态管理的场景 🍽️
为了说明一个需要管理状态的场景,我们考虑一个帮助用户监控饮食摄入、促进健康生活的小型React应用。这个应用追踪每日的餐食计划,用户可以点击每顿已消费的餐食。应用随后会更新显示当天还剩多少餐需要消费。
该应用由三个组件构成:一个名为App.js的根组件,以及两个子组件:MealList和Counter。
组件结构分析 🔍
上一节我们介绍了应用的基本场景,本节中我们来详细看看每个组件。
首先,App组件导入MealList和Counter组件,并将它们渲染到屏幕上。
接下来,MealList组件使用useState钩子来列出一天的餐食,这些餐食存储在一个数组中。数组元素保存在todayMeals变量中,然后meals状态变量被初始化为持有这个值。换句话说,meals状态变量持有这个数组。
最后,Counter组件追踪用户今天允许食用的餐食数量。
状态共享的问题 ❓
虽然这个组件结构看起来不错,但存在一个问题。Counter组件需要从MealList组件获取状态信息,但这两个组件都由App组件渲染。换句话说,MealList和Counter组件是兄弟关系,而非父子关系。
这就引出了一个问题:如何将状态信息从MealList组件传递到Counter组件,因为Counter组件并非MealList组件的子组件?
解决方案:状态提升 ⬆️
让我们探讨一个可能的解决方案。首先,你可以通过将返回值提取到其自己的组件中来简化MealList组件。然后,你可以使用单独的组件来显示不同的餐食项。我们称这个新组件为MealItem。
为了实现这一点,你可以使用被称为“状态提升”的实践。这意味着你将状态从MealList上移到App组件。
然后,你可以通过props传递状态,使用MealList组件作为状态数据传递到其目的地MealItem组件的桥梁。接着,你只需要在Counter组件中计算可用的数据。
现在,状态已经上移到了App组件,而MealList组件变成了状态数据传递到其目的地MealItem组件的管道。

属性钻取的问题 🚧
你现在必须问的问题是:属性钻取有什么问题?

属性钻取是一个常用术语,用于描述必须通过props在多层组件中传递状态,从父组件到子组件,再到该子组件的子组件,依此类推。
需要注意的是,如果源数据发生变化,你将不得不将这些变化传递到整个属性钻取的结构中。这使事情变得复杂,因为状态更新会传递到所有子组件及其兄弟组件,然后这些组件都需要更新以反映状态的变化。
此外,随着应用的增长,问题会变得更大,你可能会在App组件中保存大量的状态。请记住,这些状态中的大部分并不真正应该放在App组件中。这是因为这些状态是关于像MealItem这样的组件的。

全局状态的视角 🌍
从全局状态的角度来看,这是另一种表述此问题的方式。每当我有状态可能需要在应用中的多个地方使用时,这就是一个全局状态问题。
优雅的解决方案:Context API 🎯

针对这个问题,一个优雅的解决方案是React Context API。理解Context API的一种方式是,它消除了中间人。不再需要属性钻取和状态提升。
相反,需要数据的组件直接从Context API获取它。

实现这一点的方式是将状态提取到一个单独的文件中,该文件在Context中保存状态。然后,任何需要它的文件只需导入并使用它。
总结 📝

本节课中我们一起学习了React中的状态管理。我们探讨了在兄弟组件间共享状态时遇到的问题,介绍了通过“状态提升”和属性钻取的解决方案及其局限性。最后,我们了解了React Context API如何作为一种更优雅的方案,通过提供全局状态管理来避免复杂的属性钻取,从而简化组件间的数据传递。
26:React 状态管理
在本节课中,我们将要学习如何管理跨多个组件层级的状态。你将理解如何使用 Context API 来更高效地管理状态,并学会使用 useContext 和 useReducer 这两个钩子进行基本的状态管理。
概述:从 Props 到 Context
到目前为止,你可能已经学会了通过使用 props 在父组件和子组件之间传递状态的方法。
但是,你是否想过,对于拥有多层组件的更复杂应用,这些方法是否依然适用?
幸运的是,有一些工具可以帮助你做到这一点。通过本视频的学习,你将理解 Context API 如何用于跨多个组件层级更高效地管理状态。
为什么需要 Context API?
通过 props 传递状态有助于管理状态,但这就像乘坐公交车,需要在到达终点前经过每一站。
相比之下,使用 Context API 就像瞬间传送到目的地。它是一种绕过在多层组件中冗余传递数据的方式。
记住,API 代表 应用程序编程接口。API 提供了一组预定义的方式,让你可以与某些代码进行交互。
因此,Context API 提供了一种在 React 中处理上下文的简化方法。
理解 Context API 的核心概念
要设置 Context API,你需要添加一段代码作为你的 Context Provider,这也是状态将被存储的地方。

当一个组件需要使用状态时,它就成为一个 Context Consumer。
核心代码结构示例:
// 1. 创建 Context
const MyContext = React.createContext();
// 2. 创建 Provider 组件
function MyProvider({ children }) {
const [state, setState] = useState(initialState);
return (
<MyContext.Provider value={{ state, setState }}>
{children}
</MyContext.Provider>
);
}
// 3. 在 Consumer 组件中使用
function MyComponent() {
const { state } = useContext(MyContext);
return <div>{state}</div>;
}
实践:一个使用 Context 的简单应用
现在,让我们分析一个利用 Context API 来控制状态的简单应用。
在我的 App.js 文件中,我使用了一些代码作为起始设置。你可以在附加资源中找到这个文件,如果你想用它来练习使用 Context API。
在 App 组件中,我有 MealsProvider 和 MealsList 的导入语句。
MealsProvider 提供上下文状态数据,并将其传递给 App 组件内它所包裹的所有组件。目前,它包裹了两个组件:MealsList 组件和 Counter 组件,它们位于 return 语句的 div 标签之间。
MealsProvider 组件借助 Context API 来组织并持有所有状态。
首先,我使用 React.createContext() 函数设置了 MealsContext 变量。
接着,我声明了 today‘sMeals 数组,其中包含几个保存为字符串的食物项。
然后,我将 MealsProvider 编码为一个接受 children 值的 ES6 函数。这个 children 值包含了当 MealsProvider 组件在 App 组件内部渲染时,将被包裹进该组件的一切内容。
children 值被包裹在 <MealsContext.Provider> JSX 元素中并从 MealsProvider 返回。
<MealsContext.Provider> JSX 元素带有 value 属性,这个属性被赋值为 meals 对象,也就是我之前用 useState 变量设置的值。
在文件底部导出 MealsProvider 组件之前,我还将 useMealsListContext 变量设置为 React.useContext() 调用,并将 MealsContext 作为其唯一参数传入。这使得我可以更容易地从 useMealsListContext 变量中解构出 meals 对象。
最后,在 MealsList 组件中,我通过从 MealsProvider 文件导入 useMealsListContext 来访问上下文数据。
深入分析 MealsList 组件
让我们更详细地分解这个组件的工作原理。
首先,我从 useMealsListContext 调用返回的对象中解构出 meals 属性。原始对象有一个名为 meals 的属性,它保存着一个包含三个餐食字符串的数组。
一旦我从该对象中解构出 meals 属性,剩下的就是保存在 meals 变量中的三个字符串的数组。这允许我对 meals 值进行映射,为 meals 数组的每个成员渲染一个 <h2> 元素。
这段代码可能比你遇到的大多数代码都要复杂。如果你需要时间来理解它是如何工作的,请不要担心。只需记住重要的一点:这个设置为你使用 Context API 提供了一个很好的起点。
查看 Counter 组件
最后,让我们检查一下 Counter 组件。
请注意,它获取上下文数据的方式与 MealsList 组件相同。这就是拥有一个集中式状态存储的用处。它允许我直接从任何需要状态的组件中获取状态,而无需进行 prop drilling 或 状态提升。
引入 useReducer Hook
接下来,让我向你展示 useReducer Hook 是如何工作的。让我们继续学习 useReducer Hook。
你可以把它看作是一个功能更强大的 useState。useState Hook 从一个初始状态开始,而 useReducer 除了初始状态外,还会接收一个 reducer 函数。
让我用一个代码示例来说明。
假设我有一个 React 应用,代表我钱包里的金额。初始状态是值 100,而“接一个新客户”这个动作会增加这个值,“给车加油”这个动作会减少它。
我应用了一个 reducer 函数,它接收 state 和 action。
与在 useState Hook 中使用 setState 不同,我使用 useReducer Hook 的 dispatch 方法。dispatch 方法接受一个对象字面量,该对象有一个 type 属性,设置为与 reducer 函数内部定义的行为相匹配的 action.type。
现在,当我在浏览器中与这个应用交互时,我可以通过点击“接一个新客户”按钮来增加金额值,或者通过点击“给车加油”按钮来减少它。
核心代码示例:
// Reducer 函数
function walletReducer(state, action) {
switch (action.type) {
case ‘ADD_CUSTOMER‘:
return state + 10; // 假设接客户赚10元
case ‘REFUEL‘:
return state - 20; // 假设加油花20元
default:
return state;
}
}
// 在组件中使用
function WalletApp() {
const [money, dispatch] = useReducer(walletReducer, 100);
return (
<div>
<p>钱包余额:{money}</p>
<button onClick={() => dispatch({ type: ‘ADD_CUSTOMER‘ })}>
接一个新客户
</button>
<button onClick={() => dispatch({ type: ‘REFUEL‘ })}>
给车加油
</button>
</div>
);
}
总结
在本节课中,我们一起学习了 useContext 和 useReducer 这两个 Hook 如何用于跨多个组件层级更高效地管理状态。
- Context API 提供了一种在组件树中共享数据的直接方式,避免了逐层传递 props 的繁琐。
useContextHook 允许函数式组件订阅 React 上下文,从而轻松访问全局或共享状态。useReducerHook 则为管理更复杂的状态逻辑提供了方案,它通过一个纯函数(reducer)来根据不同的动作(action)更新状态。

通过结合使用这些工具,你可以构建出状态管理更清晰、组件间通信更高效的 React 应用程序。
27:有状态与无状态组件 🧩
在本节课中,我们将学习 React 中有状态组件与无状态组件的核心区别,以及如何根据应用需求做出合适的选择。理解这一概念对于构建高效、可维护的 React 应用至关重要。
生活中很少存在能满足所有需求的完美解决方案。例如,在选择交通工具时,小型汽车通常更省油,但空间有限;而 SUV 能容纳更多乘客,但油耗较高。😊
做出最佳选择始于明确你的具体需求。在编程中选择有状态或无状态组件时,道理也是如此。
通过本课学习,你将能够描述不同类型状态之间的差异,根据给定需求选择最佳类型,并解释 React 的动态特性如何促使结构化决策影响应用复杂度。
核心概念定义 📖
有状态组件与无状态组件的主要区别在于:有状态组件将状态作为内部数据存储,其状态会根据应用构建方式(通常是用户操作的结果)而改变。而无状态组件不存储状态,任何变化都必须通过 props 继承。
上一节我们介绍了基本定义,本节中我们来看看如何在实际开发中做出选择。
选择规则 📋
以下是决定组件应为无状态还是有状态时可参考的规则:

- 使用无状态组件:当你的组件无需维护自身状态即可正常工作时。
- 使用有状态组件:当你的组件需要维护自身状态才能正常工作时。
这听起来可能过于简化,但让我们深入探讨为何这条通用规则已足够。
常见的组件组织模式 🏗️
React 中组织组件的一种常见模式是:让一个有状态组件作为父组件,然后将其状态传递给多个无状态子组件,子组件接收状态并将其渲染到屏幕上。
子组件之所以是无状态的,是因为它们没有自己的状态,仅通过 props 接收父组件传递下来的状态。

请记住,永远不应在子组件中更改 props 的值,因为它们是不可变的。
现在你已了解基本逻辑,让我们通过一个具体示例来分解这种模式的实际应用。
示例分析 🔍

我们从两个组件开始:App 组件和 Child 组件,后者返回一条消息。
在 App 组件中,useState Hook 定义并保存了将作为 props 对象传递给子组件的状态。
App 组件渲染 Child 组件,并以字符串格式将数据作为名为 message 的 prop 传递给它。

有一点需要牢记,并且常被 React 初学者忽视:prop 并不总是必须传递状态。
除了状态,JavaScript 值和函数也可以传递给子组件。这仍然是数据,但它是 prop 数据而非 state 数据。
在 Child 组件中,有一个 H1 元素。该元素的内容将是传入组件的 message prop。
请注意,props 在组件中不会被更改或更新,因为它们是不可变的,意味着无法被改变。由于 Child 组件不存储任何状态,因此它是一个无状态组件。其所有数据都来自传入组件的 props。

App 组件存储了这些状态,这些状态可以通过事件和函数进行更改,因此它是一个有状态组件。
总结 📝
本节课中,我们一起学习了如何根据具体需求,在 React 应用中选择使用有状态或无状态组件。你也观察到,尽管无状态组件不能直接传递状态,但它仍然可以触发更新其他组件状态的操作。掌握这一区别,将帮助你设计出更清晰、更高效的组件结构。
25:模块总结
在本模块中,我们探讨了 React 中的数据与状态概念。现在,是时候回顾一下你获得的关键知识与技能了。
模块二概述了状态与状态管理的概念及实际应用。在这个过程中,你学习了如何处理事件,以及如何动态更改网页内容。
第一课:动态事件及其在 React 中的处理


上一节我们介绍了模块的整体目标,本节中我们来看看第一课的核心内容。第一课是关于动态事件以及如何在 React 中处理它们。
你现在知道,每次点击或轻触按钮、滚动页面时,浏览器都会产生事件。事件是 JavaScript 与 HTML 交互的桥梁。你学习了如何识别事件类型,以及如何使用组件来处理它们。
React 可以处理大多数与 HTML 相同的事件,但 React 的处理方式有所不同。这意味着在运行事件驱动的 React 代码时,你可能会遇到不熟悉的错误。因此,你学习了与事件相关的常见错误及其处理方法。你还学习了在 HTML 和 React 中使用事件处理程序的语法差异,以便能够使用不同类型的语法编写事件处理代码。


另一个重要主题是 React 事件处理程序中嵌入表达式的不同方式。以下是几种主要方法:
- 使用内联匿名 ES5 函数。
- 使用内联匿名 ES6 函数。
- 使用单独的函数声明。
- 使用单独的函数表达式。
之后,你继续学习了所有事件处理概念如何与状态、样式以及三元表达式的使用协同工作。为了测试你处理动态事件的技能,你完成了一个不计分的实验,通过构建一个简单的猜数字游戏来练习事件处理。
第一课到此结束。


第二课:数据与事件
在模块的这一部分,你学习了 React 的数据流层次结构,即数据如何从父组件流向子组件。

React 数据流是单向的。它从根组件开始,可以流向多个嵌套层级,从根组件到子组件,再到孙组件,依此类推。
这种数据流确保了数据通过组件层次结构自上而下移动。它也确保了变化能够在系统中传递。
接下来,你学习了状态及其与组件行为的关系。React 中的所有数据可以分为 props 数据和 state 数据。
- Props 数据 是组件外部接收并使用的数据,组件不能改变它。
- State 数据 是组件内部控制的数据,组件可以改变它。
跨组件跟踪状态可能很困难,这正是 React Hooks 的用武之地。Hooks 是函数。Hooks 的一个关键好处是解决了跨组件不必要的代码重复问题。例如,你可以使用 useState Hook 来跟踪任何类型的数据,它可以是字符串、数字、数组、布尔值或对象。Hooks 的最大好处是为代码提供了可读性和简洁性。
你还学习了一些常见的状态管理方法。例如,如何使用 Context API 来更高效地跨多个组件层级管理状态。你学习了如何使用 Context API 中的 useContext 和 useReducer Hooks 来执行基本的状态管理。
此后,你了解了有状态组件和无状态组件,并学习了如何根据给定需求选择最佳类型。
- 有状态组件 将状态作为内部数据持有,其状态会根据应用的构建方式(通常是用户操作的结果)而改变。
- 无状态组件 不存储状态,任何更改都必须通过 props 继承。
你学习了一些规则来决定组件应该是无状态还是有状态:
- 当你的组件不需要维护自身状态即可工作时,使用无状态组件。
- 当你的组件需要维护自身状态才能工作时,使用有状态组件。
最后,你完成了一个不计分的实验,测试了你在 React 中管理状态的能力。
模块总结与展望
完成本模块后,你现在能够:
- 识别与事件相关的一些常见错误,并使用处理它们所需的语法。
- 使用事件动态更改网页内容。
- 解释 React 中数据的层次化流动。
- 识别数据在有状态和无状态组件中的流动方式。
- 解释状态和状态变化的概念与本质。
- 使用常见方法在 React 中管理状态。

在接下来的模块中,你将学习如何处理链接和路由,以及在 React 中使用资源。这将为你最终使用 React 编写自己的作品集项目做好准备。
非常棒,你在成为 React 开发者的道路上取得了巨大进步。
29:基本导航类型 🧭
在本节课中,我们将要学习网站导航的基本类型,并了解在React中导航过程是如何工作的。
概述

在互联网早期,设计没有真正的标准。这意味着开发者常常进行大量实验。当时存在各种设计和尝试,但最终,Web开发社区确立了一些最佳实践。如今,网络已成为一个成熟的媒介。
从历史看发展
上一节我们提到了Web设计的演变,本节中我们来看看一个有趣的类比。回顾网页布局和导航的历史,很可能类似于历史上其他伟大发明的发展过程。


例如,莱特兄弟于1903年在北卡罗来纳州基蒂霍克首次飞行后,工程师们有几十年的时间都在试验不同的设计。当时,拥有两到三组机翼的飞机是主流。最终,在最初的探索阶段之后,飞机设计趋于稳定,飞机制造的规则和最佳实践得以确立。
就像飞机建造规则的发展一样,在早期Web的实验性年代之后,网页设计和开发社区也确立了一些被广泛接受和预期的设计模式。
现代导航的核心原则

现代网站导航用户界面的重点是实用性。史蒂夫·克鲁格关于用户体验的著名书籍《Don‘t Make Me Think》总结了开发者今天遵循的规则。
作为网页开发者和设计师,遵循已确立的最佳实践是你的责任。例如,方向盘不属于洗衣机,老式电话拨号盘也不属于汽车。同样,你也不应该通过提供那些看起来和感觉上很聪明,但与用户习惯完全不同的导航方式来迷惑你的网站访问者。
常见的导航类型
所以你可能会想,什么是被接受的现代网站导航?它在React中又是如何工作的?

网站导航是任何网站中允许你从单个组件浏览该网站各个页面或链接的部分。这种用户界面模式有几种实用的实现方式。
以下是几种最常见的导航组件:


- 水平导航栏:通常被称为导航栏。
- 垂直导航菜单:也被称为侧边栏导航。
- 隐藏在按钮后的菜单:通常由一个有三条水平线的图标表示,因此被称为汉堡图标或汉堡菜单。
- 页脚导航菜单:通常显示为包含链接的几个视觉列。

所有这些提到的菜单模式通常可以在同一页面的不同部分同时使用。
组合与响应式导航


此外,更复杂的导航用户界面可以在单个组件中包含多种导航方法。例如,你可能有一个带有下拉菜单项的水平导航栏。如果你使用较小的分辨率,导航栏会显示为汉堡菜单图标。
当你点击汉堡菜单图标时,会出现一个移动侧边栏或垂直菜单。
React中的导航机制

好了,现在你已经熟悉了React应用中可用的一些导航类型,让我们来探索它们是如何实现加载不同页面的。
如果你将用React构建的网站导航与用HTML和CSS构建的进行比较,你可能发现视觉上没有区别。虽然视觉上一切看起来都一样,但在代码层面,React处理页面间导航的方式是不同的。
这是因为整个应用都加载在一个单一的<div>中。所以你实际上并不是像使用HTML文件中的超链接那样访问不同的页面。相反,那个单一<div>的内容由React控制,它基于虚拟DOM的变化,要么更新现有视图,要么加载一个全新的视图,给用户一种访问了完全不同URL的印象。
回想一下,在HTML中,开发者可以使用列表来制作导航菜单。每个列表项包含一个指向HTML文件的超链接,然后用一些CSS来为菜单添加样式,例如使用display: inline属性让列表水平显示。
为了帮助说明在React中页面间导航是如何工作的,可以想想电梯内部按钮的工作原理。按下按钮会带你到选定的楼层。类似地,网站上的每个链接在你点击时都会带你到一个不同的页面。
然而,如果你在一个“React电梯”里,就好像电梯从未移动过。相反,当你在这个React电梯里按下一个按钮时,那个指定楼层的整个构造会被注入到这个不可能建筑的一个单一楼层中。
这意味着React本身只负责单个页面的视觉效果,但没有关于页面间导航的概念。然而,这个功能并不是React库本身提供给开发者的。为了实现这种多页面网站的“错觉”,你需要将react-router库添加到你的React项目中。你可以再次使用import语句来添加它,我们很快会学习更多关于如何操作的内容。
总结

本节课中,我们一起学习了网站导航的基本类型,并了解了在React中导航过程的工作原理。关键在于理解React通过控制单一容器内的内容更新来模拟多页面体验,这通常需要借助react-router这样的库来实现。
30:29_导航栏 🧭
在本节课中,我们将学习如何安装和使用 React Router 库,为你的 React 应用设置基本的导航功能。你将学会创建导航栏,并实现不同页面组件之间的切换。
概述
React 默认不支持多页面应用的路由功能。为了实现类似传统网站的多页面导航体验,我们需要借助一个名为 React Router 的第三方库。本节课将指导你完成 React Router 的安装,并利用其核心组件构建一个基础的导航栏。

检查初始应用
首先,我们来查看一个已经准备好的初始应用。这个应用包含两个组件:HomePage 和 AboutMe。

目前,HomePage 组件在页面上显示标题文本 “Welcome to my site”。AboutMe 组件则显示标题文本 “About me”。这两个组件都是 App 组件的子组件。
注意,HomePage 和 AboutMe 都被导入到 App 组件中,并使用 <a> 标签进行引用。然而,仅使用默认的 React 库,这些锚点标签无法按预期工作,因为 React 本身无法模拟多页面网站的行为。
安装 React Router
为了让导航生效,我们需要借助 React Router 库。顾名思义,React Router 为你提供了对组件路由的更多控制权。
我将使用 npm 命令来安装它:


npm i react-router-dom@6


为了确认安装成功,可以检查 package.json 文件。你会在 dependencies 部分找到一个新的条目:"react-router-dom": "^6.3.0"。
配置路由
现在 React Router 已经安装完毕,我们可以开始修复那些失效的链接了。
首先,访问 index.js 文件,并添加一条导入语句来引入 BrowserRouter:
import { BrowserRouter } from ‘react-router-dom’;
导入之后,需要将 App JSX 元素包裹在 <BrowserRouter> 标签内:
<BrowserRouter>
<App />
</BrowserRouter>
完成这一步后,让我们回到 App.js 文件。在这里,我们需要从 react-router-dom 导入 Routes 和 Route:
import { Routes, Route } from ‘react-router-dom’;
接着,需要用不同的代码替换原有的子 JSX 元素。以下是具体的步骤:
以下是配置路由的步骤:
- 配置首页路由:将
HomePage组件替换为<Route path=“/” element={<HomePage />} />。注意,<Route>标签是自闭合的,内部没有子元素。 - 配置“关于我”页面路由:添加类似的一行代码,但在路径的斜杠后加上
about-me:<Route path=“/about-me” element={<AboutMe />} />。 - 包裹路由:这两行代码需要被包裹在
<Routes>标签内。
现在,如果我打开浏览器并输入其中一个路由的确切链接(例如 /about-me),我将只看到导航栏下方显示 AboutMe 组件的内容。反之,如果我从 URL 中移除 about-me(即打开由单个斜杠 / 表示的根路由),那么导航栏下方将显示来自 HomePage 组件的文本。
请注意,我通过将所有的 <Route> 包裹在 <Routes> JSX 元素中来对它们进行分组。同时,<nav> 标签位于 <Routes> 标签之外,这意味着导航栏独立于路由内容之外。
将锚点标签替换为 Link 组件
最后,我们需要将 <a> 标签替换为 React Router 的 <Link> 组件。这能确保点击链接时加载正确的组件,而不是简单地刷新整个页面。
因此,在 App 组件中:
- 首页的
<a>标签变为:<Link to=“/” className=“nav-item”>Home</Link> - “关于我”页面的
<a>标签变为:<Link to=“/about-me” className=“nav-item”>About Me</Link>
同时,需要从 react-router-dom 导入 Link 并保存更改:
import { Link } from ‘react-router-dom’;
现在,当我点击导航栏中的任何一个链接时,浏览器中就会加载正确的内容。


总结
在本节课中,我们一起学习了如何安装 React Router 库,并利用其关键功能(如 BrowserRouter、Routes、Route 和 Link)来创建一个可工作的导航栏。你现在已经掌握了为用户提供应用内导航的基础方法,为学习更高效的导航模式做好了准备。


31:条件渲染 🎬
在本节课中,我们将要学习 React 中的一个核心概念——条件渲染。你将了解如何根据应用的状态或特定条件,动态地决定在用户界面上显示哪些组件。
现在你应该已经熟悉 React 能够动态改变网页内容这一概念。
例如,你已经发现,当一个 React 网站从主页文本切换到“关于我”文本时,它并非跳转到一个新页面,而是在渲染一个组件的同时停止渲染另一个组件。
虽然这很有用,但你需要给 React 非常具体的指令,告诉它应该渲染什么以及何时渲染。当你的组件需要响应诸如点击之类的事件时,这可能会增加另一层复杂性。




幸运的是,有几种编写逻辑的方法可以确保这个过程顺利进行,并减少你的工作量。
在本视频结束时,你将从高层次理解条件渲染,并知道如何使用三元运算符来设置它。

状态与条件渲染
上一节我们提到了动态渲染的概念,本节中我们来看看实现它的基础——状态。
回想一下,状态 是一个组件的内部数据,该组件可以控制或更改它;而 属性 则是组件接收但无法更改的数据。


在应用中,你可以根据特定状态数据是否具有特定值,来有条件地渲染组件。
换句话说,当你在主应用组件中编写渲染逻辑时,你需要引用其他组件的状态。
例如,假设你有一个组件,其中包含一个用于显示侧边栏的按钮代码。


该按钮控制着 toggleSidebar 变量的状态,其初始值设为 false。当按钮被点击时,toggleSidebar 变量的状态被更新为 true,侧边栏组件随之显示。


为了实现这一点,React 利用了 JavaScript 中已有的条件概念和语法。
使用三元运算符

我们已经了解了状态如何驱动渲染,现在让我们看看实现条件渲染的一种简洁语法。
例如,回想一下 JavaScript 中的条件 if 语句,开发者用它来根据某条件是真是假,有条件地运行代码。

为了演示条件渲染的实际应用,我们首先考虑一个示例生产力应用。
根据访问时设备的日期,该应用会显示两条消息中的一条:在工作日,消息显示“完成任务”;在周末,则显示“好好休息”。


作为开发者,你有几种方法可以在 React 中实现此功能。但在本视频中,你将重点学习使用 三元运算符 来编写简化的 if-else 条件。

以下是实现步骤:
-
首先,创建一个名为
CurrentMessage的组件。该组件使用 JavaScript 内置的Date函数和getDay方法,将星期几存储为一个数字,其中 0 代表周日,6 代表下周六。const day = new Date().getDay(); // 返回 0(周日)到 6(周六)的数字 -
接下来,创建两个组件,分别包含要显示的一条消息。我们将这些组件命名为
Workday和Weekend。function Workday() { return <h1>Get it done!</h1>; } function Weekend() { return <h1>Get some rest!</h1>; } -
CurrentMessage组件需要根据getDay函数调用返回的值,来渲染相应的组件。![]()

让我们设置条件来实现这一点。


回想一下,三元运算符接受三个部分:
- 条件:在本例中,使用逻辑与运算符
&&。该条件检查day变量中存储的值是否大于等于 1 且小于等于 5(即周一至周五)。 - 问号符号
?:后面跟着如果条件评估为true时要执行的表达式。在本例中,渲染Workday组件。 - 冒号符号
::代表如果条件评估为false时要执行的代码。如果发生这种情况,则渲染Weekend组件。
在条件中使用逻辑与运算符意味着两个表达式都必须返回 true,才能渲染 Workday 组件,否则将渲染 Weekend 组件。
function CurrentMessage() {
const day = new Date().getDay();
return (
<div>
{day >= 1 && day <= 5 ? <Workday /> : <Weekend />}
</div>
);
}
一个更简单的例子
虽然使用三元运算符是你在 React 代码中会看到的常见模式,但如果你是 React 新手,可能难以理解发生了什么。
所以让我们参考一个使用布尔值的更简单版本。
在这个示例组件 IsItSummerYet 中,变量 summer 被设置为 true。

function IsItSummerYet() {
const summer = true;
return (
<div>
{summer ? "Let's go to the beach!" : "Better stay indoors."}
</div>
);
}
三元运算符的工作原理是:如果问号前的条件为 true,则返回问号后的表达式;否则,返回冒号后的表达式。
因此,由于变量 summer 评估为 true,渲染此组件将返回字符串 “Let‘s go to the beach!”。


总结
本节课中我们一起学习了 React 中的条件渲染。你了解了如何根据组件的状态来动态控制 UI 的显示内容,并重点掌握了使用 三元运算符 来实现简洁的条件渲染逻辑。通过结合 JavaScript 的条件语法与 React 的组件模型,你可以构建出能够智能响应数据和用户交互的动态应用。
32:31_单视图条件更新 🎬
在本节课中,我们将学习如何在 React 中根据特定条件来渲染不同的内容。我们将重点介绍两种核心方法:if 语句和三元运算符,并通过一个动态显示工作日问候和早餐提醒的小应用来实践。

项目目标与初始化 🎯
上一节我们介绍了条件渲染的概念,本节中我们来看看如何具体实现。本应用的目标是:利用本地计算机的时间,根据不同的返回值,在同一个 return 语句中输出各种动态消息,所有内容都包裹在一个 div 元素内。
具体来说,我们将编写一个应用,为工作日显示特定消息。如果是上午,还会询问用户是否吃过早餐。
首先,我使用 Create React App 来构建一个初始的 React 应用。我将通过这个定制化的启动应用中的代码,来演示一些条件渲染的实践。
核心逻辑:变量声明与条件判断 ⚙️
以下是应用组件的核心代码逻辑。我们首先声明几个变量来获取和处理时间信息。
// 获取当前时间
const time = new Date();
// 获取当前是星期几(长格式,如“Monday”)
const day = time.toLocaleString('en-US', { weekday: 'long' });
// 判断当前是否为上午(6点至12点之间)
const morning = time.getHours() >= 6 && time.getHours() <= 12;
// 声明一个变量用于存储动态消息,初始不赋值
let dayMessage;
接下来,我们使用 if 语句来根据 day 变量的值,为 dayMessage 变量赋予不同的字符串内容。
// 根据星期几生成不同的消息
if (day.toLowerCase() === 'monday') {
dayMessage = 'Happy Monday.';
} else if (day.toLowerCase() === 'tuesday') {
dayMessage = 'Tuesday, four days to go.';
} else if (day.toLowerCase() === 'wednesday') {
dayMessage = 'Halfway there.';
} else if (day.toLowerCase() === 'thursday') {
dayMessage = 'Thursday is the new Friday.';
} else if (day.toLowerCase() === 'friday') {
dayMessage = 'Weekend is coming!';
} else {
// 对于周末或其他情况
dayMessage = 'Stay calm and keep having fun.';
}
在 JSX 中进行条件渲染 ✨
现在,我们进入 return 语句部分。在这里,我们将之前定义的变量和条件逻辑整合到 JSX 中,实现最终渲染。
在 return 语句中,我们有一个主标题 h1,用于显示 dayMessage 变量的内容。此外,我们使用三元运算符来条件性地评估 morning 变量。
return (
<div>
<h1>{dayMessage}</h1>
{
morning ? <h2>Have you had breakfast yet?</h2> : ''
}
</div>
);
三元运算符的语法结构如下:
条件 ? 条件为真时渲染的内容 : 条件为假时渲染的内容
在这个例子中,如果 morning 为 true,则渲染一个包含“Have you had breakfast yet?”的 h2 元素;如果为 false,则渲染一个空字符串。
运行结果与动态验证 📱

运行上述代码,输出结果会根据当前时间和星期几动态变化。
例如,在星期一上午,你可能会看到:
Happy Monday.
Have you had breakfast yet?
如果将代码中判断上午的时间上限从 12 改为 19 并保存,那么在下午时段,“Have you had breakfast yet?”这条消息将不再显示。这证明了我们的条件渲染逻辑是有效的。
关键要点总结 📝
本节课中我们一起学习了 React 中条件渲染的两种主要方法:
- 使用
if语句 在渲染逻辑之外进行复杂的条件判断和变量赋值。 - 使用 三元运算符 在 JSX 内部简洁地进行内联条件渲染。

通过结合这两种方法,我们可以构建出能够根据状态(如时间)动态显示不同内容的 React 组件,这大大增强了用户界面的交互性和灵活性。
33:什么是资源及其存放位置 📁
在本节课中,我们将要学习React应用中的“资源”概念,了解它们是什么,以及如何在项目中有效地组织和管理这些资源。资源是让你的应用变得生动有趣的关键元素。
什么是资源?📦


上一节我们介绍了React组件和文本内容,但一个完整的应用远不止于此。本节中我们来看看什么是“资源”。
在React中,资源指的是应用在运行时所需的各种文件。具体来说,资源可以包括:
- 图像
- 样式表
- 字体
- 媒体文件(如视频、音频)


资源 就是你的React应用为了按预期工作而需要访问的所有文件。

例如,你的应用代码可能指定了要显示特定的图片或使用特定的字体。如果这些文件在应用运行时不可用,应用就可能出现意外行为,比如显示占位符或使用默认字体。因此,确保组件能够方便地访问到这些资源至关重要。

资源存放在何处?🗂️


了解了资源的定义后,我们来看看如何组织它们。一个常见的做法是在项目的 src 文件夹内创建一个名为 assets 的文件夹,并将应用的所有资源文件存放在那里。
your-react-app/
├── public/
└── src/
├── assets/ <-- 存放资源文件的推荐位置
│ ├── logo.png
│ └── banner.jpg
├── components/
└── App.js
有些资源也可以放在 public 文件夹中。例如,在默认的React项目安装中,你会发现 favicon.ico 和 logo512.png 等图片默认就存储在那里。



关于资源存储位置的一般规则是:如果你的应用在编译时不需要某个文件,就可以将其放在 public 文件夹中。
举例来说,favicon.ico 存放在 public 文件夹,是因为没有哪个React组件依赖于它。换句话说,React在将你的所有组件编译成可以在本地浏览器中运行的应用程序时,并不需要使用这个文件。

然而,假设你有一张图片需要被导入到某个应用组件中,那么最好将它存储在 src/assets 文件夹里。


如何在组件中使用资源?🖼️

现在你已经熟悉了React中资源的概念和存放位置,让我们探索一下如何在组件中使用它们。
假设你正在开发一个帮助人们在当地领养动物的应用。你已经构建了大部分组件,现在需要将动物收容所提供的动物照片添加到应用中。为此,你已经在React应用的 src 目录下创建了 assets 文件夹,并将收到的图片放了进去。

要将资源文件添加到组件,你首先需要导入它。这可以通过 import 语句完成。


例如,假设这个组件将显示一张猫的图片。以下是导入和使用图片的步骤:
- 导入资源:使用
import关键字,后跟你为资源指定的变量名(例如cat),然后是from关键字和资源的相对路径。 - 在JSX中使用:在组件的返回语句中,使用
<img>标签,并将其src属性设置为用花括号{}包裹的变量名。

以下是具体代码示例:
// 第一步:导入图片资源
import cat from './assets/cat.jpg';
function CatComponent() {
// 第二步:在JSX中使用
return (
<div>
<img src={cat} alt="A cute cat" />
</div>
);
}
export default CatComponent;


除了使用 import 语句,你还可以直接在JSX中通过相对路径引用资源。这可以通过 require 关键字实现,同样需要用花括号 {} 将其包裹起来作为JSX表达式。

function CatComponent() {
return (
<div>
{/* 使用 require 语法直接引用路径 */}
<img src={require('./assets/cat.jpg')} alt="A cute cat" />
</div>
);
}
export default CatComponent;


需要注意的是,使用 require 方法时,你不再需要顶部的 import 语句。这是因为你直接在赋值给 src 属性的JSX表达式中使用了 require 语法。

总结 📝
本节课中我们一起学习了React中“资源”的核心概念。我们了解到资源是应用运行所需的图像、字体等文件,并掌握了在项目中组织资源的最佳实践:将编译依赖的资源放在 src/assets 文件夹,而将独立文件(如网站图标)放在 public 文件夹。最后,我们实践了在React组件中导入和使用图片资源的两种常见方法:使用 import 语句或 require 语法。掌握这些知识将帮助你构建内容更丰富、体验更完整的React应用。
34:使用嵌入式资源 🖼️
在本节课中,我们将学习在 React 应用中显示图像的三种不同方法。你将掌握如何通过 import 语句、require 函数以及直接使用网络图片 URL 来嵌入和使用图像资源。
概述
为了演示如何在 React 应用中处理嵌入式资源,我创建了一个名为 “embedded assets” 的基础应用。在应用的 src 文件夹中,我添加了一个 assets 文件夹,其中又包含一个 images 文件夹,里面存放了一张名为 “Central Park” 的 JPEG 图片。


现在,让我们打开 App.js 文件来查看应用组件的初始代码。代码中包含了一些描述当前任务的文本,即展示三张带有样式的图片。接下来,我将逐一演示三种导入图片的方法。
方法一:使用 import 语句
第一种方法是使用 import 语句。这种方法允许你为导入的图片设置一个变量名。
要导入 “Central Park” 图片文件,我需要输入 import 关键字,然后提供图片的相对路径。在我的例子中,文件位于 assets/images 文件夹内。
import rooftops from ‘./assets/images/CentralPark.jpg‘;
导入后,我将在代码中渲染一个 <img> 标签。我使用 height 属性将图片高度限制为 200 像素,并将 src 属性设置为变量 rooftops,它包含了图片文件的路径。最后,遵循最佳实践,我添加了一个 alt 属性来描述图片内容。
<img height={200} src={rooftops} alt=“Central Park rooftops view” />

保存文件后,可以看到图片已成功在浏览器中显示。这就是使用 import 语句导入图片的第一种方法。
方法二:使用 require 函数

上一节我们介绍了使用 import 语句,本节中我们来看看第二种方法:使用 require 函数。
同样,我先创建一个 <img> 标签,并将高度限制为 200 像素。但这次,我将 src 属性设置为 require() 函数调用的结果。我需要将图片的相对路径作为字符串传递给这个函数。
<img height={200} src={require(‘./assets/images/CentralPark.jpg‘)} alt=“Central Park view from require” />
保存代码后,第二张图片也成功显示。请注意,使用这种方法时,我不需要在文件顶部预先导入图片,只需在 src 属性中直接调用 require 并传入路径字符串即可。
方法三:使用网络图片 URL
前面两种方法都用于加载本地图片文件,现在我们来学习第三种方法:加载托管在互联网上的图片。
这次,我想展示一个来自图片托管网站的随机图片。首先,我创建一个变量来存储这个图片的 URL。
const randomImageUrl = ‘https://images.unsplash.com/photo-...‘;
然后,我可以在 return 语句中添加第三个 <img> 元素,并将 src 属性设置为上面定义的变量 randomImageUrl。
<img height={200} src={randomImageUrl} alt=“Random image from the web” />

总结
本节课中,我们一起学习了在 React 组件中使用图像资源的三种不同方式:
- 使用
import语句预先导入本地图片。 - 使用
require()函数在需要时动态引入本地图片路径。 - 直接使用 网络图片的 URL 来加载远程资源。

每种方法都有其适用场景,你可以根据项目需求灵活选择。
35:音频和视频 🎬
在本节课中,我们将学习如何在 React 应用中处理音频和视频资源。你将了解加载本地媒体文件的基本方法、嵌入第三方内容,以及如何通过 NPM 包生态系统来寻找和使用合适的工具来简化开发流程。
加载本地视频资源
上一节我们介绍了课程概述,本节中我们来看看如何将本地视频文件加载到 React 应用中。
你可以直接使用 HTML5 的 <video> 标签来加载本地视频资源,就像加载图片或其他资源文件一样。你可以在组件的 return 语句上方声明一个变量,然后将其作为 JSX 表达式添加到 src 属性中。


import myVideo from './assets/my-video.mp4';
function VideoComponent() {
return (
<video src={myVideo} controls />
);
}
然而,这种方法在处理某些大型视频内容提供商(如社交媒体和视频分享平台)的内容时可能不会这么简单。
嵌入第三方视频
既然我们已经学会了加载本地文件,接下来探讨如何处理来自外部平台的视频。

在这种情况下,你可以选择实现自己的解决方案。这可能比仅仅在应用中添加一个 <video> 标签要复杂一些,具体实现可能取决于特定社交媒体平台推荐的嵌入其视频和音频资源到网站的方式。通常,你会获得一个可以复制粘贴的代码片段。

但是,具体的实现方式可能会略有不同,因为你可能希望将其添加为一个独立的 React 组件。你可能会问,为什么要将其准备成一个单独的组件?其中一个原因是,你可以通过将特定视频的唯一 ID 作为属性(prop)传递,从而轻松地切换不同的视频。这样你就可以控制视频的输出。
使用第三方 NPM 包
到目前为止,你已经学习了两种向应用添加视频的方法。现在,我们来看看第三种方法:使用第三方 NPM 包来简化添加视频的过程。
NPM 包生态系统非常庞大。如果你访问 npmjs.org 网站,你会发现数百万个不同的包。为了更轻松地找到合适的包,你可以在 npmjs.org 网站的搜索栏中输入 “React video” 来查找一些经常被下载的 NPM 包。
以下是选择合适包时需要考虑的几个关键点:
- 更新频率:检查包的更新频率。一个经常维护且拥有许多贡献者的包通常是可靠的选择。
- GitHub 页面:务必查看包的 GitHub 页面。
- 网络搜索:你也可以在互联网上搜索该包名。
这些行动将帮助你做出明智的决定,选择哪个包。一个符合所有这些标准的包的例子是 react-player 包。访问该包的 GitHub URL:github.com/CookPete/react-player。你会发现 react-player 包拥有超过 6000 个星标(star),大约 115 位贡献者,并且定期维护。
那么,星标(star)的含义是什么?当开发者在 GitHub 上访问任何代码仓库时,他们可以通过点击星标按钮来表达对该项目的赞赏,这被称为“给仓库加星标”。这可能有各种原因,但如果一个项目被加了很多星标,这通常表明它很受欢迎。这些都是好的迹象。因此,如果你需要一个用于 React 视频需求的自定义开源解决方案,你可以选择这个包或类似的包。
总结

本节课中我们一起学习了如何在 React 应用中使用音频和视频资源。我们探讨了三种主要方法:使用 HTML5 <video> 标签加载本地文件、嵌入第三方平台提供的视频代码,以及利用 NPM 和 GitHub 来寻找和评估高质量的第三方 React 包(如 react-player)来简化开发工作。
36:创建音频-视频组件 🎬
在本节课中,我们将学习如何在 React 应用中安装和使用 react-player 包来渲染一个媒体播放器。我们还将了解如何应用一些常见的播放器设置,例如自动播放和初始音量。
概述
首先,我们将从一个使用 Create React App 创建的基础应用开始。目前,这个应用仅渲染一个显示“react player example”的 H1 标题。我们的目标是为这个应用添加一个视频播放器。
安装与导入 React Player

上一节我们介绍了课程目标,本节中我们来看看实现的第一步:安装和导入 react-player 包。
以下是安装和导入 react-player 的步骤:
- 安装模块:在项目根目录下运行命令
npm install react-player。 - 导入模块:在需要使用播放器的组件(例如
App.js)中,使用以下代码导入:import ReactPlayer from ‘react-player‘;
安装完成后,该模块即可在项目中任何导入它的组件中使用。
配置与使用播放器
现在我们已经导入了 react-player,接下来将其作为组件添加到应用中,并进行一些基本配置。
在 App 组件的 return 语句中,我们可以像使用普通组件一样使用 ReactPlayer。同时,我们可以通过属性(props)来配置播放器的行为。
以下是配置播放器的关键属性:
url:用于指定视频源的网络链接。我们需要先定义一个变量来存储这个链接。playing:控制视频是否自动播放。设置为false可禁止页面加载时自动播放。volume:设置播放器的初始音量,范围从0(静音)到1(最大音量)。例如,0.5表示 50% 音量。
一个配置好的组件示例如下:
<ReactPlayer
url={videoUrl}
playing={false}
volume={0.5}
/>
注意:关于所有可用设置的完整列表,请参考
react-player的 GitHub 官方文档。
设置视频链接并测试
上一节我们配置了播放器组件,本节中我们需要为 url 属性提供一个有效的视频链接,并测试整个应用。
首先,定义一个变量(例如 videoUrl)来存储你的视频链接。然后,保存所有文件。

在浏览器中打开你的 React 应用。你应该能看到视频播放器。验证以下功能是否正常工作:
- 视频没有自动播放。
- 初始音量约为最大音量的 50%。
- 可以使用播放器内置的控制按钮进行播放、暂停、调节音量等操作。
如果一切如预期,说明配置成功。
进一步探索与资源
为了加深对 react-player 的理解,你可以进一步探索其功能和设置。
本项目示例的代码托管在 GitHub:github.com/CookPete/react-player。在该仓库的右侧“About”部分,你可以找到一个“Live Demo”链接。
在演示页面中,你可以:
- 选择不同的视频源进行测试。
- 动态调整播放器的各种设置,例如播放速度、是否开启灯箱模式、是否循环播放等。

如果你想了解更多,强烈建议你亲自试验这些设置,并查阅该项目的 GitHub 文档以获取更深入的信息。
总结

本节课中我们一起学习了如何在 React 应用中集成 react-player 包来创建功能丰富的音频视频播放器。我们掌握了从安装、导入、基础配置(如控制自动播放和初始音量)到最终测试的完整流程。通过利用该组件丰富的配置项,你可以轻松地为你的应用添加强大的媒体播放功能。
37:导航、更新与资源模块总结 🎯
在本节课中,我们将总结在 React.js 中设置导航、更新和使用资源模块的关键知识与技能。我们将回顾单页与多页导航的基础、条件渲染,以及如何在 React 项目中有效管理和使用各类资源。
导航基础与路由 🧭
上一节我们介绍了模块的整体目标,本节中我们来看看导航的基础概念。网站导航是任何网站中允许用户从单一组件浏览不同页面或链接的部分。

在现代网站导航中,用户界面的核心在于功能性。

以下是常见的导航组件示例:
- 水平导航栏(Navbar)
大多数网站拥有更复杂的导航界面,会在单个组件中结合多种导航方式。
- 例如,将水平导航栏与下拉菜单项结合使用。

你了解到,使用 React 构建的网站导航与使用 HTML 和 CSS 构建的网站存在关键区别。React 驱动的网页称为单页应用程序(SPA),整个应用加载在一个单一的 div 中,因此用户并非像在 HTML 文件中使用超链接那样访问不同的页面。
这是因为单页应用程序(SPA)有其特殊的锚标签和链接实现方式,营造出加载不同页面的错觉。
为了实现多页网站的效果,你需要将 React Router 库添加到你的 React 项目中。你练习了使用它来为网页创建和实现基本导航路由。
在本次未评分的实验中,你使用了名为“Navbar”的课程项目代码,并需要向现有代码中添加另一个链接。
条件渲染 ⚙️

在掌握了基础导航后,React 需要能够动态更改网页内容,这就引入了条件渲染的概念。
作为本部分内容,你学习了如何使用三元运算符来设置条件渲染,以编写简化的 if-else 条件语句。
示例代码:
{isLoggedIn ? <WelcomeMessage /> : <LoginPrompt />}
在 React 中使用资源 🖼️
上一节我们探讨了如何根据条件改变内容,本节中我们来看看如何在 React 应用中管理和使用各类资源文件。
资源是指你的应用在运行时需要的文件,如图像、样式表、字体、视频或音频。你学习了开发者如何在 React 中组织资源,以及一些导入资源文件的常用方法。
以下是组织资源的常见方式:
- 在
src文件夹内添加一个assets文件夹,并将所有应用资源存放在那里。 - 一些资源也可以放置在
public文件夹中。
资源存储的一般规则是:如果你的应用在没有该资源的情况下也能编译,则可以将其保存在 public 文件夹中。


在本模块的这一部分,你学习了如何使用嵌入式资源,了解了嵌入资源的优点和缺点,以及在使用资源密集型应用时固有的权衡。
在本次课程的第一次未评分实验中,你学习了如何添加已存在于 src 文件夹的 assets 文件夹中的图像。更重要的是,你还进一步学习了如何在应用中使用音频和视频资源。
处理音频与视频资源 🔊🎬
为了在应用中集成多媒体,你需要掌握处理音频和视频资源的方法。

你学习了在处理音频和视频文件时,如何寻找合适的 React 包来使用。
以下是向应用添加视频的三种方法:
- 使用
<video>元素提供本地视频。 - 使用嵌入的第三方视频。
- 使用第三方 NPM 包来简化向应用添加视频的过程。
此外,你学习了如何决定使用哪个软件包。
- 检查更新频率。
- 查看包的 GitHub 页面。
- 在互联网上搜索该包名。


为了帮助你熟悉使用此类包,你学习了如何安装 react-player 包,并使用它在 React 应用中渲染媒体播放器。你还学习了如何应用自动播放和起始音量等常见设置。
本模块最后一个未评分实验是完成一个已构建的应用,该应用的界面允许访问者通过按下按钮来播放鸟叫声。
总结与展望 🏁

现在你已经完成了本模块的学习,你应该能够:
- 描述以单页和多页导航为重点的 React 导航设计。
- 以导航栏的形式创建并实现路由。
- 演示多个组件的条件实现和渲染。
- 根据嵌入式或引用式资源,解释 React 项目的文件夹结构。
- 演示如何使用引用路径操作图像资源。
- 在 React 中使用音频和视频等媒体资源。
恭喜!你现在已经涵盖了 React 中的大部分基本概念,可以完成本模块的测验并查看本模块的额外资源了。
在本课程结束前,只剩下一个模块。在下一个模块中,你将通过构建一个计算器应用来完成一个 React 迷你项目,从而应用你所学的知识。
38:37_React 基础课程回顾 📚
在本节课中,我们将回顾你在React基础课程中学到的所有关键主题。我们将系统地梳理从React简介到组件、数据状态管理,再到导航与资源使用的核心知识,帮助你巩固所学内容,为接下来的评估做好准备。
开篇介绍 🎯


在课程的开篇,你学习了React的简介。你了解了React是什么,熟悉了React在现实世界中的应用场景,并掌握了如何高效利用课程内容以确保达成学习目标。

第一模块:React组件 🧩

上一节我们介绍了课程概览,本节中我们来看看React的核心构建块——组件。


在这一课中,你学习了如何解释组件、其架构以及它们是如何被渲染的。
以下是你在组件部分掌握的核心技能:
- 解释组件及其架构。
- 使用JSX创建和更新组件。
- 描述React项目的文件夹结构及其对开发的益处。
- 演示如何导入组件。

深入使用组件 🔧

在学习了组件基础后,下一课我们更深入地探索了组件的使用方法。

这意味着你现在可以:
- 声明带props的函数式组件,并将其传递给其他组件。
- 使用JSX为组件添加样式。
- 保存样式以便后续复用。
- 在属性中嵌入JavaScript表达式。

第二模块:数据与状态 📊
接下来,我们开始了专注于React中数据和状态角色的第二模块。
在本模块的第一课,你学习了动态事件及其处理方法。
你现在可以:
- 识别最常见的事件类型。
- 在代码中使用一些常用的事件处理器。
- 使用不同类型的语法编写事件处理器。
- 演示对用户触发事件概念的理解。
随后,你学习了数据与事件之间的关系。



因此,你现在可以:
- 描述数据如何在父组件和子组件之间流动。
- 解释React中状态(state)的概念及其管理方式。
- 了解Hooks,并知道可以使用它们来扩展状态的功能。
- 识别有状态组件和无状态组件的一些常见用例。



第三模块:导航、更新与资源 🗺️
在第三模块中,你学习了React中的导航、更新和资源。
通过完成第一课,你现在可以:
- 识别网站上的基本导航类型。
- 在React Router库中创建基本的导航路由。
- 解释如何有条件地渲染组件。
- 使用几种不同的方法来设置条件渲染逻辑。
在本模块的最后一课,你探索了React中的资源及其使用方法。



你现在可以:
- 解释什么是资源以及存储它们的最佳方式。
- 使用嵌入在数据文件中的资源。
- 在组件中使用音频和视频资源。

总结 🎉


本节课中,我们一起回顾了React基础课程的全部关键内容。我们从React简介开始,逐步深入到组件创建、数据流与状态管理,最后涵盖了导航和资源使用。你已经掌握了构建React应用的基础知识。课程回顾到此结束,现在是时候在分级评估中尝试运用你所学的知识了。祝你顺利!
39:38_范例-构建计算器应用程序
在本节课中,我们将学习如何使用 React 构建一个简单的计算器应用程序。这个应用将能够执行加法、减法、乘法和除法运算。我们将从修复一个不完整的代码开始,逐步添加缺失的功能,最终完成一个功能齐全的计算器。


修复初始编译问题
首先,我们有一段不完整的代码,它在编译时会遇到问题。第一个问题是 useRef 未定义。为了解决这个问题,我们需要从 React 中导入 useRef 钩子。
import { useRef } from 'react';
导入后,我们保存并重新编译代码。
接下来,我们遇到了第二个问题:useState 钩子也未定义。同样地,我们需要导入它。
import { useState, useRef } from 'react';
再次保存并重新编译后,所有的编译问题都应该得到解决。现在,应用的结构已经存在,但它目前只能执行加法运算。我们需要为其添加减法、乘法和除法的功能。
构建运算功能函数
上一节我们修复了代码的编译问题,本节中我们来看看如何构建核心的运算功能。我们将以现有的加法函数为模板,来创建其他运算函数。
以下是加法函数的原始代码:
function plus(e) {
e.preventDefault();
setResult((result) => result + Number(inputRef.current.value));
}
这个函数执行了三个关键操作:
- 阻止表单的默认提交行为。
- 调用一个用于更新状态变量的函数。
- 将当前输入框的值加到之前的结果上。
我们可以将此代码作为模板,复制并粘贴到其他已创建的函数框架中,然后进行修改。
- 减法函数 (
minus): 将更新逻辑从加法改为减法。function minus(e) { e.preventDefault(); setResult((result) => result - Number(inputRef.current.value)); } - 乘法函数 (
times): 将更新逻辑改为乘法。function times(e) { e.preventDefault(); setResult((result) => result * Number(inputRef.current.value)); } - 除法函数 (
divide): 将更新逻辑改为除法。function divide(e) { e.preventDefault(); setResult((result) => result / Number(inputRef.current.value)); }
实现重置功能
除了基本运算,一个完整的计算器还需要重置功能。我们有两个重置函数:resetInput 用于清空输入框,resetResult 用于将计算结果归零。
以下是这两个函数的实现方式:
resetInput函数: 此函数阻止默认事件后,直接通过引用将输入框的值设置为0。function resetInput(e) { e.preventDefault(); inputRef.current.value = 0; }resetResult函数: 此函数采用另一种方法,它通过将之前的结果值乘以0来将其归零。这展示了setState函数如何使用前一个状态值。function resetResult(e) { e.preventDefault(); setResult((prevValue) => prevValue * 0); }
完善UI与交互
功能函数准备就绪后,我们需要完善用户界面,将结果显示出来并添加触发这些功能的按钮。
首先,在 App 组件的 return 语句中,我们需要添加一个地方来显示当前的计算结果。我们可以通过一个 JSX 表达式来实现。
<div>当前总数:{result}</div>
接下来,我们需要添加按钮来触发我们编写的各个函数。以下是需要添加的按钮组件示例,每个按钮都绑定到对应的 onClick 事件。
<button onClick={plus}>+</button>
<button onClick={minus}>-</button>
<button onClick={times}>*</button>
<button onClick={divide}>/</button>
<button onClick={resetInput}>重置输入</button>
<button onClick={resetResult}>重置结果</button>
测试应用程序
所有代码修改完成后,保存文件。现在可以测试我们的应用程序了。
- 在输入框中输入数字
2,然后点击 + (加) 按钮。结果显示会从0变为2。再次点击 + 按钮,结果会更新为4。 - 在输入框中输入数字
1,然后点击 - (减) 按钮。结果会从4变为3。 - 在输入框中输入数字
10,然后点击 * (乘) 按钮。结果会从3变为30。 - 在输入框中输入数字
6,然后点击 / (除) 按钮。结果会从30变为5。 - 点击 重置输入 按钮,输入框的值会被设置为
0。 - 点击 重置结果 按钮,计算结果会被清零,重新变回
0。
至此,代码已完成,应用程序的功能符合所有要求。

总结

本节课中我们一起学习了如何使用 React 构建一个功能完整的计算器应用。我们从修复基础的导入错误开始,然后以加法函数为模板,逐步创建了减法、乘法和除法函数。我们还实现了分别用于清空输入框和重置计算结果的功能。最后,我们通过 JSX 将结果展示在界面上,并绑定了按钮的点击事件来触发对应的功能函数。通过这个练习,我们实践了 React 中状态管理 (useState)、DOM 引用 (useRef)、事件处理以及组件渲染的核心概念。
40:恭喜你完成了 React 基础 🎉
在本节课中,我们将回顾你在 React 基础课程中的学习成果,总结你已掌握的技能,并展望接下来的学习路径。
你已经完成了这门 React 基础课程。你付出了巨大的努力才走到这一步,并在此过程中掌握了许多新技能。你在 React 的学习之旅中取得了巨大的进步。
在实验项目中,你能够展示部分所学知识以及你的实用 React 基础技能。
课程成果总结 🏆

上一节我们介绍了你的学习历程,本节中我们来看看具体的成果。
完成本课程后,你现在应该能够创建一个简单的计算器应用。

你的计算器应能执行四种基本数学运算。
以下是计算器应实现的核心功能:
- 加法:
a + b - 减法:
a - b - 乘法:
a * b - 除法:
a / b
这个实验不仅为你提供了所需的实践经验,还带来了另一个重要的好处。你现在拥有了一个用 React 构建的、功能齐全的计算器,可以将其纳入你的作品集。这有助于向潜在雇主展示你的技能。
这不仅向雇主表明你具有自我驱动力和创新精神,也充分展现了你个人的能力以及你新获得的知识。
后续学习步骤 🚀

在课程中,你接触了几个关键主题,它们能帮助你继续学习之旅。你可能已经意识到,你还有更多需要学习的内容。

随着你在项目中的持续学习,你将不断发展你的技能组合。
因此,如果你觉得本课程有帮助并希望了解更多,何不注册下一门课程呢?一旦你成功完成了本项目的所有课程,你将获得一份证书,可用于验证你的成就。


根据你的目标,在获得此证书后,你可以选择深入学习高级的、基于角色的认证,或参加其他基础课程。
证书的价值 📜
完成整个项目后获得证书,是构建你资质档案的一种方式。该证书也证明了你对终身学习的承诺,是行业认可的、你技术技能增长的证据。丰富你的资质档案意味着增加你的职业前景并打开新的大门。


总结
本节课中我们一起学习了 React 基础课程的完结总结。我们回顾了你已成功构建的计算器应用及其功能,探讨了将项目纳入作品集的价值,并规划了通过后续课程和认证继续提升技能、开拓职业道路的下一步。感谢你一同踏上这段探索之旅。祝你未来一切顺利。😊
41:设置 VS 代码 🛠️
在本节课中,我们将学习如何为 JavaScript 开发设置必要的软件环境。具体来说,我们将安装 Visual Studio Code 编辑器、Node.js 运行时以及一个名为 Code Runner 的扩展。完成设置后,你将能够创建并运行一个简单的 JavaScript 文件。
下载与安装 VS Code

首先,我们需要获取并安装代码编辑器 Visual Studio Code。

- 打开浏览器中的搜索引擎,搜索“VS code”或“Visual Studio Code”。
- 点击第一个链接,进入官方网站
code.visualstudio.com。 - 在网站主页上,选择“Download for Windows”按钮。
- 下载完成后,点击文件开始安装过程。
- 阅读并同意许可协议,然后点击“下一步”。
- 接受默认的安装目标位置和开始菜单文件夹。
- 在“选择其他任务”页面,建议勾选以下选项:
- 创建桌面快捷方式
- 将“通过 Code 打开”操作添加到 Windows 文件资源管理器上下文菜单
- 将“通过 Code 打开”操作添加到 Windows 文件资源管理器目录上下文菜单
- 点击“下一步”,然后点击“安装”。
- 安装完成后,勾选“启动 Visual Studio Code”并点击“完成”。
VS Code 程序将在新窗口中打开,并显示“开始”页面。
安装 Code Runner 扩展

上一节我们安装了 VS Code 编辑器,本节中我们来看看如何为其添加一个能快速运行代码的扩展。
- 在 VS Code 窗口的最左侧,点击最底部的图标以打开扩展面板。
- 在扩展面板的搜索框中,输入“code runner”进行搜索。
- 从搜索结果中找到“Code Runner”扩展,点击“安装”按钮。

安装 Node.js
为了能够运行 JavaScript 代码,我们还需要安装 Node.js 运行时环境。

- 返回浏览器,搜索“node.js”。
- 访问官方网站
nodejs.org。 - 点击直接的下载链接,确保下载的是 Windows 版本。
- 下载完成后,点击“打开文件”启动 Node.js 安装向导。
- 点击“下一步”,接受许可协议。
- 点击“安装”开始安装过程。
- 安装完成后,点击“完成”关闭安装向导。
配置 VS Code 与运行代码
现在,所有必要的软件都已安装完毕。让我们回到 VS Code 进行最终配置并运行我们的第一段 JavaScript 代码。
首先,确认 Code Runner 扩展已安装完成。你可以在扩展面板看到“此扩展已在全局启用”的提示信息。
接下来,我们创建一个 JavaScript 文件并运行它:
- 在 VS Code 左侧,双击“资源管理器”区域中的“文件”标签页,或通过“文件”菜单选择“新建文件”。
- 点击右下角的“选择语言模式”,然后从列表中选择“JavaScript”。你也可以在搜索框中输入“JS”来快速筛选。
- 关闭扩展面板,点击顶部菜单栏的“查看”,选择“终端”以打开终端面板。
- 如果需要清空终端,可以输入命令
clear并按回车键。 - 将鼠标悬停在终端面板的标题栏上,点击并拖动到窗口右侧,将其停靠在右边。
- 现在,在新创建的 JavaScript 文件中,输入以下代码:
在这段代码中,console.log('Hello World');console.log()是一个 JavaScript 函数,用于将括号内的信息输出到控制台。 - 要运行这段代码,可以点击编辑器右上角的“播放”图标(由 Code Runner 提供),或者使用快捷键
Ctrl + Alt + N。 - 运行后,“Hello World”这行文字将出现在右侧的输出面板中。

总结
本节课中,我们一起学习了如何为 Windows 操作系统设置 JavaScript 开发环境。你掌握了安装 Visual Studio Code 编辑器、Node.js 运行时和 Code Runner 扩展的步骤。同时,你也学会了在 VS Code 中创建新的 JavaScript 文件,并使用 Code Runner 扩展来运行文件。最后,我们通过 console.log(‘Hello World’) 这行代码,实践了如何向控制台输出信息。现在,你的开发环境已经准备就绪,可以开始编写 JavaScript 代码了。
42:0_简介
概述 📋

在本节课中,我们将要学习《Meta前端开发(React/UI、UX/毕业项目/代码审查)》系列课程中,关于高级React概念的简介。本课程是React基础课程的延续,旨在深入探讨更复杂的React主题。

课程内容概览
上一节我们介绍了本课程的整体定位,本节中我们来看看课程将涵盖的具体模块和核心技能。

以下是本课程将学习的几个关键模块及其主要内容:

-
模块一:列表、表单与状态管理
- 学习如何使用
map方法渲染和转换列表。 - 理解列表转换中不可或缺的
key标识符。 - 深入学习受控组件,重点是创建受控表单组件并实现一个反馈表单。
- 复习props和state的知识。
- 探索在某些情况下,React Context作为局部状态管理的可行替代方案。
- 通过实际例子学习使用Context API来管理React应用中的全局状态。
- 学习如何使用
-
模块二:深入理解Hooks
- 学习React Hooks的用途和使用目的。
- 掌握在React中使用Hooks的规则。
- 学习如何在React中构建自定义Hooks。
- 例如,你将学习
useState、useEffect和useReducer等Hooks的用途及使用方法。
-
模块三:高级JSX概念与性能优化
- 介绍与React中使用的JSX相关的各种高级概念,例如组件和元素。
- 探索JSX中的
children类型。 - 发现组件组合和
children属性的重要性。 - 学习如何在JSX中操作
children以及React中的扩展运算符。 - 介绍创建高阶组件和渲染属性的过程与目的,从横切关注点开始。
- 涵盖React应用性能优化、测试与调试等重要主题。
- 学习使用React测试库编写集成测试,重点测试表单行为并探索测试的实际应用。


在整个学习过程中,你将通过一个名为“Little Lemon”的餐厅实例,实践基于理论概念的实际例子,并参与活动来测试你的知识和技能。
在最后一个模块中,你将有机会在一个实验项目中展示你的学习成果和高级React技能,即编写你自己的作品集应用。你还将通过一个分级评估来展示你对这些主题的掌握程度。
总结 🎯
本节课中我们一起学习了高级React课程的整体框架和内容规划。本课程将从列表渲染、表单处理、状态管理进阶到Hooks的深入使用,再到高级JSX模式、性能优化与测试,最终通过实践项目巩固所学。希望你对即将开始的学习旅程充满期待,让我们开始吧。
43:React与职业机会 🚀
在本节课中,我们将探讨React框架的流行度、其应用场景以及相关的职业机会。我们将了解为什么React如此重要,它如何被用于构建现代Web和移动应用,以及掌握这项技能对开发者职业生涯的意义。
React的流行度与行业地位
上一节我们介绍了课程背景,本节中我们来看看React在行业中的实际地位。
“React”这个关键词的搜索热度超过了“橙汁”和“可再生能源”等词汇。因此,React已成为一个非常广泛流行的框架,并且是当前许多公司在招聘新工程师时要求掌握的技能。
我的名字是Mortra,是Meta公司西雅图办公室的一名软件工程师。我在Meta职业生涯中构建的几乎所有东西都使用了React。如果你正在使用任何Meta的产品,无论是Facebook还是Instagram,你所点击的按钮、菜单以及进行的交互操作,其背后的事件和组件很可能都是由React框架处理的。我在Meta的职业生涯就是开发这类组件。无论你是在网页上发布照片还是发表评论,所有这些交互都是由幕后的React组件驱动的。


React带来的开发体验变革
了解了React的普及程度后,我们来看看它给开发者带来了怎样的改变。
当我最初发现React时,我认为它非常酷,它让构建Web应用程序变得容易得多。这个框架极大地简化了我作为工程师的工作,使用它进行开发非常直观,并且更不容易出错。观察趋势图可以发现,在2018年,React的搜索量上升并超过了之前最流行的Web框架JQuery,成为了最热门的搜索关键词。因此,用React来替代一些跟不上行业发展速度的旧框架是合理的。


React的技术生态与扩展应用
React不仅改变了Web开发,其技术栈和应用领域也在不断扩展。

创建React组件使用的主要语言是JavaScript。实际上,有多种风格的JavaScript可以与React一起使用。在Meta,我们使用Flow风格的JavaScript,这能确保我们在开发React组件过程中的类型安全。但构建React组件还有其他方式,行业中一种流行的方法是使用TypeScript。

除了在Web上使用React组件的不同方式,你实际上还可以使用React来开发移动端组件,例如用于Android和iOS平台。最近,React正在发布一种开发VR(虚拟现实)组件的方式,让人们能够使用与Web和移动端开发相同且一致的框架来开发虚拟现实应用。其理念是简化开发流程,让开发者能够创建复杂的虚拟现实应用,同时利用React的简洁性和友好性。这正是推动React在不同行业领域创建多种框架的动机。
掌握React的职业建议与总结
最后,我们来谈谈学习React对职业发展的价值,并给出一些学习建议。
目前,React已成为一项不可或缺的技能,几乎你构建的每一个应用和申请的每一份工作都可能需要它。掌握这项知识将对你的职业生涯大有裨益,同时学习使用这样的框架也会让你的开发工作变得更轻松。
以下是我给即将踏入React世界的你的一些建议:
- 保持坚持:起初它可能看起来有点复杂或令人不知所措,有些概念可能难以理解,但请坚持下去。
- 提升价值:学习这些原则会让你的工作更轻松,让你在未来对雇主更具吸引力。
- 增强能力:使用这个框架将极大地提升你构建可扩展Web应用的能力。
所以,请继续前进,不要让React中一些令人困惑的部分阻碍你。


本节课总结:我们一起学习了React框架在当今开发领域极高的流行度和行业需求,了解了它在Meta等公司产品中的核心作用。我们探讨了React如何简化开发、其主流的JavaScript技术变体(如Flow和TypeScript),以及它从Web扩展到移动端乃至VR领域的广阔生态。最后,我们明确了学习React对开发者职业生涯的重要性,并获得了坚持学习、克服困难、最终提升自身价值的实用建议。
44:设置 VS Code(可选)🛠️
在本节课中,我们将学习如何为JavaScript开发设置必要的软件环境。具体来说,我们将安装Visual Studio Code编辑器、Node.js运行时以及一个名为Code Runner的扩展插件。完成设置后,你将能够创建并运行一个简单的JavaScript文件。
下载与安装 VS Code

首先,我们需要获取并安装代码编辑器Visual Studio Code(简称VS Code)。

- 打开你的浏览器,使用搜索引擎搜索“VS Code”或“Visual Studio Code”。
- 点击搜索结果中的第一个链接,进入官方网站
code.visualstudio.com。 - 在网站主页上,点击“Download for Windows”按钮。
- 下载完成后,点击下载的文件开始安装。
- 阅读并同意许可协议,点击“Next”。
- 接受默认的安装路径和开始菜单文件夹。
- 在“选择其他任务”界面,建议勾选以下选项:
- 创建桌面快捷方式
- 将“通过Code打开”操作添加到Windows文件资源管理器上下文菜单
- 将“通过Code打开”操作添加到Windows文件资源管理器目录上下文菜单
- 点击“Next”,然后点击“Install”开始安装。
- 安装完成后,勾选“启动Visual Studio Code”并点击“Finish”。
此时,VS Code将会启动并显示“开始”页面。
安装 Code Runner 扩展
上一节我们成功安装了VS Code,本节中我们来看看如何为其添加一个能快速运行代码的扩展。
- 在VS Code窗口的最左侧,点击最底部的图标(方块形状)以打开“扩展”面板。
- 在扩展市场的搜索框中,输入“Code Runner”进行搜索。
- 在搜索结果中找到“Code Runner”扩展,点击“Install”按钮进行安装。

安装过程中,扩展面板会显示进度。安装完成后,通常会看到“此扩展已在全局启用”的提示信息。

安装 Node.js
为了能够运行JavaScript代码,我们还需要安装Node.js运行时环境。
- 返回浏览器,搜索“Node.js”。
- 访问官方网站
nodejs.org。 - 点击直接的下载链接,确保下载的是Windows版本。
- 下载完成后,点击“打开文件”启动Node.js安装向导。
- 点击“Next”,阅读并接受许可协议。
- 点击“Install”开始安装过程。
- 安装完成后,点击“Finish”关闭安装向导。

创建并运行你的第一个JavaScript文件
现在所有必要的工具都已就绪,让我们来创建并运行一个简单的JavaScript程序。
以下是配置工作区和编写代码的步骤:
- 在VS Code中,点击左侧活动栏的“资源管理器”图标(或使用快捷键
Ctrl+Shift+E)。 - 点击“打开文件夹”选择一个项目文件夹,或者直接点击“新建文件”图标。
- 将新文件命名为
hello.js。VS Code通常会根据.js后缀自动识别为JavaScript文件。如果没有,你可以点击右下角的“选择语言模式”,然后搜索并选择“JavaScript”。 - 在新文件中输入以下代码:
这行代码的作用是向控制台输出一条信息。console.log('Hello World');console.log()是JavaScript中的一个内置函数,用于在控制台(通常是终端或开发者工具)打印信息。 - 要运行这段代码,你可以点击编辑器右上角的“播放”按钮(由Code Runner扩展提供),或者使用快捷键
Ctrl+Alt+N。 - 代码运行后,输出结果会显示在VS Code底部的“输出”面板中。你应该能看到“Hello World”这行文字。
调整工作区布局(可选)
为了使开发体验更舒适,你可以调整VS Code的界面布局。
- 点击顶部菜单栏的“查看”,选择“终端”,或使用快捷键
Ctrl+`打开集成终端。 - 若要清理终端屏幕,可以输入命令
cls并按回车键。 - 你可以拖动终端面板的标题栏,将其停靠在窗口的右侧。
- 同样,你也可以拖动面板之间的分隔线来调整它们的大小。

本节课中我们一起学习了如何为Windows系统设置完整的JavaScript开发环境。你成功安装了Visual Studio Code编辑器、Node.js运行时以及Code Runner扩展。现在,你能够创建新的JavaScript文件,并使用Code Runner扩展来运行代码,同时也了解了 console.log() 函数的基本用途。准备工作已完成,可以开始愉快的编程之旅了。🚀
45:网格布局
概述
在本节课中,我们将要学习CSS网格布局。这是一种强大的CSS布局规范,能够帮助开发者以网格形式排列网页元素。我们将了解其基本概念、优势以及如何在实际项目中应用它来创建响应式且结构清晰的界面。
网格布局简介
上一节我们介绍了前端开发中布局的重要性。本节中我们来看看一种专门用于解决复杂布局问题的工具:CSS网格布局。
CSS网格布局是一种CSS规范,它允许你将项目排列在网格中。在过去,实现复杂的网格布局是一项非常困难的任务。使用网格布局,你可以直接指定某个项目位于网格的特定部分、特定的行或列。
核心概念公式:display: grid;
网格布局的优势
了解其定义后,我们来看看为什么网格布局如此有用。它主要解决了响应式设计和布局计算两大难题。
CSS网格布局能帮助你构建更具响应性的应用程序。与之前需要大量JavaScript计算来获取图像宽度、高度和定位的布局方法相比,网格布局简化了这一过程。

使用CSS网格,通常只需一行代码就能实现高性能、响应式且可靠的布局。
网格布局的实际应用
理解了优势,现在让我们看看它在实际网站构建中是如何被使用的。
在构建网站时,你通常会将多种不同的布局组合在一起。例如,你可能在某个位置使用基于表格的响应式网格布局,而在另一个位置使用包含嵌套网格的其他布局。
学习如何排列项目至关重要,这使你能够以灵活的方式构建应用并定义其布局。例如,你可以指定一个元素在作为页脚时出现在屏幕底部,或作为标签栏时出现在屏幕顶部。
学习网格布局的意义

那么,为什么值得花时间学习网格布局呢?以下是几个关键原因。
大多数网站都可以简化为网格结构。深入理解一种布局的工作原理,能够让你更容易地掌握未来可能需要的其他布局技术。
从一种布局开始并扎实掌握,是学习其他布局引擎的绝佳基础。即使你未来想从Web开发扩展到iOS或Android平台,它们也拥有各自的布局引擎,此时你已建立的核心概念将大有裨益。
总结
本节课中我们一起学习了CSS网格布局。我们了解到它是一种强大的CSS规范,能够简化复杂网格结构的创建,并提升应用的响应性。更重要的是,掌握网格布局为你打下了一个坚实的基础,你可以凭借这个基础去学习其他布局规范,并构建下一代应用程序。
希望你现在对如何在应用中使用网格布局及其用处有了良好的理解。请记住,它只是浏览器中可用的更广泛的布局规范集合中的一员。
46:在 JavaScript 中转换列表 📋
在本节课中,我们将学习如何使用 JavaScript 中的 map 方法来处理和转换列表数据。列表是几乎所有应用程序中常见的元素,掌握如何操作它们是前端开发的重要技能。
你是否经常浏览各种应用程序?例如,你可能使用过点餐应用,滚动浏览不同的菜单以寻找喜欢的食物。这类列表在应用中极为常见,因此了解如何在 JavaScript 中操作它们至关重要。在向用户展示最终列表之前,你很可能需要转换其中的各种元素。
理解数据转换的需求
上一节我们介绍了列表在应用中的普遍性。本节中,我们来看看为何需要对数据进行转换。
想象一家名为“小柠檬”的餐厅希望展示其热门甜点列表。在 JavaScript 中,列表通常表现为数组。这些数组可以包含任何类型的数据,但每个元素最常见的是对象。
假设“小柠檬”使用外部服务来查询用户最常请求的甜点列表。然而,从第三方获取数据时,你通常会得到比所需更多的信息,并且数据的格式由第三方决定。

这意味着你可能需要编写更多代码来处理数据,以仅提取你需要的信息。这时,map 方法就派上用场了,它可以帮助你忽略屏幕上不想显示的内容,只提取用户关心的数据。接下来,让我们探索如何使用 map 方法来转换这个甜点列表。

认识 JavaScript 的 map 方法
在 JavaScript 中处理任何类型的项目列表时,你需要使用数组类型。JavaScript 提供了多种可与数组一起使用的方法来执行各种操作。要执行转换操作,你必须使用 map 方法。
回到“小柠檬”的例子,假设你有一个包含其最受欢迎甜点的列表,存储在一个名为 data 的变量中。每个甜点具有以下属性:id、title、image、description 和 price。
在这种情况下,你希望展示一个非常简单的甜点列表,其中包含一个名为 content 的属性。你可以通过合并 title 和 description 以及美味菜肴的 price 来创建这个属性。
使用 map 方法转换数据
首先,我将定义一个新变量,因为 map 方法总是返回一个新数组。让我们称这个新数组为 topDesserts。
接下来,我将对原始 data 数组应用 map 方法。目前,我将按原样返回数据,以便你可以检查 map 转换的基本结构。
以下是转换步骤的详细说明:
- 创建新数组:
map方法会遍历原数组的每个元素,并返回一个新数组。 - 定义转换逻辑:在
map的回调函数中,指定如何转换每个元素。 - 构建新对象:我希望新项目具有两个属性。第一个是
content,它将是title和description的组合。让我们使用破折号字符来分隔两者。第二个是price,我将按原样传递它。 - 输出结果:最后,我将使用
console.log来展示我创建的新列表包含我最初预期的形状或格式。
以下是实现此转换的代码示例:
const topDesserts = data.map(dessert => {
return {
content: `${dessert.title} - ${dessert.description}`,
price: dessert.price
};
});

console.log(topDesserts);



转换后的列表如下所示:


总结
本节课中,我们一起学习了如何使用 JavaScript 中的 map 方法来转换数据。这是一个简单而强大的工具,在处理来自外部提供者的数据时,你会发现自己会经常使用它。当用户在你的应用中体验到轻松导航和消费信息时,他们会感谢你所做的努力。
47:渲染简单列表组件 📋
在本节课中,我们将学习如何使用 React 将一组数据项转换并渲染为一组 React 组件。我们将通过一个餐厅甜点列表的例子,掌握 map 函数与 JSX 语法结合使用的核心方法。
概述
你是否知道,使用 React 可以将任何项目列表转换为一组 React 组件?基于之前“小柠檬”餐厅甜点列表的例子,想象一下,餐厅希望在其网站上专门开辟一个区域,向在线访客展示其最佳甜点,以鼓励他们下单。本节视频将教你如何通过使用 map 函数和 JSX 语法,在 React 中显示这样的元素集合。
回顾一下,JSX 是 JavaScript 的一种特殊语法扩展,React 用它来描述用户界面(UI)。而组件是一个返回 JSX 的函数。


渲染列表的核心步骤
上一节我们介绍了 JSX 和组件的基本概念,本节中我们来看看如何具体渲染一个列表。
目标是展示“小柠檬”餐厅最佳甜点集合的一个简化版本,仅显示甜点的标题和价格。每个甜点对象具有以下属性:id、title、image、description 和 price。
第一步:使用 map 函数遍历数组
首先,创建一个名为 listItems 的新变量,用于存储转换操作的结果。我们将使用 JavaScript 的 map 函数遍历甜点数组。
const listItems = desserts.map(dessert => {
// 转换逻辑将在这里编写
});
你可能会想,在 map 函数内部应该返回什么。在传统的 JavaScript 中处理列表时,你可以返回任何数据类型。但在 JSX 中,你也可以将一个 React 组件作为应用于每个元素的转换结果返回。这在后续会非常有用,因为你可以直接将结果嵌入到返回的 JSX 中。
第二步:选择并返回 JSX 元素
同时回顾一下,默认情况下所有 HTML 标签都是组件,因此你可以利用所有已熟知的 HTML 语义化标签。对于列表项,最佳选择是列表项(<li>)语义标签。
目前,我们先返回一个空的列表项。
const listItems = desserts.map(dessert => {
return <li></li>;
});
第三步:构建列表项内容
因为目标是显示甜点的标题和价格,所以首先创建一个名为 itemText 的新变量来存储文本。
我们将使用破折号分隔标题和价格,并使用点符号从甜点对象中访问所需的属性(title 和 price)。
const listItems = desserts.map(dessert => {
const itemText = `${dessert.title} - ${dessert.price}`;
return <li></li>;
});
由于这是一个将作为组件渲染方法一部分的 JSX 转换,你必须使用花括号 {} 来包裹你的数据。在本例中,就是每个列表项的文本,即变量 itemText 的值。
const listItems = desserts.map(dessert => {
const itemText = `${dessert.title} - ${dessert.price}`;
return <li>{itemText}</li>;
});
第四步:在 JSX 中嵌入列表
最后一步是进入组件的渲染方法(或函数组件的返回值),将 listItems 嵌入到 HTML 列表包装器组件中,即无序列表(<ul>)。
function DessertList() {
const listItems = desserts.map(dessert => {
const itemText = `${dessert.title} - ${dessert.price}`;
return <li key={dessert.id}>{itemText}</li>; // 注意:添加了 key 属性
});
return (
<ul>
{listItems}
</ul>
);
}


就这样,甜点以一种简洁明了的方式显示出来了。这个改进版功能肯定能帮助“小柠檬”餐厅增加订单,做得很好。


总结
本节课中我们一起学习了如何使用 map 函数和 JSX 花括号 {} 的组合来转换和渲染元素集合。列表是应用开发的核心构建块之一,掌握这项技能让你在创建优秀应用的道路上又前进了一步。😊
48:理解 React 中的 Key 🔑
在本节课中,我们将要学习 React 中一个重要的概念:key。你将了解 key 的作用、如何为列表项选择合适的 key,以及错误使用 key 可能带来的问题。
概述:React 的自动优化与开发者介入
使用 React 的一个重要优势是其自动优化用户界面更新的能力。在大多数情况下,React 能高效地处理 UI 变化,就像飞机的自动驾驶模式。然而,在某些特定场景下,开发者需要采取额外步骤来指导 React 应如何应对 UI 的变更。
本节中,我们将探讨在处理元素列表时,如何使用 key 来实现这种指导。
React 的 Diffing 算法及其局限
因为 React 默认追求高性能,你通常无需考虑 UI 的更新过程。在计算变更时,React 会应用其 Diffing 算法 来计算更新组件树所需的最小变更次数。
虽然该算法在大多数情况下运行良好,但正如前面提到的,存在一些情况,React 无法做出关键假设来找到最优的更新路径,这就需要开发者介入。

让我们来看一个具体的例子。

一个低效更新的例子
想象一下 Little Lemon 餐厅菜单的饮品区,餐厅经理可以根据季节添加新的饮品。
当他们在列表末尾添加一个新元素时,Diffing 算法工作得很好。React 会匹配两个“啤酒”组件树,匹配两个“葡萄酒”组件树,然后插入新的“苹果酒”组件树。
然而,当在列表开头插入一个新元素时,该算法的性能表现较差。因为 React 会改变每一个子元素,而不是意识到它可以保持“啤酒”和“葡萄酒”的子树不变。
这种低效可能成为一个问题。
Key 的引入与作用
为了解决这个问题,React 支持 key 属性。
那么,什么是 key?key 是帮助 React 识别哪些项目被更改、添加或移除的标识符。它们还指示在更新发生时如何处理特定元素,以及其内部状态是否应被保留。
为了说明这一点,在上一个例子中添加 key 可以使组件树的转换变得高效。这是因为 React 现在知道带有 key="cider" 的元素是新的,而带有 key="beer" 和 key="wine" 的元素只是移动了位置。
关于 key 的一般经验法则是:使用一个在其兄弟元素中唯一且稳定的标识符。这允许 React 尽可能多地复用列表中的元素,避免不必要的重新创建,尤其是在内容完全相同、仅位置发生变化的情况下。
如何选择合适的 Key
最常使用的 key 是来自数据本身的唯一 ID。这些 ID 通常对应数据库中的 ID,这种 ID 本质上是保证唯一的。
但是,如果你的数据没有任何合适的 ID,或者你正在渲染一个不依赖于任何服务器数据的列表,该怎么办?在这些场景中,你可能会认为生成自己的唯一 ID 就足够了,无论是通过外部库还是像内置的 Math.random() 这样的随机函数。
然而,虽然这种方法确实可以避免 key 的任何冲突(即不会产生两个相同的 key),但它无法保留列表项的内部状态。这是因为当重新渲染发生时,这些 key 会不同,导致 React 不得不从头开始重新创建你的列表。
作为最后的手段,你可以使用项目的索引作为 key。由于索引决定了每个元素在列表中的位置,它保证了没有重复项。
但是,如果项目的顺序可能改变(例如,你的列表支持排序,或者用户可以添加或删除项目),则不推荐使用索引作为 key。
错误使用 Key 的影响

当 key 使用不当时,可能会对性能产生负面影响,并在更新列表时导致 UI 出现意外的故障。这就是为什么有意识地决定 key 的实现方式非常重要。
总结

本节课中,我们一起学习了 React 中的 key,包括如何处理项目列表时使用 key 来区分元素、如何选择正确的 key,以及错误使用 key 对应用性能的影响。
一个主要的要点是:始终使用一个在其兄弟元素中保证唯一的 key。因此,尽可能使用数据中的唯一 ID。你可以将索引作为最后的手段,但请记住,当列表项的顺序容易发生变化时,这种方法将无效。
接下来,你将有机会在实践中探索如何在列表组件内使用 key。
49:在列表组件中使用 key 🔑
在本节课中,我们将学习如何在 React 的列表组件中正确使用 key 属性。通过一个待办事项列表的实例,我们将理解为何需要 key,以及如何选择合适的值作为 key,以避免常见的渲染问题。
项目概述与问题引入
想象一下,Little Lemon 餐厅的经理需要一个独立的应用程序来记录他们需要完成的任务。为此,我构建了一个非常简单的待办事项列表应用。它包含两个可编辑的待办事项(由文本输入框表示)以及一个可以反转待办事项顺序的按钮。
我将首先带您浏览我之前使用 Create React App 创建的代码示例。
组件结构分析

Todo 组件本质上是一个表格行 (<tr>),它包含一个 ID、一个用于输入值的文本输入框以及创建日期。ID 和创建日期 (createdAt) 作为属性 (props) 传入,而输入框的值状态则存在于 DOM 节点中。换句话说,这个文本输入框是一个非受控组件。
// Todo 组件示例结构
function Todo({ id, createdAt }) {
return (
<tr>
<td>{id}</td>
<td>
<input type="text" />
</td>
<td>{createdAt}</td>
</tr>
);
}
主 App 组件包含了屏幕上显示的整个界面。待办事项的数据模型是一个状态片段,本质上是一个对象数组,每个对象包含一个 ID 和创建日期。
// App 组件状态示例
const [todos, setTodos] = useState([
{ id: 1, createdAt: '2023-10-01' },
{ id: 2, createdAt: '2023-10-02' },
]);
此外,还有一个 reverseOrder 函数,用于有效地改变待办事项的顺序。数组的 reverse 方法是一个可变操作,这意味着它会修改原始数组,而不是其副本。为了避免直接改变 React 状态(这是绝对不应该做的),首先创建数组的副本非常重要,我通过使用 ES6 扩展运算符 (...) 来实现这一点。
// 反转顺序的函数
const reverseOrder = () => {
// 先创建副本,再反转,避免直接修改状态
const reversedTodos = [...todos].reverse();
setTodos(reversedTodos);
};
最后,在用户界面的 JSX 部分,有一个包裹性的 div、一个用于反转待办事项顺序的按钮以及一个表格,其中每个表格行代表一个待办任务。每个 Todo 实例都接收 id 和 createdAt 作为属性,这些值我们从数据模型中传递。
初次渲染与警告
现在回到应用程序,我首先要做的是打开浏览器控制台。在开发模式下运行应用程序时,会显示一条红色的警告。React 在通过控制台错误提供上下文警告方面做得很好,能提示应用程序中的潜在问题。

警告信息:
Each child in a list should have a unique "key" prop. Check the render method of \App`.(列表中的每个子元素都应该有一个唯一的 “key” 属性。请检查App` 组件的渲染方法。)
这个警告明确指出,列表中的每个子元素都需要一个唯一的 key 属性。待办事项的索引位置似乎满足了 React 警告的要求,所以我将使用它。

// 初始渲染 - 使用索引作为 key
{todos.map((todo, index) => (
<Todo key={index} id={todo.id} createdAt={todo.createdAt} />
))}

现在,当我重新加载应用程序时,警告消失了。
测试与发现问题
然而,我还没有测试应用程序。让我们输入一些待办事项,并探索当我反转顺序时会发生什么。
- 对于第一个,我输入 “stock take”。
- 对于第二个,我输入 “process payroll”。
现在我想反转顺序,因为经理应该先处理工资单。哦,这并没有按预期工作,是吗?文本输入框没有移动,但其他所有内容都移动了。您刚刚发现了使用索引作为 key 时的一个主要问题,即当列表项的顺序可能发生变化时。
问题根源分析
那么到底发生了什么?如果我回到代码并查看每个待办事项的 JSX:当我反转待办事项的顺序时,传递给 Todo 组件的 id 和 createdAt 属性已经改变。但是 key 仍然是相同的,因为我使用的是索引。由于 key 相同,React 会指示保留该节点的内部状态。这就是为什么待办事项输入框的状态被保留了下来。
解决方案:使用唯一且稳定的标识符
现在,如何解决这个问题?回顾 key 的要求:它必须是唯一的,并且能够正确识别每个待办事项,无论它在列表中的位置如何。在这个案例中,我绝对可以使用数据模型中的 id 属性作为我的 key。毕竟,这能保证每个待办事项都是唯一的。
// 正确的做法 - 使用唯一 id 作为 key
{todos.map((todo) => (
<Todo key={todo.id} id={todo.id} createdAt={todo.createdAt} />
))}

所以现在我将实现这个更改,并再次运行之前的测试,再点击一次按钮。很好,这次它按预期工作了!
总结
在本节课中,我们一起学习了在 React 列表渲染中选择 key 的重要性。您将经常遇到元素集合,凭借您对 key 的知识,让您的用户优先处理他们想要完成的任务将不再是一个问题。


核心要点:
key的作用:帮助 React 识别哪些项被更改、添加或删除,从而高效地更新和重渲染列表。key的选择原则:必须在其兄弟节点中是唯一的,并且最好是稳定的(不随渲染顺序改变而改变)。- 避免使用索引:当列表顺序可能改变时,使用数组索引作为
key会导致状态混乱和渲染错误。 - 最佳实践:使用数据中固有的唯一标识符(如
id)作为key。
50:什么是受控组件 📝
在本节课中,我们将学习React中一个重要的概念——受控组件。我们将了解它是什么,为什么需要它,以及如何在表单处理中使用它。通过本教程,你将掌握如何让React完全控制表单元素的状态。
当你在互联网上浏览时,有很大概率会遇到表单。即使你没有意识到,从简单的电子邮件输入和订阅新闻通讯,到更复杂的表单,例如在你最喜欢的社交媒体平台上创建账户,表单无处不在。因此,你可能会发现自己需要经常在应用程序中实现表单。
HTML表单与React表单的区别

上一节我们提到了表单的普遍性。本节中,我们来看看React应用中的表单与传统HTML表单有何不同。

在React应用中,HTML表单的工作方式与其他DOM元素不同。你可能还记得,DOM是一个逻辑上的树状结构,代表HTML文档,它使用节点来描述文档的各个部分。

传统的HTML表单在DOM内部保持一些内部状态,并且在提交时具有一些默认行为。这通常通过action属性完成,该属性指向将处理请求的端点。
但是,如果你想要更精细的控制级别呢?例如,小柠檬餐厅的顾客可以通过网站上的表单预订餐桌。想象一下,如果有一个函数可以处理表单的提交,并访问用户输入到其中的数据。
什么是受控组件?🎯
这就是受控组件发挥作用的地方。受控组件是一组组件,它们提供了一个声明式的应用程序编程接口(API),允许你在任何时间点使用React状态完全控制表单元素的状态,而不是依赖于DOM元素的原生状态。React状态成为单一数据源,始终控制着你表单元素的显示值。
实现这种状态委托的方式是通过value属性。value是React添加到大多数表单元素的一个特殊属性,用于在渲染生命周期的任何时间点确定输入内容。因此,为了创建一个受控组件,你需要结合使用本地状态和value属性。


如何构建受控组件 🛠️
了解了受控组件的定义后,现在我们来具体看看如何构建一个。
最初,你将本地状态分配给value属性。但是,你如何从输入框中输入的每个新字符获取更新呢?

为此,你需要第二个属性来完成受控组件的设计:onChange回调函数。onChange回调接收一个event参数,这是一个表示刚刚发生动作的事件对象,类似于DOM元素上的事件。为了从每次按键获取新值,你需要从事件中访问target属性,并从该对象中获取value,它是一个字符串。

以下是构建受控输入组件的核心代码示例:
import { useState } from 'react';
function ControlledInput() {
const [inputValue, setInputValue] = useState('');
const handleChange = (event) => {
setInputValue(event.target.value);
};
return (
<input
type="text"
value={inputValue}
onChange={handleChange}
/>
);
}

处理表单提交 ✅
最后,为了在表单提交时能控制表单值,你可以在表单HTML元素上使用onSubmit属性。onSubmit回调也接收一个类似DOM的事件作为参数。在那里,你可以访问你的表单值,以执行提交前必须进行的任何所需逻辑,例如验证输入值。此外,如果你想阻止默认的HTML表单行为,你需要在onSubmit回调内部调用event.preventDefault()。
以下是处理表单提交的示例:

function ControlledForm() {
const [formData, setFormData] = useState({ email: '' });
const handleChange = (event) => {
setFormData({ ...formData, [event.target.name]: event.target.value });
};
const handleSubmit = (event) => {
event.preventDefault(); // 阻止默认提交行为
console.log('提交的数据:', formData);
// 在这里执行验证或发送数据到服务器等逻辑
};
return (
<form onSubmit={handleSubmit}>
<input
type="email"
name="email"
value={formData.email}
onChange={handleChange}
/>
<button type="submit">提交</button>
</form>
);
}
总结 📋
本节课中,我们一起学习了React中受控组件的概念及其在表单处理中的应用。你探索了HTML表单的基本概念,并了解了如何将表单创建为React组件。
你已经掌握了一种称为“受控组件”的技术,它使React能够成为表单输入状态的单一数据源。React提供了大多数输入类型的受控版本,并推荐使用受控组件来实现表单。
然而,请记住,仍然有一些表单元素保持非受控状态,类似于它们的DOM对应物。随着你学习的深入,你将更深入地了解受控和非受控表单元素。
51:在 React 中创建表单组件 📝
在本节课中,我们将学习如何在 React 中创建受控的表单组件。我们将了解如何利用 React 的状态管理来控制表单数据,如何阻止表单的默认提交行为,以及如何根据表单的有效性来禁用提交按钮。
表单看似简单,但其包含许多不同的特性和功能。对于开发者而言,构建表单是一门艺术。React 让开发者构建和定制表单变得更加容易,这也是它如此受欢迎的原因。
小柠檬餐厅的在线网页是早些时候创建的。虽然小柠檬餐厅对之前的结果一度满意,但他们开始意识到用户在使用旧的联系表单时遇到了问题。在收到一些建议后,他们决定重建表单,并选择 React 作为最合适的框架,因为它能轻松实现表单所需的功能和控制。
受控组件与不受控组件
在上一节中,我们了解了表单的重要性。本节中,我们来看看 React 中处理表单的两种主要方式:受控组件和不受控组件。
回忆一下,React 中的受控组件是指表单数据由组件的状态(state)处理的组件。而不受控组件则是指表单数据由 DOM 本身处理的组件。
受控组件公式:表单数据 = 组件状态
为了更深入地了解如何在 React 中创建表单组件,我们现在将分析一个基础表单示例的代码。
构建一个基础表单
我将使用之前构建的一个应用来演示如何在 React 中创建表单组件。在这个例子中,项目是使用 create-react-app 创建的。

这个函数组件的 return 方法本质上包含一个表单,该表单有两个元素:一个用于输入用户名的文本输入框和一个提交按钮。这个表单类似于经典的 HTML 版本,因此无论你是否使用 React,它的工作方式都相同。
为了测试应用,我输入名字“John”并点击提交按钮。这样做会触发表单的默认行为,即向根路径发送一个 GET 请求并刷新页面。
在 React 中,当前的实现被认为是一个不受控表单,所有状态都存在于 DOM 中。让我们逐步完成必要的步骤,将这个表单转换为受控版本。
将表单转换为受控组件
以下是创建受控表单的关键步骤:

首先,我需要为文本输入框创建一些本地状态,我将其称为 name。
const [name, setName] = useState('');
其次,我需要通过两个属性将这个状态连接到我的文本输入框:value 属性将输入框变为受控输入,onChange 属性接收每次按键的更改,从而更新输入框的状态。
最后,为了控制表单的提交,我必须在 form 标签中使用 onSubmit 属性。目前,我将在控制台记录一条基本消息,说明提交成功。

现在让我们检查表单是否仍像以前一样工作。我将输入一个名字并点击提交。
它正在工作。我的消息被记录到控制台,并且表单的默认行为仍在继续。
虽然这很好,但我实际上希望更多地控制表单的提交。具体来说,我不希望触发默认的调用服务器根路径和刷新页面的行为。

你可能想知道如何防止这种情况发生。
阻止表单的默认行为
在传统表单中,你可以通过从 onsubmit 属性返回 false 来实现这一点。然而,在 React 中,做法是使用你在 onSubmit 回调中作为参数获取的事件属性,并对其调用 preventDefault 方法。
const handleSubmit = (event) => {
event.preventDefault();
console.log('提交成功!');
};

现在,当我再次提交表单时,不会发生页面刷新,也不会向服务器发送请求。
让我们更进一步,在提交后清空输入框。为此,我在 onSubmit 回调中调用状态设置函数并传入一个空字符串。

const handleSubmit = (event) => {
event.preventDefault();
console.log('提交成功!', name);
setName(''); // 清空输入框
};
很好,我的表单正在成形。为了展示受控组件的更多好处,让我们进行一项额外的改进:仅当文本输入框不为空时才允许用户提交表单。

根据条件禁用提交按钮
禁用按钮就像使用 disabled 属性一样简单。在这种情况下,如果 name 是一个空字符串,这个表达式将被评估为 true,按钮将被禁用。
<button type="submit" disabled={name === ''}>提交</button>
所以在应用中,如果没有提供名字,我就无法点击按钮。

最后,为了遵循最佳的无障碍实践,让我们将标签(label)与输入框(input)连接起来。我为输入框设置了一个 ID 叫 name,现在我将连接标签。
在传统的 HTML 表单中,你会使用 for 属性,但在 React 中,for 是一个保留字,所以你必须使用 htmlFor 并传入输入框的 ID。

<label htmlFor="name">用户名:</label>
<input id="name" type="text" value={name} onChange={(e) => setName(e.target.value)} />

现在,如果我点击标签,其对应的输入框会获得焦点。

总结
本节课中,我们一起学习了 React 中受控表单的基础知识。
你学会了如何使用本地状态和 onChange 事件,以及 onSubmit 属性,将一个不受控的表单转换为受控版本,并了解了这样做的一些好处。在表单提交方面,你还学会了如何避免默认行为,以及在表单无效时如何禁用提交按钮。
做得好,你取得了很大的进步。
52:10_创建受控表单组件 📝
在本节课中,我们将学习如何使用 React 的受控组件来构建一个用户反馈表单。我们将实现一个包含评分滑块和评论区的表单,并为其添加自定义验证逻辑,确保用户提交的数据符合要求。
你是否曾通过电商网站购买商品,或在最喜爱的餐厅预订座位?如果是,你可能会在事后收到一封友好的电子邮件,其中包含一个链接,引导你到特定页面提供体验反馈。这就是反馈表单的一个例子。现在你已经熟悉了 React 中的受控组件,我将演示如何自己构建此功能。
你还将使用范围输入(range input)和自定义验证作为构建反馈表单的一部分。想象一下,城里最好的餐厅之一“小柠檬”希望向顾客发送反馈表单。让我们开始用 React 实现一个反馈表单。请注意,在此示例中,项目是使用 Create React App 创建的。我还添加了一些初始代码,即一个仅包含标题和提交按钮的表单。
表单需求与初始设置

本示例的需求是创建一个界面,允许用户提供 0 到 10 的评分,以及一个额外的评论框,让顾客告诉厨师几天前享用的美食有多么美味。
第一步是实现评分的控件。你可以用不同的方式实现,但针对这个用例,我选择使用范围输入(range input),因为它为用户提供了一个简单的滑块。
让我们创建一个新的 div 来包装这个组件。它将包含一个标签,我将其命名为“评分”,以及一个 type 为 range 的输入框。Range 输入提供了两个属性来定义范围:min 和 max。对于此示例,我将最小值设置为 0,最大值设置为 10。
现在,应用程序显示了一个用户友好的滑块来提供评分。
实现受控评分滑块

为了完善这个范围输入组件,我还需要做两件事:将输入框转变为受控组件,并直观地显示代表滑块选择的数值。
为此,我将定义一个新的状态变量 score,并将其初始化为 10,因为我知道厨师的食谱通常是无与伦比的。这样可以让用户在想的时候通过滑块将分数从 10 分向下调。
现在,在 range input 中,我必须使用 value 属性将状态连接起来,并使用 onChange 来接收更改并相应地更新状态。

由于我还希望将数字评分与此滑块一起显示,我将在“评分”标签中添加该信息以及一个星号,使界面简洁明了。
很好,反馈表单开始成形了。

添加评论区
现在,让我们实现表单的第二个元素:一个用于提供额外评论的小部件。虽然我可以在这里使用文本输入,但评论可能很长。因此,更合适的选择是文本区域(textarea)。

为此,我将声明另一个名为 comment 的状态变量,并将其初始化为空字符串。

对于界面,我将创建一个新的 div,其中包含一个标签和一个用于任何额外反馈的 textarea 组件。
现在它已经渲染好了,我需要将状态连接到 value 属性,并通过 onChange 更新更改。至此,反馈表单的界面就完成了。

实现自定义验证逻辑
我想实现的最后一件事是一些验证逻辑,以确保当评分小于或等于 5 时,必须提供评论,并且评论至少应有 10 个字符。这样厨师就能收到用户真实的反馈,用于改进食谱。
为此,我将在表单组件上使用 onSubmit 回调函数。首先,我将调用 preventDefault 来避免默认的 HTML 表单行为。
然后,我将编写一个 if 语句来检查评分是否小于或等于 5,并且评论字符数是否少于 10。如果是这种情况,我将显示一个警告框来通知用户相关要求,并从函数中返回。
否则,用户就可以提交了,我将记录一条消息以确认反馈提交成功。提交后重置表单值也是一个好习惯,因此我将把两个状态变量都设置为其初始值。

总结
至此,一切运行良好,提交消息已记录到控制台中。


在本节课中,我们一起学习了如何使用 React 的受控组件和自定义验证来构建一个反馈表单。我们实现了评分滑块和评论区,并添加了验证逻辑,确保在评分较低时用户必须提供详细的反馈。这确保了用户拥有流畅的体验,同时也能收集到对厨师有用的高质量反馈。
53:Props 与 State 详解 🧩

在本节课中,我们将要学习 React 中两个最核心的数据概念:Props 和 State。理解它们的区别、联系以及各自的适用场景,是构建高效、可维护 React 应用的基础。
想象一下,小乐餐厅取得了巨大成功,计划在其他地方开设分店。作为建设项目的一部分,建筑师绘制了蓝图,其中厨房和用餐区的尺寸在所有分店都是统一的。虽然蓝图已经确定,但具体的配置,如餐桌的大小、形状和数量,椅子的类型,厨房的餐具以及墙面颜色,仍将由每家分店的经理来决定。
在这个类比中,新分店的蓝图就是 React 组件,而每家分店的具体配置则代表了 React 中的 Props 和 State。
那么,什么是 Props 和 State 呢?Props 和 State 都是普通的 JavaScript 对象,React 使用它们来存储信息。随着本视频的深入,你将发现 Props 和 State 在 React 中的异同。
你还会学习如何判断某个属性属于 Props 还是 State,何时需要使用 State,以及如何根据 Props 和 State 来区分不同的 React 组件。
虽然 Props 和 State 都会影响渲染输出,但它们在一个重要方面有所不同:Props 像函数的参数一样传递给组件,而 State 则像函数内部声明的变量一样,由组件自身管理。
因此,尽管 Props 和 State 本质上是不同的,但它们在某些方面存在重叠。值得注意的是,在设计组件时,其主要职责是将原始数据转换为丰富的 HTML。在 React 生态系统中,Props 和 State 共同构成了 HTML 输出所依赖的原始数据。
Props 和 State 都是普通的 JS 对象,并且是确定性的。这意味着对于相同的 Props 和 State 组合,你的组件总是生成相同的输出。
另一个相似点是,Props 和 State 的变化都会触发渲染更新。
现在,如何判断某个属性应该放在 Props 还是 State 中呢?一个经验法则是:如果一个组件需要在某个时间点改变它的某个属性,那么这个属性就应该成为其 State 的一部分。否则,它应该只是该组件的一个 Prop。
让我们更详细地探讨一下。
Props,是 Properties 的缩写,是组件的配置。它们从组件树中的父组件接收而来,并且对于接收它们的组件来说是不可变的。一个组件不能改变自己的 Props,但它负责组装其子组件的 Props。
除了 Props,React 组件还有另一个内置对象,名为 State。这个对象是 React 用来确定何时应该重新渲染组件的一种方式。React 的机制是,对 State 对象中值的任何更改都会触发给定组件的重新渲染。
State 的生命周期始于组件挂载时的默认值,然后随着时间的推移,该值会发生修改,这些修改大多由用户事件产生。因此,State 是某个时间点的可序列化表示,或者说,是一个快照。组件在内部管理自己的 State,你也可以说 State 是私有的。
现在,在设计组件时,你可能会问的另一个问题是:它是否应该有任何 State?答案是:State 是可选的。由于 State 会增加复杂性并降低可预测性,因此没有 State 的组件更可取。尽管在交互式应用中无法避免拥有某种 State,但你应该避免让太多组件拥有 State。
最后,让我们探讨一下基于 Props 和 State 来区分 React 组件类型的主要方法之一。
组件可以分为无状态组件和有状态组件。
无状态组件只有 Props,没有 State。除了 render 函数外,没有太多其他逻辑,所有逻辑都围绕它们接收到的 Props 展开。这使得它们非常易于理解和测试。
另一方面,有状态组件同时拥有 Props 和 State。它们负责客户端与服务器的通信、处理数据以及响应用户事件。这类逻辑应该被封装在数量适中的有状态组件中,而所有的可视化和格式化逻辑则应向下游移动,放入尽可能多的无状态组件中。

本节课中,我们一起学习了 React 用于构建应用的两种数据类型:Props 和 State。我们了解了它们的定义、区别、如何选择使用,以及如何根据它们来划分组件类型。接下来,你将被介绍一种名为 Context 的额外信息管理方法。
54:什么是上下文,为什么使用它 🧩
在本节课中,我们将要学习 React 中的一个重要概念——上下文(Context)。我们将探讨什么是上下文,为什么需要它,以及如何在实际项目中应用它来解决组件间数据传递的难题。
概述:数据传递的挑战
在典型的 React 应用中,数据通过属性(props)以自上而下的方式从父组件传递到子组件。
然而,应用中可能存在某些数据,需要被许多不同的组件所使用。在这些场景下,仅使用 React 提供的 props 来向下传递数据并不总是高效。
因此,本节视频将介绍一种替代的数据传递方式,称为“上下文”。你将学习它的定义、引入的原因,并通过实例进行探索。
为什么需要上下文?🌍
想象一下,Little Lemon 食品订购应用提供了一个浅色或深色主题,用于改变所有元素的背景和文本颜色。
或者是一些通用偏好设置,例如根据访问者的地理位置确定特定的区域设置,这些信息需要被多个组件知晓。
这些数据有什么共同点呢?它们代表了整个应用的全局状态。
随着应用规模的增长,构成应用的组件树也会变得庞大。如前所述,props 是 React 提供的向下传递数据的方式,但在此场景下,它可能变得繁琐。因为你必须显式地将数据穿过树的每一层,其中可能包含许多并不真正需要该数据、仅仅充当代理的中间组件。
这个问题通常被称为 “属性钻取”(prop drilling)问题。顾名思义,父组件必须将属性一直“钻取”到需要消费它们的子组件。
React 的解决方案:上下文 API
React 解决此问题的方法是引入了上下文应用程序编程接口(Context API)。

上下文提供了一种替代方式,可以在组件树中传递数据,而无需在每一层手动传递 props。当你需要共享可被视为 React 组件树全局状态的数据时,它是合适的工具。
核心概念公式:
Context = 跨组件树的全局数据共享机制
实践探索:创建一个上下文
让我们花些时间通过实际操作来研究上下文 API。在这个演示中,我将使用之前通过 Create React App 创建的一个简单应用。

它代表了一个简单的博客平台,Little Lemon 用它向订阅者发布创新的食谱。它包含一个标题栏,其中有标题和显示在右上角的当前已认证用户。其余部分由页面组件渲染,该组件本身包含用户的博客条目,每个条目都有标题、内容和作者姓名。
请注意,有两个组件需要知道已认证的用户:标题栏内的“已登录用户”组件和页面组件。因为已认证用户属于需要在多个组件间共享的全局数据性质。这是一个清晰的例子,表明上下文是完成这项工作的完美工具。
以下是创建和使用上下文的步骤:
第一步:创建上下文对象
首先,需要从 React 导入 createContext 函数。这个函数会返回一个新的上下文对象。
import { createContext } from 'react';
export const UserContext = createContext(undefined); // 默认值设为 undefined

第二步:创建提供者组件
接下来,需要创建一个提供者(Provider)组件。提供者组件允许消费组件订阅上下文的变化。
export function UserProvider({ children }) {
const [user, setUser] = useState(null); // 假设这是认证用户状态
return (
<UserContext.Provider value={user}>
{children}
</UserContext.Provider>
);
}
UserContext.Provider 组件接受一个 value 属性,这个值将被传递给该提供者后代的所有消费组件。
第三步:包装应用
为了让整个应用感知到这个上下文,需要用提供者组件包装整个应用。
// 在应用的根组件(如 App.js)中
import { UserProvider } from './context/UserContext';
function App() {
return (
<UserProvider>
{/* 应用的其他组件 */}
</UserProvider>
);
}
第四步:在组件中消费上下文
最后一步,在需要显示用户名的地方消费用户上下文,即在“已登录用户”组件和页面组件中。
为了方便使用,可以创建一个自定义钩子来包装 useContext 钩子。
// 自定义钩子
import { useContext } from 'react';
import { UserContext } from './UserContext';
export function useUser() {
return useContext(UserContext);
}
然后在组件中使用这个钩子:
// 在需要用户信息的组件中
import { useUser } from './context/UserContext';
function LoggedInUser() {
const user = useUser(); // 消费上下文
return <div>User: {user?.name}</div>;
}
应用现在成功显示了已认证用户的名称。

总结与最佳实践 📝

本节课中,我们一起学习了 React 上下文的概念、其引入的原因以及如何通过一个实际示例来使用它。
你了解了上下文是用于解决跨组件树共享全局状态数据(如用户主题、区域设置、认证信息)的有效工具,它能避免繁琐的属性钻取问题。
核心要点总结:
- 问题:跨多层级组件传递相同数据导致“属性钻取”。
- 解决方案:使用 React Context API。
- 关键步骤:
createContext->Provider->useContext。 - 适用场景:真正的全局状态共享。
但请记住,尽管上下文对于管理全局状态非常有用,仍然建议你尽可能坚持使用 props 和组件自身状态(state)。这样,你的应用数据流将更容易追踪和理解。上下文应谨慎使用,主要用于那些在组件树中许多不同层级都需要访问的数据。
55:13_组件模块总结
在本节课中,我们将回顾React组件模块的核心知识点,包括列表渲染、表单处理、Props与State的区别以及Context API的应用。
🎯 模块回顾
恭喜你完成了React组件模块的学习。现在,让我们花几分钟时间来回顾一下到目前为止所学到的内容。
上一节我们介绍了Context API,本节中我们来对整个模块进行总结。
📋 列表渲染与Keys
你首先学习了如何使用React渲染列表。课程介绍了JavaScript中的map方法,该方法可用于对数组执行转换操作。当你需要以不同方式显示来自外部提供者的数据时,map方法是一个非常有用的工具。
以下是使用map方法转换列表的示例:
const numbers = [1, 2, 3];
const doubled = numbers.map((number) => number * 2);
// 结果: [2, 4, 6]
接下来,你学习了如何结合map方法与JSX来渲染组件列表,并使用React转换元素集合。
最后,你理解了Keys的概念,并学习了一套实用的指导原则,帮助你根据具体用例选择合适的Key。
你了解到,Keys是帮助React识别哪些项目被更改、添加或删除的标识符。它们还指示React在更新发生时如何处理特定元素,以及是否应保留其内部状态。
在处理项目列表时,当你需要提供明确信息来告诉React在UI变化时如何表现,可以使用Keys。
关于Keys的一般规则是:使用一个在其兄弟元素中稳定且唯一的标识符。这就是为什么最常用的Key是数据中的唯一ID。

你也了解到,在万不得已时可以使用索引作为Key,但如果列表项的顺序可能改变,这可能会对性能产生负面影响。
📝 表单处理:受控组件
接下来,你学习了关于表单的课程,以及React处理表单与传统HTML方式的区别。

你首先认识了受控组件。这是一组提供声明式API的组件,允许你在任何时候使用React状态完全控制表单元素的状态。
你学习了如何使用一组受控组件将任何传统的HTML表单转换为React表单。换句话说,你学会了如何使用本地状态、onChange事件和onSubmit属性,将非受控表单转换为受控版本。
你还发现了受控组件相较于非受控组件的一些优势。例如,它使你能更好地控制表单提交,例如在表单无效时禁用提交按钮。
最后,你学习了如何实现一个反馈表单,以及如何在表单提交前执行任何自定义的验证逻辑。
⚖️ Props与State辨析
最后一课首先回顾了Props和State。你学会了清晰地区分Props和State,以及何时使用其中之一。
请记住,尽管Props和State有相似之处(例如,它们都是React用来保存信息的普通JavaScript对象),但Props是传递给组件的,而State是在组件内部管理的。
关键要点包括:如果一个组件需要在某个时间点更改其某个属性,那么该属性应成为其State的一部分;否则,它应该只是该组件的一个Prop。此外,无状态的组件更可取。
你还学习了无状态组件(有Props但无State)和有状态组件(两者都有)的概念。
🔗 Context API与Props Drilling
之后,你了解了Props Drilling问题及其如何影响组件的模块性,因为父组件必须将Props一直向下传递到需要消费它们的子组件。

课程介绍了Context API作为此问题的解决方案,并展示了如何使用它来封装任何全局状态片段,从而避免在组件之间手动传递Props。
React Context的强大功能被阐述为本地状态的一个可行替代方案。
📚 总结
本节课中我们一起学习了React组件模块的核心概念:从使用map方法和Keys高效渲染列表,到利用受控组件处理表单,再到清晰区分Props与State,最后通过Context API解决Props Drilling问题。这些知识为你掌握React奠定了坚实的基础。
期待在下一个模块中继续与你一同学习。
56:使用 React 钩子 🪝
在本节课中,我们将学习 React 钩子的引入背景、设计初衷及其为开发带来的变革。我们将了解钩子如何解决类组件时代的问题,以及它如何让代码更简洁、更易维护和复用。
钩子的诞生与社区反响
React 钩子最初是在一次 React 会议上向社区公布的。这个社区由众多 React 爱好者组成,他们热衷于尝试新事物,我们也信任他们能提供宝贵的反馈并测试我们的想法。
当时我度假归来,我的同事兴奋地告诉我有一个令人激动的新东西,并建议我们开始使用它。我最初的反应是觉得这很“傻”,不明白为什么需要它。但同事坚持说这将会很棒。于是我开始学习并使用钩子,现在我已经无法想象没有钩子该如何编写代码了。
初识钩子:从困惑到掌握
起初,钩子有时会让人觉得不太直观,或者感觉背后有很多“魔法”让人一时难以理解。然而,一旦你掌握了窍门,开始使用并理解它,你会发现钩子确实能让开发工作变得更轻松。
钩子带来的优势
钩子可以极大地简化你的应用程序并带来性能提升。它们能让你的代码更具可读性、更易于管理,同时也让代码更易于共享和复用。
在钩子引入之前,我们使用类组件。它们似乎能完成工作,但随着时间的推移,这些类组件变得越来越大、越来越复杂。我们无法将某些组件拆分成更模块化的部分。我们的目标是让代码更具可用性、更简单,并摆脱那些无法拆分的庞大组件。
Meta 的内部实践与验证
在 Meta,当我们开发新技术时,我们会首先成为这些技术的使用者,以确保它们真正有效,并直接收集来自工程师的反馈。钩子也是如此,我们在内部大量使用了它们,并看到内部团队也开始真正欣赏钩子带来的改进。这充分证明了钩子在 React 应用开发过程中产生了重大影响,也推动了其更广泛的推广。
非破坏性引入与学习建议
钩子是以一种非破坏性的方式引入的。这意味着你并非必须使用它们,你可以继续使用旧的 React 组件编写风格,而之前编写的任何代码都不会被破坏。这是我们推出新功能时采用的方法之一:确保新事物不会破坏人们正在使用的旧事物。
对于初次学习和使用钩子的你,我的建议是坚持下去。起初它们可能看起来很难且令人困惑,但它们实际上会随着时间的推移改善你的代码。虽然使用旧的 React 代码编写方式可能很诱人,但你会发现,一旦学会了钩子,代码会变得更有意义,即使起初它看起来有点令人困惑和不直观。所以,请花时间学习它们,进行前期投资,你会发现从长远来看这是值得的。
核心概念与代码示例
钩子的核心是函数式组件中使用的特殊函数,它们让你能够“钩入” React 的状态和生命周期特性。
一个基础的钩子示例是 useState:
import React, { useState } from 'react';
function Example() {
// 声明一个名为 “count” 的 state 变量,初始值为 0
const [count, setCount] = useState(0);
return (
<div>
<p>你点击了 {count} 次</p>
<button onClick={() => setCount(count + 1)}>
点击我
</button>
</div>
);
}
在这个例子中,useState 就是一个钩子。count 是当前的状态值,setCount 是一个用于更新该状态的函数。每次点击按钮,count 的值都会增加 1,并触发组件重新渲染。
总结
本节课我们一起学习了 React 钩子的引入背景和核心价值。我们了解到钩子是为了解决类组件的复杂性问题而诞生的,它通过非破坏性的方式引入,让函数式组件也能拥有状态和生命周期能力。尽管初学时有挑战,但掌握钩子能显著提升代码的可读性、可维护性和复用性。记住,对钩子的前期学习投资将在未来的开发工作中带来丰厚的回报。




57:修改 useState 钩子 🛠️
在本节课中,我们将学习如何修改和使用 React 的 useState 钩子。我们将通过一个为 Little Lemon 餐厅构建的库存追踪应用实例,来复习 useState 钩子的调用方式、返回值以及如何用它来更新状态。
概述
useState 钩子用于在 React 组件中处理状态。状态指的是应用在特定时刻正在处理的所有数据。我们将通过一个追踪餐厅食材库存的例子,来深入理解其工作原理。
复习数组解构
在深入 useState 之前,我们先回顾一下数组解构的概念。数组解构是一种从数组中提取单个项目,并将这些项目保存为独立变量的方法。
以下是数组解构的一个实际例子。假设你正在编写一个应用,用于追踪 Little Lemon 餐厅后厨的当前食材储备。
你使用一个数组变量来存储所有蔬菜。在编码过程中,你需要将数组中的每一项提取到其独立的变量中。例如,第一项命名为 v1,第二项命名为 v2,依此类推。使用数组解构,你可以用一行代码轻松完成这个操作:
const veggies = [‘tomato‘, ‘cucumber‘, ‘onion‘];
const [v1, v2, v3] = veggies;
关于数组解构的更多信息,你可以参考本课末尾的补充资源。
需要注意的是,使用数组解构时,你可以自由地为从数组中解构出的项目命名。然而,在解构对象时,你必须使用对象属性的确切名称作为解构变量的名称,这使得对象在命名解构变量方面严格得多。因此,React 选择使用数组数据结构作为 useState 钩子的返回值。

useState 钩子如何工作
上一节我们介绍了数组解构,本节中我们来看看 useState 钩子实际是如何工作的。我将演示如何使用 useState 钩子来设置餐厅名称的初始值为 “Lemon”,然后仅使用 useState 的更新函数将其更新为 “Little Lemon”。
useState 钩子允许你在组件中处理状态。让我们从讨论调用 useState 钩子时会发生什么开始。
import React, { useState } from ‘react‘;

function App() {
const stateArray = useState(‘Lemon‘);
console.log(stateArray);
return null; // 暂时返回 null 以聚焦控制台输出
}

在控制台中,你会看到类似这样的输出:[‘Lemon‘, function]。控制台记录的值是一个数组,其中状态变量的值作为数组的第一项,而数组的第二项是用于更新状态的函数。
所以,useState 钩子的调用返回一个包含两个成员的数组。
const [stateVariable, stateUpdatingFunction] = useState(initialState);
按照惯例,状态更新函数使用驼峰命名法命名。另一个惯例是在用于解构状态变量的变量名前加上 “set” 前缀。换句话说,正确处理状态意味着我最初的代码示例应该改进,以正确解构从 useState 调用返回的数组。
现在,解构出的 restaurantName 变量保存着状态,而解构出的 setRestaurantName 变量保存着状态更新函数。这就是使用 useState 钩子进行数组解构的一个例子。


如何更新状态
你可能会想知道如何更新状态。刚开始使用 useState 钩子时,一些开发者尝试用各种方式更新状态变量。但在使用 useState 时,只有一种正确的方式来更新状态,那就是通过状态更新函数。
换句话说,更新 restaurantName 变量状态的唯一方法,就是调用 setRestaurantName 函数。
在 React 应用中,状态变化通常由用户与应用交互的行为触发。这意味着状态变化通常由用户生成的事件触发,例如鼠标移动、按钮点击和按键按下。开发者的角色是以对正在编写的应用有意义的方式,响应特定类型的事件。
用户与 Web 应用交互的一种常见方式是通过按钮点击。因此,让我们看一个响应用户生成事件(即按钮点击)来改变状态的例子。
function App() {
const [restaurantName, setRestaurantName] = useState(‘Lemon‘);
function updateRestaurantName() {
setRestaurantName(‘Little Lemon‘);
}
return (
<div>
<h1>{restaurantName}</h1>
<button onClick={updateRestaurantName}>
更新餐厅名称
</button>
</div>
);
}

在这段示例代码中,我添加了一个按钮,当点击时,会执行 updateRestaurantName 函数。这个函数在用户点击按钮时被调用。
现在,当我点击“更新餐厅名称”按钮时,H1 标题将从 “Lemon” 变为 “Little Lemon”,因为 updateRestaurantName 函数的调用触发了对 setRestaurantName 状态设置函数的调用。


总结
本节课中我们一起学习了 useState 钩子的核心用法。你现在应该能够回忆起 useState 钩子的用途以及它在实践中如何工作。我希望在未来的开发中,尤其是在处理组件中用于追踪状态的原始数据类型时,使用 useState 钩子对你来说将是一件轻松无压力的事情。
58:使用 useState 钩子构建目标追踪应用 🎯
在本节课中,我们将学习如何使用 React 的 useState 钩子来构建一个简单的目标追踪应用。我们将通过一个“小柠檬餐厅”的业务目标管理示例,理解如何在组件中声明、读取和更新状态。

想象一下,在小柠檬餐厅的早期阶段,餐厅还只存在于纸面上。店主希望有一个应用来追踪餐厅业务的发展和所有相关目标的达成情况。让我们探索如何使用 React 的 useState 钩子在组件中构建并更新状态,以满足这些需求。

当代码编译完成后,屏幕上会显示完整的应用。我们得到一个标题为“我的小柠檬目标”的页面,以及一个包含两个输入框的简单表单:goal 和 by。第一个输入框让店主输入他们的目标,第二个输入框让他们输入达成该目标的时间框架。
代码本身由三个独立的组件构成:
GoalForm组件:通过表单捕获新目标。ListOfGoals组件:遍历所有已添加的目标,并将它们显示为一个无序列表。App组件:将上述两个组件组合在一起,渲染它们,并通过props传递它们需要使用的函数。




让我们深入探索这些组件。
分析 GoalForm 组件
第一个组件是 GoalForm 组件,它在函数体中接收 props 对象。
在 GoalForm 函数体内,我首先声明了一个状态变量 formData,它通过解构调用 useState 钩子得到。我将这个 formData 变量初始化为一个包含两个属性的对象:goal 和 by,它们的值都设置为空字符串。
const [formData, setFormData] = useState({ goal: '', by: '' });
接下来,我声明了两个函数:changeHandler 和 submitHandler。
首先是 changeHandler 函数,它接受一个参数 e。这个 e 参数是一个现成的事件对象。换句话说,我不需要在我的 changeHandler 中传递这个对象,它是由 React 之外的机制提供的。即使在普通的 JavaScript 中,每当事件被触发时,也会创建一个包含许多与该事件相关数据的事件对象。然后,我可以通过简单地为其分配一个自定义名称(例如 e、evt 或 event)来使用这个事件对象。这里我使用字母 e 来保持代码简洁。
在 changeHandler 函数体内,我通过调用之前从 useState 钩子解构出来的状态设置函数 setFormData 来更新 formData 变量的状态。
setFormData 函数接收 formData 变量前一个值的浅拷贝(即前面使用了扩展运算符的 formData 变量)。
const changeHandler = (e) => {
setFormData({ ...formData, [e.target.name]: e.target.value });
};
请记住,你不应该直接操作 formData 变量,这就是我制作副本的原因。这是因为 React 优化其虚拟 DOM 的方式。保持状态不可变使得能够更高效、更具成本效益地比较虚拟 DOM 的先前版本和更新后的版本。
然后,我通过添加这段代码来更新这个新的 formData 对象副本:它读取 e.target.name 并使用方括号表示法,然后将此属性的值设置为事件对象实例中 e.target.value 属性内的任何内容(该事件对象是在该特定事件触发时构建的)。
之所以使用方括号表示法有效,是因为它允许我动态地设置 e.target.name 的值。换句话说,如果用户在 name 属性设置为 goal 的输入框中键入,它允许我将其设置为 goal;如果用户在 name 属性设置为 by 的输入框中键入,则将其设置为 by。
其次,我声明了一个 submitHandler 函数,它也接受 event 参数。
GoalForm 组件接收名为 onAdd 的 prop,我将 addGoal 函数作为 prop 值传递给它。但这不仅仅是任何函数,它实际上是在 App 组件内部第 43 行声明的函数。
这个 addGoal 函数接受一个目标条目,并更新保存在 App 函数内部的 allGoals 状态变量的值。它通过将这个目标条目添加到 App 组件的 allGoals 状态变量中保存和跟踪的先前目标列表中来做到这一点。
任何状态变量的更新都必须通过之前解构的状态更新函数进行。对于 App 组件,状态更新函数是 setAllGoals 函数。这就是为什么我在 addGoal 函数内部调用这个状态更新函数。
为了使一切正常工作,我还需要将 addGoal 函数定义传递给 App 组件返回语句中的 GoalForm JSX 元素。这就是为什么 addGoal 函数现在可以作为名为 onAdd 的 prop 在 GoalForm 函数内部使用,也是为什么我现在可以在第 10 行开始的 submitHandler 函数内部使用它。
因此,一旦在这里第 12 行调用 props.onAdd 函数,我就会回到 GoalForm 函数声明中。它接收 formData 变量,这会触发 App 组件中 allGoals 状态变量的更新,如前所述。
但现在我仍然需要处理表单显示用户输入的值的问题。我需要将 formData 状态变量重置为空字符串,既重置 formData 状态对象的 goal 属性,也重置其 by 属性。
const submitHandler = (e) => {
e.preventDefault();
props.onAdd(formData);
setFormData({ goal: '', by: '' });
};
现在,我来到了 GoalForm 函数的返回语句。在这里,我希望你关注 form 元素,它的 onSubmit 事件处理属性被设置为 submitHandler 函数。
第一个和第二个输入框都遵循相同的结构,具有 type、name、placeholder、value 和 onChange 属性,这些属性连接到了前面描述的功能。
分析 ListOfGoals 组件
接下来看 ListOfGoals 组件,该组件从其父组件 App 接收 allGoals 状态变量作为 prop。
这样做的目的是映射 allGoals 对象数组,其中每个对象都包含描述单个目标的两个属性,如前所述。通过映射 allGoals 对象数组,我现在输出这个无序列表,为每个单独的目标提供一个列表项条目。
总结
在本节课中,我们一起学习了在 React 组件中使用 useState 钩子,包括如何声明、读取和更新状态。通过编码实现一个 React 目标应用,你应该对在组件内使用 useState 钩子有了更深入的了解。


59:什么是副作用 🧪
在本节课中,我们将要学习 React 组件中的一个重要概念:副作用。我们将探讨纯函数与不纯函数的区别,理解副作用是什么,并初步了解如何使用 useEffect 钩子来处理副作用。
在开始学习另一个名为 useEffect 的钩子之前,我们先花点时间思考一下它的名字。useEffect 钩子的名称与“作用”或更精确地说“副作用”的概念密切相关。本节视频中,你将学习 React 组件内的副作用是什么,包括纯函数和不纯函数及其与副作用的关系,以及 useEffect 如何在函数组件中执行副作用。
什么是副作用?
副作用是使函数变得“不纯”的东西。


纯函数与不纯函数

你知道函数可以分为纯函数和不纯函数吗?

简单来说,纯函数没有副作用,而不纯函数则有副作用。让我们进一步展开与副作用相关的纯函数和不纯函数的概念。
一个纯函数应该:
- 接收特定的输入(即特定的参数)。
- 无论被调用多少次,总是返回完全相同的输出。
为了说明这一点,让我们探索一个使用 Little Lemon 餐厅成立年份的函数。在这个例子中,EstablishedYear 组件接收一个 props 对象作为参数。它返回一个输出“Established year: ”后跟 year 属性值的标题。






只要 year 属性的值是 2003,无论 EstablishedYear 函数被调用多少次,或者从 App 组件渲染多少次,输出都将保持不变。这是一个纯函数的例子。EstablishedYear 函数没有副作用。




不纯函数与副作用
相比之下,不纯函数会执行副作用。这意味着它会做一些事情,例如调用 console.log、调用 fetch 或调用浏览器的地理位置功能。




在这个上下文中,副作用可以被认为是函数外部或函数之外的东西。考虑为 Little Lemon 应用构建的购物车函数示例。




它之所以是一个不纯函数,是因为包含 console.log 的那行代码。console.log 调用使得函数不纯,因为它是对浏览器应用程序编程接口(API)的调用。现在,ShoppingCart 函数依赖于其自身之外、甚至 React 应用之外的东西才能正常工作。




引入 useEffect 钩子
那么,在 React 中应该如何应对不纯函数的问题呢?关键在于将不纯的操作封装在它们自己特定的区域内。在 React 中,你需要使用 useEffect 钩子来实现这一点。
让我们用 useEffect 钩子更新 ShoppingCart 组件,以妥善处理由 console.log 引起的副作用。首先,你需要从 React 导入 useEffect 钩子。


useEffect 钩子通过接收两个参数来工作。第一个是回调函数。第二个参数是一个数组。这个数组可以保持为空,这是完全有效的。





虽然这种语法是有效的,但通常使用箭头函数作为调用 useEffect 钩子的第一个参数。

请注意,useEffect 的用法被简化成了一行代码。它通常需要跨越多行代码,因为它通常需要做一些比仅仅在控制台记录组件变量值更有意义的事情。
总结
本节课中,我们一起学习了纯函数和不纯函数及其与副作用的关系,探讨了 React 组件内的副作用是什么,并简要介绍了如何使用 useEffect 来执行副作用。


你可以期待应用这些知识,使用本节视频中介绍的 useEffect 钩子来执行副作用。
60:使用 useEffect 钩子 🪝
在本节课中,我们将学习如何在 React 组件中使用 useEffect 钩子来处理副作用,例如更新浏览器标签页的标题。我们将通过构建“小柠檬”餐厅应用的一个功能来演示,并重点讲解如何使用依赖数组来控制 useEffect 的执行时机。
为了演示如何在组件内使用 useEffect 钩子,我们将继续开发“小柠檬”应用。
餐厅老板希望添加一种特定的用户交互方式:点击一个按钮时,显示欢迎信息;点击另一个按钮时,隐藏该信息。此外,老板还希望这个变化能反映在运行该应用的浏览器标签页上。
浏览器标签页的更新就是一个副作用的例子。在本视频中,我将演示如何使用 useEffect 钩子在 React 中执行副作用,以及如何使用依赖数组来控制 useEffect 函数的运行时机。


我将使用之前通过 Create React App 构建的一个应用来演示如何使用 useEffect 钩子。让我们从 return 语句开始分析。
我有一个包裹性的 div,其内部包含一个 H1 标题、一个按钮,以及一个使用逻辑与 (&&) 运算符进行条件渲染的 H2 的 JSX 表达式。
按钮有一个 onClick 事件处理属性,它会触发 clickHandler 函数。该函数是我在第7行开始声明的一个函数表达式。
在第5行,我通过调用 useState 钩子解构出了 toggle 变量,用于跟踪其状态,从而使 return 语句中的条件渲染成为可能。


现在,让我们在浏览器中检查当前运行的应用。
一切运行正常。当我点击按钮时,如果之前没有显示,按钮下方会出现“欢迎来到小柠檬”的句子,反之亦然。
尽管应用运行良好,但我的应用目前无法按照餐厅老板的要求更新浏览器标签页中的文本。
这是一个副作用的例子,因此添加此功能的正确方法是使用 useEffect 钩子。
所以,在 return 语句上方,我将按如下方式添加对 useEffect 函数的调用:
React.useEffect


我需要向 useEffect 调用传递一个函数,因此我将添加一个不带参数的箭头函数。
在箭头函数体内,我将添加这个三元表达式,它检查 toggle 变量的值是 true 还是 false。如果是 true,则返回字符串“欢迎来到小柠檬”。否则,返回字符串“使用 useEffect 钩子”。
无论返回什么,都将赋值给 document 对象的 title 属性。这个属性会动态更新运行此 React 应用的浏览器标签页上显示的文本。
让我们在浏览器中检查更新后的应用。


我可以确认,每次点击按钮时,标签页的标题都会更新为两个指定字符串中的一个。
假设老板改变了主意,他希望文档标题在组件初始渲染时被设置一次,之后就不再更新。


这时就需要用到依赖数组。依赖数组决定了 useEffect 钩子何时被调用。
现在,我将用空依赖数组更新我的代码,这意味着我没有跟踪任何状态变量的状态。换句话说,无论我的应用中发生什么,我都不希望 useEffect 钩子被调用。
这意味着它只会被调用一次。之后,无论应用中发生什么,useEffect 钩子都不会再运行。
更新应用后,让我们保存更改,并在浏览器中检查这如何影响应用的行为。


useEffect 钩子只运行一次,输出“使用 useEffect 钩子”字样。之后,无论我点击多少次“切换信息”按钮,标签页标题都没有进一步更新。
依赖数组的作用是监视特定变量的变化,并基于此执行作为 useEffect 函数调用第一个参数传入的函数。


这意味着,如果我希望每当存储在 toggle 变量中的值更新时都运行 useEffect 钩子,就需要将 toggle 变量添加到依赖数组中。


完成此更改后,回到浏览器中,每次点击“切换信息”按钮时,useEffect 钩子都会运行。因为点击处理程序通过调用 setToggle 函数更新了 toggle 状态变量的值。
这反过来又触发了 useEffect 的调用,因为依赖数组被设置为监视 toggle 变量值的变化。
这样,我就有办法满足餐厅老板可能涉及 React 应用中副作用的任何请求。
现在,你应该对如何使用 useEffect 函数处理副作用,以及如何使用依赖数组来确定其调用时机有了更好的理解。




本节课中,我们一起学习了 useEffect 钩子的核心用法。我们了解到 useEffect 是 React 中处理副作用(如操作 DOM、设置订阅等)的主要工具。通过依赖数组,我们可以精确控制副作用执行的时机:空数组 [] 表示仅在组件挂载时执行一次;包含特定状态变量的数组 [toggle] 表示在该变量变化时执行;不提供数组则表示在每次渲染后都执行。掌握这些概念,你就能在 React 应用中灵活地管理各种副作用逻辑了。
61:19_钩子的规则是什么
概述
在本节课中,我们将要学习 React 钩子的核心使用规则。理解并遵守这些规则对于正确、高效地使用钩子至关重要,它们能帮助你避免常见的错误,并确保你的 React 应用稳定运行。
现在,你应该对 React 中钩子的目的和功能有了相当好的理解。你可能已经开始在一些解决方案中使用钩子。如果是这样,你可能遇到过一些情况,钩子可能使你的代码无效。这是因为在使用钩子时,有一些基本规则需要你在 React 应用中注意并遵循。
钩子的四大核心规则
以下是钩子的四个主要规则。
第一,你应该只从 React 组件函数中调用钩子。


第二,你应该只在 React 组件函数的顶层调用钩子。
第三,你可以在一个组件内调用多个状态钩子或副作用钩子。
第四,始终确保这些多个钩子调用的顺序一致。
现在,让我们更详细地解读每一条规则。
规则一:仅从 React 函数中调用钩子
第一条规则意味着你不应该从普通的 JavaScript 函数中调用钩子。相反,你应该只从 React 组件函数内部、内置钩子调用(例如 useEffect)或自定义钩子内部调用它们。
以下是一个代码示例,描述了一个可以点击为宠物选取新名字的按钮。nameLooper 函数用于将宠物名字选项限制为 “Fluffy”、“Rexxi” 或 “Gizmo”。请注意,useState 钩子是在 App 函数的最外层作用域中调用的,它没有在 nameLooper 函数作用域内部等其他地方使用。
function App() {
const [petName, setPetName] = useState(‘Fluffy‘); // ✅ 正确:在组件顶层调用
function nameLooper() {
// 这里不能调用 useState
// 但可以使用状态设置函数
setPetName(‘Rexxi‘); // ✅ 正确:状态设置函数可以在任何需要的地方使用
}
return ( ... );
}
然而,你可能已经注意到,这条规则并不阻止你使用状态设置函数(这里命名为 setPetName)。状态设置调用可以在任何需要的地方使用。
规则二:仅在顶层调用钩子
第二条规则意味着你必须在 return 语句之前、在循环、条件或嵌套函数之外调用你的钩子。如果你在条件语句中使用钩子,你就违反了规则。
例如,在下面的代码中,useEffect 钩子被用在 if 条件语句内部,这使得这段代码中的钩子使用无效。
// ❌ 错误示例:在条件语句内调用钩子
if (someCondition) {
useEffect(() => {
// 副作用逻辑
});
}
规则三与四:顺序一致性与条件调用
第三条和第四条规则紧密相关。只要始终以相同的顺序调用,一个组件内可以有多个钩子调用。这意味着你不能将钩子调用放在条件语句中,因为这可能导致与上一次渲染相比,某个钩子的调用被跳过,从而破坏调用顺序。
让我们通过一个例子来理解违反规则的后果。在之前描述宠物名字的代码中,如果错误地在 nameLooper 函数内部使用了 useState 钩子,而不是使用状态设置函数 setPetName,就会违反规则。
function nameLooper() {
// ❌ 错误:在嵌套函数内调用钩子,破坏了调用顺序
const [tempName, setTempName] = useState(‘’);
// ... 其他逻辑
}
如果你编译并运行这个应用,初始时可能看到预期的输出,例如 “I‘d like to name my pet Fluffy” 和一个 “Pick a new name” 按钮。然而,一旦你点击按钮,就会收到一个“无效的钩子调用”错误。这违反了第四条规则,即破坏了渲染之间钩子调用的顺序,从而导致错误。
如果你想有条件地调用一个副作用,你仍然可以做到,但必须确保将条件放在钩子内部。
// ✅ 正确示例:将条件逻辑放在 useEffect 钩子内部
useEffect(() => {
if (someCondition) {
// 条件性的副作用逻辑
}
}, [someCondition]); // 依赖项中包含条件变量

在上面的正确示例中,useEffect 钩子被正常调用,随后在钩子内部执行 if 条件判断。这样就没有违反规则,代码是有效的。
总结
本节课中,我们一起学习了在 React 中使用钩子的主要规则及其重要性。我们了解到,必须只在 React 组件函数的顶层调用钩子,并且要确保多次调用的顺序一致。
让我们回顾一下钩子的四大核心规则:
- 只从 React 组件函数中调用钩子。
- 只在 React 组件函数的顶层调用钩子。
- 允许在组件内调用多个状态或副作用钩子。
- 始终确保这些多个钩子调用的顺序一致。
只要遵循这些简单的规则,你就可以成功地在你的 React 解决方案中享受使用钩子带来的便利。
62:在获取数据之前需要知道什么 🧠
在本节课中,我们将要学习 fetch 函数的工作原理。fetch 是一个非常有用的工具,但在开始使用它之前,你需要理解一些重要的概念。首先,了解 JavaScript 如何委托任务将有助于你洞察 fetch 函数在此过程中的作用。我们将通过一个简单的 JavaScript 示例,来学习 fetch 如何从网络获取数据。
JavaScript 的单线程与异步模型
上一节我们提到了 fetch 函数的作用,本节中我们来看看 JavaScript 执行任务的基本方式。JavaScript 是单线程的,这意味着它一次只能做一件事。这就像一个邮局里只有一个柜员。
想象一下,你带着一个包裹去邮局,而你是队列中的第一个人。柜台后的柜员就是 JavaScript。由于他一次只能处理一件事,他必须按顺序完成:获取你的信息、称重包裹、贴邮票、收费、将包裹送到后台办公室,最后找到正确的邮寄位置。
这种方法的问题是,前一个步骤未完成,下一个步骤就无法开始。这就是所谓的单线程执行。由于 JavaScript 本身不具备多任务处理能力,解决这个问题的方法如下:
以下是 JavaScript 委托任务的过程:
- JavaScript 获取你的信息。
- 同时,它调用一个“职员”来测量包裹重量。
- JavaScript 调用另一个“职员”来贴邮票。
- JavaScript 调用另一个“职员”来收取服务费。
- 再调用一个“职员”将包裹送到后台办公室。

这样,JavaScript 就能去服务下一位顾客了。
本质上,这种职责委托就是异步 JavaScript。在这个比喻中,浏览器是邮局,JavaScript 是邮局里的一个柜员,而所有其他“职员”可以被称为浏览器 API 或 Web API。
一个实际的代码示例

现在让我们探索一个 JavaScript 中职责委托如何工作的实际例子。我有一个本地的 HTML 文件,其中最重要的部分是 script 标签,它从一个名为 script.js 的文件中获取 JavaScript 代码。

假设 JavaScript(邮局工作人员)需要从计算机数据库中获取一些用户数据。以下是 script.js 文件中的 JavaScript 代码:
console.log("another customer approaching");
fetch("https://randomuser.me/api/")
.then(response => response.json())
.then(data => console.log(data));
console.log("our valued customer");
由于 JavaScript 在任何给定时间只能做一件事,你期望这段代码的输出顺序是什么?让我们逐步分析代码在做什么。
在第一行,它执行 console.log,输出“another customer approaching”。然后,它联系 fetch API——这是一个外部的、独立于 JavaScript 的浏览器 API。JavaScript 不会等待 fetch API 返回信息,而是继续执行其后的代码,输出以“our valued customer”开头的文本。
与此同时,fetch API 向一个可用的第三方基于 Web 的 API(randomuser.me 网站)请求一些用户数据。fetch 函数被称为外观函数,这意味着它看起来像是 JavaScript 的一部分,但实际上它只是从 JavaScript 调用浏览器 API 的一种方式。换句话说,它是我访问 JavaScript 之外的浏览器功能的一种方式。
你可以把它想象成 JavaScript 邮局柜员,呼叫邮局的记录部门以获取一些客户数据。当另一个柜员带着信息回来并交给邮局柜员时,他们将获得一个 JSON 表示,并最终将该数据记录到控制台。
这意味着代码中控制台日志的顺序将如下:
- 初始的
console.log,输出“another customer approaching”。 - 第二个
console.log,输出“our valued customer”。 - 最后一个
console.log,输出从 API 调用返回的数据。
执行此代码后,我们在浏览器控制台中首先看到“another customer approaching”,然后是“our valued customer”,最后是调用第三方 API 的结果。这正是所描述的行为。
这就是 JavaScript,尽管是单线程的,却可以执行异步操作的方式。


总结
本节课中我们一起学习了 fetch 函数如何从网络检索数据,并通过一个简单的 JavaScript 示例了解了整个过程。你应该在开始在 React 中获取数据之前熟悉这个概念,我们很快将会探讨。理解 JavaScript 的异步模型和 fetch 作为浏览器 API 接口的角色,是进行高效前端开发的基础。
63:获取数据并汇总 📊
在本节课中,我们将学习如何在React应用中从外部API获取数据,并利用状态和副作用钩子来管理数据加载过程。我们将通过模拟“小柠檬餐厅”抽奖活动的场景,演示如何随机选取一位幸运顾客。
概述

“小柠檬餐厅”希望为其顾客举办一场抽奖活动,一位幸运顾客将获得餐厅的免费餐食。所有注册了“小柠檬”应用的顾客都将被纳入抽奖池,并随机选出一位获胜者。在本视频中,我将展示如何从一个网站获取随机用户数据。我将使用 randomuser.me 网站来获取用于演示的随机用户数据。
上一节我们介绍了React组件的基础结构,本节中我们来看看如何与外部API进行交互并动态更新UI。
代码实现与演示
我已经为此应用准备了一些代码,用于从网站获取用户数据。如果我执行这段代码,它最初会在H1标题中输出“数据加载中”的文本。同时,在后台,它将执行 fetchData 函数,从随机用户网站检索用户信息。
请注意,我打开了开发者工具并激活了网络选项卡。我将点击“节流”下拉菜单,人为地减慢我的连接速度。在下拉菜单中,我将选择“慢速3G”预设。这样我就可以在从网络获取数据之前,观察到标题中的“数据加载中”文本。
一旦数据成功获取,视图将更新为返回的数据:H1标题和已检索到的用户信息。在这个例子中,请求的数据是这位随机用户的名字和姓氏。

让我们更详细地逐步分析这段代码。
代码分步解析

首先,我有 App 函数组件,在它内部,遵循钩子的规则,我在组件的顶层调用了 useState 钩子。状态变量的初始值是一个空数组。
接下来,我定义了 fetchData 函数,它从 randomuser.me API 获取数据。然后,它从API获取JSON格式的响应,并用这个JSON数据更新状态变量。
你可能会注意到,我没有在 fetchData 函数内部使用钩子,因为这违反了钩子的规则。
之后,我调用了 useEffect 钩子,并从 useEffect 内部调用我之前定义的 fetchData 函数。
最后,我使用条件逻辑来决定返回什么。首先,我使用 Object.keys 代码片段将用户对象的所有键放入一个数组中。
由于 Object.keys 返回一个数组,我可以访问这个数组的 length 属性,并检查其长度是否大于0。如果是,则意味着状态数组的内容已经改变,因为你可能还记得,状态变量数组最初是空的。所以,如果数组不再为空,那么将返回带有H1标签和几个H2标签的 div 部分;否则,将返回下面显示“数据加载中”的H1标签。
数据加载与渲染流程
有时,fetchData 函数检索所请求的数据可能需要一点时间。因此,代码执行后最初会显示“数据加载中”的消息。一旦从 fetchData 调用中获取到数据,状态的这种变化会导致我的组件重新渲染。因此,返回语句中的三元运算符被重新求值,并返回我从调用Fetch API获得的所有数据。
这基本上就是你在React中从网络获取数据的方式。因此,“小柠檬餐厅”可以对其客户列表API应用相同的逻辑,来为他们的抽奖活动随机选择获胜者。
核心概念总结
在本视频中,你学习了如何使用状态和副作用钩子来获取数据。我们通过以下步骤实现:
-
初始化状态:使用
useState定义一个状态变量来存储数据。const [userData, setUserData] = useState([]); -
定义数据获取函数:创建一个异步函数(如
fetchData)来调用API。const fetchData = async () => { const response = await fetch('https://randomuser.me/api/'); const data = await response.json(); setUserData(data.results[0]); }; -
触发数据获取:在
useEffect钩子中调用数据获取函数,通常依赖项数组为空[]以在组件挂载时执行一次。useEffect(() => { fetchData(); }, []); -
条件渲染:根据状态(数据是否已加载)决定渲染加载指示器还是实际数据。
return ( <div> {Object.keys(userData).length > 0 ? ( // 渲染数据 ) : ( // 渲染加载中状态 )} </div> );
总结


本节课中我们一起学习了在React应用中从外部API获取数据的完整流程。我们了解了如何结合使用 useState 和 useEffect 钩子来发起网络请求、管理加载状态,并根据数据是否可用来条件性地渲染UI。这种方法为构建动态、数据驱动的React应用奠定了基础,正如“小柠檬餐厅”抽奖功能示例所展示的那样。
64:22_API
概述
在本节课中,我们将学习应用程序编程接口(API)在应用开发中的核心作用、设计原则以及开发流程。API是连接前端与后端、实现数据交互的桥梁,其设计的质量直接影响到应用的稳定性和可维护性。


想象一下,当你打开Facebook或Instagram的首页,如果看到的只是一片空白,那会是什么情景。填充其中的照片、文字、点赞和评论等所有数据,都是通过API获取的。因此,编写高效、可靠的API是应用开发过程中至关重要的一环。没有它,你的应用基本上无法运行。
API的设计与开发流程 🛠️
上一节我们了解了API的重要性,本节中我们来看看在Meta这样的公司里,API是如何被设计和开发出来的。
我的名字是Mortara,是Meta西雅图办公室的一名软件工程师。
在Meta,API的设计过程通常涉及多次迭代,这与我们编写代码的过程类似。当我们编写代码时,会请同事进行评审,设计API时也是如此。
通常,个人或团队会撰写一份文档,作为API的提案。这份提案会详细说明API的结构,以及应用各个部分之间需要达成的协议。然后,这份提案会被提交给其他工程师进行评审。他们会留下评论,指出哪些部分合理、哪些需要修改、哪些不够好。接着,我们进行迭代和改进。当API设计趋于完善,进入最终阶段后,我们就会进入下一步:构建它。
在构建过程中,同样的事情会再次发生。我们将评审实现该设计的代码,期间会有评论、反馈和迭代。一旦完成,我们的API就准备就绪了。
通常,开发API的过程会涉及全栈的工程师。我们需要与前端工程师、后端工程师以及中间层的工程师沟通,以确保API在整个技术栈中保持一致性和合理性。
简单来说,API可以被视为一种与不同事物对话的方式。例如,你手中的手机应用需要与服务器通信,而它们对话的方式就是通过API。要进行对话,就必须建立某种共识。这种共识由开发者之间确立,并最终被构建到应用程序中,体现在API的设计里。
API设计的核心原则 🔑
在了解了开发流程后,我们来看看设计API时需要关注和遵循的几个核心原则。
以下是设计API时我们关注和遵循的几个要点:
-
确保类型安全:类型安全是确保当你从后端获取数据时,能得到你所期望的内容。例如,如果你请求一张照片,你实际得到的就是一张照片,而不是视频或其他东西。这是设计API的重要部分,可以防止应用崩溃。许多应用故障都源于数据不匹配,即你期望得到某个对象,却得到了其他东西。类型安全可以最大限度地减少这类错误或问题,并在整个技术栈中保持一致的类型安全,从而提高应用的稳定性。
- 代码示例:
const response: Photo = await fetchPhotoAPI(photoId);
- 代码示例:
-
平衡当前需求与未来扩展:我在设计API时经常遇到的一个难题是如何让它适应未来的需求。这里有一个需要把握的平衡点。我们可能过于执着于让API对未来非常兼容,试图预测应用将如何演变,并提前加入各种功能。但有时这可能做得过头了。找到那个平衡点——既考虑未来,又不至于因为过度设计而阻碍当前工作的完成——就像生活中的许多事情一样,你既要尝试预测,也要专注于眼前的问题,并思考如何有效地解决它。
给初学者的建议与鼓励 🌟
上一节我们探讨了API设计的技术原则,本节中我们来听听一些实用的建议。
我知道API有时会让人感到不知所措,所以祝你一切顺利。请花时间学习它们,不要让这种感受压倒或吓退你。当你研究API时,你将会深入到后端领域,所以要知道,你正在承担更广的范围和更高的复杂性。这对你是有益的,因为你会因此对雇主更具吸引力,并扩展你的知识面。不要让这些吓倒你或阻碍你前进,学习这些技能和概念将会非常有帮助。
我想给自己以及所有正在思考API设计的人一个建议:保持简单。

有时我们容易想得太多,试图把太多东西塞进API,或者试图让它变得非常花哨,使用复杂的设计模式。但实际上,简单性在API设计中至关重要,其可读性也是一大优点。所以,保持简单,并努力让它保持稳定、没有错误。
总结
本节课中,我们一起学习了API在连接应用前后端中的关键作用。我们了解了在团队协作中,API从提案、评审到构建的完整开发流程。同时,我们探讨了设计API时应遵循的核心原则,特别是类型安全和平衡当前与未来需求的重要性。最后,我们获得了保持设计简单、稳定并勇于面对复杂性的宝贵建议。掌握API设计与理解,将帮助你构建更健壮、可扩展的应用程序。
65:什么是 useReducer,它与 useState 有何不同 🧩
在本节课中,我们将要学习 useReducer Hook,了解它的工作原理,并探讨它与我们熟悉的 useState Hook 有何不同。我们将通过一个为“小柠檬”餐厅构建费用追踪应用的实例,来直观地理解 useReducer 如何管理更复杂的状态逻辑。
概述:useState 的局限性
到目前为止,你应该对 useState Hook 有了相当好的理解,并能将其实际应用于你的解决方案中。
然而,useState Hook 确实有其局限性。例如,当你涉及多个子值的复杂状态逻辑,或者下一个状态依赖于前一个状态时,使用 useState 可能会变得繁琐。
在这些情况下,useReducer Hook 可以提供一个更好的替代方案。
什么是 useReducer? 🚀
你可以将 useReducer 视为一个功能更强大的 useState。
useState Hook 从一个初始状态开始,而 useReducer Hook 除了初始状态外,还会接收一个 reducer 函数。
这非常有益,因为 reducer 函数的第二个参数是一个 action 对象。这个对象有多个类型值,基于这些类型值中的每一个,你可以调用 dispatch 函数来执行特定的操作。
应用场景:小柠檬餐厅的费用追踪
现在,假设小柠檬餐厅越来越受欢迎,需求不断增长,因此,追踪支出成了一个难题。到目前为止,他们一直在手动计算收入和支出,包括向顾客销售餐点和购买食材补充库存。

小柠檬餐厅正在寻找一个使用 React 的解决方案,以便在他们的应用中追踪支出,减轻员工的负担。

因为使用 useState Hook 会使这个解决方案变得不必要的复杂,所以这是实现 useReducer Hook 的绝佳机会,以便简单地追踪购买食材的成本和向顾客销售成品餐点所产生的收入。
代码实现:深入 useReducer
现在,让我们通过一个代码示例来探索如何实现 useReducer Hook,以加深你的理解。
想象一下,我正在使用 React 和 useReducer 为之前讨论的小柠檬餐厅编写费用追踪应用程序。
通过这个应用,我可以追踪两个操作:在小柠檬餐厅购买食材准备餐点的成本,以及在餐厅向顾客销售成品餐点的收入。为了简单起见,我只添加了两个操作:buy_ingredients(购买食材)和 sell_meal(销售餐点)。
Reducer 函数与 Action
reducer 函数接收先前的状态和一个 action,并返回新的状态。action 的 type 属性决定了 reducer 要执行的具体操作。
按照惯例,action 通常被传递为具有 type 属性的对象。它应该包含 reducer 计算下一个状态所需的最少必要信息。你可以在本课末尾的补充阅读中了解更多关于将状态逻辑提取到 reducer 的信息。
Dispatch 方法
与 useState Hook 使用 setState 不同,你使用 useReducer Hook 的 dispatch 方法。它接受一个字面量对象,该对象有一个名为 type 的属性,其值设置为与 reducer 函数内部定义的行为相匹配的 action.type。
由于我已经在浏览器中运行这个应用,让我演示一下它是如何工作的:当我按下“购买蔬菜”按钮时,钱包金额减少 10;当我按下“为顾客提供餐点”按钮时,钱包金额增加 10。


扩展功能:添加更多 Action 类型
使用 useReducer,你可以根据需要定义更多类型。这样,你就可以轻松地在 React 应用中使用更复杂的逻辑,这些逻辑在使用 useState 时可能难以合理化。
为了在实践中探索这一点,让我们添加另一个 action 类型。我将其命名为 celebrity_visit(名人到访)。当有名人到访餐厅时,应触发此操作,这将为餐厅带来 5000 美元的收入。
为了实现这个功能,我在 reducer 函数中添加了另一个 action 类型,然后添加了另一个按钮来触发它。




我将保存更改并在浏览器中预览更新后的应用。看,一切都按预期工作。点击“名人到访”按钮会使钱包金额增加 5000。就这么简单。
现在,小柠檬餐厅将能够追踪他们的支出,从而清楚地了解他们从业务中赚取了多少利润。
总结与回顾
在本视频中,你学习了 useReducer Hook,了解了它与 useState Hook 的不同之处,以及为什么在某些情况下它可以成为一个有益且更高效的解决方案。


核心概念回顾:
useReducer:适用于管理包含多个子值或下一个状态依赖于前一个状态的复杂状态逻辑。- Reducer 函数:格式为
(state, action) => newState,根据action.type决定如何更新状态。 - Dispatch 方法:用于触发状态更新,发送一个包含
type的 action 对象给 reducer。 - 与
useState对比:useReducer通过集中化的 reducer 函数和分发的 action,让复杂状态变化的逻辑更清晰、更易于维护和测试。
66:使用 useRef 访问底层 DOM 🔍
在本节课中,我们将学习如何使用 React 的 useRef Hook 来访问和操作底层的 DOM 元素。我们将通过一个具体的例子——创建一个点击按钮后自动聚焦到输入框的功能——来演示其核心用法。
概述
假设“小柠檬”餐厅的店主希望为库存搜索功能添加一个特性:点击按钮后,光标能自动聚焦到搜索输入框。在本节中,我们将在一个独立的应用中编写代码来实现这一特定需求,以便专注于 useRef Hook 的功能。
初始应用设置
首先,我们有一个使用 Create React App (CRA) 构建的示例应用。为了更清晰地展示 useRef 的用法,我对初始代码进行了一些调整。
初始应用仅包含一个返回语句,其中有一个 Fragment,内部是一个显示“使用 useRef 访问底层 DOM”的 H1 标题。

import React from 'react';
function App() {
return (
<>
<h1>使用 useRef 访问底层 DOM</h1>
</>
);
}
export default App;
添加输入框和按钮
为了演示 useRef 如何用于访问 DOM,我们将用它来将光标聚焦到一个输入框。因此,我们首先需要添加一个输入框和一个按钮。
function App() {
return (
<>
<h1>使用 useRef 访问底层 DOM</h1>
<input type="text" />
<button>聚焦输入框</button>
</>
);
}
实现点击处理函数
现在,我们为按钮添加一个 onClick 事件处理属性。我们需要定义 focusInput 函数来处理按钮点击。
function App() {
const focusInput = () => {
// 这里将实现聚焦逻辑
};
return (
<>
<h1>使用 useRef 访问底层 DOM</h1>
<input type="text" />
<button onClick={focusInput}>聚焦输入框</button>
</>
);
}
引入 useRef Hook
为了访问输入框的 DOM 节点,我们需要使用 useRef Hook。useRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数(这里为 null)。
import React, { useRef } from 'react';
function App() {
const formInputRef = useRef(null);
const focusInput = () => {
// 聚焦逻辑将在这里使用 formInputRef
};
return (
<>
<h1>使用 useRef 访问底层 DOM</h1>
<input type="text" ref={formInputRef} />
<button onClick={focusInput}>聚焦输入框</button>
</>
);
}
连接 Ref 与 DOM 节点
我们将 formInputRef 作为 ref 属性的值传递给 input 元素。React 在创建该输入框的 DOM 节点并渲染到屏幕上后,会将该 DOM 节点赋值给 formInputRef.current 属性。
实现聚焦逻辑
现在,我们可以在 focusInput 函数中访问这个 DOM 节点。输入框的 DOM 节点有一个 .focus() 方法,可以使其获得焦点。
const focusInput = () => {
formInputRef.current.focus();
};
代码解释:
formInputRef.current指向<input>的 DOM 节点。.focus()是该 DOM 节点自带的方法,用于将焦点设置到该元素上。
这样,用户无需手动点击或按 Tab 键切换到输入框,点击按钮即可直接开始输入。
测试功能
保存所有更改,在浏览器中查看运行中的应用。点击输入框外部,然后点击“聚焦输入框”按钮。可以看到光标成功跳转到了输入框内,功能正常工作。
核心概念总结
useRefHook: 用于创建一个可变的 ref 对象,其.current属性可以持有任何值。- 访问 DOM: 将 ref 对象通过
ref属性附加到 JSX 元素时,React 会将对应的 DOM 节点赋值给其.current属性。 - 操作 DOM: 通过
refObject.current可以访问该 DOM 节点,并调用其原生方法(如.focus())或读取/修改其属性。


本节总结

在本节课中,我们一起学习了如何使用 useRef Hook 来“钩入”DOM,并基于手头的任务(例如聚焦输入框)来操作特定 DOM 节点的属性。useRef 是连接 React 声明式世界与命令式 DOM 操作的重要桥梁。
67:React 钩子与自定义钩子 🎯
在本模块中,我们深入学习了 React 钩子和自定义钩子。我们探讨了许多重要的概念和实际应用,这些知识将帮助你在后续课程中更好地进行前端开发。现在,让我们来回顾一下你所学到的关键知识和技能。
第一课:useState 钩子
上一节我们介绍了模块概述,本节中我们来看看第一个核心钩子:useState。它用于在 React 函数组件中管理状态。
你学习了如何使用 useState 钩子进行数组解构,以及如何通过状态更新函数来更新状态。我们还探讨了如何响应用户事件(如按钮点击)来改变状态。通过一个详细的演示,你掌握了在组件内声明、读取和更新状态的方法。
核心概念代码示例:
const [state, setState] = useState(initialState);
完成本课学习后,你现在应该能更好地在 React 中处理组件状态了。
第二课:useEffect 钩子与副作用
在掌握了状态管理后,本节我们将关注另一个重要概念:副作用,以及与之相关的 useEffect 钩子。
你了解到副作用会使函数变得“不纯”,这些不纯函数会执行诸如调用 console.log、fetch 或浏览器的地理位置功能等操作。除了理论学习,你还通过实践演示学习了如何在函数组件中使用 useEffect 钩子来执行副作用,以及如何使用依赖数组来控制 useEffect 函数的执行时机。
需要记住的是,依赖数组决定了 useEffect 钩子何时被调用。
核心概念公式:
useEffect(effectFunction, dependencyArray)
第三课:钩子规则与数据获取
在学习了两个核心钩子后,本节我们将了解使用钩子时必须遵守的规则,以及如何从网络获取数据。
首先,你学习了 React 中钩子的使用规则并理解了它们。这些规则是:
- 只能在 React 组件函数中调用钩子。
- 必须在 React 组件函数的顶层调用钩子。
- 可以在一个组件内调用多个状态钩子或副作用钩子。
- 应保证多次钩子调用的顺序一致。

以下是关于 fetch 函数的内容。你学习了 JavaScript 中被称为“异步 JavaScript”的职责委托机制。fetch 函数本身看起来是 JavaScript 的一部分,但实际上它是从 JavaScript 调用浏览器 API 的一种方式。这引出了使用状态和副作用钩子获取数据的课程。你通过一个演示,学习了如何使用 fetch API 在 React 中从网络获取数据。
核心概念代码示例:
fetch(url)
.then(response => response.json())
.then(data => setState(data));
完成本课后,你应该更深入地理解了如果开发者不遵守 React 钩子规则会发生什么,并且能够描述在 JavaScript 和 React 中是如何获取数据的。

第四课:useReducer 钩子与自定义钩子
在掌握了基础钩子之后,本节我们将探索更高级的状态管理钩子 useReducer,并学习创建自定义钩子。
你学习了 useReducer 钩子,它与 useState 的不同之处在于,除了初始状态外,它还需要一个 reducer 函数。你了解到在 useState 效率低下的情况下可以使用 useReducer,例如当你拥有复杂的状态逻辑时,并学习了如何在代码中实现 useReducer 钩子。
核心概念代码示例:
const [state, dispatch] = useReducer(reducer, initialState);
此外,你还接触了 refs 的概念,了解了它们如何用于超越虚拟 DOM 并访问底层 DOM 元素及其他应用。你也有机会探索如何编写自己的自定义钩子。

核心概念代码示例:
function useCustomHook() {
// 钩子逻辑
return value;
}
完成本课后,你应该能够使用 useReducer 钩子来跟踪状态,并在你的 React 应用中创建自己的自定义钩子。

总结与展望 🚀
在本节课中,我们一起学习了 React 钩子的核心知识。你取得了出色的进展,现在对使用钩子已经有了扎实的掌握。
接下来,是时候深入探讨 JSX 了,我期待在下一个模块中继续指导你。
68:JSX 组件与元素 🧩
在本节课中,我们将要学习 React 如何利用 JSX 来描述用户界面,理解组件与元素之间的区别,并探索 React 声明式编程模型背后的核心概念。

业务背景与需求
Little Lemon 餐厅的老板们长期以来对他们的原始网站感到满意。但随着业务增长,他们现在希望添加更多功能、交互性和数据分析。他们发现原始网站在进行此类增强时存在诸多限制。在寻求建议后,他们得出结论:应该使用 JSX 和 React 来开发一个应用。他们希望更深入地理解 JSX 和 React,以便为商业计划构建合理的依据。

什么是 JSX?

上一节我们介绍了项目的背景,本节中我们来看看 JSX 是什么。

JSX 是 JavaScript 的一种语法扩展,React 使用它来描述用户界面应该是什么样子。然而,尽管 JSX 看起来像 HTML,但它本质上是一个更强大的抽象,它将标记语言和业务逻辑结合到一个称为 组件 的实体中。


从 JSX 到页面:理解元素
在编写 JSX 后,React 如何为你的页面创建所需的资源?要理解所有涉及的步骤,你需要了解 React 中 元素 的概念。

到目前为止,你已经对组件有了很好的理解,知道整个用户界面是由一个组件树表示的。你也知道 React 生成的最终网页不过是纯粹的 HTML、CSS 和 JavaScript。
当 React 分析你所有组件的渲染方法时,它会从根组件开始,获取整个 JSX 树,并创建一个中间表示。这个表示本质上是另一个树形结构,但其中的每个节点不再是 JSX,而是一个描述组件实例或 DOM 节点及其所需属性的普通对象。这个普通对象就是 React 定义的 元素。
元素只是一种将最终的 HTML 输出表示为普通对象的方式。它主要由两个属性构成:type 和 props。
type:定义节点的类型,例如一个button。props:在一个对象中包含了组件接收到的所有属性。
请注意元素如何通过 children 属性实现嵌套,就像按钮示例中那样。当 React 从根元素开始创建整个元素树时,根元素将所有子元素指定为 children 属性,每个子元素也做同样的事情,直到到达树的末端。
这个新结构的重要之处在于,子元素和父元素都只是描述,而不是实际的实例。换句话说,当你创建它们时,它们并不指向屏幕上的任何东西。毕竟,它们只是对象。
但这些对象易于遍历,并且当然比实际的 DOM 元素更简单。

组件作为元素类型
到目前为止,我们介绍了使用简单 DOM 节点(如按钮)进行树转换的例子。在元素树中,这被指定为 type 属性。但元素的 type 也可以是一个函数,对应一个 React 组件。
想象你创建了一个名为 SubmitButton 的组件来封装传统的 HTML 按钮。在这种情况下,元素的 type 属性将指向该组件的名称。这就是 React 的基本思想:在元素树中,用户定义的组件和 DOM 节点可以相互嵌套和混合。
例如,如果你正在为 Little Lemon 餐厅应用创建一个注销流程,你可以用 JSX 编写一个 Logout 组件来实现。在这个 Logout 组件中,JSX 将被转换为以下的元素树。
这允许你将组件和 DOM 元素作为 type 属性进行混合和匹配,而无需担心 SubmitButton 是渲染成一个 button、一个 div 还是其他东西。这保持了组件之间的解耦,通过组合来表达它们的关系。
当 React 看到一个 type 为函数(如 SubmitButton)的元素时,它会知道去询问该组件,在给定的 props 下它渲染成什么元素。因此,React 会再次询问 SubmitButton 它渲染成什么,并将其转换为一个元素。
React 会不断重复这个过程,直到它知道页面上每个组件底层对应的 DOM 标签元素。


虚拟 DOM 与更新过程

一旦 React 完成了从元素树中识别所有用户定义组件的过程,它就会将它们转换为 DOM 元素。结果就是通常所说的 虚拟 DOM,它是真实 DOM 的一个 JavaScript 替代表示。
那么,当你的用户界面发生新变化时,涉及哪些步骤呢?
以下是更新过程的关键步骤:
- 生成新树:React 会获取你所有的 JSX,并生成一个新的 UI 表示,即一个元素树。
- 对比差异:它会将这个新树与内存中保存的先前表示进行比较。
- 计算差异:计算两棵树之间的差异。回想一下,由于树中的每个节点都是一个 JavaScript 对象,这个差异计算操作非常快。
- 应用更新:基于这个差异,React 会对底层的 DOM 节点应用最少数量的更改来处理更新。
就是这样。你可能已经开始体会到 React 声明式编程模型的优美之处了。

总结
本节课中我们一起学习了 React 如何使用 JSX 来描述用户界面,理解了组件(可复用的 UI 构建块)与元素(描述 UI 的普通对象)之间的区别,并探索了 React 声明式模型背后的核心概念。我们还看到了 React 如何将你的 JSX 转换成一个内部的元素树(这些元素只是 JavaScript 对象),正是这种轻量级的表示方式,使得 React 能够以可预测的方式更新你的 UI,同时为高性能应用提供足够快的速度。
69:性能对软件开发的重要性 🚀
在本节课中,我们将探讨性能在软件开发中的关键作用。性能优化有时只需一行代码的改动,有时则可能需要重构整个应用。我们将了解为何性能至关重要,以及如何通过渐进式改进和特定设计模式来提升应用性能。
性能的有趣之处在于,有时仅仅一行代码的改动就能决定其成败。
当然,有时为了提升性能,可能需要重写整个应用程序并从头开始。
但有时,微小的改变也能带来巨大的差异。
我叫莫塔拉,是Meta西雅图办公室的一名软件工程师。
在Meta,我们努力思考用户的多样性和不同背景,以及他们可能使用何种技术来访问我们的产品。


因此,性能的好坏可能直接决定我们是赢得还是失去一位用户。如果应用程序性能不佳、响应迟钝或速度不够快,人们就不会使用它。
所以,在开发这些应用时,思考性能对我们来说至关重要。
我们的应用程序并非天生就具备高性能。我们总是努力寻找可以提升其性能的方法。
性能通常是我们通过增量构建和逐步改进来实现的。我们可能认为性能优化不那么重要,开发新功能或让界面更丰富多彩才更令人兴奋。
性能是一种无形的特性,你可能无法立即注意到它,也无法展示它并引以为豪。

但它实际上非常重要,是应用程序的基础组成部分。没有它,应用程序就无法真正运行。

React做得非常出色的一点,也是它得以脱颖而出并广受欢迎的原因,在于它在构建和渲染Web应用方面非常高效。
React的卓越之处在于它能够将变更局部化。例如,如果你只需要更改一小段文本,React可以确保只执行更改该文本所需的工作,而不是重新渲染整个页面。
对整个页面进行重新渲染对计算机来说可能成本非常高。
因此,我们会努力寻找性能瓶颈,然后深入探究,分析为什么某个组件如此缓慢,为什么它需要这么长时间。
我们可能会思考如何重构这个组件以使其更快,或者是否可以将部分计算任务委托给后端,从而避免让用户和前端承担这部分成本。

工程师、产品经理和设计师都会在开发过程中尽可能多地使用应用程序,以设身处地地体验用户在使用我们产品时的感受。
一旦我们通过检查性能、稳定性、可用性以及功能完整性等各项指标,对应用程序的准备程度有了足够信心,我们就会将其发布到Alpha阶段。

在这个阶段,只有少量用户尝试使用,以便我们收集反馈。
随着在反馈周期中信心不断增强,我们会逐步扩大用户范围,直到最终完全发布。
高性能和良好的可用性是应用程序需求中非常重要的一部分。
为了实现这一目标,工程师们长期以来开发出了一些特定的设计模式和成熟的方法,这些方法被证明非常有效,能够提升应用性能。
例如,使用记忆化(Memoization) 或将部分处理过程委托给后端(Delegation to Backend)。
我鼓励你去寻找这些模式,并探索如何将它们引入到你的应用程序中。

本节课中,我们一起学习了性能在软件开发中的核心重要性。我们了解到,性能优化可能源于微小的代码改动,也可能是系统性的工作。关键在于持续关注、寻找瓶颈,并应用经过验证的设计模式(如记忆化和后端委托)来逐步提升应用体验。记住,性能虽“无形”,却是决定产品成败的基础。
70:带有子组件的组件组合 🧩
在本节课中,我们将要学习React中一个强大但常被忽视的概念:组件组合。我们将重点探讨如何使用特殊的 children 属性来构建更灵活、更可复用的组件。
概述
当设计React组件时,开发者常常会忽略一个最重要的属性:children 属性。这个所有组件都具备的特殊属性,是React强大组合模型的基础。
假设Little Lemon餐厅希望用户能在其应用上拥有账户。这意味着需要在应用中构建创建、管理和删除账户的流程。这类流程可以通过使用 children 属性的组件组合来简单高效地构建。
组件组合的两大特性
组件组合主要依赖于两大特性:包含 和 特化。让我们来详细分解这两个特性。
包含
包含特性指的是,某些组件在编写时并不知道其子内容是什么。这对于像侧边栏或对话框这类组件尤其常见,它们在你的UI中划定一个特定区域来容纳其他元素。你也可以将它们视为通用盒子。
注:对话框是一种模态窗口,在用户处理并与之交互之前,UI的其余部分会被禁用。
对于这些“盒子”组件,推荐的方法是使用 children 属性,直接将子元素作为其内容传递。
让我们通过一个对话框的例子来探索这一点。
以下是一个 Dialog 组件的示例,它充当“盒子”,负责样式化容器,使其看起来像一个模态窗口:
function Dialog({ children }) {
return (
<div className="dialog-overlay">
<div className="dialog-box">
{children}
</div>
</div>
);
}
通过使用 children 属性,它变成了一个通用组件,我们可以向其提供任何有效的JSX作为子内容。
为了说明这一点,我们定义了一个 ConfirmationDialog 组件,它使用 Dialog 组件,并将一个标题和一段描述渲染为其子内容:
function ConfirmationDialog() {
return (
<Dialog>
<h2>确认操作</h2>
<p>你确定要执行此操作吗?</p>
</Dialog>
);
}
这个例子也展示了组件组合的第二个特性:特化。
特化

特化定义了组件是其他组件的特殊情况。在上面的例子中,ConfirmationDialog 就是 Dialog 的一个特例。
实践:构建账户删除对话框
现在你已经熟悉了组件组合的基础知识,让我们动手编写一个应用来演示所学内容。这个应用是使用 create-react-app 创建的。
想象一下,Little Lemon希望为用户提供一种简单的方式来删除他们的账户(如果他们愿意的话)。我们的目标是构建一个通用的对话框组件,其中包含一个标题、一段描述和一个警告按钮,以确保用户了解操作的后果——所有这些都使用组件组合。
我已经创建了两个通用组件:一个 Button 和一个 Alert。Button 使用 children 属性来指定其文本,而 Alert 是一个通用盒子,它在背景渲染一个遮罩层,并在屏幕中央渲染一个白色模态框。children 属性决定了该模态框的内容。
以下是构建步骤:
第一步是使用组件组合的特化特性创建一个警告按钮。为此,我将定义一个名为 DeleteButton 的新组件,在其中渲染 Button 组件,并将其属性配置为红色和文本“删除”。
function DeleteButton() {
return <Button color="red">删除</Button>;
}
接下来,我将渲染 Alert 组件。

function App() {
return (
<Alert>
{/* 内容将在这里定义 */}
</Alert>
);
}
目前,它只是一个通用的白色盒子或容器。这说明了组件组合的第二个特性:包含。
我可以按照我想要的任何方式自定义盒子的内容,只需提供JSX作为其子元素。为了满足Little Lemon的要求,我将创建一个标题为“删除账户”的标题,以及一个告知用户相关操作的段落。
function App() {
return (
<Alert>
<h2>删除账户</h2>
<p>此操作将永久删除您的账户。您确定要继续吗?</p>
</Alert>
);
}

我想明确指出,如果他们删除账户,将错过主厨的美味食谱,所以我将在描述中反映这一点。
最后一步是渲染之前定义的 DeleteButton。

function App() {
return (
<Alert>
<h2>删除账户</h2>
<p>此操作将永久删除您的账户,您将无法再访问主厨的美味食谱。您确定要继续吗?</p>
<DeleteButton />
</Alert>
);
}
这样就完成了。
总结
本节课中,我们一起学习了React中称为组件组合的技术及其重要性。你也学会了如何运用组合的两大关键特性——包含和特化,并通过一些实际例子探索了如何利用特殊的 children 属性来创建更健壮、更可复用的组件。
71:在 JSX 中动态操作子组件 🧩
在本节课中,我们将要学习 React 中两个强大的顶层 API:React.cloneElement 和 React.Children.map。它们允许你动态地操作和转换组件的子元素,从而创建出更灵活、更智能的组件设计模式。
理解 React 子元素与组合模型
React 的 children 是所有组件隐式拥有的特殊属性之一。结合 React 的组合模型,它开启了一种新的组件设计范式。到目前为止,你可能已经学会了如何利用这个特殊属性,并且是以“只读”模式使用它,即你的组件按原样消费它。
但是,如果我们能更进一步,以任何方式、形状或形式来转换你的子元素呢?为了说明这一点,假设 Little Lemon 餐厅希望有一种方式,能在顾客下单时可视化实时订单的摘要。对于厨房工作的厨师来说,将每个顾客订单显示在单独的一行中,包含菜品名称、每道菜的数量、总价、提交时间和顾客全名,会非常有用。
通过使用一组新的 React API 来提升你的组件设计技能,你将能够为 Little Lemon 提供一个智能且高效的解决方案。让我们从探索其中两个强大的 React API 开始:React.cloneElement 和 React.Children。
探索 React.cloneElement API
React.cloneElement 是 React 顶层 API 的一部分,用于操作和转换元素。顶层 API 指的是你从 react 包中导入这些函数的方式。你可以在文件顶部将 react 作为全局对象导入,并将其作为该对象上的方法来访问,或者也可以使用命名导入。
请记住,元素只是 React 内部用来描述你希望在屏幕上显示内容的普通 JavaScript 对象。React.cloneElement 可以有效地克隆并返回所提供元素的一个新副本。
其函数签名如下:
React.cloneElement(element, [props], [...children])
- 第一个参数是你想要克隆的 React 元素。
- 第二个参数是将要添加并与传递给组件的原始属性合并的新属性。
在 React 中,属性(props)是不可变的对象。因此,你必须先创建元素的副本,然后在副本上执行转换。这正是 React.cloneElement 允许你实现的功能。
这个 API 非常有用,它允许父组件执行以下操作:
- 修改子组件的属性。
- 向子组件添加属性。
- 扩展子组件的功能。
例如,你可以动态地向之前示例中的提交按钮元素添加另一个属性。
探索 React.Children 工具集

另一个用于操作子元素的重要顶层 API 是 React.Children,它提供了用于处理 props.children 数据结构的实用工具。
其中最重要的方法是 map 函数。React.Children.map 与数组的 map 函数非常相似,它对其 children 属性中包含的每个子元素调用一个函数,执行转换并返回一个新元素。
其函数签名如下:
React.Children.map(children, function[(thisArg)])
实战演练:为 Little Lemon 餐厅创建订单行

如果这些概念听起来还有点令人困惑,不用担心。让我们通过一些代码来更好地理解。Little Lemon 餐厅正忙于接收实时订单。为了可视化这些订单的摘要,我的任务是将其中的每一个显示在一行中,并且在每条关键信息之间保持固定的水平间距。
在这个应用程序中,有一个客户提交的订单,要求一份玛格丽特披萨。每个订单包含菜品名称、数量、总价、提交时间和客户全名。我创建了一个 Row 组件来处理每个项目之间的分隔。目前,它只是按原样返回子元素,因此屏幕上的所有项目都挤在一起,没有分隔。
让我们开始使用 React.cloneElement 和 React.Children.map 来实现解决方案,以解决分隔问题。

首先,我将使用 React.Children.map 函数来遍历每个子元素。
const Row = ({ children, spacing }) => {
const childStyle = {
marginLeft: `${spacing}px`,
};
return (
<div className="Row">
{React.Children.map(children, (child, index) => {
return React.cloneElement(child, {
style: {
...child.props.style,
...(index > 0 ? childStyle : {}),
},
});
})}
</div>
);
};
到目前为止,我按原样返回每个子元素,所以屏幕上仍然没有变化。为了实现均匀的水平间距,我将添加一些自定义样式,为每个子元素(第一个除外)设置左边距。为了指定间距大小,我将创建一个名为 spacing 的新属性,它代表像素数,并使用字符串插值来正确设置以像素为单位的边距样式。
现在样式已经定义好了,我需要将其作为 style 属性附加到每个元素上。因此,在 map 函数的回调中,我将使用 React.cloneElement 返回元素的一个新副本。第二个参数允许你指定新的属性。在这种情况下,我想添加一个新的 style 属性,它将与之前的样式合并。如果元素不是第一个子元素,那么我也会合并包含 marginLeft 声明的 childStyle 对象。
最后一步是在 Row 组件中使用 spacing 属性。让我们将其设置为 32 像素。
<Row spacing={32}>
<span>Pizza Margarita</span>
<span>2</span>
<span>$30</span>
<span>18:30</span>
<span>John Smith</span>
</Row>
完成了!现在,每个实时订单都清晰地展示了所有信息,这样厨师就不会犯任何错误,也不会做错披萨。


总结
在本节课中,我们一起学习了两个新的 React API:React.cloneElement 和 React.Children.map。它们为你提供了一个强大的工具集,让你能够轻松地动态操作子元素,从而极大地增强了组件的灵活性和可复用性。通过实际案例,我们看到了如何利用这些 API 解决现实中的布局问题,例如为 Little Lemon 餐厅的订单创建清晰的可视化行。
72:传播属性


在本节课中,我们将要学习 JavaScript 中的扩展运算符,并了解如何在 React 中利用它来高效地传递属性。扩展运算符极大地简化了对象复制、合并以及属性传递等常见操作。
扩展运算符简介
扩展运算符是 JavaScript 语言中一个非常出色的新增特性。它由三个点 ... 表示。得益于扩展运算符,一些原本需要更多代码才能完成的操作,例如克隆对象或数组,现在变得非常简单。
在 JavaScript 对象中的应用
在深入探讨 React 之前,让我们先回顾一下扩展运算符在纯 JavaScript 中对对象的操作。扩展运算符可以应用于 JavaScript 中的不同数据类型,例如数组、对象甚至字符串。由于 React 的属性本质上就是对象,因此本节课将严格聚焦于对象类型。
复制和合并对象是使用此运算符可以执行的两个主要操作。
以下是复制对象的语法:使用花括号,并在要复制的对象前加上三个点。
const newObject = { ...originalObject };
在这个例子中,orderAmend 代表了对客户所点披萨类型的最后一刻更改。
对于合并操作,首先需要展开原始对象的属性,然后提供要添加或替换原始属性的新属性。
const mergedObject = { ...originalObject, newProperty: 'value' };
名为 item 的属性已被新的订单信息替换。
在 React 中使用扩展运算符
现在基础知识已经介绍完毕,让我们来探索 React 如何使用扩展运算符。
这个订单列表组件示例渲染了一个订单组件。每个订单组件期望接收四个属性:id、username、item 和 price。
第一个例子展示了通常的做法:在返回语句中显式传递所有属性。
然而,如果你已经将订单组件所需的属性放在一个对象中,这可以简化。在返回语句中,你只需要使用扩展运算符即可,这节省了时间,只需展开所有属性,而无需手动键入它们。
function OrderList(props) {
return <Order {...props} />;
}
这种模式允许你创建灵活的组件。但在使用此语法时,也需要注意一些注意事项。

注意事项与示例演示
让我们通过一个示例演示来更详细地探讨这一点。在这个应用程序中,我为 Little Lemon 餐厅创建了一个简单的欢迎屏幕,用户可以根据是否拥有账户进行注册或登录。

在顶部,我定义了一个包装了 DOM 原生按钮的按钮组件。该组件期望接收与其原生对应物相同的属性,并额外添加了一个 type 属性。这是一个自定义属性,根据提供的主题决定按钮的背景色。
这里有一个清晰的例子,展示了如何使用扩展运算符来分组属于原生按钮的所有属性,并显式提取我为该组件定义的、React 特有的自定义属性:type 和 children。
function Button({ type, children, ...nativeProps }) {
const theme = type === 'primary' ? 'blue' : 'gray';
return (
<button style={{ backgroundColor: theme }} {...nativeProps}>
{children}
</button>
);
}
这种实现方式对开发者来说很清晰,因为他们可以提供原生按钮所期望的所有属性。
第二个例子是一个登录按钮组件,它渲染了我自己创建的自定义按钮组件。这个登录按钮通过固定按钮组件的一些属性(在本例中是 type 和 onClick)来进行一些预配置,同时仍然使用扩展运算符将原生按钮属性向下传递。
现在,App 组件渲染了两个按钮,并使用按钮组件进行注册,使用登录按钮组件进行登录。这里的按钮都被配置为将用户引导至注册页面,除非他们拥有账户,在这种情况下,登录按钮组件原始的 onClick 函数会将他们引导至登录页面。
我还为两个按钮都提供了一个 onClick 处理程序,用于在按下按钮时显示有关预期操作的警报。然而,请注意我错误地在登录按钮组件上提供了与注册相同的警报消息,从而覆盖了登录按钮已经定义的 onClick 处理程序。
那么,当我点击它时,警报的消息会是什么呢?我给你几秒钟时间思考一下。


如果你猜的是“正在登录”,那么你猜对了。原因是,尽管我在登录按钮组件中覆盖了 onClick 属性,但其实现方式阻止了这种覆盖的发生。



为什么会这样?这是因为扩展运算符的顺序。如果我改为在最后,即在 onClick 之后展开属性,行为就会不同,输出的将是“正在注册”。

总结
本节课中我们一起学习了扩展运算符。扩展运算符是一个强大的工具,它支持创建更灵活的组件,特别是在自动将属性转发给期望它们的其他组件时,以及为你的组件使用者提供一个良好的组件 API。
然而,请记住,根据扩展的顺序,行为可能会有所不同,因此在涉及组件设计时,你需要有意识地做出决定。在之前的登录按钮示例中,防止覆盖 onClick 属性可能是有意义的,但对于其他属性,这可能并非本意。
希望你现在对使用这些 React 工具所能获得的多种好处有了更清晰的认识。
73:React 中的跨切面关注点 🧩
在本节课中,我们将要学习 React 中的“跨切面关注点”概念。你将了解为什么组件有时不足以复用某些通用逻辑,并探索一种名为“高阶组件”的解决方案。

在构建 React 应用时,你会发现自己需要创建一些与应用程序业务逻辑无关,但在许多地方都需要的通用功能。

例如,管理不同的权限角色、处理错误,甚至日志记录。
这些功能是所有应用都需要的,但从业务角度来看并非必需。




这类功能就属于“跨切面关注点”的范畴。在本视频中,你将学习什么是跨切面关注点,以及为什么组件虽然是 React 中代码复用的主要单元,但对于这类逻辑来说并不总是最佳选择。你还将了解为何需要引入新的模式来解决这个问题。


让我们开始吧。😊




一个具体的例子
想象一下,你的任务是构建“小柠檬餐厅”应用中显示实时订单列表的逻辑。



让我们探索如何实现它。以下是一个有效的实时订单列表实现,其中使用本地状态来存储当前的订单列表,useEffect 负责处理对实时数据的订阅和取消订阅,并在新订单到达时更新订单值。





现在,假设“小柠檬”还需要在应用中有一个地方来实时跟踪订阅其新闻通讯的用户数量。



这可能是该功能的一个有效实现。
你能找出“实时订单列表”和“新闻通讯列表”之间的相似之处吗?

它们肯定不完全相同,因为它们调用了数据源上的不同方法并渲染了不同的输出。但经过检查,你会发现大部分实现是相同的:
- 它们都在组件挂载时向数据源添加一个变更监听器。
- 每当数据源发生变化时,它们都设置新的状态。
- 它们都在组件卸载时移除变更监听器。


你可以想象,在一个大型应用中,这种订阅数据源并用新数据设置本地状态的模式会反复出现。
复用逻辑的挑战

到目前为止,你已经看到,使用自定义 Hook 来封装这种逻辑是你可以采用的解决方案之一。


然而,这会带来一个问题:你必须修改每个需要该数据的组件的实现,从而使它们都变成有状态的。
那么,如何才能在一个地方定义订阅逻辑,在许多组件之间共享它,同时保持这些组件不变且无状态呢?




高阶组件解决方案

这正是高阶组件成为完美解决方案的地方。
高阶组件,也称为 HOC,是一种源于 React 组合特性的高级模式。具体来说,高阶组件是一个接收一个组件并返回一个新组件的函数。如果说组件将 props 转换为 UI,那么高阶组件则是将一个组件转换为另一个组件。换句话说,它增强或扩展了所提供组件的能力。




让我们来研究一下,使用高阶组件实现这种可复用的订阅逻辑会是什么样子。
withSubscription 是一个高阶组件,它接收你想要增强订阅能力的包装组件,以及一个 selectData 函数来确定你从数据源获取的数据类型(在本例中是订单或用户)。然后,它返回一个新组件,该组件渲染提供的组件,并向其提供一个名为 data 的新 prop,该 prop 将包含来自目标数据源的最新项目列表。它还将其他 props 传递给包装组件,这是 HOC 中的一种惯例。










这是实现,那么它的用法呢?在这种情况下,你可以定义两个配置了不同参数的组件,一个用于实时订单,另一个用于新闻通讯订阅者,而无需在 LiveOrders 或 UserList 中重复订阅实现,这使得它成为一个更高效的解决方案。



后续展望
还有一种用于处理跨切面关注点的模式,你很快就会学到,它叫做 Render Props。



总结
本节课中我们一起学习了跨切面关注点的概念,以及为什么组件并不总是足以复用行为。你还探索了一种在 React 应用中封装通用行为的替代模式——高阶组件。
74:32_为光标位置创建高阶组件 🖱️
概述
在本节课中,我们将学习如何使用React的高阶组件(HOC)来封装和复用追踪用户鼠标位置的功能。我们将通过一个为“小柠檬餐厅”网站分析用户对披萨图片关注度的实际案例,来掌握HOC的核心概念和实现步骤。
背景与需求 🍕
上个月,由于主厨推出的几款特色披萨深受喜爱,“小柠檬餐厅”网站的访问量激增。
然而,并非所有披萨都获得了同等的关注度。
因此,餐厅决定实施基础数据分析,以更好地了解哪些披萨最畅销,哪些披萨点单频率较低。
为此,他们希望在用户浏览披萨版块时追踪其鼠标位置,从而精确掌握哪些图片吸引了用户注意,哪些被忽略了。
高阶组件解决方案 🧩
在接下来的内容中,我将展示如何通过使用高阶组件来实现此功能。
该高阶组件将负责封装追踪访客光标位置的逻辑和状态。

之后,我将展示两个不同的展示型组件,它们将消费这些数据并以不同的方式呈现出来。
初始应用状态
当前应用是使用 create-react-app 创建的。在 App 组件的渲染部分,有一个显示餐厅标题的页头,以及两个组件:PanelMouseLogger 和 PointMouseLogger。
这两个组件都期望接收一个名为 mousePosition 的属性(prop),如果未提供该属性,它们将返回 null。这就是目前它们完全不渲染任何内容的原因。
你可以在每个组件中分别实现鼠标追踪逻辑。
但请注意,这会导致代码重复,相同的逻辑会在两个不同的地方出现。
因此,推荐的方法是使用React提供的封装横切关注点的技术之一。
在本示例中,我将演示如何使用高阶组件来实现。
创建高阶组件
我将这个高阶组件命名为 withMousePosition。名称以 with 开头是React推荐的一种通用约定,因为它表达了该技术的“增强”本质,即为组件提供额外的功能。
回顾一下,高阶组件本质上是一个函数,它接收一个组件作为参数,并返回一个新的组件。
让我们完成初始的脚手架代码,返回一个渲染传入函数的组件的组件,同时不要忘记展开它接收到的props,以确保它们被传递下去。
const withMousePosition = (WrappedComponent) => {
return (props) => {
return <WrappedComponent {...props} />;
};
};
很好,现在为了追踪光标位置,我需要定义一个新的局部状态。
我将状态数据命名为 mousePosition,状态设置函数命名为 setMousePosition。
初始状态是一个包含 x 和 y 两个属性的对象,用于定义屏幕上的二维坐标,我将它们都初始化为 0。
x = 0 和 y = 0 代表屏幕的左上角。
接下来,我需要在 window 对象上为 mousemove 事件设置一个全局监听器。
由于这是一个副作用,我需要在 useEffect 钩子内部执行订阅和取消订阅的逻辑。
让我们开始实现。我将为 window 对象添加一个鼠标移动事件监听器。对于回调函数,我将其命名为 handleMousePositionChange,目前它不执行任何操作。
在组件卸载时移除任何订阅非常重要。
实现方法是从 useEffect 返回一个函数,并在其中执行所需的清理工作。在本例中,我需要调用 window.removeEventListener,并传入 mousemove 事件和之前相同的回调函数作为参数。
为了完成逻辑并用当前鼠标位置更新状态,我需要从作为参数传递给回调函数的浏览器事件对象中读取信息。
该事件对象包含定义坐标的两个属性:clientX 和 clientY。因此,我将把它们分别赋值给对应的维度。
最后,完成实现的最后一步是在被包装的组件上设置一个名为 mousePosition 的新prop,以将该信息传递给所有对此数据感兴趣的组件。
import React, { useState, useEffect } from 'react';
const withMousePosition = (WrappedComponent) => {
return (props) => {
const [mousePosition, setMousePosition] = useState({ x: 0, y: 0 });
useEffect(() => {
const handleMousePositionChange = (e) => {
setMousePosition({
x: e.clientX,
y: e.clientY,
});
};
window.addEventListener('mousemove', handleMousePositionChange);
return () => {
window.removeEventListener('mousemove', handleMousePositionChange);
};
}, []);
return <WrappedComponent {...props} mousePosition={mousePosition} />;
};
};
应用高阶组件
现在高阶组件的实现已经完成,让我们添加最后几个部分,以在屏幕上显示鼠标位置。
为了增强之前定义的两个组件 PanelMouseLogger 和 PointMouseLogger,我将使用高阶组件来创建两个知晓鼠标位置数据的新组件版本。
我分别将它们称为 PanelMouseTracker 和 PointMouseTracker。
最后,我将在 App 组件的渲染部分使用这些增强后的版本。
// 增强组件
const PanelMouseTracker = withMousePosition(PanelMouseLogger);
const PointMouseTracker = withMousePosition(PointMouseLogger);

// 在App组件中使用
function App() {
return (
<div className="App">
<header>
<h1>小柠檬餐厅</h1>
</header>
<PanelMouseTracker />
<PointMouseTracker />
</div>
);
}
太棒了,需求现已全部实现。如果我在屏幕上移动光标,可以看到两个不同的追踪器以不同的方式显示相同的信息:一个以面板形式显示,下方的一个以数据点形式显示。
成果与影响 📈
虽然本视频到此结束,但“小柠檬餐厅”现在使用这个解决方案来追踪他们喜爱披萨的顾客,并发现了一些影响其“魔鬼披萨”销量的因素。

猜猜发生了什么?一名员工调查了应用中该特定披萨后,他们注意到其中一张照片有点模糊。
多亏了这个小小的追踪应用,他们现在已经采取行动,上传了新的高质量照片。这样,下次顾客浏览菜单时,就不会错过“魔鬼披萨”了。
总结

在本节课中,我们一起学习了如何创建和使用React高阶组件。我们通过封装鼠标位置追踪逻辑,解决了代码复用的问题,并将该功能以props的形式注入到不同的展示组件中。这种模式是处理横切关注点、增强组件功能的强大工具。
75:渲染属性模式 🧩
在本节课中,我们将要学习一种名为“渲染属性”的代码复用模式。这是一种与高阶组件功能类似,但实现方式不同的技术,它通过一个特殊的属性来动态地向组件注入功能。
到目前为止,你已经学习了一种名为高阶组件的技术,它用于封装通用功能。但这并非唯一可用的技术。代码复用有多种工具可供选择,选择哪种取决于具体的场景。
渲染属性模式简介
渲染属性模式之所以得名,是因为它几乎可以顾名思义。其核心是使用一个名为 render 的属性,并且这个属性的值必须是一个函数。
更准确地说,一个使用渲染属性的组件会接收一个返回 React 元素的函数,并在其自身的渲染逻辑中调用这个函数。例如,一个数据提供者组件就可以使用这种模式。
如果你还记得,高阶组件通过向被包装的组件提供新的属性来增强它。而渲染属性模式则是将新的属性动态地作为参数注入到函数中。你可能已经发现了二者的一些相似之处。它们的最终目标是一致的:在不修改原始组件实现的前提下增强组件。它们之间的区别在于注入这些新属性或增强功能的方式。
应用场景示例
为了更好地说明这一点,让我们通过一个应用示例来探索一个使用渲染属性的组件的实现。
在这个例子中,假设小柠檬餐厅希望统计菜单上所有甜点和饮品的数量。这些信息需要从他们控制的服务器上获取,并在应用中通过一段文本显示出来。数据获取逻辑是“横切关注点”的一个典型例子。多个组件可能都依赖于外部数据,但你肯定不想在每个需要数据的组件中重复实现相同的获取逻辑。
这正是使用渲染属性模式来抽象此功能的绝佳场景。
实现步骤解析

为了展示其工作原理,我创建了一个名为 DataFetcher 的组件,其唯一目的是根据 URL 获取数据。URL 是它的一个属性,但第二个属性 render 更值得你关注。
以下是该组件的核心代码结构:
function DataFetcher({ url, render }) {
const [data, setData] = useState(null);
useEffect(() => {
// 模拟数据获取逻辑
const fetchData = async () => {
// 根据 url 返回模拟数据
const mockData = url.includes('desserts') ? ['Cake', 'Pie'] : ['Coffee', 'Juice'];
setData(mockData);
};
fetchData();
}, [url]);
// 关键:调用 render 函数,并将数据作为参数传递
return render(data);
}
在这个例子中,我没有获取真实数据,而是创建了一个模拟的 if 语句,根据 URL 路径返回可用的甜点或饮品列表。通常,这种获取逻辑是副作用,应放在 useEffect 中。
最后,让我们看看 return 语句。这是不同寻常的部分。该组件返回调用 render 函数的结果,自身没有其他渲染逻辑。这使其非常灵活。DataFetcher 只有一个目的:获取数据。而接收数据的方式,是通过 render 函数的参数,该参数是用于存储甜点或饮品列表的本地状态。然后,由开发者决定他们希望如何在屏幕上呈现这些数据。
使用渲染属性
接下来,让我们看看我定义的两个展示组件,它们分别用于显示菜单中可用的甜点和饮品数量。
以下是使用 DataFetcher 的组件示例:
function DessertsCount() {
return (
<DataFetcher
url="/api/desserts"
render={(data) => <p>我们有 {data ? data.length : 0} 种甜点。</p>}
/>
);
}
function DrinksCount() {
return (
<DataFetcher
url="/api/drinks"
render={(data) => <h3>饮品数量:{data ? data.length : 0}</h3>}
/>
);
}
DessertsCount 组件使用特定的端点获取甜点数据,并使用一个段落元素作为 render 属性的返回值,将甜点数量显示为单一文本。DrinksCount 组件同理,区别在于它使用了另一个 URL,并显示一个反映饮品数量的标题元素。
最后,App 组件渲染它们两者,结果将显示在屏幕上。

总结

本节课中我们一起学习了另一种可以和高阶组件搭配使用的技术,即渲染属性模式。现在,希望小柠檬餐厅能够实时统计饮品数量,以便用你最喜欢的饮料填满他们的冰箱。😊
总而言之,渲染属性模式通过一个函数属性,将数据或逻辑动态地传递给子组件进行渲染,提供了一种灵活且强大的组件复用和组合方式。
76:为什么使用React测试库 🧪
在本节课中,我们将学习如何为React组件编写自动化测试。我们将探讨测试的重要性、最佳实践,并介绍Jest和React测试库这两个核心工具。通过一个实际例子,你将了解如何从零开始构建一个测试。
为什么需要自动化测试?
作为Little Lemon餐厅应用的开发者,你如何保证你创建的应用能按预期工作?你可以选择依靠自己系统化的能力,手动与应用的所有不同部分进行交互,以确保应用功能完整。然而,手动测试每一个新增的改动,可能会变得繁琐、容易出错且耗时,尤其是在应用复杂性增加时。
这正是自动化测试的用武之地。
测试的重要性与最佳实践
就像工厂对其生产的产品进行测试以确保其符合预期一样,开发人员也需要对自己的代码进行同样的测试。一套设计良好的自动化测试套件,能让你在交付给客户之前有效地发现缺陷或错误。因此,测试对于保证所开发软件的质量至关重要。
此外,通过在错误进入线上应用之前发现它们,测试可以减少用户投诉,并最终为组织节省时间和金钱。
既然你已经了解了测试的重要性,那么在编写测试时需要牢记哪些最佳实践呢?
以下是需要遵循的核心原则:
- 避免包含组件实现细节:React只是一个工具,你的最终用户根本不会意识到React的存在。因此,你的测试不应处理已渲染的React组件实例,而应处理实际的DOM节点。
- 测试应模拟软件的使用方式:你的测试越接近软件的实际使用方式,它们给你的信心就越足。
- 测试应具备长期可维护性:只要功能不变,组件实现的任何更改都不应破坏你的测试,从而拖慢你和团队的进度。
测试工具:Jest与React测试库
现在,让我们来探索React官方推荐用于构建测试的两个工具:Jest和React测试库。
Jest是一个JavaScript测试运行器,它让你可以访问一个名为jsdom的模拟DOM。虽然jsdom只是对浏览器工作方式的近似模拟,但对于测试React组件来说通常已经足够。Jest提供了良好的迭代速度,并结合了诸如模拟模块等强大功能,让你能更好地控制代码的执行方式。
回忆一下,模拟指的是制作一个仿制品,它使你能够用更简单、能模拟相同行为的函数来替换代码中的复杂函数。模拟功能可用于确保你的单元测试是独立的。
React测试库是一组实用工具,让你能够在不依赖组件实现细节的情况下测试React组件。它的设计初衷就是为了满足前面强调的所有最佳实践,让你能够开箱即用地获得一个配置良好的测试环境,并专注于测试需要运行断言(assertions)的业务逻辑。

实践:编写你的第一个测试
理论部分已经介绍完毕,接下来让我们使用Jest和React测试库从头开始实现一个测试。
当你使用create-react-app启动一个新项目时,默认已经预装了Jest和React测试库。这两个工具都已预先配置好,并且在你的根文件夹中有一个名为App.test.js的示例测试文件。
假设Little Lemon与一家热门餐厅聚合平台达成协议,将其网页作为一个新URL列入其列表。在App.js文件中,App组件在页面上渲染了一个指向Little Lemon网页的链接。
让我们逐步分析我创建的、用于自动验证该链接是否始终存在的测试。
首先,需要从@testing-library/react中导入render和screen。
import { render, screen } from '@testing-library/react';
render函数用于渲染你想要测试并对其执行断言的组件。- 由于查询整个
document.body非常常见,React测试库还导出了一个screen对象。这是对document.body的引用,并且所有查询方法都已预先绑定到它上面,这意味着在执行搜索时,它会自动在整个文档中查找。
现在,为了包装测试场景,Jest提供了全局的test函数。它接收两个参数:第一个是文本描述,第二个是一个函数,用于组合你的测试需要执行的所有步骤。这个函数不需要导入,因为Jest会自动将其注入到所有测试文件中。
以下是测试步骤:
- 在模拟的DOM环境中渲染
App组件。 - 使用
screen对象对document.body进行查询。这里,我使用getByText工具函数,询问文档的body标签是否能找到一个内部文本为“Little Lemon Restaurant”的元素,并将查找结果存储在linkElement对象中。如果搜索成功,getByText将返回找到的元素;否则,返回null。 - 最后,为了完成测试,我执行一个断言,询问上述查询得到的
linkElement是否存在于文档中,即它当前是否在屏幕上可见。为此,使用了全局的expect函数,这是Jest全局提供的另一个实用工具,无需显式导入。
expect函数接收查询结果,并附加一个特定的匹配器。在这个例子中,匹配器指的是在整个文档中可见的元素。
test('renders learn react link', () => {
// 1. 渲染组件
render(<App />);
// 2. 查询元素
const linkElement = screen.getByText(/Little Lemon Restaurant/i);
// 3. 执行断言
expect(linkElement).toBeInTheDocument();
});
如果我运行这个测试,它会失败。让我们检查输出日志以了解问题所在。日志指出,它无法找到文本为“Little Lemon Restaurant”的元素。
有趣,让我们再次检查App.js组件。啊哈,我犯了一个错误,把“Lemon”打成了“Orange”,而这个错误被测试捕捉到了。这正是测试失败时你希望看到的情况。
同时,你可能已经注意到编写测试断言是多么直接。你在代码中看到的一切,都能很好地转化为真实用户与你的应用交互的方式,并产生你期望的行为。
现在问题已经修复,让我们再次运行测试。太好了,测试通过,Little Lemon的线上曝光度即将进一步增长。

总结
本节课中,我们一起学习了测试的重要性以及测试的最佳实践。你现在已经掌握了如何使用Jest和React测试库来测试你的React组件。请保持关注,因为很快你将发现编写更复杂的测试是多么容易。
77:为表单编写第一个测试 🧪
概述
在本节课中,我们将学习如何为React表单组件编写自动化测试。我们将通过一个具体的案例——为“小柠檬餐厅”的反馈表单编写测试,来确保当用户评分低于5分时,必须提供至少10个字符的额外评论才能提交表单。我们将使用Jest和React Testing Library来实现这一目标。
背景与需求
小柠檬餐厅开始收到一些顾客的差评。问题在于,他们无法准确找出问题所在并采取相应措施,因为用户只提供了较低的数字评分,跳过了提供额外反馈的环节,导致信息无法传递给厨师。

为了解决这个问题,他们决定在用户提供的评分低于5分(评分范围为0到10)时,将评论文本框设为必填项。


此外,他们希望用自动化测试来保护这项新逻辑。这样,每当进行任何更改时,测试套件都会运行,并能捕获由错误更新引起的任何潜在问题。


应用程序结构
该应用程序包含一个反馈表单,表单中有一个用于输入0到10分数字评分的范围输入框,以及一个用于填写额外评论的文本框。
为了满足小柠檬餐厅的要求,当评分低于5分时,提交按钮将被禁用,从而强制用户添加至少10个字符的评论。现在,让我们来查看代码。

入口点是App.js组件,其中渲染了一个FeedbackForm组件。该组件接收一个名为onSubmit的prop,这是一个包含表单值作为参数的函数,以便父级App组件可以执行提交操作。
FeedbackForm代表一个HTML表单,并通过本地状态包含两个受控组件:一个范围输入框和一个文本区域。

该组件中有两个关键部分需要强调。第一部分是按钮的禁用逻辑。变量isDisabled控制该状态,当评分低于5分且评论少于10个字符时,它被设置为true。

另一个重要部分是handleSubmit函数,它被挂载到表单的onSubmit属性上。当点击提交按钮时,将调用handleSubmit函数。该函数本身会调用父组件提供的prop函数,并传入相应的表单值。
编写测试
很明显,FeedbackForm组件包含了所有相关的业务逻辑。因此,让我们开始为提交逻辑编写测试。
测试的惯例是将其创建在具有.test扩展名的文件中。这样,当你在终端运行测试命令时,测试运行器Jest就能自动识别它们。
我已经编写了一个测试场景,现在将带你逐步了解每一行代码,以便你理解测试是如何构建的。该测试场景旨在检查:如果评分低于5分,并且没有额外反馈或反馈过短,用户是否被阻止立即提交表单。
以下是测试步骤的分解:
首先,我使用Jest创建一个新的模拟函数。回想一下,模拟函数是一种特殊的函数,允许你跟踪外部代码如何调用特定函数。当FeedbackForm调用你作为onSubmit prop提供的函数时,你将能够检查调用时传递的参数。
const handleSubmit = jest.fn();
然后,我渲染FeedbackForm组件,并将模拟函数作为onSubmit prop传递。
render(<FeedbackForm onSubmit={handleSubmit} />);
接下来的步骤是定位范围输入框并为其填充一个值。请注意,为了找到输入框,我使用了screen.getByLabelText并传递一个正则表达式进行匹配。
screen是React Testing Library提供的一个实用对象,代表整个页面。这基本上等同于要求根文档查找文本包含“score”一词的label标签,然后返回与该标签关联的input元素。
const rangeInput = screen.getByLabelText(/score/i);
为了给输入框填充值,你必须使用React Testing Library的fireEvent工具并调用change函数。虽然React受控组件通过onChange prop更新其状态,但React Testing Library遵循一个略有不同的约定:去掉“on”部分,并将更新方法改为小写。
fireEvent.change(rangeInput, { target: { value: '4' } });
为了模拟表单提交,我必须定位按钮元素。请注意我使用了不同的查询方法:getByRole,它查找具有特定role属性的元素。由于HTML按钮内部已经将role属性设置为“button”,所以这种方法效果很好。
const submitButton = screen.getByRole('button');
要执行按钮点击,我必须使用fireEvent.click。它遵循与之前相同的约定:去掉prop名称中的“on”部分,并将所有内容改为小写。
fireEvent.click(submitButton);
最后两个语句是测试的断言。
第一个断言展示了一个expect匹配的例子,它通过在调用最终匹配器之前加上.not来检查相反的情况。它断言处理表单提交的函数没有被调用,这正是当省略额外评论时期望的结果。
expect(handleSubmit).not.toHaveBeenCalled();
此外,我添加了第二个断言,通过使用toHaveAttribute匹配器来确保提交按钮确实被禁用了。
expect(submitButton).toHaveAttribute('disabled');
总结
在本节课中,我们一起学习了如何使用React Testing Library编写强大的代码来保护你的业务逻辑。通过为“小柠檬餐厅”的反馈表单编写测试,我们确保了在评分较低时,用户必须提供有意义的反馈才能提交。更重要的是,这使得小柠檬餐厅的厨师最终能够收到必要的反馈,并确保所有新披萨都能多加点奶酪!


78:样式指南 📚
在本节课中,我们将要学习样式指南的重要性及其在软件开发中的核心作用。我们将探讨为何遵循一致的代码风格对于代码的可读性、可维护性以及团队协作至关重要。

我曾经非常不喜欢样式指南,认为它们不重要。但现在,如果看到不符合风格的代码,我会立刻注意到,并且感到非常沮丧。因此,我现在非常重视它们。


我的名字是Martra,是Meta公司西雅图办公室的一名软件工程师。
很多时候,人们认为样式指南很傻或没用,可能并不真正重视它们。我在审查许多新开发者的代码时经常看到这一点,我的很多评论实际上是关于代码风格的。这可能看起来我很烦人,或者我在挑剔一些不重要的事情,因为遵循风格并不会从功能上改变代码的运行方式,它只是改变了代码的外观。所以人们会想:这有什么关系呢?代码的功能还是一样的。
但这很重要,因为它使代码更容易阅读,也更容易调试和使用。 这是一个大问题,因为很多时候我们写代码不仅仅是为了自己,也是为了别人。因此,提前投入时间,正确地设计代码风格是非常值得的。

我们遵循样式指南的一个目标是确保代码是自文档化的。这非常有帮助,因为开发者无需花费时间撰写详细的注释来解释一切。如果你查看代码,通过变量命名的方式或函数结构的方式,你就能理解它,代码自身就说明了问题。这非常重要,因为它减轻了开发者编写文档的负担,也减轻了其他开发者阅读文档的负担。同时,当新成员查看代码时,也更容易发现问题。很多时候,我们编写了代码,但后续并非由我们持续改进或维护,而是由其他人接手。因此,以清晰、风格良好的方式编写代码,是对后来者的一种服务,当他们查看代码时,能够轻松理解。
在Meta,我们有一个团队(或几个团队)负责制定样式指南,以确保公司在不同的代码库中遵循相似或一致的指南。因为样式指南是那种可能涉及不同观点、品味和实现同一目标的不同方式的事情。即使有多种正确的方法,我们也希望保持一致。我们希望选择一种方式,并在整个代码库中保持一致,这样开发者在查看代码库的不同部分时就不会感到困惑。

Meta的每个人都在使用或遵循由负责团队创建和管理的样式指南。我是一名工程师/开发者,和公司里所有编写代码的人一样,每天都在遵循和使用这些样式指南。


学习编码和样式指南,你可能会想:我为什么需要这个?实际上这并不复杂。你需要它,因为它就像一项投资。遵循这些样式指南、坚持它们并学习好的样式指南,将对应用程序及其稳定性、可用性、可读性以及未来为改进它所做的任何事情(无论是添加新功能还是修复错误)产生重大影响。因此,它可能看起来是开发和编写软件中不重要的部分,但如果你是为长期编写代码,它实际上会产生巨大的影响。
本节课中我们一起学习了样式指南的核心价值。我们了解到,良好的代码风格并非无关紧要的装饰,而是提升代码可读性、可维护性和团队协作效率的关键实践。通过遵循一致的样式指南,我们可以编写出自文档化的代码,这不仅减轻了开发者的负担,也为项目的长期健康发展奠定了基础。记住,编写整洁的代码是对未来接手项目的同事的一份重要礼物。
79:37_JSX 和测试模块总结 📚
在本节课中,我们将回顾并总结关于 React JSX 高级模式与测试模块的核心知识。我们将系统梳理从 JSX 基础到组件组合、高级复用模式,再到组件测试的完整学习路径。
你已经完成了 React JSX 高级模式与测试模块的学习。
让我们花几分钟时间来回顾一下到目前为止所学到的内容。

JSX 深度解析 🧩

你首先深入学习了 JSX。
你了解了组件(Components)和元素(Elements)之间的区别。


你学习了组件是接收数据或属性(props)作为输入,并返回一个元素树作为输出的函数。
// 组件示例
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
你还学习了元素只是普通的 JavaScript 对象,它们提供了 DOM 的轻量级表示,并让 React 能够以快速且可预测的方式更新你的用户界面。

// React.createElement 创建的元素对象
const element = React.createElement('h1', {className: 'greeting'}, 'Hello, world!');
组件组合 🧱

接下来,你发现了组件组合的重要性以及 children 属性的使用。你被介绍了组件组合的两个主要特性:容器化(Containment) 和 特例化(Specialization)。

你学习了容器化是一种适用于那些事先不知道其子组件是什么的组件的技术,例如对话框或侧边栏。它使用特殊的 children 属性来直接将元素作为其内容传递。
// 容器化示例:Dialog 组件
function Dialog(props) {
return (
<div className="dialog">
{props.children}
</div>
);
}

你也被介绍了特例化,这是一种允许你定义其他组件的特殊用例组件的技术,例如基于一个通用对话框创建一个确认对话框。

// 特例化示例:ConfirmationDialog 组件
function ConfirmationDialog(props) {
return (
<Dialog>
<h1>Are you sure?</h1>
<p>{props.message}</p>
<button onClick={props.onConfirm}>Yes</button>
<button onClick={props.onCancel}>No</button>
</Dialog>
);
}
动态操作子元素 ⚙️
然后,你进入了一节关于在 JSX 中动态操作子元素的课程。
在这里,你被介绍了一些新的 React API:React.cloneElement 和 React.Children。
你学习了 React.cloneElement 会克隆并返回一个新元素,允许你直接在 JSX 中操作和转换元素。
const newElement = React.cloneElement(oldElement, newProps, ...children);
你还学习了 React.Children.map 对于子元素操作非常有用,并且当与 React.cloneElement 结合使用时,能够为创建灵活的组件提供一个强大的组合模型。
你通过一个实际例子学习了这两个 API,其中实现了一个 Row 组件来均匀地分隔其子元素。


扩展运算符的应用 🔄
最后,你被介绍了 React 中的扩展运算符。

你学习了扩展运算符在对象中支持两种主要操作:复制和合并。
// 复制对象
const original = { a: 1, b: 2 };
const copy = { ...original };
// 合并对象
const merged = { ...obj1, ...obj2 };


然后你看到了 React 如何使用该运算符来展开所有属性,而不是必须手动逐个键入它们。
function MyComponent(props) {
return <ChildComponent {...props} />;
}
最后,你看到了一些实际例子,说明了扩展运算符如何允许创建灵活的组件。
复用通用行为的高级模式 🧠
接着,你进入了一节关于复用通用行为的高级模式的课程。
该课程首先概述了 React 中的横切关注点(Cross-Cutting Concerns)。
你学习了横切关注点指的是与应用程序业务逻辑无关,但在许多地方都需要用到的通用功能,例如错误处理、身份验证或数据获取。


你理解了为什么组件虽然是 React 中代码复用的主要单元,但并不适合封装这类逻辑。
之后,你被介绍了处理横切关注点的两种技术。
你被介绍的第一种技术是高阶组件(Higher-Order Component, HOC) 技术,你了解到它为实现横切关注点提供了一个强大的抽象。
// HOC 基本结构
const EnhancedComponent = higherOrderComponent(WrappedComponent);
你还看到了一个如何使用这种技术来抽象数据获取的示例。
作为关于高阶组件的实践课程的一部分,你学习了高阶组件只是一个接收一个组件并返回一个新组件的函数。

你被介绍了创建高阶组件所需的代码结构,并研究了一个处理屏幕上鼠标指针位置的高阶组件的应用。

然后你学习了第二种处理横切关注点的技术,称为 Render Props。
这是一种你添加到组件中的特殊属性,其特点是一个返回 React 元素的函数。
<DataProvider render={data => <h1>Hello {data.target}</h1>} />

你发现,与高阶组件不同,新的属性是作为函数的参数动态注入的。你通过一个实际例子进行了学习,在该例子中,Render Props 技术被用来抽象从服务器获取数据的功能。


使用 React Testing Library 进行组件测试 🧪
该模块以一节关于使用 React Testing Library 进行组件测试的课程结束。
该课程首先全面解释了为什么 React Testing Library 是你测试的推荐工具。

你学习了为了保证你的应用程序按预期工作,一套自动化测试是至关重要的。
你被介绍了测试时的最佳实践,并了解了 React Testing Library 是如何在设计时就考虑了所有这些实践的。


最后,你被介绍了一个使用推荐测试运行器 Jest 和 React Testing Library 的基本测试示例,以说明测试的基本结构。
import { render, screen } from '@testing-library/react';
import MyComponent from './MyComponent';

test('renders learn react link', () => {
render(<MyComponent />);
const linkElement = screen.getByText(/learn react/i);
expect(linkElement).toBeInTheDocument();
});
关于组件测试的课程以一个真实世界示例中的测试实际应用作为结束。
通过这个例子,你学习了设计良好的测试如何能够捕获代码中的错误,并为你提供修复它们所需的上下文。
你还发现了一个更复杂的应用程序,只需几行代码就可以轻松测试。
然后你被介绍了 React Testing Library 的几个 API,例如用于在全局文档上查询的 screen,以及不同的查询类型,例如按文本查询或按角色查询。
最后,你学习了用于你的期望和断言的不同匹配器,例如用于模拟函数的 toHaveBeenCalled 和用于元素属性的 toHaveAttribute。
总结 🎉
本节课中我们一起学习了 React JSX 高级模式与测试模块的核心内容。

我们从 JSX 的本质和组件与元素的区别开始,深入探讨了组件组合的两种主要模式:容器化和特例化。接着,我们学习了如何利用 React.cloneElement 和 React.Children 动态操作子元素,以及扩展运算符在 React 中的强大应用。


在高级模式部分,我们理解了横切关注点的概念,并掌握了两种复用通用逻辑的强大技术:高阶组件和 Render Props。最后,我们系统学习了如何使用 React Testing Library 和 Jest 为 React 组件编写有效、可维护的测试,包括查询元素、进行断言等关键技能。


恭喜你完成了本模块的学习!现在是时候运用你所学到的一切,去构建一些出色的应用程序了。
80:38_高级React课程回顾 📚
在本节课中,我们将一起回顾整个高级React课程的核心内容。我们将梳理从项目初始化到高级概念的所有关键知识点,帮助你巩固所学,为接下来的综合评估做好准备。
概述
本课程涵盖了React的高级主题,包括组件渲染、状态管理、Hooks、JSX深入以及测试。通过回顾,我们将串联起各个模块的知识点,构建完整的知识体系。
模块一:组件与数据管理
在开篇模块中,你首先对课程有了整体了解。在第一课中,你学习了如何设置VS Code项目,并掌握了如何高效利用课程内容以确保达成学习目标。
列表渲染与表单
接着,你进入了下一课,学习如何在React中渲染列表。在这一课中,你掌握了如何转换和渲染列表组件,创建基础列表组件,并理解了React列表组件中键(Keys) 的重要性。
在随后的课程中,你深入探索了React中的表单。首先,你学习了受控组件与非受控组件的区别。接着,你发现了如何在React中创建受控表单组件,并通过创建自己的注册表单来测试所学技能。
以下是受控组件的基本代码结构示例:
function ControlledForm() {
const [value, setValue] = useState('');
const handleChange = (event) => setValue(event.target.value);
return <input type="text" value={value} onChange={handleChange} />;
}
React Context
在模块一的最后一课,你学习了React Context。你探索了Props和State,以及它们与Context的关系。你也学习了Context如何触发重新渲染,并应用这些知识创建了一个明暗主题切换器。
模块二:深入Hooks
接下来,你开始了第二个模块的学习,该模块涵盖了React中所有常见的Hooks以及如何构建自定义Hooks。
Hooks基础
在本模块的第一课,你开始了Hooks的学习。你学习了如何实现useState Hook来处理复杂数据,并应用这些知识来管理组件内部的状态。你也了解了副作用和useEffect Hook。
Hooks规则与数据获取

在下一课中,你继续学习了Hooks的规则以及如何使用Hooks获取数据。你发现了Hooks的基本规则,以及如何在你的解决方案中有效地使用它们。

你还深入了解了如何使用Hooks获取数据,并在一个实践练习中测试了你的数据获取技能。
高级Hooks
在本模块的最后一课,你通过高级Hooks提升了知识和技能。你学习了useReducer Hook的好处,它与useState的区别,以及如何使用useRef访问底层DOM。
接着,你构建了自己的自定义Hook:usePrevious。

以下是自定义Hook usePrevious 的一个简单实现示例:
function usePrevious(value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
});
return ref.current;
}
模块三:JSX深入与测试

在第三个模块中,你更详细地探索了JSX以及如何测试你的解决方案。
JSX深度解析
在第一课中,你深入研究了JSX。你对JSX组件和元素有了更详细的概述。
接着,你探索了使用children进行组件组合的两个关键特性:容器化(Containment) 和特例化(Specialization),以及如何在JSX中动态操作子元素。在构建一个单选按钮组组件时,你有机会应用新技能,并了解了JSX中的扩展属性(Spread Attributes)。
行为复用
在下一课中,你专注于行为复用。你学习了React中的横切关注点(Cross-Cutting Concerns),探索了高阶组件(HOC) 及其如何被高效使用。你也学习了一个特定的HOC模式,称为Render Props,并使用Render Props在一个解决方案中实现了滚动位置跟踪。
集成测试
在本模块的最后一课,你被介绍了使用React Testing Library进行集成测试。你发现了Jest和React Testing Library,为一个表单编写了测试场景,并了解了持续集成(CI)。
总结

本节课中,我们一起回顾了整个高级React课程的核心旅程。我们从项目搭建和基础组件渲染开始,逐步深入到状态管理、高级Hooks、JSX的灵活运用以及应用测试。每个模块都构建在前一个模块的基础上,旨在让你能够构建更健壮、可维护的React应用。
你已经到达本课程回顾的终点,现在是时候在综合分级评估中尝试运用你所学的知识了。祝你成功!🚀
81:投资组合解决方案演练 🧑💻
在本节课中,我们将一起演练一个个人作品集网页的实现方案。我们将分析其整体结构、使用的技术栈,并深入探讨关键组件的实现细节,特别是表单的处理逻辑。

概述
本方案使用 React 框架构建,并借助 Chakra UI 组件库来简化样式开发。应用包含页头、全屏展示区、项目展示区、联系表单和页脚等部分。我们将重点关注布局结构、组件设计以及使用 Formik 和 Yup 库实现的表单验证与提交逻辑。
整体布局与应用结构
首先,让我们概览整个应用程序的布局。应用由一个包含社交媒体链接和页面导航的页头、三个全屏区域以及一个页脚构成。

三个全屏区域分别是:
- 展示个人简介和头像的“着陆区”。
- 展示过往项目的“项目区”。
- 包含联系表单的“联系我”区。

应用的整体布局在入口文件 App.js 组件中定义。除了顶部的两个 Provider,<main> 标签内的内容对应上述的各个区块,包括页头、着陆区、项目区、联系表单区、页脚以及一个全局提示组件。
这个全局提示组件(Alert)扮演着对话框的角色,可以通过 React Context 在应用的任何地方触发。
页头组件详解

上一节我们介绍了应用的整体结构,本节中我们来看看页头组件的实现。它的渲染部分很好地展示了 Chakra UI 库的一个优势。
首先,Chakra 提供了通用的布局组件,例如用于水平排列的 HStack 和用于垂直排列的 VStack。这些组件使用特殊的 children 属性来放置内容,并可以通过 padding、margin 或 spacing 等属性进行样式控制。
其次,Chakra 使用 Props 来设置所有 CSS 样式,以及一些额外的属性,如 spacing 可以均匀地分隔一组子元素。
以下是渲染社交媒体图标列表的代码片段,使用了 map 函数,并以每个图标的唯一 URL 作为 key:
<HStack spacing={4}>
{socials.map(({ icon, url }) => (
<a key={url} href={url} target="_blank" rel="noopener noreferrer">
<FontAwesomeIcon icon={icon} size="2x" />
</a>
))}
</HStack>
在这个组件的渲染逻辑中,你还会发现两个核心的 React Hook:useRef 和 useEffect。它们共同协作,实现了页头的平滑动画效果。


着陆区与全屏组件
现在,让我们检查下一个组件:着陆区。这个组件本身没有特别复杂之处,它是一个展示型组件,占据整个屏幕高度,并在中央放置图片和文本。
然而,这里使用了一个我创建的可复用组件 FullScreenSection,它应用了你已学过的技术。这个组件同时利用了 children 属性和扩展运算符(spread operator)。
它的目标很简单:创建一个宽度为 1280 像素的全屏容器,并根据背景是深色还是浅色来设置特定的背景色和文字颜色。
扩展运算符允许你将其他布局属性传递给内部的 VStack 组件,以便进行进一步的定制。
function FullScreenSection({ children, backgroundColor, isDarkBackground, ...boxProps }) {
return (
<Box backgroundColor={backgroundColor} color={isDarkBackground ? "white" : "black"} {...boxProps}>
<VStack maxWidth="1280px" minHeight="100vh">
{children}
</VStack>
</Box>
);
}
项目展示区
接下来是项目展示区。这是另一个展示型组件,它以网格布局渲染所有精选项目。
联系表单组件:实现与验证
最后,我们来到本项目的核心——联系表单组件。这可能是所有组件中最有趣的一个,因为它实现了一个带有验证功能的受控表单,并处理了一些内部状态。
为了简化表单的业务逻辑,我决定使用 React 社区中另一个知名的库:Formik。Formik 是最流行的 React 开源表单库,其声明式的特性非常突出,让你能花更少的时间连接状态和变更处理器,而将更多时间专注于业务逻辑。
观察应用,我添加了四个输入字段:
- 用于输入名字的文本输入框。
- 用于输入邮箱的文本输入框。
- 用于选择咨询类型的下拉选择框。
- 一个通用的文本区域。
要定义表单的配置,你需要使用 useFormik 这个 Hook。
这个 Hook 接收一个直观且易于理解的配置对象:
initialValues:定义表单的初始状态。onSubmit:用户点击提交按钮后将执行的函数。validationSchema:指定客户端表单验证规则。
Formik 与 Yup 库配合得非常好。Yup 允许你通过一系列操作符链以声明式的方式定义验证规则。
import { useFormik } from 'formik';
import * as Yup from 'yup';
const formik = useFormik({
initialValues: { firstName: '', email: '', type: '', comment: '' },
onSubmit: (values) => { /* 提交逻辑 */ },
validationSchema: Yup.object({
firstName: Yup.string().required('Required'),
email: Yup.string().email('Invalid email address').required('Required'),
// ... 其他字段规则
}),
});
这个 Hook 返回一个 formik 对象。你需要用它来将你的输入字段与 Formik 的内部状态连接起来。
现在,让我们探讨渲染部分的业务逻辑。我将每个输入字段分组到一个从 Chakra 导入的 FormControl 组件中。

例如,名字字段有一个标签、输入框本身以及一个用于显示该特定输入字段错误信息的组件。
FormControl 的 isInvalid 属性决定了错误信息是否显示。对于名字字段,如果根据验证规则存在错误,并且该字段已被“触碰过”(即用户至少聚焦过该输入框一次),则会显示错误。
在输入框组件中,我需要通过调用 formik.getFieldProps('firstName') 来连接状态和变更处理器,其中参数值 'firstName' 必须与 initialValues 中使用的属性名匹配,然后展开其返回的结果(这是一个包含所有需要从 Formik 连接的属性的对象)。
<FormControl isInvalid={formik.errors.firstName && formik.touched.firstName}>
<FormLabel htmlFor="firstName">First Name</FormLabel>
<Input id="firstName" {...formik.getFieldProps('firstName')} />
<FormErrorMessage>{formik.errors.firstName}</FormErrorMessage>
</FormControl>
如果我聚焦名字输入框然后离开(失去焦点),底部会弹出提示“Required”的错误信息。这很简洁,不是吗?
其余输入字段遵循类似的模式,在此不再赘述。
表单提交与反馈
最后,我们来看表单提交。还记得 useFormik Hook 配置中的 onSubmit 属性吗?那就是表单提交时将被调用的函数。
在该函数中,会向一个端点执行 API 调用以提交表单及其值。随后,一个 useEffect Hook 会监听响应值的变化。一旦服务器响应,它将打开一个提示对话框,显示确认或错误信息。

这个 API 调用只是为了练习目的而模拟的,其编程方式有 50% 的成功几率。

如果响应成功,将使用 formik 对象中的 resetForm 函数来重置和清空表单。
让我们看看表单的实际运行效果:填写表单值,点击提交。
等待后,提示框出现。我们可以再试一次以展示相反的状态。


总结

本节课中,我们一起学习了一个 React 作品集网页的完整实现方案。我们分析了使用 Chakra UI 构建的页面布局,深入探讨了页头动画、可复用全屏组件等细节,并重点剖析了如何利用 Formik 和 Yup 库高效地实现带有验证和状态管理的复杂表单。这为你完成自己的作品集挑战提供了一个可行的参考思路。
82:40_恭喜你完成了高级React课程
在本节课中,我们将对您完成的高级React课程进行总结,回顾您所掌握的核心技能,并展望接下来的学习路径与职业发展机会。
您已经完成了这门高级React课程。
您付出了艰苦的努力,并在此过程中掌握了许多新技能。
您在前端开发者的旅程中取得了巨大进步。

您现在应该理解如何使用更高级的React功能、测试React应用程序以及更熟练地使用JSX。
您能够在实验项目中展示部分所学知识以及您实用的React技能集。
🎯 课程核心技能回顾
上一节我们祝贺您完成了课程,本节中我们来具体回顾一下您所获得的关键能力。
完成这门高级React课程后,您现在应该能够:
- 高效渲染列表和表单组件:在React中高效地处理动态列表和用户输入表单。
- 理解并使用Context:理解Context如何通过定义的API在组件间共享全局值及其潜在应用。
- 掌握并使用所有常用Hook:使用React中的所有常用Hook,并将它们应用到您的应用程序中。
- 构建自定义Hook:根据需求构建您自己的自定义Hook以复用逻辑。
以下是课程评估中衡量的关键技能,它们揭示了您的能力:
- 深入理解JSX:掌握JSX的深层原理与使用技巧。
- 运用高级模式:通过高阶组件和渲染属性等高级模式来封装通用行为。
- 为应用编写测试:为您的React应用程序编写测试,确保代码质量。
- 使用React构建作品集:运用React技术构建个人作品集项目。
🚀 后续步骤与展望
在掌握了上述核心技能之后,您可能意识到仍有更多知识需要学习。本节我们来看看接下来的发展方向。
这门高级React课程增强了您在多个关键领域的知识和技能。

您可能意识到自己还有很多东西要学。
如果您觉得本课程有帮助并希望了解更多,那么何不注册下一门课程?
在每一门前端开发课程中,您都将持续发展您的技能集。
在最终的实验项目中,您将运用所学的一切,使用React构建一个功能完整的网站。
无论您是刚刚起步的技术专业人士、学生还是商业用户,本课程和项目都能证明您对前端开发价值和能力的了解。
该实验项目通过实际应用来巩固您的能力,但它还有一个重要的好处:这意味着您将拥有一个使用React构建的、可正常运行的网站,可以将其收录在您的作品集中。
这有助于向潜在雇主展示您的技能。它不仅向雇主表明您具有自我驱动力和创新能力,也充分说明了您作为个人的特质以及新获得的知识。
一旦您完成了所有课程,您将获得前端开发证书。
这些证书为您的技术技能提供了全球认可和行业认可的证明。
感谢您。很荣幸能与您一同踏上这段探索之旅。祝您未来一切顺利。
83:0_用户体验和用户界面设计原则简介 🎨
概述
在本节课中,我们将要学习用户体验和用户界面设计的基础知识。课程将围绕一个餐厅网站的设计问题展开,引导你了解UX/UI的核心概念、工具和工作流程。
课程简介
欢迎来到这门关于用户体验和用户界面设计原则的课程。用户体验和用户界面,简称UX和UI,其核心是关于人的。每个人都希望体验最新的技术,但他们不希望为此花费过多时间,或给本已繁忙的思绪增加负担。因此,若想使一个网站成功,正确把握这两者至关重要。
本课程将为你介绍UX和UI。关于这些学科还有更多内容需要学习,但本课程将为你提供一个样本,帮助你理解其中涉及的内容。
课程围绕Adrian和Mario的“小乐餐厅”网站所面临的问题展开。该网站在设计时,没有将UX和UI原则应用于在线订餐和餐厅订座这两个核心功能。






课程结构与目标
在整个课程中,你将跟随指导,运用UX和UI的方法论来解决其中一个问题:通过移动设备在网站上在线订餐。另一个问题——预订餐桌,将成为你最终课程作业的主题。


以上只是对课程内容的简要概述,但它确实能说明UX/UI专业人员会遇到的一些任务。
在课程中,你将接触到这些任务,以及构成UX/UI专业人员职责的许多其他任务。具体来说,你将学习:
- 区分UX和UI。
- 使用Figma——当今最流行的设计工具之一。
- 评估交互设计。
- 创建现代用户界面。
你将学习的设计流程

上一节我们介绍了课程目标,本节中我们来看看你将经历的具体设计流程。你将学习创建一个低保真设计,这是一个简单且低技术含量的概念原型。

你只需要纸和笔。然后,你将学习如何将其转化为高保真设计,这种设计功能完善、交互性强,并且非常接近最终产品。最后,你将学习创建原型。


如果你不熟悉这些术语,请不要担心。你将在本课程的学习旅程中逐步掌握它们。换句话说,在课程项目中,你将创建一个原型或最终提案设计的模型,该模型可用于测试和验证提出的想法与设计假设。
学习建议与支持
但你可以放松,现在并不要求你立即成为UX或UI专家。你的课程中包含许多视频,将逐步引导你实现该目标。观看、暂停、回放并重新观看视频,直到你对自己的技能充满信心。




然后,通过查阅课程阅读材料来巩固你的知识,并在课程练习中将你的技能付诸实践。




在学习过程中,你会遇到几个知识测验,你可以在那里自我检查学习进度。就像你一样,有许多人正在考虑成为UX/UI专业人员,课程讨论提示将使你能够与你的同学——那些和你一起学习材料的人——建立联系。这是一个分享知识、讨论难点和结交新朋友的好方法。




成功学习的关键
若想在本课程中取得成功,采用规律且自律的学习方法会很有帮助。你需要认真对待学习,如果可能的话,制定一个学习计划,标明你可以投入课程学习的日期和时间。这是一门在线的自定进度课程,但将你的学习想象成在培训机构定期上课,会对你有所帮助。




总结
本节课中我们一起学习了用户体验和用户界面设计的完整入门知识。总而言之,本课程为你提供了关于UX和UI的完整介绍。祝你在学习旅程中一切顺利。
84:Meta产品设计师的一天 🎨

在本节课中,我们将跟随Meta的产品设计师Petra,了解她一天的工作内容、职责以及她如何与工程师等跨职能团队合作。这有助于我们理解产品设计师在大型科技公司中的角色,以及如何与设计师进行有效协作。
Meta每天有35亿用户使用我们的产品。他们以各种不同的方式使用产品。产品设计师的职责,就是提醒跨职能团队我们构建这些产品的初衷。
我是Petra,是Meta Reality Labs的一名产品设计师,住在伦敦。人们常常认为产品设计师把所有时间都花在设计上。实际上并非如此。产品设计师大部分时间都在与人沟通。大部分时间都在处理文档,并全面地思考产品。
我每天都会对用户与产品互动的方式感到惊讶。意识到自己的假设是错误的,这非常有价值。看到这一点让人谦逊。显然,当你为如此多的人构建全球性产品时,你必须提醒自己,并非每个人都会以你设想的方式使用产品。
我们不断地与产品的目标用户交谈。说实话,这可能是我工作中最好的部分之一。这些对话提醒你,你不是你的用户。我几乎每天都会对人们如何与我们正在构建的产品互动感到惊讶。
在我的成长过程中,我很早就接触了软件。原因很普通。我想在12岁时,把自己P到奥兰多·布鲁姆旁边。这是我第一次接触Photoshop。从那以后,我开始探索其他类型的软件,并自学了编程。
从插画专业毕业后,我已经开始尝试像Connect这样的新技术,以及Arduino、树莓派等有趣的科技产品,构建互动体验。这很自然地引导我进入了当时的数字设计师角色。我的第一份工作是作为数字平面设计师为某个平台做设计。
从那以后,我转向前端工程。我得承认我不是最好的工程师。我更感兴趣的是用户旅程、产品如何运作以及产品解决了什么问题。因此,从前端开发转向产品设计师,并更多地发挥我的视觉技能,是一个非常自然的转变。

我的工作涉及网页、移动端、VR和增强现实。具体取决于手头的项目。通常,我的一天从查看邮件和了解我睡觉期间发生的事情开始。Meta是一家全球性公司,夜间可能发生很多事情。
大约一小时后,我会与我的首席工程师和产品经理沟通,然后一天的工作正式开始。我们在非常跨职能的团队中工作。这意味着你可能会与内容设计师开会,他们负责我们的书面内容;或者,如果你有即将上线的新功能,可能会与营销人员合作;当然,最常合作的还是工程师。
我认为工程师和设计师之间有时确实存在脱节。特别是因为设计师可能不了解代码库,可能不理解工程上的限制。反过来,工程师也可能不太了解设计流程。重要的是,从一开始就在这两个职能之间建立非常牢固的伙伴关系。


实际上我想说,作为一名工程师,当你开始与设计师合作时,不要立即投入工作。暂停一下,花些时间了解这个人,讨论双方都感到舒适的工作流程。之后,再深入探讨工作中更技术性的方面,比如需求、依赖关系,或者讨论你们将要共同构建的实际功能。
不要害怕设计师。我见过不少次,前端工程师倾向于不反驳他们的设计师。但请自由地进行对话。如果你发现设计师的设计不符合要求、不易执行,或者有成本更低的执行方式,去找你的设计师,和他们谈谈,进行一次对话。设计师是来帮助你的。我们是这项事业的合作伙伴,我们最终朝着同一个目标前进。因此,协作对我们来说绝对至关重要。

祝你在成为工程师的旅程中好运。有时可能会感到畏惧,但请坚持下去。当你看到自己如何影响产品用户的生活时,这一切都是值得的。
本节课中,我们一起学习了Meta产品设计师Petra的日常工作。我们了解到产品设计师的核心工作远不止视觉设计,更包括大量沟通、用户研究和跨团队协作。她强调了工程师与设计师建立牢固伙伴关系的重要性,并鼓励工程师主动与设计师沟通,共同寻找最佳解决方案。理解这些,将帮助我们在未来的工作中更好地进行团队合作。
85:用户体验与用户界面简介 🎨
概述
在本节课中,我们将学习用户体验(UX)和用户界面(UI)的基本概念,了解它们之间的区别,并探讨在产品设计生命周期中各自扮演的角色。我们还将了解在不同规模的公司中,UX和UI设计师的职业发展路径。
小柠檬餐厅的挑战
小柠檬餐厅的网站目前面临一些问题。餐厅的联合创始人之一阿德里安注意到,他们的网站没有获得太多订单或预订。他喜欢网站旧有的外观和感觉,但也认识到必须做出一些改变。
阿德里安知道你对用户体验和用户界面设计感兴趣,因此询问你是否愿意重新设计小柠檬餐厅的网站。这为你提供了一个绝佳的学习和实践技能的机会。你接受了阿德里安的邀请,并迫不及待地想要学习更多关于UX和UI的知识,以帮助阿德里安,并探索该领域的职业发展。



区分UX与UI
上一节我们介绍了小柠檬餐厅面临的挑战,本节中我们来看看如何区分UX和UI这两个学科,并识别它们在产品设计生命周期中通常承担的角色。

什么是用户体验(UX)?
首先,UX不仅仅关乎小柠檬餐厅网站或应用程序的用户界面。UX关乎顾客或用户与小柠檬餐厅整个业务的体验,包括员工、服务、食物、装饰、菜单设计、洗手间,当然也包括网站和应用程序。然而,在本课程中,我们将重点放在应用于小柠檬网站设计和重新设计的UX流程上。
UX的核心是提出问题并寻找答案。以下是UX设计师关注的一些典型问题:
- 用户需求是什么?
- 是什么阻碍了用户实现他们的目标?
- 网站的使用直观性如何?
- 用户能否轻松订购食物?
- 顾客能否快速高效地在不同板块间切换,例如选择菜品和定制订单?
UX的最终目标是在产品中为这些问题提供解决方案。
什么是用户界面(UI)?
现在,我们来看看用户所看到的部分——UI。在这个情境下,小柠檬餐厅网站是用户的主要接触点。UI提供了用户首先看到并与之交互的信息。这包括字体、颜色、按钮、形状、图标和图像等元素。
成功的UI设计在于以某种方式分组和组合这些元素,这种方式既能帮助用户快速高效地实现目标,同时看起来美观并符合餐厅的品牌形象。其核心公式可以概括为:
美观的界面 + 高效的交互 = 成功的UI
UX与UI的协作关系
UX和UI彼此相互影响。当它们结合使用时,可以共同创造出美观且令人愉悦的产品。例如:
- UX设计师 会通过研究识别用户需求,并通过原型迭代提出解决方案。
- UI设计师 则会根据系统化的设计库,创建出逻辑清晰、美观的布局和交互流程。
它们的关系可以用一个简单的协作流程来描述:
用户研究 (UX) -> 信息架构与原型 (UX) -> 视觉与交互设计 (UI) -> 开发实现


不同公司的职业角色
值得注意的是,UX和UI领域内的角色可能和学科本身一样细致入微。在不同的公司,这些角色的含义会有所不同。
以下是不同规模公司中可能存在的角色差异:
- 在小型公司,一名设计师可能需要同时承担UX和UI的角色,包括研究用户需求、设计解决方案甚至进行编码。
- 在大型公司,学科内部可能会有更专业化的角色。例如:
- UX研究员 可能只专注于用户研究。
- UX文案 可能专注于产品文案的内容、措辞和语调。
- UI设计师 可能完全专注于设计系统(一个交互元素和组件的仓库)的维护和管理。
- 无障碍设计专家 则专注于确保为可能有障碍的用户提供最佳的体验。
可以肯定的是,了解这些学科各自涉及的内容并恰当地应用它们,能够催生出设计精美且易于使用的产品。
课程实践与应用
在本课程中,你将跟随小柠檬餐厅网站订餐功能的重设计过程,观察UX和UI原则是如何被应用的。然后,在最终的课程项目中,你将有机会在“餐桌预订”功能部分亲自应用这些原则。

总结
本节课中,我们一起学习了UX和UI之间的关键区别,以及产品设计生命周期中存在的一些工作岗位。我们还了解了UX或UI设计师在不同规模公司中的职业发展情况。掌握这些基础知识,将为你后续深入学习和实践打下坚实的基础。
86:Meta产品经理的一天 🧑💼
在本节课中,我们将跟随Meta的产品经理Jason,了解他典型的工作日是如何度过的。我们将学习产品经理的核心职责、所需技能以及他们如何与设计师和工程师协作,共同打造优秀的产品。
产品经理不会命令设计师或工程师去做什么。我们的工作重点是团结所有人,围绕一个我们共同相信的战略展开协作。我们非常重视开发者和工程师的想法,因为我们是一个团队,致力于共同构建产品。
产品经理的三大核心工作
产品经理的日常工作主要围绕三件核心事情展开。
以下是这三项核心工作的具体内容:

- 阅读与理解:了解产品、市场、领导层以及其他团队的动态。我必须对正在发生的事情有清晰的认知,才能进行下一步。
- 思考与规划:这通常通过写作来完成,目的是明确产品战略、理清沟通思路、为整个团队制定计划或设定目标。
- 协作与沟通:这通常发生在与他人会面时。虽然我会通过书面文件传递信息,但很多时候是与工程师一起在会议室里解决问题。我们需要共同探讨:这是产品战略,实现它需要什么?在H1或今年内真正落实需要哪些步骤?我们如何将其变为现实?应该做出哪些权衡?
这三项工作通常占据了我作为产品经理的大部分时间。
产品经理的核心技能
上一节我们介绍了产品经理的日常工作,本节中我们来看看他们需要具备哪些核心技能。
谈到技术技能,产品经理实际上比我们合作的许多同事掌握的技术要少。我主要使用的技能是分析能力。我依赖数据科学家来提取和处理大量复杂数据,但我需要消化并理解这些数据的含义。
协作能力是一项核心技能,因为我们的工作是与工程师合作,让产品变为现实。
以下是一些关键的软技能:

- 书面沟通:能否清晰地表达我们的想法和思考。
- 口头沟通:能否帮助他人理解并认同我们的方向,共同前进。
与工程师及所有其他利益相关者一起,我们共同构建出色的产品,这本身就是一项极具挑战性的任务。首先,我们必须知道什么是成功的产品,并制定清晰的战略来实现它。这种挑战性以一种最佳的方式存在,也正是这份工作让人充满成就感的原因。
与设计师和工程师的协作

我们已经了解了产品经理的职责和技能,现在让我们聚焦于他们如何与团队中的关键角色——设计师和工程师进行协作。
我与产品设计师和工程师的合作非常紧密,他们是我最重要的两个利益相关者。

与产品设计师协作:我主要负责提出产品战略,并将其转化为可视化的表现形式。举个例子,假设我有一个战略概念,认为导航需要更具持久性,以便用户能更方便地访问某些内容。
我会将这个想法写成战略文档。产品设计师则会将其可视化,设计成实际的菜单:像素大小是多少?颜色用什么?是否显示通知?他们将产品战略转化为了一个视觉化的方案。
与工程师协作:然后,我们会与工程师一起,将这个由设计师设计的菜单(比如宽度是80或100像素)用代码实现出来。我们三人紧密合作,共同实现这个在产品大战略中占据重要地位的功能。
给工程师的建议
在了解了产品经理的视角后,本节我们来看看,工程师可以从哪些方面努力,以成为更高效的团队成员。

工程师要成为高效的团队成员,首先必须精通本职工作,即具备出色的编码和执行能力。

除此之外,协作能力也非常重要。产品设计师、产品经理和工程师共同协作,才能打造出卓越的产品。在团队工作中,沟通能起到巨大的作用。
良好的沟通能确保我们步调一致,从而帮助我们打造出非凡的产品。
总结
本节课中,我们一起学习了Meta产品经理Jason的日常工作。我们了解到,产品经理的核心工作包括阅读理解、战略规划和团队协作。他们需要强大的分析能力和沟通技巧,并与设计师和工程师紧密合作,将战略转化为可视化的设计,最终通过代码实现产品。构建产品本身是一件极具成就感的事情,当你发布的产品受到用户喜爱时,那种感觉无与伦比。完成本课程后,你将有能力去实现这一切。
87:4_最终项目概览 🎯
在本节课中,我们将要学习Meta前端开发课程中最终项目的整体背景与目标。我们将了解项目背景、需要解决的问题、你将遵循的设计流程,以及最终需要交付的成果。
项目背景介绍
Adrian是Little Lemon餐厅的联合创始人之一,负责管理所有与产品相关的事务。他要求你遵循UX/UI流程,对Little Lemon网站进行重新设计。
在整个课程中,你将学习支撑UX和UI的理论知识,并将新学到的技能应用于重新设计他们网站的部分功能。在本视频中,你将了解探索UX和UI的具体情境,并概述重新设计过程将如何进行。




最后,你将获得对最终课程项目所能完成内容的描述。


项目核心问题
在你将要工作的情境中,顾客在使用Little Lemon网站订餐外卖和预订餐桌时遇到了问题。在整个课程中,你将学习用于识别和更好地理解这些问题的UX和UI原则及最佳实践。
你将遵循以顾客为所有决策核心的在线订餐元素重新设计流程,然后探索解决方案并进行测试,以确保顾客觉得这个元素直观且使用愉快。






你的实践任务
在学习了UX/UI流程所涉及的内容后,将轮到你练习所学的技能,并为餐桌预订元素创建一个解决方案。你将收到一份作业,概述课程结束时需要产出的内容。
你在课程结束时产出的将是一个交互式元素,你可以向未来的雇主展示。你将基于从顾客使用网站时收集的数据开始你的工作。






最终项目:餐桌预订交互元素设计
在整个课程中,你将学习UX和UI原则及最佳实践,并将其应用于你的项目——设计“餐桌预订”交互元素。
预订餐桌元素将广泛包含顾客可以选择的功能,例如:
- 用餐人数
- 日期和时间
- 特殊要求备注框


在选择这些选项后,顾客将添加他们的详细信息。这些信息包括:
- 姓名
- 电话号码
- 电子邮件地址
- 信用卡详情


当他们完成这些阶段并确认预订后,他们将在屏幕上并通过电子邮件收到确认信息。这是你的创作,因此在设计过程中,你可以加入任何你认为必要的附加功能。
课程目标总结
在完成课程时,你将运用所学的方法和流程,为移动版Little Lemon网站的用户设计餐桌预订流程。
本节课中,我们一起学习了最终项目的整体框架。我们明确了项目背景是重新设计Little Lemon网站以解决用户订餐和预订餐桌的痛点。我们了解到,你将先学习并实践UX/UI流程来重新设计在线订餐功能,然后独立运用这些技能完成餐桌预订交互元素的设计,最终产出一个可用于作品集的可展示成果。
88:什么是用户体验 🧠
在本节课中,我们将要学习用户体验(UX)的基本概念、核心流程及其重要性。通过学习,你将能够定义什么是UX,并认识到它是一个包含多个阶段的系统性过程。
Adrian 了解到,他的顾客对于无法轻松通过网站预订餐桌或下单外卖感到沮丧。他知道,解决这些问题有助于获得更多业务。你将运用 UX/UI 原则来帮助 Adrian 实现他的商业目标。你将开始探索 UX,并学习遵循 UX 过程如何能让你从用户的角度解决这些问题,从而增加销售额并留住回头客。
什么是用户体验?🤔
UX 即用户体验。它涵盖了用户与公司服务及产品进行的所有互动。例如,使用遥控器、手机、应用程序或网站的感觉如何?在与一个产品互动之前、之中和之后,你的感受是怎样的?用户能否轻松地通过产品实现他们的目标?界面是否提供了流畅、愉快的体验?用户是否感觉一切尽在掌握?为什么顾客会持续回头消费?让我们进一步探讨。
著名设计领域研究者 Don Norman 创造了“用户体验”这个术语。他关注的是顾客使用产品时的整体体验:用户如何在商店里找到产品?如何将其带回家?打开包装的体验如何?组装说明是否易懂?使用起来感觉怎样?它是否直观且令人愉悦?UX 关乎所有这一切。
你是否曾用过某个电器却不知道它是开是关?或者用过某个应用程序,却很难找到你想要的功能?😊 这些问题都可以通过 UX 来解决。
优秀用户体验的核心要素 🔑
一个产品的功能需要易于发现,这样用户才知道自己在寻找什么。成功的产品设计还需要能够与你对话,它需要对你的操作给予反馈。你不希望用户因为点击按钮后毫无反应而感到困惑。
成功 UX 的关键在于为用户考虑和设计,而不是为你认为用户想要的东西设计。请花一分钟思考一下,哪些产品或设计非常直观,而哪些用起来完全不合逻辑。你会如何改进那个设计,使其适合所有用户,无论其年龄和能力如何?你会做出哪些改变?
用户体验是一个过程 🔄
要理解 UX 的真正含义,重要的是认识到它已被提炼为一个过程,你将在后续课程中更详细地探索。但让我们先快速概览一下,以便了解这个过程涉及哪些环节。
这个过程是迭代的,你可能需要多次回到之前的步骤,调整你的设计以适应顾客和商业目标。

以下是UX设计过程的核心阶段:
- 观察与发现问题:如果你在设计某个东西,你需要观察人们执行任务,并从中识别他们在完成任务时可能遇到的问题。
- 构思与创意:基于观察,你可能会产生一些需要着手实施的想法。
- 原型制作与测试:接着,为你设想的解决方案制作原型并进行测试。

这个过程可以总结为:观察 -> 构思 -> 原型 -> 测试。

你需要在每个阶段及其内部步骤中不断重复,并一遍又一遍地测试你的设计,直到得到一个对所有人都可用且令人愉悦的产品。😊

遵循这些步骤,并考虑当前网页和产品设计中的最佳实践方法与行为,将帮助你重新设计 Little Lemon 网站的点单表单。
本节课中,我们一起学习了 UX 是什么、它包含哪些阶段,以及如何通过从一开始就考虑你的用户,将这些阶段成功应用于产品或服务的重新设计。
89:用户体验目标与质量组件 🎯
在本节课中,我们将学习如何识别用户体验目标,如何在用户设计过程中应用这些目标,并了解尼尔森提出的可用性质量组件。这些知识将帮助你设计出更智能、更愉悦的用户体验。
与Adrian交谈后,你意识到顾客无法通过Little Lemon网站轻松下单或预订餐桌,这不仅是设计问题,更是一个可用性问题。作为一名有抱负的UX/UI设计师,你需要运用方法来构建你的设计思维,从而为用户提供最佳体验。在这个过程中,你将更好地理解客户及其痛点。你需要思考如何将客户目标与Adrian的商业目标结合起来。
理解可用性:用户体验的核心
上一节我们提到了可用性问题,本节中我们来看看可用性这一UX和UI设计中的核心概念。
可用性衡量的是一个产品有多直观或多容易使用。该领域的知名专家雅各布·尼尔森提出,可以通过五个可用性组件来评估可用性。
以下是这五个组件:

- 易学性:当顾客第一次尝试下单外卖时,Adrian希望这个过程从一开始就易于学习。
- 效率:如果用户想修改订单,操作是否简单?他们能否快速高效地完成?
- 可记忆性:如果用户中途被打断,当他们返回时,是否容易记起刚才进行到哪一步?他们能多快找到之前的位置?
- 容错性:良好的可用性需要考虑错误。无论用户何时犯错,设计都应提供解决方案,并在错误发生前就加以防范。
- 满意度:网站使用起来是否愉悦或令人满意?用户喜欢使用它吗?它是否易于使用?

衡量用户满意度并非易事,但当你使用一个直观且设计精良的产品时,你自然能感受到。在你的设计各个阶段,思考这些可用性质量组件都很有帮助。如果你从一开始就考虑它们,就能在流程早期解决问题。



设定用户体验目标:连接用户与设计

了解了评估标准后,现在我们需要思考使用你设计的用户,并考虑如何让你的设计对他们而言是令人满意和愉悦的。
当你使用一个产品时,可能会经历一系列情绪,从无聊到开心,从兴奋到困惑,有时甚至会放弃。尽早考虑用户的体验目标,是一种可以帮助你记住用户体验不断变化本质的方法。
你可以将体验目标组织为期望的和不期望的两个方面。
让我们思考一下你的客户及其目标:
期望的体验目标包括:
- 你希望你的产品使用起来有趣且令人愉悦。
- 你希望你的设计能让人放松、满意且高效。
- 你当然希望客户能投入、有动力并受到激发。
- 另一个重点是,你不希望用户需要费很大劲才能使用你的产品,它应该是直观的。
不期望的体验目标包括:
- 另一方面,你不希望你的网站或应用速度慢、令人困惑或使用复杂。
- 你不希望你的客户感到无聊、沮丧或有压力。
应用目标:从理论到实践思考
那么,如何将这些目标应用到具体设计中呢?客户需要能轻松找到网站上正确的部分来完成他们想做的事。
你的解决方案如何能在提供帮助的同时,又不显得居高临下或自以为是?例如,在Little Lemon预订餐桌时,顾客需要知道餐桌是否可用以及可以预订多久。你的设计能否向用户提供这些信息,让他们感到被支持并被赋能?信息呈现是否清晰而不惹人厌烦?
假设一位顾客使用拐杖,希望坐在门边。你的设计能否潜在地满足这一要求?如果不能,用户需要时能否足够容易地找到这个信息?
尽管你尚未实施任何具体设计,但你现在已经在考虑用户的目标和需求,并思考如何通过你的设计方案来帮助他们满足这些需求。
总结与回顾 📝

本节课中,我们一起学习了:
- 识别用户体验目标的重要性,以及如何将其融入设计思维。
- 尼尔森的五个可用性质量组件:易学性、效率、可记忆性、容错性、满意度。它们是评估设计好坏的关键标准。
- 如何将体验目标分类为期望的和不期望的,从而更全面地指导设计。
这些是构建智能且令人愉悦的设计的关键方法。请记住,在你的设计流程早期就将它们考虑在内非常重要,这有助于在问题发生前就尝试避免它们。
90:用户体验流程概览
概述
在本节课中,我们将要学习用户体验设计的基本流程。我们将了解一个成功的UX项目通常包含哪些关键阶段,以及如何将这些阶段应用到“小柠檬”餐厅网站的重设计项目中。理解这个流程将帮助你系统地创建出以用户为中心、能提升销售并留住顾客的网站。
UX流程简介
上一节我们介绍了学习UX流程的重要性,本节中我们来看看这个流程具体包含哪些阶段。UX是一门非常注重流程的学科。在进行UX设计或重新设计时,可以遵循多种不同的模型。虽然没有放之四海而皆准的方法,但遵循一些关键步骤有助于确保成功实施丰富的用户体验设计。
让我们来了解UX流程,以及在本课程中如何将其应用于“小柠檬”网站的重设计。不必担心现在不理解某些术语,我们后续会详细讲解。
UX流程包含五个阶段:共情、定义、构思、原型和测试。
需要牢记的是,UX是一个迭代过程。这意味着你可能需要回到之前的阶段进行调整和完善。
迭代过程:设计 → 测试 → 反馈 → 优化设计 → 再次测试...
第一阶段:共情

在定义了流程框架后,我们首先进入第一阶段:共情。这个阶段的目标是深入理解你的用户。

你获得了许可,可以采访并观察阿德里安的顾客,了解他们在餐厅网站上尝试完成任务(例如订购外卖)时的各个阶段。你需要倾听他们的困扰。
此阶段的关键在于从研究结果中理解用户的需求。基于这些信息,你可以创建一个用户画像,并在整个设计过程中参考它。
用户画像 = 基于真实用户数据构建的虚构角色,代表一类用户群体

你还会创建共情地图、场景和旅程地图,以更深入地与这个用户画像产生共情。这也能让你的想法立足于实际,避免做出假设。
第二阶段:定义
理解了用户之后,下一步就是明确问题所在。第二阶段是定义阶段。
你需要整理并提炼从用户那里收集到的所有信息,识别出他们面临的关键问题和需求。你还需要根据重要性对这些困扰或痛点进行优先级排序。

痛点优先级 = 评估每个问题对用户体验的影响程度和发生频率
现在,你知道了你的用户是谁、他们的困扰是什么以及你需要解决什么问题。你将创建一个用户需求陈述,清晰地概述用户的需求。
第三阶段:构思

一旦明确了问题和解决对象,就可以开始构思解决方案了。构思阶段是关于产生想法的阶段。

你可以通过草图绘制、头脑风暴、思维导图甚至手写笔记来进行构思。
构思方法:草图、头脑风暴、思维导图

此阶段的关键是保持开放的心态,不要过早地锁定某一个具体的想法。在整个设计过程中,构思可能会被反复迭代。
迭代 = 反思你的工作,并优化改进你的设计
你将把你的想法草图化,形成能够解决“小柠檬”顾客需求的方案,然后将它们发展成线框图。
线框图 = 用户界面的二维表现形式,关注内容的布局与层级、提供的功能以及用户的预期操作

第四阶段:原型
有了初步的设计框架,接下来需要将其具体化。接下来是原型阶段,即最终产品的模拟。
你已经通过迭代和完善将想法变成了一个全新的解决方案,但不能假设它对所有人都有效并直接发布。你应该先用一个原型来模拟它的行为。
原型 = 最终产品的交互式模拟

拿出你的线框图,将其丰满起来:添加一些颜色、放入按钮和文本,并使其具有交互性。你可以为你的顾客模拟一个真实的场景,帮助他们实现所需的目标。
由于UX是一个基于用户和客户反馈的迭代过程,你可能也需要在这个阶段进行迭代,因此你的想法会不断被精炼,逐渐接近最终的设计方案。

第五阶段:测试
设计完成后,必须验证其有效性。测试阶段是你向用户展示解决方案并获取反馈的阶段。
你需要创建一个测试脚本,其中包含一些清晰的指示,专注于完成一项或多项任务。你的测试参与者(本例中是顾客)会与你的原型进行交互,同时尝试完成手头的任务。
测试脚本:1. 向参与者介绍任务。 2. 观察其操作过程。 3. 收集口头与行为反馈。
任何困扰都可以在这个阶段被沟通和突显出来,你可以在进入流程的下一个阶段之前返回并解决它们。
最终阶段:构建
最后一个阶段是构建阶段。你已经倾听了用户的声音,与他们产生了共情,并通过迭代设计技术力求解决他们的需求。你观察了他们使用你的产品,并对其进行了调整,使其更简单、更直观。
现在,是时候开始构建了。确保遵循这些步骤可以帮助你构建出满足用户需求并提供优秀用户体验的产品。

总结
本节课中,我们一起学习了UX流程的关键步骤,并明确了如何应用它们。你认识了共情、定义、构思、原型和测试这五个核心阶段,以及迭代在整个过程中的重要性。你可以利用这些工具,使“小柠檬”网站的重设计更加用户友好。
91:什么是用户界面 👨💻
在本节课中,我们将要学习用户界面(UI)的核心概念、其历史演变以及它在日常生活中的重要性。我们将了解UI如何作为用户与科技产品沟通的桥梁,并探讨优秀UI设计的关键作用。


你已经学习了可以应用于小柠檬网站的用户体验(UX)原则,以改进其点餐和预订功能。但你也知道需要改进用户界面。因此,你将深入了解UI设计。
什么是界面? 🤔

与某物“交互”意味着与其互动或沟通。你可以与其他人交互,也可以与计算机和应用程序交互。人们每天通过电子邮件和短信与同事和朋友沟通,或者与技术交互,例如通过更改设置与打印机交互。

在与咖啡师交互的同时,他们也在与制作咖啡的机器交互。思考一下你如何使用设备来完成各种任务,例如获取地点路线、参加在线课程或订购食物。


图形用户界面(GUI) 🖥️


图形用户界面(GUI)在屏幕上呈现交互式视觉组件,人们可以通过这些组件与技术进行沟通。为保持一致性,我们将其简称为UI。
界面上的信息及其布局方式、图标、颜色、文本、形状和所有信息,共同构成了UI。当你点击或轻触某个东西时发生的反应,也属于UI的一部分。
界面的本质:沟通与任务 🗣️



界面关乎沟通。自远古以来,人类就使用工具作为彼此沟通的手段。洞穴壁画表明,人们使用木棍来描绘故事和事件。木棍演变成了羽毛笔,羽毛笔又演变成了钢笔,至今仍被用于传递信息和沟通意图。
现代键盘源自打字机。人类通过用手指敲击打字机的按键来组合单词,与之交互。今天,当你在最喜欢的社交网络上发布状态更新时,你也在做同样的事情。


界面也关乎任务。以汽车为例,仪表盘是用户界面的完美范例。如果驾驶员执行踩油门的任务,汽车会做出响应,仪表盘上的速度表会反应,同时汽车也会加速。


当你与手机交互以检查Wi-Fi信号和电池电量时,这也是一种沟通。

优秀UI设计的重要性 ⭐

一个设计精良的用户界面的重要性再怎么强调也不为过。试想一下,一个能将生命体征传达给医生的医疗设备界面,或是在风暴中能保障乘客安全的飞机操控界面。它们设计的直观性有助于拯救生命。

施乐帕克研究中心推出了第一台带有图形用户界面的个人电脑。这个UI经过多年完善,施乐之星于1981年作为个人电脑推出。许多设计隐喻,如桌面、窗口、菜单和图标的引入和广泛应用,至今仍在沿用。
这些设计隐喻通过将用户熟悉的心智模型与当时这个新的、陌生的数字空间联系起来,帮助用户完成任务。


另一个里程碑是智能手机和平板电脑的引入,它们利用多点触控屏幕,促进了使用手势与之交互。开发者和设计师能够创新并创建在这些设备上运行的应用程序。特别是这个阶段,带来了UI的现状:它无处不在。从你使用的应用程序到现代电动汽车的仪表盘,随处可见。
在本课程中,你将专注于应用于网站设计的UI,然而,这些方法和概念同样适用于应用程序等一系列输出产品。

总结 📝
本节课中,我们一起学习了用户界面设计、它的历史与演变,以及它在日常生活中扮演的重要角色。牢记这些历史和概念,可以帮助你为小柠檬网站创建更好的UI设计。
92:Figma入门指南 🎨
在本节课中,我们将学习如何开始使用Figma,这是一个行业标准的设计工具。我们将探索其界面布局、核心功能区域以及基本操作方式,为后续的网站重新设计项目打下基础。
界面概览与导航
上一节我们介绍了学习Figma的必要性,本节中我们来看看如何访问并初步认识其工作界面。
Figma是一个免费的在线UX/UI设计与原型工具。它具有协作性,能帮助设计师和开发者共同构建数字产品。它允许用户共同编辑、评论和审阅设计与代码。Figma基于Web,因此可在所有平台上运行,例如Mac、运行Windows的PC、Linux和Chromebook。
在本课程中,你将学习在Figma中设计网站的主要阶段,从线框图和模型到交互式原型。你将探索基本工具,并熟悉更高级的功能,如自动布局、组件和原型制作。
要访问Figma,请访问 www.figma.com 并注册一个免费账户。在Figma中,你可以组建团队并创建共享工作区,以便同时处理文件。你可以选择从免费的入门版或付费的专业版计划开始。如果你是学生或教育工作者,可以免费访问Figma的所有专业功能,只需访问 figma.com/education/apply 来验证你的教育账户。
现在,让我们探索Figma。登录后,系统会提供“新设计文件”、“新Fig Jam文件”和“导入文件”的选项。我选择“新设计文件”。界面会显示编辑器。第一次打开时,它看起来相当空白,但不必担心。你现在将了解Figma界面中的各个部分。
核心界面区域详解
了解了如何进入Figma后,我们来详细分解其用户界面的各个主要部分。
顶部有一个工具栏,包含各种工具和功能。屏幕中央是画布,你的设计将在此呈现。左侧是左侧边栏,包含文件中的图层、资源库和页面。右侧是右侧边栏,包含三个面板。现在,让我们轻松一点,更详细地介绍每个主要部分。
画布与菜单
让我们从画布开始。画布是你所有设计的背景。你将在这里创建和评估你的工作。
现在来看菜单。可以通过点击屏幕左上角的Figma图标来访问Figma菜单。花点时间探索菜单中的项目。你也可以通过在“快速操作”选项中输入来搜索特定命令。
工具栏与选项栏
现在让我们简要检查一下工具栏。在工具栏中,你可以快速访问你最可能经常使用的工具,如画框、形状、平移和文本。工具栏是上下文相关的,这意味着其中显示的内容取决于你选择了哪个项目。
接下来是选项栏。它位于顶部中央,显示你选择的任何工具的额外选项。如果未选择任何内容,Figma会显示文件名。当选择了某些内容时,会出现上下文选项。例如,如果我选择了一个形状,选项会变为编辑对象、编组组件或创建蒙版。
图层与右侧边栏
现在让我们探索图层。我通过点击左侧边栏中的“图层”选项卡来访问图层。我添加到设计中的每个元素都会出现在左侧的图层面板中,它们在那里被列出并组织到画框和组中。如果你不完全理解图层、画框和组是什么,请不要担心,你将在本课程后面详细学习它们。
现在让我们介绍右侧边栏。它是一个上下文相关的部分,可以显示“设计”、“原型”或“检查”面板。在本课程中,你将主要关注“设计”和“原型”面板。“检查”面板可用于复制CSS值。如果我在画框上选择一个对象,该面板会在“设计”面板中显示与该特定对象相关的属性。右侧边栏中的面板是上下文相关的,意味着信息和设置会根据所选对象而变化。它包含位置信息、尺寸、填充、描边和其他效果。
总结与鼓励

本节课中,我们一起学习了如何注册Figma、导航其用户界面并识别其各个部分。

我鼓励你亲自注册并探索Figma的用户界面。
93:10_你的用户是谁
在本节课中,我们将学习如何利用用户访谈和观察的研究结果来指导设计。我们将探索如何识别用户,理解他们的需求,并学习一种名为“客户旅程地图”的工具来可视化用户体验过程。
概述
你已经准备好为Little Lemon网站进行工作,现在你获得了五位同意接受访谈并在使用网站时被观察的用户的研究结果。这种信息收集被称为用户研究,它能为你提供关于客户在线订餐和预订体验的宝贵信息。换句话说,它帮助你确定用户是谁。
用户研究的重要性
如果不进行用户研究就进行设计,你最终会基于对客户的假设来工作,这可能导致产品失败。对你和你的经验来说看似合乎逻辑的东西,在创建时对另一个人来说可能被证明是无法使用的。
还记得之前学过的用户体验流程阶段吗?设计师需要共情、定义、构思、原型制作和测试。其中,共情和测试的重要性怎么强调都不为过。在本案例中,测试参与者几乎无法成功完成订餐流程。这些数据对你构建问题和构思解决方案非常有价值。
用户访谈与观察发现
以下是用户访谈和观察中发现的主要问题:
首先,所有参与者都发现很难找到特定的菜品。菜单按钮不易识别,而且一旦发现,下拉菜单没有按食物类别(如前菜、主菜)进行分类。
其次,发现购物篮图标不可见。没有购物篮图标,一位参与者无法确认是否成功添加了物品。这个简单的疏忽迫使用户试图重新开始,却发现没有返回按钮或导航回主页的方法,因此他们放弃了任务。
第三,另一位参与者最终成功下单。然而,他们无法编辑或更新该订单。例如,在观察参与者尝试订餐时,注意到他们想为菜品添加额外的配料,但网站上没有这个功能。
最后,在另一次观察中,参与者在注册表单中填写了所有信息。然而,由于一个错误,他们无法完成订单。他们尝试了多次,但一直收到验证错误,而这个错误只在输入信息后才显示。他们再次放弃了任务。
客户旅程地图
你可以通过客户旅程地图来说明参与者的体验。它展示了某人完成任务所采取的步骤。在追踪用户体验目标时,你可以识别他们在各个步骤中的情绪。
让我们探索一个说明他们体验的客户旅程地图。
请记住,你应该记录用户在执行每项任务时的互动和反应。
从客户旅程地图的顶部开始,你可以列出客户的姓名、图像、场景和期望。
接下来,你可以列出她试图完成的步骤,并追踪她的想法和感受。
例如:进入网站、选择和定制订单、添加详细信息并付款。随着流程推进,旅程地图捕捉了她的行为、想法、言论以及在与网站互动时的感受。
最终,你会得到一组有用的要点,可以帮助你与用户共情,从而改进你的重新设计。
总结


本节课中,我们一起学习了如何利用来自用户访谈和观察这两种研究方法的数据。你学会了如何使用这些数据来优先处理关键的设计改进机会。我们还探索了客户旅程地图,以及如何用它来可视化客户的任务流程。请务必查阅补充阅读材料,以了解更多关于用户研究、访谈和观察的知识,它们将为你重新设计Little Lemon网站做好更充分的准备。
94:同理心工具 🧠
在本节课中,我们将要学习用户体验设计流程中的“同理心”阶段,并了解该阶段的核心工具。我们将重点探讨如何利用用户研究、访谈和观察的发现,来创建用户画像、同理心地图和用户场景,从而确保设计决策以用户为中心。
上一节我们介绍了用户研究、访谈和观察的发现。本节中,我们来看看如何将这些信息整合,并应用于用户体验设计流程的“同理心”阶段。
你可能还记得,用户体验流程主要包括五个阶段:同理心、定义、构思、原型和测试。通过综合所有访谈用户的信息,你将学会如何创建用户画像。这个画像将指导你在用户体验设计流程中的决策,并始于“同理心”阶段。
用户画像:虚构的核心用户模型


“同理心”阶段的一个关键工具是用户画像。用户画像是一个虚构的角色档案,代表公司的主要客户群体。它是你所寻求帮助人群的真实模型。它能引导设计师避免做出假设,并鼓励基于对这个虚构用户的同理心来构思解决方案。
虽然这个用户是虚构的,但其特征基于用户访谈和观察的结果。

可以将用户画像视为研究参与者的特质混合体。用户体验团队可以创建多个画像来解决不同问题,具体数量取决于项目规模和研究数据的结果。
以下是用户画像通常包含的几个部分:
- 基本信息:包括照片、姓名、年龄、性别、职业和技术能力。
- 一句话描述:概括角色的核心特征或身份。
- 简短简介:提供更多背景信息。
- 核心需求:列出用户的目标。
- 痛点:描述阻碍用户实现目标的挫折或障碍。

案例分析:用户画像“Tilly”

以“小柠檬餐厅”项目为例,团队创建了两个用户画像:一个是典型回头客,另一个是新顾客。让我们深入了解其中一个。

她的名字叫Tilly。她60岁,热爱美食,住在小柠檬餐厅附近。她喜欢餐厅的素食版佩内阿拉碳烤面。她享受外卖的便利,这样她就可以在家一边享用美食,一边看她最喜欢的电视节目和电影。她通过手机下单。Tilly喜欢可靠的东西,不介意为确定好用的产品多花点钱。她使用社交媒体与朋友和家人保持联系,但仍然觉得有些网站和应用程序非常难用。
查看Tilly的画像,顶部是她的照片、姓名、年龄、性别、职业和技术能力。接下来是一句描述:“慈爱的阿姨,好朋友,糟糕的园丁”。右侧是她的简短简介。然后是“核心需求”部分和“痛点”部分,列出了Tilly的目标,并描述了阻碍她实现这些目标的挫折。这一部分揭示了她很容易感到沮丧。值得注意的是,这是一种定性的画像。画像有不同的类型,你可以在补充资源中阅读更多相关内容,那里还提供了一个用Figma创建的画像模板链接。
同理心地图:洞察用户全貌

接下来,你将探索一个已创建的同理心地图。

Tilly位于同理心地图的中心,地图被分为四个象限,分别描述她说什么、想什么、做什么以及感受什么。同理心地图既非按时间顺序也非按序列排列,它能让你整体地窥见用户的性格。
同理心地图帮助你更好地了解Tilly,并让你设身处地为她着想。你同样可以在补充资源中找到同理心地图的模板。
用户场景:定义问题与目标

最后,为Tilly创建一个场景,这将帮助你解决她的问题。请记住,你目前是在与Tilly共情,而不是重新设计任何东西。
这些工具共同帮助创建了以下场景:
- 画像:Tilly
- 目标:享用她最爱的餐食
- 期望:订购一份素食版佩内阿拉碳烤面外卖
- 需求:访问网站,选择并定制订单
- 解决方案:输入她的详细信息,支付并跟踪订单

本节课中我们一起学习了设计师在用户体验流程的“同理心”阶段可以使用的一些工具,包括用户画像、同理心地图和用户场景。我们也学习了如何描述它们在“同理心”阶段的作用。请记住,将你的思维围绕用户而非你自己展开,不对客户做主观假设,这一点至关重要。这种方法将指导你改进“小柠檬”网站。
95:案例研究-Meta的用户研究 👥
在本节课中,我们将学习Meta公司如何进行用户研究,了解其在产品开发周期中的重要性,以及它如何影响产品方向、团队协作和最终的用户体验。
用户研究可能是开发过程中比较令人沮丧的部分,因为它可能意味着你需要改变已经成型的想法或产品。但它也是最有价值的环节之一,因为你将看到自己的产品如何真实地影响用户,并为你的工作赋予意义。
我的名字是Katie,我是Meta公司React应用团队的一名软件工程师。我和我的团队致力于为Facebook.com构建新功能。
什么是用户研究? 🔍
上一节我们提到了用户研究的价值,本节中我们来看看它的具体定义。用户研究是一种收集定量反馈的方法,旨在了解产品如何满足用户需求,以及最终用户对产品的理解程度。
用户是那些每天都会与我们产品互动的人。我们必须确保产品能满足他们的需求,而实现这一目标的最佳途径就是进行用户研究。
用户研究的方法 📊
我们通过多种技术来收集用户研究数据。以下是Meta常用的一些方法:
- A/B测试:比较产品不同版本的效果。
- 问卷调查:直接收集用户的意见和反馈。
- 用户观察:观察用户实际使用产品的过程。
- 创建用户画像:构建典型用户模型以指导设计。
用户研究的技术有很多种,在Facebook,我们有专门的用户研究员几乎每天都在进行这项工作。
用户研究的时机与重要性 ⏰
用户研究对Meta的产品质量至关重要。我们在产品开发的各个阶段都会进行用户研究:
- 构思阶段:开始思考产品创意时。
- 开发阶段:构建产品并发布最小可行产品(MVP)时。
- 发布后阶段:产品上线后,确保其持续满足用户需求。

我们应该尽早并频繁地收集用户反馈,因为我们产品的方向和外观设计往往会基于用户反馈进行调整。

参与用户研究的角色 👥
那么,哪些角色会参与到用户研究过程中呢?设计师、产品经理(PM)和用户研究员通常都会参与。
- 用户研究员:负责实际执行研究,包括向用户提问、进行问卷调查、观察用户使用产品并记录他们的感受。
- 设计师与产品经理:更多地参与对用户研究的响应。他们收集这些用户反馈,并据此决定需要对产品做出哪些改变。
用户研究的影响:一个实例 💡
用户研究对产品方向的影响之大,有时会让我感到惊讶。我记得我和我的团队曾开发一个新的群组功能。我们刚刚完成MVP版本,并将其交给一组群组管理员实际使用。
在收集了用户反馈后,我们最终对这个新功能做出了许多修改,甚至包括产品名称。那个名称对我们来说有意义,但对最终用户来说却不然。因此,我们不得不重新审视这个产品的方向。
用户研究带来的挑战与回报 ⚖️
我认为,作为一名工程师,在听到用户研究结果时面临的最大挑战之一是,看到它如何违背你作为工程师的预期。改变产品中非常重要的方面,尤其是那些你已经构建完成的功能,可能会非常困难。
因此,尽早并频繁地进行用户研究很重要,这样可以避免因为产品不符合用户预期而不得不彻底返工所带来的巨大损耗。
然而,用户研究也能带来巨大的回报。有时,当你向用户展示产品时,你会意识到一个具体的个人从你的产品中获得了多少价值,这真的让你对自己的工作有了新的认识。
用户研究是了解我们构建的功能如何影响日常使用它的真实用户的绝佳方式。听到关于某个功能如何积极影响某个群体或社区的个人轶事,真的非常酷,这为我每天的工作增添了人性化的色彩。
进行有效用户研究的建议 💎
我认为用户研究是设计过程中非常重要的一环。你应该仔细思考所提出的问题,并尽量避免让用户研究偏向于你认为产品“应该”是什么样子。
从用户那里获得开放、诚实的答案是有益的,因为这将使你能够以真正满足用户需求的方式去改变和发展产品。这样一来,你就不会构建并发布一个无人使用或无人理解的产品。


本节课中,我们一起学习了用户研究在Meta产品开发中的核心作用。我们了解了它的定义、常用方法(如A/B测试、问卷调查)、在开发周期中的关键时机,以及不同团队角色(如工程师、研究员、设计师)如何协作。通过真实案例,我们看到了用户研究如何带来挑战(如需要改变已开发的功能),也如何带来巨大回报(如看到产品真实影响用户)。最后,我们记住了进行有效研究的关键:提出无偏见的问题,并始终以满足真实用户需求为目标。
96:13_用户体验和用户界面简介模块总结 🎯
在本节课中,我们将回顾并总结“用户体验和用户界面简介”模块的核心内容。你将清晰地了解UX与UI的区别、关键概念、设计流程以及在本模块中获得的重要技能。
模块概述
恭喜你完成了“用户体验和用户界面简介”模块。在本模块中,你探索了UX和UI的基础知识,并开始学习以用户为中心的设计。现在,是时候回顾你学到的关键点、概念以及获得的技能了。


第一课:UX与UI基础介绍
你从对课程的整体介绍开始了本模块。完成第一课后,你现在能够区分UX(用户体验) 和UI(用户界面),并能识别UX和UI领域的职业选择。

第二课:深入用户体验

在介绍了UX和UI之后,你进入了第二课,重点聚焦于UX。在本课中,你学会了定义UX,并识别了UX流程中的关键阶段。
现在,你也能够讨论UX的目标,并识别可用性质量组件。
以下是五个核心的可用性质量组件:
- 易学性:用户首次接触设计时完成基本任务的容易程度。
- 效率:用户学会使用后,执行任务的速度。
- 可记忆性:用户一段时间未使用后,重新使用时恢复熟练程度的容易程度。
- 容错性:系统防止错误发生,以及帮助用户从错误中恢复的能力。
- 满意度:用户在使用设计时的愉悦程度。

仍在第二课中,你学习了UX流程的概述,识别了流程中的每个阶段。

UX设计流程通常包含以下阶段:
- 共情:理解用户的感受和需求。
- 定义:明确要解决的核心问题。
- 构思:为已定义的问题产生广泛的创意和解决方案。
- 迭代:制作原型并不断改进设计。
- 测试:与用户一起评估你的解决方案。
第三课:探索用户界面
学习了UX之后,你已准备好了解更多关于UI的知识。
在第三课中,你学会了解释UI的概念并描述其历史。你还学会了识别UI的应用,认识到成功UI设计的重要性,并解释了不同类型的设计。
这些设计类型包括:
- 交互设计:关注用户与产品互动方式的设计。
- 以人为本的设计:将人的需求、能力和行为置于设计过程中心的方法。
- 统一设计:确保产品在不同部分保持视觉和体验一致性的设计。
- 设计思维:一种用于解决复杂问题的创新性、以用户为中心的方法论。
此外,你还初步了解了Figma,这是本课程中你将探索的设计工具。


第四课:实践以用户为中心的设计


在介绍了UX、UI和Figma之后,你学习了以用户为中心的设计。在本课中,你利用客户访谈和观察的数据,更好地理解了他们在使用Little Lemon网站时遇到的问题。


在这个过程中,你探索了客户旅程图,并在UX流程的“共情”阶段回顾了用户画像。
总结与收获
现在,你对UX、UI和以用户为中心的设计有了初步的理解。你可以解释UX和UI之间的差异,并认识到在进行重新设计时,将客户置于决策前沿的重要性。这为你的UX和UI之旅开了一个好头。做得好。
97:14_评估设计 📐
在本节课中,我们将学习如何评估设计。你将了解用户体验和用户界面设计中流行的评估方法论,并探索如何应用这些方法来改进你的设计。
概述
现在你已经了解了用户在小柠檬网站上遇到的问题。接下来,我们将探讨一些备受推崇的设计师提出的原则。你可以使用这些原则来评估当前的小柠檬网站,并在创建自己的设计时应用它们。
设计就像食物一样,具有主观性。对某人来说美丽的东西,可能并不符合其他人的品味。但就像食物一样,有一些规则我们可以遵循,以确保为客户提供愉快的体验。
功能之美 💡
上一节我们提到了设计的主观性,本节中我们来看看功能的重要性。

需要考虑的另一点是功能之美。

考虑一个注册表单。当然,它可能不是最漂亮的设计,但它可以运行得非常出色。并非所有美丽的东西都实用,反之亦然,并非所有运行良好的东西都看起来漂亮。功能应始终优先于美观。
现在,试着考虑一些既易于使用又看起来很棒的东西,这说起来容易做起来难,对吧?但有一些评估方法会有所帮助。
评估方法的价值 🧭
重要的是要知道,尽管你即将了解的指导原则非常宝贵,但你并不需要以非常详细的方式应用它们。相反,将这些广泛的建议用作指南,在坏习惯发生之前发现它们,并思考能满足这些指导原则的、更具可用性的替代方案。
更重要的是,尽早注意这些点可以节省时间、金钱,并有助于你从长远来看创建更具可用性的解决方案。
因此,让我们从讨论设计评估方法开始,看看这一切从何开始,以及谁的方法影响了UX/UI世界。
主要评估方法论 📋
以下是三种核心的设计评估方法论,它们为评估和改进设计提供了坚实的框架。


迪特·拉姆斯的设计十诫
迪特·拉姆斯是一位德国工业设计师和学者,在UX/UI领域非常有影响力。他的设计具有持久的吸引力,至今仍受到重视并影响着设计师。他撰写了“好设计的十项原则”,这为所有设计师提供了一个很好的评估方法。
雅各布·尼尔森的十大可用性启发式原则
另一个重要的方法是雅各布·尼尔森的“用户界面设计的十大可用性启发式原则”。所有这些都特别与人机交互相关,也称为HCI。这种方法与拉姆斯的原则有一些重叠。
本·施奈德曼的八项黄金法则
最后一种设计方法是美国计算机科学家本·施奈德曼的“界面设计八项黄金法则”,它被命名为“施奈德曼评估法”。同样,它与其他方法论有一些重叠。
应用与实践 🛠️

上一节我们介绍了三种主要的评估方法论,本节中我们来看看如何应用它们。

请务必探索提供的评估速查表作为阅读材料,以指导你判断哪些方面可能存在问题。
确保根据这些原则评估表单流程,并尝试优先处理最严重的问题。
你现在应该能够运用好设计的原则来评估各种设计了。
总结
在本节课中,我们一起学习了流行的评估方法论,以及如何应用这些评估方法来改进你的设计。
请记住,如果你在构思阶段就注意这些方面,即在设计之前,你可以为自己节省大量时间。

做得好。
98:案例研究 - Meta的无障碍设计 🎯
在本节课中,我们将通过Meta(原Facebook)工程师Katie的分享,学习构建产品时确保无障碍访问的重要性、具体策略以及Meta的实践经验。无障碍设计旨在让所有人,包括残障人士,都能平等地使用产品。
无障碍设计的重要性
超过一亿用户在我们的应用中使用不同的字体大小进行屏幕缩放,另有约25万用户使用屏幕阅读器。因此,确保这些用户在Facebook.com上获得与其他用户同样有价值的体验至关重要。
我的名字是Katie,是Meta React应用团队的一名软件工程师。我和我的团队致力于为Facebook.com构建新功能。我希望通过这个视频,你能认识到无障碍性的重要性,并了解在构建和发布产品时可以使用的不同策略,以确保它对所有人都是可访问的。
Meta的无障碍产品目标
在Meta创建无障碍产品时,我们有以下几个目标:
- 我们希望产品能与屏幕阅读器协同工作,以便视障人士能以与他人相同的方式使用Facebook.com。
- 我们还要确保为可能无法阅读小字体的人提供可调节的字体大小。
- 我们希望确保为使用我们应用的用户提供多种键盘快捷键。
工程师的日常实践与测试

当你访问Facebook.com时,无障碍性可能不是你首先想到的事情,但它无疑是Meta工程师和团队最关心的事情之一。作为一名工程师,我尝试对我所做的每一项更改都进行无障碍测试,这也是我们核心UI组件内置的功能。
当我测试无障碍性时,我会确保:
- 能够使用键盘通过Tab键浏览我的功能。
- 键盘快捷键设置正确。
- 屏幕阅读器能够正确识别页面的所有部分。
此外,还有一些全站性的功能,比如可变的字体大小,在我构建新功能时几乎是“免费”提供的。
内置的自动化保障机制
作为一名工程师,我测试我个人的更改,但也有一些内置的解决方案来确保产品在整体上是无障碍的。
例如,当工程师添加一个新按钮时,如果它无法通过屏幕阅读器访问,按钮上实际上会出现一个红色覆盖层,这会标记给工程师,提示他们缺少一个aria-label或该按钮中其他必要的无障碍组件。
代码示例:
<!-- 缺少无障碍属性的按钮 -->
<button>点击我</button>
<!-- 带有无障碍属性的按钮 -->
<button aria-label="提交表单">点击我</button>
因此,工程师很容易发现他们是否遗漏了与无障碍性相关的内容。

发布后的专业评估
在产品构建和发布后,我们还会与无障碍专家合作,以确保我们的产品符合无障碍标准。
几年前我们重写了Facebook.com,当时我们必须认真思考:如何让这个网站对需要使用屏幕阅读器和不同字体大小的那部分用户变得可访问。

全局字体大小解决方案
我们使Facebook.com变得无障碍的一个例子是,我们实际上在整个站点设置了不同的字体大小作为默认值。要求每个产品团队和每位工程师在构建产品时都考虑不同的字体大小是很困难的。
幸运的是,我们的许多工程师使用相同的核心组件,并且我们实际上使用了一种样式转换,以便我们可以在全局范围内更改整个站点的字体大小。
核心概念: 通过样式转换(Style Transform) 或CSS变量(CSS Custom Properties) 集中管理字体大小。
公式/代码示例:
:root {
--base-font-size: 16px;
--font-scale-factor: 1.2;
}
body {
font-size: var(--base-font-size);
}
.user-setting-large {
--base-font-size: 20px;
}
现在,当用户指定他们想要更大或更小的字体时,它会自动应用到所有地方,工程师在构建产品时几乎不需要考虑这个问题。

给开发者的建议
我想说,如果你从未使用过屏幕阅读器或键盘快捷键,一定要做研究并了解它们。根据世界卫生组织的数据,大约15%的人有某种形式的残疾。确保你为这些人提供与你所有其他用户相同质量的体验,这一点非常重要。
这绝对至关重要,你希望每个人都能使用你的产品,因此这是你可以采取的一个非常重要的步骤来确保这一点。
总结


本节课中,我们一起学习了Meta在无障碍设计方面的实践。我们了解到无障碍设计关乎数亿用户的体验,其核心目标包括支持屏幕阅读器、提供可调字体大小和键盘导航。Meta通过工程师的日常测试、内置的自动化检测工具(如缺失aria-label的红色提示)、与专家合作评审以及全局样式解决方案(如样式转换)来系统化地保障产品的无障碍性。作为开发者,主动学习和测试无障碍工具是构建包容性产品的关键第一步。
99:16_表单设计 📝
在本节课中,我们将学习优秀表单设计的重要性,并通过一个为“小柠檬”网站设计的登录表单示例,来探讨如何应用最佳实践以提升表单的可用性和用户体验。
你之前已经学习过优秀的设计原则。在实际创建自己的设计时,这些原则通常需要与最佳实践相结合。这甚至包括像表单这样非常简单的元素。如果设计时没有充分考虑用户体验,表单可能会让用户感到沮丧。为了说明优秀表单设计的重要性,我们将以“小柠檬”网站的登录表单为例进行讲解。
表单设计的重要性 🤔

你可能会想,表单设计真的有那么重要吗?对某些人来说,这可能并不显而易见。但事实是,在可用性和用户目标的背景下分析交互式表单,能帮助你创建逻辑清晰、以用户为中心、且所有用户都熟悉的解决方案。理解开发者和设计师可用的简单元素的最佳实践建议,有助于你建立一种设计思维方式。

当然,简单表单和复杂交互组件的行为可能大不相同,但重要的在于你设计和组合它们时的思考方式。这些基本规则始终是相同的。最有效的方法是查阅最佳实践,并尽可能多地应用它们。


稍后,你将找到额外的资源和一份表单设计最佳实践指南,它们将帮助你更全面地理解这些实践。
实践:重新设计表单 🔧
现在,假设你已经采纳了之前访谈和观察中获得的一些评论与反馈。基于从用户那里收到的反馈,你决定用HTML重新设计你的表单进行测试。
你随后询问了你的同事恩佐——一位经验更丰富的UX/UI设计师——是否愿意对你用HTML创建的简单原型发表看法。他同意了并签署了同意书。你已经尝试改进了食品订购表单。现在,让我们探讨一下恩佐的一些评论,以及可能帮助你改进表单的最佳实践解决方案。



改进表单的具体建议 📋

以下是恩佐提出的主要问题及相应的最佳实践解决方案:


恩佐似乎不理解你表单上某些问题的所指。 他还提到输入框之间距离太近。他建议你增加用户输入字段(如文本字段、密码字段、复选框和单选按钮)之间的间距。通过更一致地间隔这些输入字段,用户可以清楚地看到每个字段是独立的,并能识别出对应的标签。
你可以通过逻辑分组使表单更易于理解。 例如,将支付信息与地址信息分开,并为每一行使用微妙的对比色进行区分。

恩佐评论说他在填写流程中感到困惑。 他无法返回上一步,并且系统状态不可见。他表示不知道自己是否接近完成表单。恩佐建议,通过实现一个清晰的完成路径和进度指示器,用户将能看到自己在流程中的位置。这意味着他们甚至可以返回去查看已输入的内容。

只有在输入了所有信息后,恩佐才被告知他犯了错误。 他表示不知道自己在表单的哪个部分犯了错,或者是什么类型的错误。恩佐建议,你的表单输入应该在填写流程中进行验证。这确保了用户当前插入的内容在输入时即得到验证。他还提醒你,所使用的语言应该清晰且具体,与用户当前所在的表单部分相关。
作为一名UX/UI设计师,你深知安全至关重要。 如今大多数网站都要求密码由小写字母、大写字母、符号和数字混合组成。恩佐指出,在设计你的表单时,你假设用户知道这一点,但没有为他们提供任何创建密码的指导。由于没有明确说明具体要求,用户并未意识到问题所在。他建议你提供清晰的说明和一个强度指示器,给用户关于其选择的即时反馈。


总结与收获 🎯
通过请恩佐对你的表单提供反馈,你获得了宝贵的见解,有助于使表单更易于访问和理解。你现在应该对表单设计以及如何利用反馈来改进“小柠檬”网站上的表单更加熟悉了。

在本节课中,我们学习了设计定义清晰表单的重要性,并通过应用最佳实践来提升表单设计和可用性。具体方法包括:使用清晰的布局来区分字段和区块,以及最终将客户的业务目标与用户目标对齐,以帮助你更接近成功的产品设计。做得好!
100:用户界面设计中的组件 🧩
在本节课中,我们将要学习用户界面设计中的核心概念——组件。你将了解什么是组件驱动设计,以及如何利用设计系统来构建一致且高效的用户界面。
什么是组件?
上一节我们介绍了课程主题,本节中我们来看看什么是组件。在UI设计中,组件是设计中任何可以被逻辑分组的、被视为独立的、并且可以复用的部分。
简单来说,组件就像可互换的积木块,你可以将它们组装和重新组装以构建用户界面。

如何构建组件?

理解了组件的基本概念后,我们来看看如何构建它们。构建组件遵循一个从简单到复杂的过程。

以下是构建组件的步骤:
- 从小处着手:一次构建一个组件。
- 组合组件:将组件组合起来,创建更复杂的组件。
- 构建界面:使用这些独立的和组合的组件来构建完整的用户界面。
让我们探索一个实际例子。你的网站可能在每个页面上都有一个页眉。因此,与其为每个页面从头开始创建页眉,不如简单地构建一个页眉组件并重复使用它。
这个页眉组件可能包含其他组件,例如:
- 一个按钮组件
- 一个搜索栏组件
- 一个导航栏组件
这些组件本身是独立的元素。它们被用在页眉中,但同样可以在网站的其他地方被复用。
以这种方式组装用户界面的优势在于,设计和开发工作可以快速复制。你可以为不同的操作创建替代组件。这也会使用户受益。如果组装得当,这些一致的组件将具有美观、易于学习和记忆的特点。
什么是设计系统?
现在,让我们探索什么是设计系统。设计系统是一套可复用的、预先制作好的设计组件和模式,可用于大规模设计产品。类似于品牌风格指南,它们包含设计组件的规则和最佳实践行为。
大型公司可以拥有非常详细的设计系统。虽然没有固定的项目清单必须包含在设计系统中,但最成功的设计系统通常具有以下特征。
以下是设计系统的核心组成部分:
- 设计指南
- 设计模式库
- UI工具包或组件库
- 流程设计指南
让我们更详细地探讨这些是什么。

设计指南
首先,也是最重要的,是设计指南。这些指南因企业而异,很可能反映了品牌的基本价值观。

设计模式库
设计模式库是已接受和广泛使用的设计模式的集合,或一个中央存储库。根据交互设计基金会的定义,模式是多个相互协作的设计元素的重复出现。请注意,这些元素可以是形状、线条和颜色。
UI工具包
UI工具包,也称为组件库或UI组件集合,由按钮、小部件等组成。借助这些资源,团队可以快速生成对用户友好的UI设计。
流程设计指南
最后,流程设计指南是已接受和广泛使用的模式的集合,例如设计模式库。这有助于设计师在执行任务时解读设计原则。
总结
本节课中,我们一起学习了用于设计用户界面的组件和库。你也了解了组合起来构成用户界面的模块化元素。最后,我们探索了设计系统,并思考了它们在产品设计中的作用。

出色的工作。
101:18_小柠檬评估 🍋
在本节课中,我们将学习如何评估一个现有的用户界面。我们将以“小柠檬”餐厅网站的在线订餐功能为例,通过代入用户角色“蒂莉”,并运用经典的设计原则和启发式评估方法,来识别界面中存在的问题和改进机会。
上一节我们介绍了用户角色和同理心地图等概念,本节中我们来看看如何将这些知识应用于实际的界面评估。
评估准备:代入用户角色

在评估“小柠檬”网站当前的设计时,你需要时刻牢记在上节课中创建的用户角色——蒂莉。尝试从她的视角出发,体验整个使用流程,并大声说出蒂莉在使用时会怎么想。你应该成为你的用户角色。在评估过程中,可以自由地使用一些工具,例如为你的角色绘制同理心地图和用户旅程图。
在本视频中,你将评估“小柠檬”网站的用户界面,并使用评估方法和界面设计准则,例如优秀设计原则、启发式评估和界面设计准则,来重新设计“小柠檬”网站的部分功能。
蒂莉的用户旅程与痛点


蒂莉是“小柠檬”餐厅的常客。她饿了,并且喜欢舒舒服服地待在家里,一边看她最喜欢的节目和电影一边吃饭,所以她决定点一些外卖。于是她在移动设备上打开了“小柠檬”网站。
她点击了“在线订购”按钮。蒂莉立刻感到困惑。内容很难阅读。她跳转到了“预订餐桌”页面。但她想点餐,而不是预订餐桌。菜单在哪里?她找不到。她只能看到“本周特价菜单”和一个“希腊沙拉”的选项。
她想知道自己是不是做错了什么。但她怎么返回呢?她没有意识到“菜单”这个词是可以点击的,也没意识到“希腊沙拉”可以点击。没有任何迹象表明这一点。有一个“前往”按钮。她怎么知道“前往”是什么意思?她想知道,难道没有其他餐点了吗?她决定点击“前往”这个词,因为它看起来可能会带她去某个地方。

蒂莉想:“哦,只有两个菜单项,一个希腊沙拉和一个布鲁斯凯塔。”她在想:“我喜欢吃什么?为什么这里有用户评价?”蒂莉不知道点击或触摸哪里才能点餐。
她观察到的两个操作令人困惑。她看到了两个按钮。她问自己:“我是点‘前往’还是‘添加’?如果我点‘前往’,会像上一个屏幕那样出现更多食物选项吗?还是如果我点‘添加’,就会把希腊沙拉或布鲁斯凯塔加到我的订单里?”
蒂莉还想知道购物篮在哪里。蒂莉觉得屏幕上似乎混合了各种信息。她还感觉到按钮标签、操作和设计不一致。她想知道为什么布鲁斯凯塔旁边没有“添加”按钮。

你的用户角色会如何看待这个设计?现在,让我们使用拉姆斯的十大优秀设计原则、尼尔森的十大可用性启发式原则以及施奈德曼的八大黄金法则来评估它。
应用设计原则进行评估
请记住,你之前已经学习过这些评估方法。


以下是蒂莉指出的问题以及它们违反的设计原则:
蒂莉指出,部分文本难以阅读。这不太美观,也不易理解或不彻底。这似乎考虑不周。她想知道为什么有一个“预订餐桌”的按钮。
这些问题违反了拉姆斯原则的第3、4和8条。

- 第3条:优秀的设计是美观的。
- 第4条:优秀的设计使产品易于理解。
- 第8条:优秀的设计是彻底到最后一个细节的。
尼尔森的启发式原则第8条也被违反了。
- 第8条:界面不应包含不相关或很少需要的信息,例如本例中的“预订餐桌”。

让我们进一步探讨蒂莉在“小柠檬”网站上提出的更多问题,以及这些问题是否违反了任何原则。
记住蒂莉的评论:“菜单”这个词看起来像是句子的一部分,没有给出任何提示表明它或“希腊沙拉”是可以点击的。她还评论说,“前往”按钮看起来不像一个按钮,也不像是希腊沙拉部分的一部分。布鲁斯凯塔的文本大部分被隐藏了,而且没有导航或购物篮。

这些问题违反了拉姆斯原则的第2、3、4和8条。
- 第2条:优秀的设计使产品有用。
- 第3条:优秀的设计是美观的。
- 第4条:优秀的设计使产品易于理解。
- 第8条:优秀的设计是彻底到最后一个细节的。
这些问题也违反了尼尔森启发式原则的第1、4、6和8条。
- 第1条:系统状态可见性:设计应始终让用户了解正在发生的事情。
- 第4条:一致性和标准:用户不必怀疑不同的词语、情况或动作是否意味着同一件事。
- 第6条:识别而非回忆:通过使元素、动作和选项可见,最大限度地减少用户的记忆负担。
- 第8条:美观和简约的设计。
不仅拉姆斯和尼尔森的原则被违反了,施奈德曼的界面黄金法则第1、7和8条也被违反了。
让我们更详细地探讨这些:
- 第1条:力求一致性。
- 第7条:设计应支持内部控制点,允许用户成为动作的发起者。
- 第8条:设计应减少用户的短期记忆负荷。
从评估到重新设计
在识别出这些违反最佳实践的问题后,考虑重新设计这两个屏幕,也许可以问问自己:蒂莉想要什么?
现在你已经评估了“小柠檬”网站,知道了哪些问题违反了重要的设计原则。这应该能帮助你创建一个更好的设计,让顾客更加满意。
总结

本节课中,我们一起学习了如何代入用户角色“蒂莉”来评估“小柠檬”网站,并使用了优秀设计原则、启发式评估和界面设计准则等评估方法来识别用户界面缺陷。干得漂亮!
102:19_导航最佳实践 🧭
在本节课中,我们将学习如何以用户体验为核心,考虑网站的内容与结构。我们将探讨信息架构与内容策略的最佳实践,并学习如何设计清晰、易用的网站导航。
现在,你已经识别出Little Lemon网站存在的导航问题。
接下来,让我们探讨在重新设计中可以运用的最佳实践。
你对设计工作开始感到自信,每次迭代和测试都能获得宝贵的反馈。尽管过程有些重复,但这正帮助Adrian实现其商业和用户目标。
你也开始感觉到,每次迭代都在进步。Tilly的一些反馈,例如“我不知道自己在哪里”,让你意识到这与内容及其组织方式有关。你非常希望解决这个问题。让我们开始吧。
在本视频中,你将学习如何在考虑可用性的前提下,最佳地规划你的内容和结构。你还将学习关于信息架构和内容策略的导航最佳实践。
你已经了解到,在UX设计领域,必须与用户保持开放的对话。有几种方法可以实现这一点。
以下是两种主要方法,可以让用户感到舒适并始终知晓自己的位置。
第一种是内容策略。第二种是信息架构。
值得注意的是,这些方法论内涵丰富且深刻。在本课程中,我们将介绍其入门知识并应用一些关键原则。😊

但你应该通过补充阅读资源来探索更多信息,以加深对这些概念的理解。

上一节我们提到了内容策略,本节中我们来详细看看它的构成。
内容策略涉及以有意义的方式呈现网站内容,以推广产品。它有助于协调客户的商业目标和UX目标,也有助于围绕用户角色和使用场景来组织内容。
一个通用的经验法则是,从以下几个标题来思考如何构建你的内容:
以下是内容策略的四个关键方面:
- 优先级:如何确定内容及其与用户的相关性。
- 组织:对内容进行分组、标记和关联的框架,以便用户找到对他们重要的信息。
- 呈现:如何将内容片段组合成用户所看到的样子。
- 规范:每个内容片段的详细需求。
了解了内容策略后,我们来看看如何用它来指导导航设计。
信息架构可用于找出设计良好导航所需了解的内容。对于大型网站,信息可能需要以特定方式构建和索引以支持良好的导航。但在本例中,一个简单的卡片分类任务就能帮助你。
在卡片分类练习中,你将想要包含在网站上的所有独立元素写在便利贴上。然后,你请潜在用户将它们按他们认为合理的逻辑进行分组,并汇总结果。

这可以在桌子或墙上完成,将卡片分组到“菜单”、“关于”等部分中。
基于卡片分类的结果,我们可以构建出清晰的网站导航结构。

网站导航可以组织成五个部分:主页、关于、菜单、预订和在线点餐。


以下是每个导航部分的说明:


- 主页:网站的简介摘要。
- 关于:包含关于他们的产品、食材、兄弟俩祖母的故事以及社交媒体链接的信息。
- 菜单:提供最新的、可打印的传统餐厅菜单明细。
- 预订:用于餐桌预订。
- 在线点餐:用于外卖点餐。
这个导航栏被称为主导航,将位于所有页面的顶部。元素(如导航栏)的层次结构和顺序有助于在产品中创造意义和位置感。
导航栏元素对位置感有很强的影响,它将出现在所有页面上以强化网站结构。

现在,让我们将最佳实践应用到Little Lemon网站的具体设计中。
你的导航位于Little Lemon网站页面的顶部,这是一个熟悉的模式。

导航有足够的呼吸空间,没有被其他信息或图形所干扰。


颜色应形成对比,以便链接突出显示。

你的页面导航现在看起来可以用于导航到网站的其他页面,并且链接没有隐藏,它们看起来就是可点击的链接。
如果用户位于某个特定页面,该链接将通过加粗字体、不同颜色或下划线来显示为活动状态。

Logo将作为返回主页的链接。页面底部的页脚也将包含指向所有独立页面和社交媒体频道的链接。



在移动版本上,由于空间有限,你决定使用汉堡菜单。当它被点击或轻触时,会打开一个侧边菜单,显示指向网站上其他页面的链接。


通过你对Little Lemon网站的全面评估和重新设计,你正在帮助Adrian实现他的商业和用户目标。
在本视频中,我们介绍了信息架构、内容策略和网站导航及其在产品设计中的作用。做得好。

本节课总结

本节课中,我们一起学习了导航设计的最佳实践。我们首先探讨了如何通过内容策略来有意义地组织内容,然后介绍了通过信息架构(如卡片分类)来理解用户心智模型并构建导航结构。最后,我们将这些原则应用到具体设计中,包括创建清晰的主导航、确保视觉对比和活动状态指示,并为移动端适配汉堡菜单,从而为用户提供始终清晰的位置感和流畅的浏览体验。
103:交互设计评估模块总结 🎯
在本节课中,我们将回顾并总结交互设计评估模块的核心内容与关键技能。你将清晰地了解在本模块中学到的评估方法、设计原则以及如何将它们应用于实际项目中。
恭喜你,你已经完成了本课程的第二个模块。
让我们花点时间回顾一下你获得的一些关键技能。
首先,你学习了如何评估UX/UI中的交互设计,现在应该能够列举并解释UX和UI设计中的评估方法,包括Rams原则、Schneiderman的八项黄金法则以及Nielsen的十大可用性原则。




上一节我们介绍了交互设计的评估方法,本节中我们来看看设计中的最佳实践原则。
你学习了如何识别设计中的最佳实践原则。在完成该部分内容后,你现在能够做到以下几点:
以下是你在表单设计和设计系统方面获得的能力:
- 解释良好表单设计的重要性。
- 实施最佳实践以改进表单设计。
- 描述组件驱动产品和设计系统。
- 解释组件驱动产品和设计系统如何吸引并留住用户。






掌握了设计原则后,我们接下来将其应用于具体案例。
你接着学习了如何评估“小柠檬”网站。完成该部分后,你现在能够做到以下几点:
以下是你在网站评估和优化方面的具体技能:
- 评估“小柠檬”网站。
- 使用评估方法和界面指南来重新设计“小柠檬”网站。
- 在考虑可用性的前提下,评估你的内容与结构。
- 列举并解释关于信息架构和内容策略的导航最佳实践。








至此,你现在应该能够做到以下几点:
- 评估UX/UI设计。
- 应用UX/UI最佳实践原则。
- 描述组件驱动产品和设计系统。
- 基于UX/UI设计最佳实践原则评估一个网站。
做得很棒。
本节课中我们一起学习了交互设计评估模块的全部要点。我们回顾了关键的UX/UI评估方法、设计最佳实践原则,以及如何将这些知识应用于评估和优化一个真实网站。你现在已经具备了系统评估设计并应用专业原则来提升用户体验的能力。
104:框架、层和基本形状 🎨
在本节课中,我们将学习Figma中的三个核心概念:框架、图层和基本形状。你将了解如何使用图层面板、如何拖拽和组织图层,以及如何构建和编辑形状。我们还将探讨如何复制、缩放、编组和对齐元素。
框架、图层与编组的概念
上一节我们介绍了课程目标,本节中我们来看看Figma中的几个基本概念:框架、图层和编组。
首先,让我们从框架开始。在其他设计工具中,框架可能被称为“画板”。你可以将其视为承载设计的容器。
接下来,定义什么是图层。图层是框架内的一个对象。当你向框架添加一个对象时,就会创建一个新图层。

第三个要介绍的概念是编组。在编组中,多个对象可以被组合在一起。一旦组合,就会创建一个单一的顶层,其中包含所有对象。
框架和编组看起来非常相似。它们都通过嵌套图层或将图层组合在一起来帮助组织文件。然而,框架提供了更多的功能,例如独立的尺寸调整。你将在整个课程中更详细地了解这一点。
在实践中探索框架

现在,让我们在Figma中实践这些概念。框架是元素的容器。可以将其想象为应用程序中的一个屏幕。例如,Sc1是你看到的第一个屏幕,Sc2是第二个。如果你添加另一个框架,那将是第三个,依此类推。
以下是创建框架的步骤:
- 点击顶部工具栏上的框架图标,然后点击并拖出一个框架。
- 另一种创建框架的方法是选择框架图标,然后转到右侧面板,Figma在那里提供了许多框架预设。

在这个例子中,我将使用一个桌面框架。
要导航你的框架或画布,请前往屏幕右上角的缩放菜单。它会告诉你当前的缩放级别,并提供一些与缩放相关的有用快捷键提示,例如 Command(或 Ctrl) + + 进行放大,或 Command(或 Ctrl) + - 进行缩小。
要在屏幕上平移,你可以选择工具栏上的手形图标,或者使用键盘上的 Shift 键。
绘制与编辑基本形状
现在,让我们绘制一些形状。我选择框架,然后转到工具栏中的基本形状工具菜单。我点击圆形图标旁边的箭头以打开形状工具,并选择一个矩形。
我点击并拖出一个矩形。如果我想要一个正方形,我选择矩形图标并在键盘上按住 Shift 键来创建一个正方形。创建圆形的过程相同。我选择椭圆工具并按住 Shift 键。
要复制矩形,我选中它并按键盘上的 Ctrl(或 Command) + D。矩形已被复制,可以放置在任何我想要的位置。
让我们看看这些形状的属性。我选择正方形,在右侧可以看到它的X和Y坐标、宽度、高度、旋转角度和圆角半径。让我们将半径改为 100。
你可能已经注意到,默认情况下每个形状都有灰色填充且没有轮廓。要更改这一点,我转到填充部分,点击小矩形打开调色板。使用圆形图标,我点击选择我喜欢的颜色。如果我想要不同的色调,可以拖动这个滑块。我还可以更改透明度或输入颜色参考编号。
在这里,我也可以应用轮廓(称为描边),并选择其位置:内部、外部或居中。在本例中,我选择外部选项。在右侧,有高级描边设置,可以更改其线条类型。我还可以添加效果。我点击加号图标并添加一个投影。
使用图层面板
在屏幕左侧的图层面板中,我可以看到我的形状按照绘制的顺序显示为图层。我将圆形图层移到正方形上方,但后来决定让正方形位于圆形之上。我只需要将正方形图层拖放到圆形图层之上即可。
现在,正确保持图层名称很重要,因此我将“rectangle2”重命名为“square”,将“ellipse1”重命名为“circle”。
图层面板内还有其他功能。我可以打开或关闭图层的可见性,也可以锁定图层,锁定意味着它无法被移动。
编组元素
我们有一个圆角正方形和一个圆形,它们是两个独立的元素,可以单独操作。但如果我想让它们作为一个对象一起移动和操作,我选中它们,然后右键点击并选择“编组选择”,或者使用键盘快捷键 Ctrl(或 Command) + G。
这允许这些元素作为一个整体移动。如果你想修改其中一个形状,只需在形状内部双击即可。
总结与练习
现在你已经探索了框架、图层和基本形状的基础知识,何不自己动手尝试,创建你自己的元素呢?

在本视频中,你学习了Figma框架、图层和形状的基础知识。我鼓励你自己练习使用这些基本工具,并注意图层面板中编组和框架之间的区别。
105:22_类型和文本
概述
在本节课中,我们将学习在Figma界面设计中,如何使用文本和字体工具。字体选择、颜色、大小、间距和宽度都会影响文本的可读性和产品吸引力。我们将从创建文本元素开始,逐步探索文本的各项属性设置。
创建文本元素
在Figma中,有两种主要方式可以创建文本元素。
以下是两种创建文本的方法:
- 点击顶部工具栏中的 T 图标,或使用键盘快捷键 T,然后在画布上单击并开始输入。这将创建一个具有自动调整宽度功能的文本框,其宽度会随文本内容水平扩展。
- 在画布上点击并拖拽以绘制一个具有特定尺寸的固定文本框。由于文本框尺寸已固定,其自动调整功能将被锁定。当文本内容到达固定文本框的边缘时,较长的文本会自动换行。
要编辑现有的文本元素,只需在文本框内双击即可。
调整文本框尺寸
上一节我们介绍了如何创建文本框,本节中我们来看看如何调整其尺寸模式。
为了让文本框能够自动调整尺寸,我可以在右侧边栏的设计面板中,将“自动调整”的值更改为“高度”。现在,当我添加新文本时,文本框会垂直扩大,但不会水平扩大。使用此设置时,我需要确保文本框的高度是基于字体样式中指定的行高来调整的。如果文本框对于其内容来说太小,它会自动调整高度以适应。
在Figma中创建文本时,可以应用三种尺寸调整模式:
- 水平扩展:单击一次创建新文本框时的默认模式。
- 固定尺寸:点击并拖拽创建新文本框时的默认模式。
- 垂直扩展:如上所述,在属性面板中手动设置。
自定义字体与样式
掌握了文本框的创建与调整后,接下来我们学习如何自定义文本的外观。
在右侧边栏的设计面板中,我可以使用下拉菜单选择新字体。我也可以在字段中输入内容来搜索特定字体,例如搜索“Oswald”字体。
为了进一步自定义文本,我可以从下拉菜单中选择样式,或使用快捷键:
- Windows系统使用 Ctrl + B,Mac系统使用 Cmd + B 来加粗文本。
- Ctrl/Cmd + I 用于斜体。
- Ctrl/Cmd + U 用于下划线。
我可以直接输入或选中字段后按上下箭头键来更改字体大小。
设置文本间距与对齐
除了字体本身,文本的间距和对齐方式也至关重要。
我可以更改每行文本之间的间距,默认值为“自动”。让我们将其增加到43。此属性右侧是字符间距选项,它调整特定字符组合之间的间距或字距。默认情况下,Figma使用百分比值。但是,如果我输入像素值,单位将变为像素。例如,让我们将间距更改为4像素。
现在让我们探索文本对齐方式。水平对齐定义了文本在其边界框内如何分布:左对齐、右对齐和居中对齐。另一方面,垂直对齐决定了文本在垂直方向上的分布:顶部、底部和中部。
探索高级文本选项
基本的字体和间距设置完成后,让我们深入了解更高级的文本格式化选项。
当我点击文本选项部分底部的三个点时,会弹出三个额外的选项卡。
在“基础”选项卡中,我可以:
- 选择文本水平调整大小的方式。
- 调整文本的水平对齐方式。
- 为文本添加装饰,如删除线和下划线。
- 更改文本段落之间的间距。
- 通过段落缩进来偏移文本的首行。
- 创建编号或项目符号列表。
- 更改文本的大小写。
在“详情”选项卡中,我可以应用诸如样式、位置(如上标、下标和分数)等设置。我还可以访问任何开放式字体特性,如字母形式、字符变体和水平间距。
最后,在“变量”选项卡中,我可以调整字体的可变轴设置。
应用颜色、描边与效果
文本的属性不仅限于文本面板内的设置,还有其他方式可以修饰文本。
例如,更改文本的颜色、添加描边或添加效果。让我们通过更改其填充色使这段文本变为绿色。现在,让我们为这个文本图层添加一个1像素的绿色描边。然后通过添加效果,为这个文本图层添加一个投影。

好的,我已经为文本应用了几个效果。

总结
在本节课中,我们一起学习了Figma中可用的一些文本和字体工具,包括创建和调整文本框、自定义字体样式、设置间距与对齐、使用高级文本选项,以及为文本添加颜色、描边和效果。我鼓励你花些时间练习使用这些工具。😊


106:网格与约束 📐
在本节课中,我们将学习如何为网页或应用创建响应式网格系统,并了解如何利用约束来确保元素在不同屏幕尺寸下都能正确布局。响应式设计是现代网页开发的核心,它能让你的设计自动适应从手机到桌面电脑的各种设备。
响应式设计简介
每个人都希望自己的网站拥有移动版本。响应式设计的目标是一套设计适配所有屏幕分辨率。例如,在手机上,用户看到的内容以单列视图呈现。而在平板电脑上,相同的内容可能以两列显示。这就是响应式设计发挥作用的地方。

响应式设计是一种利用弹性布局的网页创建方法,它消除了为每种设备单独设计布局的需要。

网格系统基础
网页或应用由方块和矩形构成,这些元素被包含在一个网格系统中。网格系统是一系列不可见的线条和列组成的结构,它们组织页面上的内容,创建对齐和秩序,构成了用户界面的基本框架。
接下来,我们看看如何在Figma中,以响应式网页设计为理念进行设计。
创建响应式网格
让我们从创建响应式网格开始。
首先,我选择顶部工具栏上的框架图标,或按键盘上的 F 键,这会调出框架面板。然后,我选择桌面框架尺寸:1440 x 1024。
在右侧边栏,有一个名为“布局网格”的区域。其右侧有一个加号图标。点击它,默认会显示一个简单的10像素 x 10像素的网格。

如果点击下方的九个点,会弹出一个窗口,提供调整网格大小和颜色的选项。这些网格是静态且像素固定的,这意味着如果调整框架大小,网格保持不变。
有一个下拉菜单提供三种不同类型的网格。我将从列网格开始。
列网格
列网格有助于水平排列内容。选中它后,框架内的网格会变为默认的5列网格,并带有间距(即列之间的空隙)。
最常见的列数配置是:桌面12列、平板8列、手机4列。常见的间距大小为20像素。因此,让我们将网格更改为12列网格。
我将类型保持为“拉伸”,这意味着当我调整框架大小时,列的宽度会自动增长或收缩。
为了使网格具有响应性,我将边距改为70。这是内容与屏幕左右边缘之间的空间。同时,我将间距保持在20。
行网格
接下来,让我们在垂直方向上也添加一些秩序。这里我引入8点网格,因为大多数流行的屏幕尺寸都能被8整除,它是保持间距一致性的基础。
因此,我选择框架,转到右侧边栏,在布局网格部分再次点击加号,以叠加另一个布局。
在弹出的对话框中,我将网格布局更改为“行”,并将行数更改为1000。

然后,我将类型改为“顶部”,高度改为8。最后,我将间距设置为0。

现在,我们拥有了一个垂直和水平交织的网格系统。
添加内容块
现在你会注意到,所有内容都会吸附到网格上。这使得排列元素变得更简单,并使所有尺寸保持一致。所有尺寸都能被8整除。
如果我想在没有网格系统的情况下查看框架,可以点击屏幕右上角的缩放百分比,这将打开“视图设置”菜单。在那里,我可以找到打开或关闭网格的选项。
或者,我也可以使用键盘快捷键来切换布局网格的显示:在Mac上是 Control + G,在Windows上是 Control + Shift + 4。使用相同的命令可以再次显示网格。
使用约束
约束是使用网格的另一个好处。我在桌面版本中选择内容框,并为它们添加约束。
约束用于将布局中的内容固定到网格的左侧、右侧、顶部、底部或中心。
在这里,我应用了左约束和右约束。现在,如果我们改变框架的大小,内容会做出响应并相应地改变尺寸。
总结
本节课中,我们一起学习了如何创建响应式网格系统,并掌握了如何约束网格内的元素。我们了解了列网格和行网格的设置,以及如何使用8点网格保持设计的一致性。最后,我们探讨了约束的功能,它确保了当屏幕尺寸变化时,界面元素能够智能地调整自身位置和大小。掌握这些基础技能,是构建适配多设备的现代化网页界面的关键。
107:24_操作元素 ✨
在本节课中,我们将学习如何在Figma中通过组合、对齐和缩放基本形状来创建新的设计元素。掌握这些操作是进行精确界面设计的基础。
组合元素 🧩
你已经知道Figma提供了一些基本形状,但有时你会需要一些现成形状库中没有的图形。因此,了解如何通过组合和操作现有形状来创建新形状非常重要。
以下是组合元素的步骤:
- 在画布上选择你想要组合的多个形状。
- 前往顶部工具栏,点击两个正方形叠加的图标。
- 在下拉菜单中,你会看到几个选项:
- 联集:将选中的形状合并为一个。
- 减去顶层:从底层的形状中移除上层的形状。
- 交集:只保留所有选中形状重叠的部分。
- 差集:排除所有形状相交的区域,保留不相交的部分。
对齐与分布对象 📐
设计师都知道,让产品看起来美观很重要,而实现这一点的方法之一就是对对象进行恰当的对齐。
要对齐对象,请先选中所有目标对象,然后转到右侧边栏的“设计”面板。对齐工具位于面板顶部。
该面板提供了多种选项。前六个选项用于让元素彼此对齐,它们分别是:左对齐、水平居中对齐、右对齐、顶对齐、垂直居中对齐、底对齐。
最后两个选项用于分布元素。你可以选中多个对象,然后选择“垂直分布”或“水平分布”,使它们之间的间距相等。
缩放对象 🔍
此外,确保对象的尺寸符合设计意图也很重要。因此,让我们来学习如何使用常规缩放来实现这一点。
缩放对象意味着调整它的大小。要缩放一个对象,只需选中它并拖动其边角即可。
现在,让我选中多个对象,看看会发生什么。当我调整一个组的大小时,其子元素会随之缩放。然而,效果、描边和文本大小不会自动缩放。
请注意,元素中的文本不会缩放,效果和描边也不会。如果我希望这些属性也一同缩放,可以使用屏幕左上角的“缩放工具”。
选中组,点击缩放工具,然后使用缩放控制点进行拖动。缩放工具有助于确保框架内元素的一致性。


在本节课中,我们一起学习了如何组合元素以创建新图形、如何精确地对齐它们以及如何缩放对象。这些是构建复杂且视觉和谐的UI设计所必需的核心技能。
108:25_处理图像
在本节课中,我们将学习如何在Figma中插入图像,以及如何使用蒙版功能来显示图像的特定部分并隐藏其余部分。掌握这些技能对于日常设计工作至关重要。
插入图像的几种方法
有多种方法可以将图像插入到Figma设计中。以下是三种主要方式。
方法一:使用“放置图像”功能
首先,我们来介绍如何使用“放置图像”功能。这是最直接的方法之一。
- 点击Figma界面左上角的菜单图标。
- 在下拉菜单中找到并选择“放置图像”。
- 或者,您可以使用键盘快捷键:在Mac上是
Command + Shift + K,在Windows上是Ctrl + Shift + K。 - 从您的设备中选择一张图片上传。
- 鼠标指针会变成一个十字准星,在画布上点击并拖动以放置图像。
- 放置后,您可以在右侧设计面板中调整图像尺寸。例如,可以将其设置为宽112像素、高112像素,以匹配目标框的大小。
- 最后,将图像与目标框对齐。
方法二:为对象填充图像
第二种方法是为一个已有的形状或对象填充图像。这提供了更多控制图像显示方式的选项。
- 在画布上选择一个对象(例如一个矩形或圆形)。
- 在右侧设计面板中找到“填充”部分。
- 点击默认的灰色纯色填充按钮。
- 在弹出的下拉菜单中,选择“图像”选项。
- 此时,您的形状会显示为棋盘格图案,并提示您选择要上传的图像。
- 上传图像后,会出现一系列用于调整图像的选项,如曝光度和对比度。
- 在下拉菜单中,您还可以更改图像的显示模式:
- 填充:默认选项,图像会拉伸以完全填满形状,可能导致图像变形。
- 适应:图像会完整显示在形状内,但可能在图像周围留下空白区域。
- 裁剪:允许您将图像裁剪到所需的尺寸。
- 平铺:此选项允许您创建重复的图像图案。
方法三:拖放图像
最后一种添加图像的方法是直接拖放。这是最快捷的方式。
- 直接从您的电脑文件夹中选中图像文件。
- 将其拖拽到Figma的画布或指定帧中即可。
使用蒙版功能
除了插入图像,另一个重要的效果是蒙版。蒙版允许您隐藏图像的一部分,只显示特定形状内的区域。当您只需要原始图像的一部分,并且希望它呈现为不同于原图形状(如圆形)时,这非常有用。
上一节我们介绍了插入图像的方法,本节中我们来看看如何应用蒙版。要创建蒙版,您需要两样东西:想要被蒙版的图像,以及作为蒙版使用的形状。
以下是创建蒙版的步骤:
- 首先,在画布上绘制一个形状,例如一个圆形。
- 确保您想要应用的图像图层位于这个形状图层的上方。您可以在左侧的“图层”面板中调整图层的上下顺序。
- 同时选中图像图层和形状图层。
- 点击屏幕顶部工具栏中的“使用蒙版”按钮(图标通常为两个重叠的方形,或带有剪切符号)。
- 现在,图像就会被限制在您绘制的形状内部显示。您可以调整图像的位置和大小,使其在圆形内完美呈现。
- 在“图层”面板中,会生成一个“蒙版组”,您可以对其进行重命名以便管理。
- 此方法适用于任何类型的形状,不仅仅是圆形。

总结
本节课中,我们一起学习了在Figma中处理图像的核心技能。我们掌握了三种插入图像的方法:使用“放置图像”功能、为对象填充图像以及直接拖放。此外,我们还深入了解了如何使用蒙版功能来裁剪和塑造图像,使其符合特定的设计需求。



鼓励您尝试练习这些新学到的技能,它们将帮助您创建出更精美、更专业的设计作品。
109:26_线框图 📐
在本节课中,我们将学习线框图的概念,并使用Figma工具为“小柠檬”餐厅的在线订餐功能设计线框图。我们将从分析现有问题开始,逐步构建移动端优先的线框图,重点关注布局和功能,而非视觉细节。
小柠檬餐厅在其网站的在线订餐功能上遇到了困难。
餐厅的菜单过长,导致需要无止境地滚动。
没有选项可以指定菜品的数量。
用户必须反复选择同一菜品并添加到购物篮。这该如何改进?
你已经学习了如何在Figma中创建网格、绘制形状和操作对象。在本视频中,你将学习描述线框图的概念,并使用Figma设计线框图。
线框图的目的是在考虑品牌、颜色和图像等元素之前,为设计中的每个屏幕创建一个基本结构。它提供了一种快速沟通想法的方式,这些想法可以在后续进行细化。你的重点是用户体验以及他们完成任务所需的内容。
我将使用Figma绘制线框图,以便与其他团队成员共享,让他们有机会在同一文档中发表评论。在这个练习中,我将创建三个线框图。
首先,让我们列出Adrian的需求。它们将成为内容区块,为我提供一个很好的骨架,展示线框图中将出现的内容。它将展示小柠檬品牌、关于我们、菜单分类、价格和一个自定义订单区域。它还将展示菜品的描述和照片、外卖或自取选项、每份订单的菜品数量以及一个“添加到订单”按钮。最后,它将显示登录和支付区域,当然,还有一个导航栏。😊
现在,我对需要放入线框图的内容有了清晰的认识。我将首先从移动版本开始。在用户体验设计中,这是一种常见做法,称为移动优先设计。这是因为如今大多数网络用户通过移动设备访问网站。因此,我想确保我的设计在移动设备上完美运行。此外,在小屏幕上解决设计问题然后将其适配到大屏幕更容易。
在第一个框架上,我需要内容区块包含导航栏、关于小柠檬、菜单分类和菜单菜品。

因此,我选择一个移动设备框架,并向该框架添加一个四列布局网格。然后,我使用矩形为每个区块构建内容区块。接着,我将这些区块移出框架。然后,我将矩形变窄。


但我没有改变文本。这为我提供了线框图中所有内容的粗略轮廓,可以作为我在框架内设计时的参考。

现在让我们专注于导航栏。我需要为用户提供一种导航到主页的方式,因此我添加了一个可以带我返回主页的徽标。我还包括了一个购物篮,因为这是一个在线配送服务。我选择使用汉堡菜单。点击时,它会打开一个弹出覆盖层,使用户可以轻松导航到网站的其他页面。对于徽标,我绘制了一个矩形并在其中画了对角线。这是表示图像占位符的常规方式。当点击购物篮时,它会打开一个弹出覆盖层,允许用户跟踪和修改他们打算购买的商品,并提供一个结账选项。

在“关于小柠檬”内容区块中,我想包含小柠檬的名称、所在城市、提供何种食物的简要描述、一张照片。Adrian希望用户可以选择在线预订餐桌。因此,我将其作为按钮放在这个内容区块中。点击后,用户将被带到另一个页面。
现在让我们处理菜单分类。我知道它们尚未确定。但是,我将它们表示为按钮,命名为cat1、cat2等。


现在,我想添加一些菜单上的菜品。我将它们添加在底部,因为这是在线页面的第一个屏幕,Adrian希望突出本周特色菜。我需要一个标题、菜品名称、描述、价格和一张图片。这个部分可以垂直滚动以查看更多项目。



我需要注明,当点击一个菜品时,用户会被带到另一个屏幕。我为在线订餐部分的所有屏幕重复这个过程。



在线框图中,不包含颜色、字体、图像,它只是一个蓝图。这里重要的是布局和功能。

在本视频中,你探索了线框图的概念以及如何使用Figma设计线框图。鼓励你进行练习,或许可以为小柠檬网站的桌面版本绘制线框图。
本节课中我们一起学习了线框图的核心价值、移动优先的设计策略,并使用Figma工具实践了从需求分析到基础布局构建的完整流程。记住,线框图是专注于结构和功能的蓝图,是高效沟通和迭代设计想法的关键工具。
110:可用性测试 🧪
在本节课中,我们将要学习可用性测试。我们将了解其定义、核心参与者以及不同类型的测试方法。可用性测试是确保产品设计有效、高效且令人愉悦的关键步骤。
什么是可用性测试?
上一节我们探讨了线框图设计,本节中我们来看看如何测试设计的可用性。可用性测试是指让真实用户使用你设计的网站、应用程序或其他产品,同时观察他们的行为和反应的过程。这个过程对于确保为用户创造有效的、高效的和愉悦的体验至关重要。
研究人员会观察用户执行特定任务,以发现他们在何处遇到困难或感到困惑。如果许多用户遇到相同的问题,就可以提出建议来修复这些可用性问题。这为产品改进提供了机会。
可用性测试的核心参与者 🎭
可用性测试有多种类型,但在大多数测试中,引导者、参与者和给定的任务是核心参与者。
以下是这些参与者的详细说明:
- 引导者:引导者负责向参与者布置任务。他们观察参与者的行为,并在参与者完成任务时听取反馈。引导者还可能提出后续问题,以从参与者那里获取更多信息。
- 参与者:参与者是你设计的产品或类似产品的用户。
- 任务:任务是基于参与者在日常生活中可能执行的实践操作来设定的。根据研究目标和测试类型,任务描述可能非常详细,也可能比较模糊。
任务设计的关键点
在进行可用性测试时,任务措辞至关重要。任务描述中的微小不准确都可能导致参与者误解他们需要完成的内容,或影响他们执行任务的方式。
引导者可以向参与者口头宣读任务指令,也可以将任务写在任务单上交给参与者。参与者通常被要求在完成任务时“出声思考”,这能让引导者跟踪他们的进度,了解用户正在完成哪个任务,并确认参与者是否正确理解了指令。
可用性测试的类型 🔬
现在,我们来探讨不同类型的可用性测试。
首先,定性可用性测试旨在收集关于用户如何与产品互动的见解、结果和叙述。定性测试是发现用户体验问题的最有效方法。与定量测试相比,这种类型的测试更为普遍。
另一方面,定量可用性测试的目标是收集能衡量用户体验的指标。任务成功率和任务完成时间是定量测试中经常收集的两个指标。定量可用性测试是收集基准数据的最有效方法。
根据研究类型的不同,可用性测试所需的人数也不同。尼尔森诺曼集团建议,针对单一用户群体的典型定性可用性研究,使用五名参与者就足以发现产品的大部分问题。
线上与线下测试 💻
现在,我们来介绍其他类型的可用性测试。可用性测试可以在线上进行。线上测试很受欢迎,因为线下研究通常需要更多的时间和金钱。
线上测试可以分为有引导的和无引导的。
线上有引导的可用性测试与线下测试的运作方式非常相似。在线下环境中,引导者与参与者交谈并布置任务。而在线上,引导者和参与者之间存在物理距离。有引导的测试通常可以使用屏幕共享应用程序进行。
相比之下,远程无引导的可用性测试没有相同的参与者与引导者互动。引导者使用在线远程测试技术来发布任务。参与者独自工作,按照自己的节奏完成任务。当参与者完成测试后,会话录像和任务成功率等指标会发送给研究人员。

总结 📝

本节课中我们一起学习了可用性测试。我们定义了可用性测试,识别了其中涉及的核心参与者(引导者、参与者和任务),并描述了不同类型的测试方法,包括定性测试、定量测试以及线上有引导与无引导测试。理解这些概念对于评估和改进产品设计至关重要。
111:28_设计基础应用模块总结 🎨
在本节课中,我们将一起回顾“应用设计基础”模块的核心内容。这个模块主要围绕Figma工具基础和迭代设计流程展开,旨在帮助你掌握UI/UX设计的基本技能。
上一节我们完成了模块的学习,现在我们来总结一下关键的知识点和获得的技能。


你从探索Figma的基础知识开始。完成第一课的学习后,你现在能够:

以下是你在Figma基础部分掌握的核心技能:
- 使用画框(Frames) 和图层(Layers)。
- 操作基本形状。
- 描述分层设计(Layered Design) 的原则。
- 使用文本工具(T) 添加和编辑文字。
- 描述响应式设计(Responsive Design) 的概念,并运用网格(Grids) 和约束(Constraints)。
- 操作元素和图像,并描述基本的设计原则。

在掌握了一些Figma基础之后,你进入了第二课,重点学习了迭代设计。
以下是迭代设计部分的核心内容:
- 如何根据设计创建线框图(Wireframes)。
- 识别快速原型设计(Rapid Prototyping)的工具和方法。
- 如何进行可用性测试(Usability Testing)。


现在,你已经对设计基础有了初步的理解。你学习了Figma,并掌握了如何使用它来创建线框图、应用设计原则和最佳实践。这是你在UI/UX设计旅程中迈出的又一个重要步骤。
本节课中,我们一起学习了Figma的核心操作、响应式设计概念以及迭代设计流程,包括线框图绘制、原型制作和可用性测试。这些技能是构建有效用户界面的基石。
112:29_使其美观 🎨
在本节课中,我们将学习如何将线框图转化为美观的设计。我们将探讨设计中的核心元素,如图像、颜色和形状,并了解它们如何共同作用以提升最终产品的视觉吸引力和用户体验。
概述
你已经设计好了线框图,现在需要让它们变得美观。为此,你必须理解经过深思熟虑的设计元素如何提升最终产品。在接下来的内容中,我们将识别图像、颜色和形状的用途及其在设计中的角色。请将设计元素视为食谱中不可或缺的部分,它们共同作用,以视觉方式构建数字界面。这些基本元素至关重要。
视觉信息的每个组成部分都至关重要,它们的组合方式影响着设计被解读的方式。根据你的目标,你可以单独使用其中某个方面,也可以混合使用它们。
主要的设计元素包括:线条、文本、颜色、形状、图形元素(如图标和图像)以及空间。


接下来,让我们花些时间更详细地探讨每个组件的功能和应用。

线条
设计中最基本的元素是线条。它们有各种颜色、大小和形状。

方向线可以是可见的,也可以是不可见的,它们有助于将视线引导到特定区域。线条的粗细可以传达额外信息:粗而宽的线条可以吸引眼球,细线条则相反。根据赋予它们的含义,颜色可以传达各种信息。你可以使用颜色和线条来强调设计布局中的特定方面。
文本与排版
现在让我们探讨文本。网页排版与印刷排版类似,但它还需要考虑额外的因素,以确保在所有屏幕尺寸上都能轻松阅读。
为了使阅读体验愉悦,必须妥善平衡一些排版元素。以下是这些元素:

- 字体:字体的风格和家族。
- 字号:文字的大小。
- 颜色:文字的颜色。
- 行高:行与行之间的垂直间距。
- 字形:字母的具体形状。
颜色

下一个元素是颜色。

你知道吗?所有元素都可以被赋予颜色。颜色可以设定氛围,是建立品牌识别和唤起情感的有效工具。

颜色也可以突出设计布局中的特定部分。
设计中使用的颜色模型是 RGB 和 CMYK。RGB 颜色系统专门用于数字设计,其原色是红、绿、蓝。当以不同组合叠加时,可以再现广泛的颜色。其他颜色则由几种原色混合而成。
让我们进一步探讨颜色的一些特性。颜色还具有色相、浅色、色调、暗色和饱和度等特性。我们来了解一下它们的含义。
- 色相:描述一种颜色与红、橙、黄、绿、蓝、靛、紫这些基本颜色的相似或不同程度。例如,当你将一种颜色定义为蓝绿色时,你使用了两种色相来描述它。
- 浅色:通过向颜色中添加白色使其变亮。
- 色调:通过添加灰色使颜色变得柔和的过程。
- 暗色:通过向色相中添加黑色,创建出该色相的暗色版本。
- 饱和度:描述颜色的强度。降低饱和度会使颜色看起来褪色且更浅,而增加饱和度则使其更丰富、更深。


下图是色相、浅色、色调和暗色的示例。


术语“浅蓝色”和“深绿色”指的是饱和度的变化。




形状
下一个设计元素是形状。形状能够将你的注意力吸引到布局中。形状有三种类型:几何形状、有机形状和抽象形状。
以下是各种形状的介绍:
- 几何形状:通常精确且结构分明,例如数学上的正方形、圆形和三角形。
- 有机形状:通常没有尖锐的边缘,感觉平滑自然。这些形状为布局增添了重点。
- 抽象形状:以极简的方式呈现现实。人类的简笔画就是一个很好的抽象形状例子。大多数徽标使用抽象形状,通过抽象图形来反映公司的精神。
图像
图像也是一个重要的设计元素,因为使用各种媒体元素可以改善用户体验。视觉媒体仍然是最流行和最容易获取的媒介,因为图像实用、吸引人、易于记忆,并且对我们有吸引力。😊




如果运用得当,图像可以吸引并引导访问者的注意力,唤起情感,并促进信任感。


空间

最后的设计元素是空间。形状所占用的区域被称为空间,它也描述了形状或形式所处的背景。

空间有正空间和负空间。设计中感兴趣的区域被称为设计的正空间,其周围的空间则是负空间。让我们看一个例子:在这张图片中,有一个白色花瓶,这是正空间。它周围的黑色区域是负空间。




在这张图片中,还有两个黑色的脸,这也是正空间,而白色则变成了负空间。


空间可以用来连接和/或分隔设计中的元素。较窄的空间将项目连接起来以显示它们之间的联系,而较宽的间隙则将元素分开以显示它们之间的分离。元素的重叠增强了它们的联系。
总结
在本节课中,我们一起学习了设计元素的基础知识及其在设计中的作用。你已经了解到,像 Little Lemon 网站这样的优秀产品不仅需要制作精良,还必须看起来美观。出色的工作!


113:从线框图到高保真设计 🎨
在本节课中,我们将学习如何将已获批准的应用程序线框图,转化为一个高保真设计。高保真设计是指与最终产品外观高度相似的设计稿。我们将通过添加品牌元素和样式来完成这一过程。
准备工作:品牌风格指南与UI套件
在开始设计之前,我们收到了品牌风格指南。这份指南规定了品牌视觉风格的各个方面,包括字体、色彩调色板以及图片和图像的使用规范。
同时,我们还获得了一个UI套件。UI套件是一组文件,包含了关键的UI组件,例如字体大小、图标和相关文档。
上一节我们介绍了设计流程的起点,本节中我们来看看如何利用这些资源创建样式。
第一步:基于UI套件创建样式

首先,打开UI套件文件。我们将从创建文本样式开始。
- 在UI套件中选择“Display Title”文本。
- 转到屏幕右侧的四个点图标处。
- 点击“样式”,然后点击加号图标。
- 将其命名为“Display”,然后点击“创建样式”。这样,文本样式就创建好了。
创建颜色样式的过程与此类似。以下是具体步骤:
- 我已经为黄色创建了颜色样式,现在需要为绿色创建一个。
- 选择绿色的色块。
- 在填充属性中找到四个点图标。
- 点击加号图标,将其命名为“Primary One”。
- 点击“创建样式”,颜色样式即创建完成。
至此,UI套件已提供了我们所需的一切基础样式。

第二步:为线框图添加样式

现在,让我们将线框图导入,并开始为其添加样式。
首先,选择第一个线框图的背景,为其添加颜色。

- 点击背景元素。
- 点击填充属性处的四个点图标,这会显示已创建的所有样式。
- 选择绿色,即我们刚才创建的“Primary One”样式。
接下来,为顶部的“Little Lemon”文本添加样式。
- 选中该文本。
- 在右侧边栏中,选择我们创建的“Display”文本样式。
- 对此线框图中的其他文本元素重复此操作。
然后,我们需要修改显示文本的颜色。
- 选中“Little Lemon”文本,将其颜色改为黄色。
- 将其他文本元素的颜色改为白色。
现在,需要根据UI套件来设计按钮样式。UI套件规定按钮应为圆角,圆角半径为16。
- 选中按钮。
- 在右侧边栏中,将圆角半径修改为
16。 - 接着修改按钮颜色:在填充属性中点击四个点,选择“Primary Two”样式。
- 同时,将按钮上的文字颜色改为黑色,因为白色在深色背景上不够清晰。
第三步:插入图片
目前的设计还需要插入图片。以下是插入图片的步骤:
- 选中线框图中的图片占位框。
- 在右侧边栏的填充属性中,将顶部的下拉菜单从“纯色”改为“图片”。
- 此时会出现一个棋盘格方框,并提示选择图片。
- 选择所需的图片,它就会出现在元素中。
我们需要为剩余的图片占位框重复此操作:
- 点击下一个图片占位框的填充属性。
- 再次从下拉菜单中选择“图片”。
- 选择“希腊沙拉”图片。
- 对另外两个图片占位框重复此操作。

完成与总结

高保真设计稿现已完成。我们成功地将一个简单的线框图,转变为了一个具有完整品牌样式和视觉元素的精美设计稿。

本节课中我们一起学习了从线框图到高保真设计的完整流程。我们首先利用品牌风格指南和UI套件创建了可复用的样式,然后逐步将这些样式应用到线框图的各个元素上,包括背景、文本、按钮,并最终插入了真实的图片。这个过程确保了设计的一致性,并使其无限接近最终产品。

现在,为什么不自己动手试一试呢?
114:什么是设计系统 🎨
在本节课中,我们将要学习设计系统与UI工具包的核心概念,了解它们之间的区别,并探索原子设计方法论。我们将通过简单的例子,帮助你理解如何利用这些工具来创建一致、可复用的用户界面。
概述
你的客户对小柠檬餐厅网站的开发进展感到非常满意。根据优秀的用户反馈,你现在了解到设计系统拥有强大的资源,可以为小柠檬网站的用户生成灵活、一致且可复用的设计。然而,构建完整的设计系统工作量巨大,因此你决定先创建一个较小的UI工具包,并在接下来的几分钟内用它来指导你的设计工作。
设计系统与UI工具包
在之前的视频中,你已经接触过设计系统。你现在应该知道,设计系统包含了创建产品所需的一切元素,例如排版、颜色、图标、布局、网格、编码标准和命名规范。它还可以作为内容语调的指南,以及为开发者提供的样式指南和文档。设计系统将所有这些东西结合在一起,使你的团队能够协同开发、学习和工作。
一些大型设计系统会包含一套规则和指南,帮助读者了解应该做什么和不应该做什么。另一些则可能只是一套用于指导设计布局的组件和文件。
UI工具包与设计系统类似,但顾名思义,它仅限于构成用户界面(UI)的元素和功能。UI工具包仍然展示了一种系统化的设计方法,例如排版比例或颜色系统。它可以列出一组按钮、菜单项或组件,但它不如设计系统那么全面。
考虑到本课程的范围,你将使用一个UI工具包。
组件示例:导航栏

让我们以这个导航栏(或称菜单栏)为例。在设计系统中,它可以被称为一个 navbar 组件。它是网站的主要导航部分。
它包含一个徽标、一些链接,以及一个用于突出小柠檬在线订餐服务的功能按钮。右侧还有登录、注册信息和搜索选项的空间。

原子设计方法论
现在你对设计系统和UI工具包有了更多了解,让我们来探索由布拉德·弗罗斯特提出的原子设计。这是一种将设计元素组合起来以创建更大设计组件的思维过程,并通过一个科学隐喻来解释。让我们更详细地探讨一下。
-
原子:是像文本框、表单输入框、链接和按钮这样的独立元素。你可以把它们看作是
HTML元素。- 例如:一个
<input>标签,一个<button>标签。
- 例如:一个
-
分子:是将原子组合在一起形成的。想象一个内部带有标签的输入框,或者一个右侧带有“登录”链接的个人资料图标。再往右,可能还有一个写着“注册”的按钮。
- 例如:一个包含
<label>和<input>的搜索表单分子。
- 例如:一个包含
-
有机体:接下来是有机体。你猜对了,有机体是分子的组合。可以把它想象成一个导航栏,它由徽标(原子)、链接列表(分子)和按钮(原子)组成。

从蓝图到成品
让我们暂时离开科学隐喻,探索一下模板。模板就像低保真或基本的线框图,它把你所有的原子、分子和有机体组合在一起。

然后是页面,它是模板更具体、更精细的版本。想象一下实际的内容和图片。
这种结构化、有序的布局构建方法是精细化的,有其原因。它强制执行一致性,并通过可复用元素节省时间。想一想搜索栏,它通常放置在网页或应用的顶部,并且在大多数应用中的显示方式都相似。
这几乎与登录分子相同,只是图标和标签不同。
作为UX/UI设计师和开发者,你现在可以参考一个单一事实来源,你所有的文件都位于一个统一的空间中。
总结

本节课中,我们一起深入了解了设计系统和UI工具包,并初步认识了原子设计流程。你学会了如何通过系统化的方法,从最小的原子元素开始,逐步构建出完整的页面,从而确保设计的一致性和开发的高效性。干得漂亮!
115:32_Figma中的设计系统 🎨
概述
在本节课中,我们将要学习如何在Figma中创建和使用组件。组件是设计系统中的可复用元素,能够显著提升设计效率和一致性。我们将以创建一个简单的按钮组件为例,介绍组件的核心概念和创建流程。

随着小柠檬餐厅的重新设计项目不断推进,设计变得越来越复杂。然而,你可以创建一些可以在整个设计中重复使用的组件或元素,从而提高设计效率。
在本视频中,你将学习如何创建一个简单的按钮组件。

什么是组件?
任何图层或创建的对象都可以转换为组件。这包括多种多样的元素,例如按钮、图标、布局等等。


一个组件有两个核心特征:主组件和实例。实例是主组件的副本。别担心,我们很快就会详细讲解这个概念。



创建组件
上一节我们介绍了组件的基本概念,本节中我们来看看如何创建一个组件。为此,你需要使用一些稍微高级的Figma UI技巧。


这些技巧包括自动布局和组件功能。首先是自动布局功能。
自动布局功能
这个功能本质上将你正在创建的任何内容转换为其自身的框架,为你提供更多的控制和自定义选项。

在在线点餐页面上,我为菜单项创建了一些分类按钮,例如开胃菜、主菜、甜点、单点菜等。我最初是通过手动调整背景按钮形状以适应文本来创建这些按钮的。
以下是我最初创建这些分类的方式:我复制了“开胃菜”按钮,并输入了“主菜”。然而,按钮形状太大了。自动布局将纠正这个问题。
- 我选中“主菜”元素。
- 转到右侧面板,点击加号图标创建自动布局。
- 按钮形状会自动调整到文本的大小。
现在,它在图层面板中已更改为一个自动布局框架。图标表明了这一点,并且属性显示在右侧边栏中。
现在,如果我将鼠标悬停在文本元素上,它会有一个外框。这就是框架。此时,按钮具有内边距,即文本与文本顶部、底部和两侧之间的一些空间,这个空间是可调整的。
从按钮到组件
了解了自动布局后,接下来我们将其转换为真正的组件。
然后,我通过点击顶部框架的“创建组件”图标(它类似于四个菱形)从这个按钮创建一个组件。这现在创建了一个主组件。
你可以基于此组件创建实例和变体。
使用组件实例
现在,让我们创建这个组件的实例。我可以通过在Windows上按 Ctrl + D 或在Mac上按 Command + D 来复制按钮,或者我可以点击并拖动主按钮组件,并在拖动时按住 Alt 键。
图层面板显示创建了第二个按钮,但图标是单个菱形。这告诉我们这是主按钮的一个实例。我将其重命名为“黄色按钮”。

- 我点击“黄色按钮”实例。
- 将框架的填充色改为黄色。
- 只有这个按钮的实例变成了黄色。
现在,我回到我的主组件。
- 我将圆角半径改为
8。 - 所有实例都将保留这个更改。
主组件按钮是“主”。如果我想更改所有实例,我在这里进行更改。如果我希望实例具有各自的特性,我就在那个特定的实例内部进行更改。

总结
本节课中我们一起学习了如何在Figma中创建和使用组件。我们了解了主组件与实例的关系,掌握了使用自动布局功能创建可自适应内容的元素,并最终将其转换为可复用的组件。你还学会了如何创建实例并分别管理主组件和实例的样式。
我鼓励你继续探索组件,通过阅读相关材料来学习如何使用这些技巧创建更复杂的卡片组件。


116:从设计到原型制作 🎨
在本节课中,我们将学习如何使用高保真设计在 Figma 中创建交互式原型。原型是获取宝贵反馈的绝佳方式,Adrian 要求你为订餐流程创建一个原型。通过学习,你将能够制作可动画化并与客户、同事分享的原型,以进行进一步测试和反馈。
什么是原型?🤔

上一节我们介绍了课程目标,本节中我们来看看原型的定义。
原型是产品的一个近乎可工作的模型或模拟。你用它来与潜在用户和利益相关者进行测试。

构建交互式原型 🛠️

现在你已经设计了一些用户界面布局,并充实了你的设计系统和 UI 套件,接下来让我们将所有内容整合到一个交互式原型中。
使用交互式原型,你可以展示设计将如何运行,这有助于帮助他人理解你的设计在最终产品建成后的外观和行为。
首先,需要创建设计,我在这里已经完成了。我想测试一些功能,所以我使用人物角色 Tiie 或 Tillly 开发了一个场景,并生成了她的用户流程。
以下是 Tillly 的用户订餐流程描述:
- Tillly 在漫长的工作日后感到饥饿。她在 Little Lemon 网站的移动版上打开了在线订餐选项。
- 起初,她不确定想点什么。她看到了意式烤面包,并想了解更多信息,于是她点击了意式烤面包。
- 这为她提供了所有想要的细节和自定义选项。她决定就这样下单,将其添加到订单中,并获得了价格摘要和明细。
- 随后,她发现了一个添加更多菜品的选项,想看看希腊沙拉。
- 同样,她获得了描述和添加其他配料的所有选项。她再次决定就这样下单,并点击添加。
- 出现了更新后的订单摘要,右上角的购物篮图标也同步更新,显示有两件商品。
- Tillly 看到购物篮中有两件商品,并考虑是否要再点一份甜点。她点击返回按钮,回到了可以浏览其他菜单类别并查看购物篮商品数量的页面。

在 Figma 中链接元素与屏幕 🔗
现在让我们看看如何在 Figma 中链接这些元素和屏幕。
- 点击右侧面板上设计面板旁边的“原型”链接。你会注意到主页屏幕画框上出现了一个带箭头的蓝色方框以及一个蓝色圆形图标。
- 选择它。我想选择意式烤面包。我双击该区域,直到相关部分高亮显示。
- 我点击右侧的蓝色按钮,并将其拖拽到屏幕二。我现在已经链接了这两个屏幕。
- 交互细节出现在右侧,上面写着“点击时导航到屏幕2”。
- 接下来,我将“添加”按钮链接到屏幕三。
- 从这里,我将“希腊沙拉”元素链接到屏幕四。
- 然后将“添加”按钮链接到屏幕五(即更新后的订单摘要)。
- 最后,从这里将“返回”按钮链接回主页屏幕。
预览与测试原型 👀
好的,现在完成了链接,让我们通过点击 Figma 画布上带箭头的蓝色方框来查看交互式原型。

这将打开原型。我点击意式烤面包,显示了它的详细信息。我将其添加到购物篮,并跳转到订单摘要。我点击希腊沙拉,查看其详细信息,这又将我带到了更新后的摘要页面。当我点击返回按钮时,我回到了主页屏幕。
成功!你现在已经学会了如何在 Figma 中创建一个简单的交互式原型。Adrian 和用户对你提供的原型感到满意。恭喜!

总结 📝

本节课中我们一起学习了如何使用 Figma 中的高保真设计创建交互式原型。这些原型可以添加动画效果,并与客户和同事分享,以进行进一步的测试和反馈。干得好!
117:制作原型并测试 🎨
在本节课中,我们将学习如何在Figma中为应用程序创建交互式原型,以便进行用户测试。我们将重点介绍如何制作可切换的按钮组件和实现水平/垂直滚动效果,最终生成一个可分享的测试链接。

概述
你已经准备好通过Figma上的交互式原型为应用程序的测试做准备。当测试准备就绪时,你可以创建原型并与用户分享链接。让我们开始了解如何制作这个原型。


首先,我打开我的Figma文件。现在,我可以水平滚动类别按钮,并垂直滚动菜品列表。我还可以切换类别的开启和关闭状态。请注意,在这个原型中动态过滤内容超出了范围,但它是为测试参与者提供丰富用户体验的一个良好起点。

创建可切换的按钮组件
上一节我们介绍了原型的目标,本节中我们来看看如何创建核心的交互组件——可切换的类别按钮。
为了复现这个效果,我需要删除主页面上当前的一组类别按钮。在图层面板的右侧,我转到本地组件页面,这个页面是我创建用来存储组件的地方。
以下是创建类别按钮切换交互组件的步骤:
- 创建基础按钮:首先,创建一个自动布局按钮并将其转换为组件。
- 设置主组件:点击组件按钮,将其命名为“Master button”,然后按回车键确认。
- 添加变体:右键单击此组件,选择“主组件”,然后选择“添加变体”。这里将放置按钮的变体状态,即用户点击或切换时发生的变化。
- 设计变体样式:选择新创建的变体,在设计面板的标签中输入“selected”并按回车。接着,自定义其外观:选择按钮并将填充色改为更深的颜色。然后,双击文本,将文本的填充色改为更浅的颜色。
- 设置原型交互:点击右侧的“原型”选项卡。双击第一个实例(默认状态),将其原型图标拖拽到第二个实例(选中状态),此时会出现原型设置面板。你会看到交互设置为“On click”时“Change to” “selected”图标。接着,双击第二个实例并将其原型图标拖拽回第一个实例,以设置反向切换过程。这样,当选中状态的按钮被点击时,它会变回默认状态。这就是在两个状态之间切换的方法。
将组件应用到原型中
现在我们已经创建了可切换的按钮组件,接下来看看如何将它应用到我们的主页原型中,并测试其功能。
我转到左侧面板的“Page 1”。然后,我转到旁边的“Assets”选项卡,输入“Master button”进行搜索。按钮组件显示出来后,我将其拖拽到“Order for delivery”标题下的自动布局区域中。
让我们现在测试它。我点击“Present prototype”按钮。当我点击按钮时,它成功改变了状态。效果完美。
制作多个按钮并实现水平滚动

单个按钮已经可以工作,但我们的界面需要一组可以水平滚动的类别按钮。本节我们将完成这个布局。

我需要制作更多的类别按钮。我选择现有的按钮,然后按 Ctrl+D(或 Command+D)复制一个按钮组件实例。按钮被复制了,但它们一个叠在另一个上面。这是因为父级的自动布局框架被设置为垂直堆叠。

以下是实现水平滚动按钮组的步骤:
- 创建按钮组框架:选择所有按钮,将它们组成一个自动布局框架。
- 调整布局方向:在右侧的自动布局属性面板中,将方向改为水平。
- 复制并命名按钮:我可以根据需要复制任意多个类别按钮。在每个按钮中输入文字,给它们独特的标题。我为每个按钮都设置了新标签。
- 设置水平滚动:双击这个包含按钮的框架(例如“Frame 5”),我将其重命名为“dish category”。为了限制这些按钮在屏幕内的滚动范围,我双击进入该框架并将其转换为组件。接着,右键单击并选择“Frame selection”。我从右侧缩放框架,使其边缘与设备窗口的边缘对齐,这样就创建了溢出边界。
- 启用滚动原型:我选择“原型”选项卡,点击“Overflow scrolling”,然后选择“Horizontal scrolling”。
让我们检查一下效果。现在,类别按钮可以切换开关状态,并且我可以左右滚动它们了。
实现菜品的垂直滚动
类别按钮的水平滚动已经完成,使用户能够浏览所有选项。类似地,菜品列表也需要支持垂直滚动,以展示更多内容。
我重复相同的过程来实现菜品的垂直滚动。创建一个包含菜品项目的框架,将其转换为组件,设置框架边界以匹配可视区域,然后在原型选项卡中为这个框架选择“Vertical scrolling”。
分享原型进行测试
所有交互功能都已就绪,原型已经成为一个可以模拟真实体验的工具。最后一步是生成一个链接,以便与测试参与者分享。

要分享我的原型,我点击“Share”按钮,复制生成的链接,然后将其分享给测试参与者。


总结

本节课中我们一起学习了在Figma中创建交互式原型的完整流程。我们首先创建了一个可切换状态的按钮组件,然后将其组合成支持水平滚动的按钮组。接着,我们为菜品列表实现了垂直滚动效果。最后,我们生成了可分享的原型链接,为后续的用户测试做好了准备。这个过程是连接设计与用户反馈的关键步骤。
118:案例研究 - Meta 如何使用微交互 🎯
在本节课中,我们将学习微交互的概念、作用、设计过程以及它们如何提升用户体验。微交互是应用中那些微小但至关重要的互动细节,它们为用户提供清晰的反馈,并让应用体验更具人性化。
什么是微交互?🤔
微交互是你在使用应用时会频繁遇到的小型互动。它们通常服务于单一目的,旨在帮助你更好地理解当前的操作流程,同时也为应用增添情感化和人性化的色彩。

一个关于交互的有趣事实是,你在使用应用时经常遇到它们,但大多数时候并未意识到。微交互的设计初衷是简单明了,因此你可能甚至不会注意到自己关闭了一个对话框,或者看到了操作成功的确认信息。
微交互的作用与价值 💡
微交互的核心作用是向用户清晰地传达信息。它们让用户明白其刚刚执行的操作是否成功,或者触发用户去完成下一步操作。它们极大地提升了用户对其所执行操作的理解程度。

以下是微交互带来的主要好处:
- 提供清晰反馈:明确告知用户操作结果(成功或失败)。
- 引导用户行为:提示用户进行下一步操作或进入流程的下一阶段。
- 增强情感连接:通过动画等细节,为应用增添趣味性和人性化体验。
- 提升状态感知:让用户清楚了解自己在应用中所处的位置以及系统状态。
Meta 的微交互实例 📱
在 Meta,微交互被广泛应用于各个产品中。许多微交互我都是在潜意识中使用的。现在,当我构建产品时,我会认真思考用户成功完成流程时会发生什么,或者他们可能遇到错误时该如何处理。
一个具体的例子是 Facebook.com 上的“点赞”按钮。当你将鼠标悬停在“点赞”按钮上时,会看到一个小栏,显示你可以对帖子做出的所有反应(如“喜欢”、“爱心”、“哈哈”等)。这个微交互的意图非常明确:用户应该点击其中一个反应,以便快速对他人的帖子做出回应。同时,它也提升了应用的愉悦感——如果你曾悬停过点赞按钮,会注意到这些反应图标带有动画效果,这为使用 Facebook 带来了更多乐趣和人性化体验。

微交互的设计与实现流程 🛠️
上一节我们看到了微交互的实际案例,本节中我们来看看它们是如何被设计和实现的。
许多微交互实际上是在设计过程中构思出来的。设计师通常会设计一个完整的端到端流程,并为流程中的每个屏幕设计不同的界面,明确点击某个按钮后应发生什么,等等。工程师负责实现这些微交互。

有时,在实现或测试阶段,我们可能会发现某些交互存在空白。例如,当你按下按钮时,没有任何反应。因此,在测试阶段,我们会意识到需要在何处添加更多微交互,或者某个微交互是否过于烦人且对用户无益。
幸运的是,在 Meta,我们拥有大量可复用的组件用于实现微交互。一个典型的例子是 Toast 提示。
Toast 是一种当你完成某个操作后,出现在屏幕底部角落的提示信息。例如:
- 成功场景:如果我尝试创建一个 Facebook 活动并成功,屏幕角落会出现一个带有绿色对勾的小提示。
- 错误场景:如果发生问题,比如网络断开导致无法创建活动,屏幕角落则会出现一个带有警告标志的小提示。
这使得用户非常清楚发生了什么,也提升了他们的体验理解度。
// 伪代码示例:显示一个成功的 Toast 提示
showToast({
message: "活动创建成功!",
type: "success", // 对应绿色对勾图标
duration: 3000 // 3秒后自动消失
});


设计微交互的最佳实践 ✨
我的最大建议是:不要过度设计微交互。有时人们学会制作非常花哨的动画,就认为必须将其应用到网站的每一个微交互中。微交互最关键的一点在于,它们应该非常清晰且易于理解,大多数时候也意味着要简单。
你既不想包含过多的微交互,也不想让你所包含的微交互过于花哨而分散用户的注意力。
通常,人们在设计产品时不会想到微交互,但在设计过程和测试过程中思考它们至关重要。我会多次使用产品,甚至不会注意到缺少一个小小的确认提示或错误对话框。因此,认真思考这些细节,并将你的产品展示给他人测试,以便他们也能发现这些空白并帮助你填补,这一点非常重要。
总结 📝
本节课中,我们一起学习了微交互的核心概念。微交互是应用中那些微小但至关重要的互动,它们为用户提供清晰的反馈、引导操作、增强情感连接并提升状态感知。在设计时,应追求清晰、简单和目的明确,避免过度设计。通过在设计、实现和测试阶段有意识地考虑微交互,并利用可复用组件(如 Toast),你可以显著提升产品的质量和用户体验,确保用户真正理解他们在你的新产品或想法中所经历的流程。

祝你在学习更多关于微交互知识的道路上一切顺利,请记住,它们将把你的应用提升到一个高质量的层次。
119:用户界面设计模块总结 🎉
在本节课中,我们将回顾并总结用户界面设计模块的核心内容。我们将梳理你在本模块中学到的关键技能,包括如何运用视觉元素、理解UX/UI设计原则、以及创建和测试交互式原型。
第一课:视觉元素在设计中的应用 🎨
上一节我们介绍了模块的整体目标,本节中我们来看看第一课的核心内容。在第一课中,你学习了如何提升设计水平,现在应当能够识别图像、颜色和形状的运用,并解释它们在设计中的作用。
以下是图像、颜色和形状在设计中的关键作用:

- 图像:用于传达信息、吸引注意力和建立情感连接。
- 颜色:用于引导视觉焦点、建立品牌识别和传达情绪。
- 形状:用于组织内容、建立视觉层次和引导用户流程。



第二课:UX/UI设计原则 📐
掌握了视觉元素的基础后,我们进入设计原则的学习。在第二课中,你学习了UX/UI设计原则。完成本课后,你能够解释良好表单设计的重要性,并应用最佳实践来改进表单设计。

此外,你还学习了以下关于组件化产品和设计系统的知识:

- 描述组件化产品和设计系统:理解如何通过可复用的
<Button />、<Card />等组件构建界面。 - 解释其如何吸引用户:说明组件化产品和设计系统如何通过保证一致性和提升开发效率,来交付吸引人的用户体验。



第三课:原型设计、分享与测试 🚀

理解了设计原则,下一步是将静态设计转化为可交互的体验。在第三课中,你学习了如何制作原型、分享并测试你的设计。
完成本课后,你现在能够使用Figma中的高保真设计来创建交互式原型。


同时,你也能够描述如何为这些原型添加动画效果,并将其分享给客户和同事,以进行进一步的测试和收集反馈。




模块总结 ✅
本节课中我们一起学习了用户界面设计模块的全部内容。完成本模块后,你现在应当能够提升你的UX/UI设计水平,应用良好的表单设计原则,理解如何在Figma中创建组件,以及制作原型并分享你的设计。
做得好,你已成功掌握了用户界面设计模块的关键技能。
120:37_课程回顾 📚
在本课程中,我们学习了用户体验和用户界面设计的原则。现在,让我们花一些时间来回顾一下所学到的关键主题。
概述 📋
本节课我们将一起回顾整个课程的核心内容,从UX/UI设计的基础概念,到交互设计评估,再到应用设计基础和高级UI设计技巧。通过本次回顾,你将巩固对UX/UI设计全流程的理解。
开篇课程:UX与UI设计导论 🎯
在开篇课程中,我们介绍了UX和UI设计的基础知识。

以下是该部分的核心学习内容:
- 能够区分UX和UI。
- 描述了什么是UX,其目标和质量构成要素。
- 定义了UI以及不同类型的设计。





此外,我们开始接触Figma,并探索了以用户为中心的设计,以及UX设计中的共情工具和用户画像概念。


交互设计评估 🔍
上一节我们介绍了设计基础,本节中我们来看看如何评估交互设计。
在这个主题中,我们探索了评估方法,并涵盖了无障碍设计。通过研究导航和表单设计的实际案例,我们还审视了评估的最佳实践原则。





应用设计基础 🛠️

掌握了评估方法后,我们进一步学习了应用设计基础。
在这个主题中,我们探索了使用Figma的基本功,并回顾了迭代设计的原则,包括线框图绘制、原型制作和可用性测试。


设计你的UI 🎨


接下来,我们开始探索如何设计你的用户界面。
在这里,你学习了如何优化设计,并创建基于组件的高保真设计。


你还学习了如何使用情绪板,以及如何在Figma中创建设计系统。


然后,我们学习了如何创建高保真设计原型,并在UI中包含微动效。




最后,你学习了如何为原型添加动画并进行测试。
总结与下一步 🚀
本节课中我们一起回顾了整个UX/UI设计课程的学习路径。
完成所有内容后,你现在已经准备好完成你的作业:在Little Lemon网站上预订餐桌,以实践你所学的知识。之后,你将参加分级评估,检验你在本课程中学到的内容。
祝你顺利!😊
121:38_恭喜你完成了用户体验和用户界面设计原则 🎉
在本节课中,我们将对您完成的用户体验与用户界面设计原则课程进行总结,回顾您已掌握的核心技能,并展望后续的学习路径。
您已经完成了这门Meta用户体验与用户界面课程。您付出了辛勤的努力,并在此过程中发展了许多新技能。您在用户体验与用户界面的学习之旅中取得了巨大进步。现在,您应该已经理解了用户体验和用户界面设计的原则。
在课程作业中,您通过为Little Lemon网站创建一个餐桌预订功能元素,展示了部分学习成果以及您实用的用户体验与用户界面技能。
完成这门Meta用户体验与用户界面课程后,您现在应该能够:
- 在采用以用户为中心的设计方法时,应用用户体验和用户界面原则。
- 使用设计方法和最佳实践原则来评估交互设计。
- 在Figma中应用设计基础知识。

上一节我们回顾了基础设计原则的应用,本节中我们来看看更具体的设计流程与工具技能。

您还应该能够:
- 使用线框图、快速原型制作和可用性测试,遵循迭代式设计流程。
- 创建基于组件的高保真设计。
- 使用情绪板。
- 在Figma中创建设计系统。
课程中的分级评估所衡量的关键技能,证明了您在这些主题上的能力。
后续步骤 🚀
那么,接下来的步骤是什么?这门Meta用户界面课程为您初步介绍了几个关键领域。您可能意识到自己仍有更多需要学习的内容。
因此,如果您觉得这门课程有帮助并希望了解更多,何不注册下一门课程呢?无论您是刚刚起步的技术专业人士、学生还是商业用户,本课程和项目都能证明您了解用户体验与用户界面的价值和能力。
最终作业通过在实际项目中应用您的用户体验与用户界面技能,巩固了您的能力。但它还有另一个重要的好处:这意味着您将拥有一个可以在作品集中引用的真实设计。
这有助于向潜在雇主展示您的技能。

它不仅向雇主表明您具有自我驱动力和创新能力,还充分说明了您作为个人的特质以及您新获得的知识。

谢谢。很荣幸能与您一同踏上这段探索之旅。祝您未来一切顺利。😊

总结 📝

本节课中我们一起学习了课程完成的总结。我们回顾了您已掌握的核心用户体验与用户界面设计技能,包括原则应用、设计流程和工具使用。同时,我们也探讨了如何将课程成果转化为作品集项目,并为您的持续学习指明了方向。恭喜您达成这一里程碑!
122:0_简介 🎯

在本课程中,我们将学习如何构建一个响应式网页应用,并通过编码一个现代化的前端应用程序来展示多种技能。该应用将为 Little Lemon 餐厅实现一个用户预订餐桌的功能。



Little Lemon 餐厅的网站收到了关于其“预订餐桌”功能的负面反馈。用户对该功能的使用方式感到困惑,并且对其外观和功能不满意。这正是本课程要解决的问题。我们将综合运用在整个学习计划中学到的所有技能和技术,来构建这个应用。
现在,你可能会好奇本课程的结构。本课程包含四个模块。

以下是四个模块的简要介绍:

- 启动项目:你将开始毕业项目。这包括简要回顾已完成的 React 课程内容,并设置编码环境、React 项目以及在 GitHub 上为项目创建 Git 仓库。同时,你将回顾 UX 和 UI 设计原则,并在项目中使用这些方法,包括准备线框图和使用 Figma 应用设计基础。
- 项目基础:你将使用语义化 HTML、元标签和开放图谱协议为网页应用创建现代化的 HTML 结构。同时,你将使用 CSS Grid 和其他 CSS 样式来建立一个响应式、清晰且吸引人的网站。此外,还会回顾 React 的基础知识。
- 项目功能:你将使用 React 编写餐桌预订系统的代码。课程将涵盖用户体验和表单验证的重要性,并通过练习在应用中进行表单验证和编写单元测试。同时,将涉及可访问性和 UX/UI 可用性评估,并通过相关练习确保你的界面符合这些要求。
- 项目评估:你将有机会反思所学到的知识和取得的成就。具体来说,你将有机会对自己的项目进行自我评审,并对其他学习者为 Little Lemon 预订餐桌网页应用提供的解决方案进行同行评审,从可用性、可访问性、设计和代码等方面评估项目。


有了这些期待,相信你已经迫不及待想要开始了。让我们开始你的项目吧。

在本节课中,我们一起学习了毕业项目的整体结构、目标和四个核心模块的内容。接下来,我们将进入第一个模块,正式开始项目的搭建工作。
123:设置项目 🛠️
在本节课中,我们将学习如何为毕业项目设置开发环境。我们将确保你的机器已准备好进行React开发,并使用Create React App初始化项目,同时建立Git版本控制。
概述
课程介绍完毕后,现在开始为毕业项目进行设置。本节课将回顾与项目设置相关的几个核心概念。
开发环境准备

首先,需要确保你的编码环境已准备就绪。具体而言,这意味着:
- 你的机器上安装了正确版本的 Node.js 和 NPM。
- 你的操作系统允许你自由地与Node.js交互并构建项目。
- 你使用的计算机拥有管理员权限,可以自由执行操作。

以下是需要安装和设置的工具列表:
- IDE或代码编辑器:已安装并设置为可与React和Git协同工作。推荐使用VS Code,因为它在其他前端课程中也被使用。
- Git:已在你的机器上安装。
- GitHub账户:用于为你的代码添加远程仓库。
使用Create React App初始化项目
你将使用 create-react-app 这个NPM包来构建一个React样板应用。这是一个最小的React启动应用,包含你可以基于其进行开发的起始代码。
以这种方式设置React项目意味着你需要理解:
- Node.js和NPM是什么。
- Node.js如何与React协同工作(高层次理解)。
package.json文件如何工作及其用途。
你还应该理解与React应用代码相关的各种概念,包括React应用的代码如何从JSX(浏览器无法理解)被编译成JS(浏览器可以理解),并借助如Webpack这样的模块打包工具在本地进行打包和服务。

这个React样板应用是你的毕业项目的起点,并将构成你在此过程中完成的大部分工作的基础。

设置Git版本控制
你可能已经知道,当你使用create-react-app构建React项目时,该项目会附带一个本地Git设置和一个初始提交。这使得设置远程仓库以正确使用此Git设置变得更容易一些。
为了能正确使用这个Git设置,你需要回顾并确保至少对以下概念有基本理解:
- Git及其如何在本地跟踪文件。
- Git中的暂存区。
- 远程仓库:如何设置仓库以及如何使用
git add、git commit、git push、git log和git branch命令。
在本节课中,你将有机会重温版本控制的工作原理,并为你的React毕业应用设置一个Git仓库。
实践:更新与代码追踪
你将通过更新启动的React应用并使用Git跟踪这些更新来实践。你将使用前面提到的一些Git命令来提交代码更改,并将其从本地文件夹推送到远程Git仓库。
请记住,你需要持续进行此操作,以确保你的远程源仓库和本地文件夹保持同步。这很重要,因为它能确保你的项目在需要时始终准备好接受更多贡献者。



现在,让我们开始设置你的项目。
总结

本节课中,我们一起学习了为毕业项目设置完整开发环境的步骤。我们回顾了确保Node.js、NPM、代码编辑器和Git正确安装的重要性,使用create-react-app初始化了React项目,并建立了Git版本控制流程,为后续的开发工作奠定了基础。
124:规划用户体验和用户界面 📋

在本节课中,我们将学习如何为“小柠檬”餐厅的在线订座功能规划和设计用户体验与用户界面。我们将回顾UX/UI的核心原则,并利用Figma工具创建线框图、组件和原型,以解决用户无法轻松预订座位的痛点。
概述
“小柠檬”餐厅的经营者了解到,顾客对于无法在其网站上轻松预订座位感到沮丧。他们希望解决这个问题,以便更好地规划员工和物资,不再仅仅依赖散客,并为食客提供出色的体验。通过将UX(用户体验)和UI(用户界面)的原则应用于当前网站的订座功能,我们可以帮助“小柠檬”实现这一目标。
UX/UI流程回顾 🧩
上一节我们介绍了项目背景,本节中我们来看看构成UX/UI流程的几个关键阶段。这些阶段包括规划、设计、开发和发布。
- 规划阶段:这被认为是UX/UI流程中最关键的阶段。它包括收集用户的目标,并确定和规划项目的整体目的。
- 设计阶段:在此阶段,设计师将规划阶段的事实和信息转化为现实。必须产出设计结构和草图以供批准和测试。
- 开发阶段:此阶段主要完成编码和编程任务,并进行测试。它本质上是实现前一步骤所获结果的过程,例如制作一个功能性的网站或移动应用程序。
- 发布阶段:产品在此阶段交付给用户。
从UX开始:解决核心问题 🎯
现在,我们将开始探索UX,并了解遵循UX流程将如何帮助我们解决“小柠檬”网站在线订座功能的问题。解决这些问题可能会增加销售额并留住回头客。
需要记住的是,因为产品可以不断改进,所以UX是一个迭代过程。发布后的功能测试和改进将持续进行。
UI虽然没有特定的设计流程,但若处理得当,它同样至关重要。出色的UI通常不会被用户注意到,但如果UI设计不佳,用户将获得负面体验,大多数人会离开网站且不再回来。
为了阐明UI和UX的目的,我们可以看两个例子:
- 出色的UI但糟糕的UX:看起来很美,但使用起来很有挑战性。
- 出色的UX但糟糕的UI:非常易用,但外观令人不悦。

用户研究与设计机会 🔍
根据已进行的用户研究,我们创建了一个用户角色和用户旅程地图,以帮助解决“小柠檬”网络应用的问题。
用户旅程地图识别出了多个改进机会,以下是具体列表:
- 允许顾客选择座位。
- 提供更多选项。
- 允许添加额外备注。
- 发送确认邮件。
- 选择日期、时间和用餐人数。
实践步骤 🛠️
在本课的后续部分,你将通过阅读材料、练习和测验,逐步学习如何为订座功能创建出色的UX和UI。





总结
本节课中,我们一起学习了UX/UI设计的基本流程和核心概念。我们了解到,规划阶段对于确定项目方向至关重要,而设计阶段则负责将想法可视化。通过分析“小柠檬”的用户旅程,我们明确了具体的功能改进点,例如增加座位选择、日期时间选择器等。接下来,我们将运用这些知识,开始动手设计解决方案。
125:3_启动项目模块总结 🚀
在本模块中,我们系统地学习了如何着手解决小柠檬网站用户在预订餐桌时遇到的问题。我们从课程介绍开始,逐步完成了开发环境的搭建、UX/UI设计流程的实践,最终为项目的编码工作做好了充分准备。
项目启动与目标定义 🎯
上一节我们介绍了课程大纲,本节中我们来看看如何明确项目范围与目标。
我们首先回顾了UX和UI设计原则,并建立了自己的UX/UI设计流程。通过收集用户的目标和需求,我们定义了项目的范围和目的。这确保了后续所有工作都围绕解决用户的核心问题——即优化小柠檬网站的餐桌预订功能——而展开。
开发环境搭建 💻
明确了目标后,我们需要一个强大的工具集来实现它。本节我们将学习如何配置高效的开发环境。

我们设置了编码环境,并安装了React.js。React.js是一个用于Web开发的JavaScript库,专门用于构建网站上的交互式元素。其核心思想是组件化,可以用以下伪代码表示一个简单的React组件概念:


function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
以下是环境搭建的具体步骤:
- 在机器上安装正确版本的Node.js和npm(Node包管理器)。
- 确保使用一个能与Node.js良好交互的操作系统,以便自由地构建小柠檬项目。
- 使用
create-react-app这个npm包来快速初始化React项目。 - 安装并设置一个IDE或代码编辑器,以支持React开发。
- 在机器上安装Git,并设置好GitHub账户,以便为代码添加远程仓库。
完成以上步骤后,我们就拥有了一个功能完备的现代前端开发环境。
UX/UI设计与原型制作 ✏️
环境准备就绪后,我们将构思转化为可视化的设计。本节我们将使用设计工具来规划网站的外观与交互。



我们使用Figma开始了UX/UI设计流程。Figma是一个基于矢量的、高度可扩展的设计工具。其基于浏览器的架构非常智能,可以实时保存工作,甚至在网络连接短暂中断时也能继续工作。
我们利用Figma为项目设计了线框图,这澄清了小柠檬网站上预订餐桌的功能流程。在此过程中,可用性被置于首位,我们的页面布局得以展示。
以下是我们在Figma中的主要工作:
- 逐一处理网站的功能、布局以及创意和品牌方面的问题。
- 让网站所有者和用户能够提供早期反馈,从长远来看节省了时间。
- 在Figma中创建了组件,这些组件可以在整个设计中重复使用,有助于在为小柠檬网站创建预订餐桌的解决方案时,建立并管理设计的一致性。
设计细化与迭代 🔄
线框图勾勒出了骨架,现在我们需要为其注入血肉。本节我们将学习如何应用品牌风格并完善设计细节。
我们使用小柠檬的品牌风格指南来充实线框图,用品牌色彩、添加图片、按钮、文本来增强它们,并使它们具有交互性。
由于UX是一个基于用户和客户反馈的迭代过程,我们可能在此阶段也必须进行多次迭代。在这个过程中,我们的想法不断被提炼,逐渐接近最终的设计解决方案。

总结 📝


在本节课中,我们一起学习了项目启动的完整流程。
我们已经设置了编码环境,并为项目创建了一套设计,现在处于一个绝佳的位置,可以开始着手编写项目代码了。我们明确了问题,准备好了工具,规划了解决方案,为后续的编码实现奠定了坚实的基础。做得好!
126:4_设置语义HTML文档 🏗️
概述
在本节课中,我们将学习如何为你的毕业项目(Little Lemon餐厅的预订网页应用)正确地组织和构建HTML结构。核心内容是掌握语义HTML,这是一种更现代的HTML5语法,它能让网页传达更多含义,从而帮助用户、屏幕阅读器和搜索引擎更好地理解网站的标记。
语义HTML的重要性
上一节我们介绍了课程的整体目标,本节中我们来看看为什么语义HTML如此关键。这些课程项目的目的是确保你正确格式化HTML结构,使其符合正确的HTML5语义规范。
需要明确的是,到处使用<div>元素并非最佳实践。你应该专注于使用当今可用的众多语义HTML5标签。这些标签能定义网页的各个部分,使代码更具表达性,有助于向人和机器(包括搜索引擎爬虫)传达意图。
以下是HTML5中一些核心的语义标签示例:


<nav> <!-- 定义导航链接部分 -->
<header> <!-- 定义文档或区域的页眉 -->
<footer> <!-- 定义文档或区域的页脚 -->
<main> <!-- 指定文档的主要内容 -->
<aside> <!-- 定义与主内容间接相关的内容(如侧边栏) -->
<article> <!-- 定义独立、可分发的内容块(如博客文章) -->

设置语义HTML文档
在理解了语义标签的重要性后,接下来我们将动手设置一个语义化的HTML文档。你将重温HTML的历史,了解HTML4与HTML5的区别,以及为何HTML5规范如此强调语义标签。
完成语义HTML文档的设置后,你需要将重点转移到处理元标签(Meta Tags)和设置开放图谱协议(OpenGraph Protocol)上。这部分内容将帮助你优化网页在社交媒体上的分享预览。
为了巩固这部分知识,你将回顾在《HTML与CSS深入》课程中学到的相关内容,并完成两项准备情况检查:一项是针对元标签和开放图谱协议设置的完成度检查,另一项是使用Git跟踪项目进度的检查。
课程总结与评估
本节课将以一个小测验结束,测验内容涵盖语义HTML、元标签和开放图谱标签。此外,还会提供额外的相关资源和阅读材料链接,以进一步强化你在这方面的知识。

现在,让我们开始学习吧!
127:5_样式化元素 🎨
在本节课中,我们将重新探讨网页和Web应用的样式设计,以便应用课程早期创建的“小柠檬”风格指南和布局设计。我们将首先在不涉及React的情况下,单独探索CSS的使用。之后,我们将从React的角度来使用CSS。
网页布局技术演进 📜
你可能还记得,CSS自20世纪90年代末就已存在。这意味着网页布局有多种不同的方式。让我们简要回顾一下。
表格布局
一切始于基于表格的布局。这是一种现已过时的布局构建方式。
浮动布局
之后,在21世纪初,另一种技术变得流行起来:浮动。作为一个CSS属性,float旨在将HTML元素从正常的文档流中移除。换句话说,当你浮动一个元素时,它不再遵循正常的文档流。
浮动流行了相当长一段时间。然而,由于它并非为构建网页布局而设计,CSS仍然需要合适的布局语法。
Flexbox布局的尝试
随着业界在2010年代初达成共识,认为需要更好的方案,曾有过几次引入CSS弹性盒布局模块规范(即Flexbox布局)的尝试。这是一种一维布局技术,用于在行或列中排列项目。
如果你想了解更多关于Flexbox布局的信息,可以查阅本课的补充资源。然而,值得注意的是,CSS规范中Flexbox布局的初次尝试并不完全成功,原因是规范不断变化,且各浏览器的支持不一致、不完整。
现代布局技术的成熟
最终,在2010年代后半期,Flexbox推出了新的规范并得到广泛采用,CSS网格规范紧随其后。CSS网格布局技术大约在2018年左右真正兴起。
CSS网格布局模块是CSS规范的一部分,其目的就是构建网页布局。原因很简单:浏览器需要一段时间才能跟上CSS网格规范的发展,而那些不支持CSS网格的旧浏览器也需要时间失去大部分市场份额。因此,在现代浏览器跟上CSS网格之后,不支持它的旧浏览器就逐渐被淘汰了。CSS网格得以广泛采用。
选择正确的布局工具 ⚖️
使用CSS Flexbox和CSS网格各有其好处。在本课中,我们将重新审视使用两者的优缺点。
你不应低估CSS Flexbox的作用和地位。重要的是要记住,CSS网格并非CSS Flexbox的替代品。了解其中任何一种都很好,但最好两者都掌握。
了解各种布局技术的另一个原因是,能够在日常工作中对布局选择做出明智的决策。开发人员经常需要判断一段代码是否足够好,或者是否需要修改。很多决策过程来自于经验。尽管如此,对于一名全面的前端开发者来说,了解现代CSS布局技术及其适用场景非常重要。
确实,你在这个主题上掌握的知识越多,就越能选择正确的方法,并判断你所参与的任何项目中现有代码是否符合现代标准。此外,你将能够确定最适合当前工作的工具。
综上所述,本课的重点将主要放在使用CSS网格构建CSS布局上,因为它是现代CSS布局工具箱中最全面、最多功能的工具。

上一节我们回顾了CSS布局技术的发展历程,本节我们将开始动手实践。


是时候开始了。
128:项目组件 🧩
在本节课中,我们将学习项目的基础元素之一:添加React组件。我们将回顾React的基础知识,理解组件的核心概念及其演变,并了解如何在现代React中构建组件。
回顾React基础知识

上一节我们介绍了项目的基础,本节中我们来看看React的基础知识。React基础知识课程涵盖了多个核心概念。

以下是该课程涵盖的主要内容列表:
- React组件介绍及其存在形式
- 组件的使用与样式设置
- 数据、状态与事件的概念
- 导航的概念
- 更新、资源以及在React中使用资源的方法
- 创建第一个React应用的机会
理解组件的核心地位
组件是React进行Web开发方法的核心。当然,React并非唯一围绕组件构建的技术。你可能会问为什么组件如此重要。简单来说,组件是目前设计、构建和描述网页的最佳方法之一。
React有其自身处理组件的方式,并且这种方式随着时间不断发展。



React组件的演变:从类到函数
在React的旧版本中,组件是基于类的。换句话说,你需要使用JavaScript类来构建React组件。这个时期可以称为“Hooks前时代”。
在Hooks前时代,你可以使用基于类的语法来构建带有状态的组件(称为有状态组件)或不带状态的组件(称为无状态组件)。你也可以使用基于函数的组件,通常称为函数组件。然而,在这个阶段,这些基于函数的组件无法拥有状态。
随后,Hooks被引入React。Hooks是让你在函数组件中使用React状态和生命周期特性的函数。这为在React中构建组件打开了一扇新的大门,形成了一种现代方法。
在这种现代方法中,你将React组件构建为函数。尽管React核心团队并未让基于类的组件过时(意味着你仍然可以使用基于类的语法编写应用),但由于其缺点,业界已呈现出一种远离类语法、拥抱函数组件的趋势。事实上,这种趋势非常强劲,以至于在现代React讨论中,人们通常只提“组件”,而不再区分“类组件”与“函数组件”。
因此,在现代React中,某种程度上暗示了编写组件的唯一方式是使用函数语法。


编写函数组件的语法



在编写这些函数组件时,需要注意,你可以选择将React组件编写为ES5函数声明,或者编写为被赋值为箭头函数的ES6常量变量。后一种方法更现代,但前一种方法同样有效。


以下是两种语法示例:
// ES5 函数声明方式
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
// ES6 箭头函数方式(更现代)
const Welcome = (props) => {
return <h1>Hello, {props.name}</h1>;
};
组件、数据、状态与事件的关系
回到React基础知识课程,除了理解组件及其在React中的工作方式,你还学习了数据、状态和事件。打个比方,组件可以说是传递数据的载体,而状态和事件则用于影响这些组件在浏览器中的渲染方式。
值得一提的是,虽然组件设置可能包含状态和事件等内容,但本节课的重点是组件本身。因此,尽管为了完成本课你需要理解和掌握许多内容,但目标是让你重温React中组件的基础知识,并为Little Lemon餐厅预订应用设置组件。


本节课中,我们一起学习了React组件的基础知识,了解了其从类组件到函数组件的演变历程,以及在现代React中编写组件的主要语法。组件作为构建React应用的基石,是传递数据和实现交互的核心载体。接下来,我们将开始为项目搭建具体的组件结构。
129:7_项目基础模块总结 🎯
在本节课中,我们将总结第二个模块的核心内容。这个模块涵盖了语义化结构、样式与响应式设计以及组件添加等关键概念,最终目标是构建一个响应式网站。
语义化HTML与元数据 🌐
上一节我们介绍了项目设置,本节中我们来看看语义化结构的重要性。你重新学习了如何设置语义化的HTML文档。本部分涵盖的另一个重要主题是元标签和开放图谱协议。

一个体现其重要性的实际例子是:在社交媒体上分享你的网页链接。如果没有正确设置元标签和开放图谱协议标签,你分享的链接可能无法在各种社交媒体或聊天窗口中显示图片,或者可能显示网站中与所分享链接不相关的其他图片。
开放图谱协议有助于解决这类棘手问题。

CSS样式与响应式布局 🎨
在模块的第二课中,你重温了CSS样式。具体来说,你有机会重新学习CSS Grid及其与响应式和网站布局的关系。
CSS Grid功能非常强大,它允许你构建几乎任何类型的网站布局。尽管它的语法有时会有些复杂,但就像任何其他与Web开发相关的技术一样,你对CSS Grid的练习越多,掌握得就越好。
这堂课是一个进一步实践你在本专业系列课程中获得的知识的机会。
React组件实践 ⚛️
在模块的第三课中,你开始为Little Lemon餐厅Web应用添加组件。组件是React乃至整个现代Web开发的基石。
这意味着理解React中组件的工作原理对你来说是一项至关重要的知识。因此,你完成了一堂完全专注于这项技能的课程,重温了React中组件的基础知识并将你的知识付诸实践。
在React中实现组件,同时确保你的Web应用看起来精致且样式正确,这与上一个模块的内容很好地结合了起来。在上一模块中,你设置了项目并围绕应用本身做了一些规划,同时考虑了所有UX/UI因素。
在本模块中,你将组件、UX/UI与一些样式和基于CSS Grid的布局结合起来。
总结与展望 🚀
现在项目基础部分已经完成,你已经准备好继续前进了。在下一个模块中,你将编写一些特定的交互功能,使Little Lemon网站的访问者能够根据客人数量和其他相关用户需求来预订餐桌。

本节课中我们一起学习了:语义化HTML与元数据的重要性、使用CSS Grid创建响应式布局,以及React组件的核心概念与实践。这些知识为构建功能完整、用户体验良好的Web应用奠定了坚实的基础。
130:8_客户表预订
在本节课中,我们将为 Little Lemon 网站实现“预订餐桌”功能。为了正确完成此功能,我们需要回顾三个主要的 React 概念:在 React 中处理状态、处理表单以及编写单元测试。
状态(State)在 React 中的重要性
上一节我们介绍了本节课的目标,本节中我们来看看 React 状态的基础概念。


能够处理状态是 React 的基石之一。试想一下,如果 React 中没有状态,那么最终得到的将只是一系列静态组件。React 将仅仅是一种将大段 HTML 代码分割成更易于理解的小部分的方式。
换句话说,你仍然会得到组件,但这些组件不允许任何交互性。在这种情况下,组件的唯一好处在于它们允许你将描述整个网页的长代码分割成更小的块。除此之外,使用它们并没有真正的优势。这说明在 React 中,组件和状态是密不可分的。
一个简单的状态示例:计数器应用
以下是学习 React 状态时,给初学者展示的最简单示例之一:计数器应用或计数器组件。
一个计数器可能包含一个段落,该段落以一个特定的数字值开始。在该段落下方会有按钮,点击这些按钮会更新段落中显示的数字值。
要能够独立编写这样的简单示例,你需要了解 React 中的渲染,以及如何使用 useState Hook 和事件处理代码。
当然,这是一个非常基础的例子,但它经常被使用,因为它很好地演示了在 React 中处理状态所需了解和使用的不同知识。
状态与表单:受控与非受控组件
状态和 React 也与表单处理紧密相关,特别是受控组件和非受控组件。
- 使用受控组件时,你通过 React 状态来控制给定的表单。
- 使用非受控组件时,你通过底层的 DOM 来控制给定的表单。这当然需要使用另一个 Hook,即
useRefHook。
考虑到这两个概念,你需要定义新的预订页面,以便 Little Lemon 的顾客可以提交新的预订。



代码质量保障:单元测试
一旦你创建了一个应用,如何知道它运行良好?如何知道之前的需求、质量保证(QA)部门、客户或顾客的要求都已得到满足?如何知道对应用功能的新增没有破坏之前的功能?
这些都是需要应对的严峻挑战。但幸运的是,我们有测试。这就是为什么在本节课中,你还需要为你的代码编写单元测试。
总结与学习建议
最后,请注意,结合本视频中简要提到的概念并非易事。掌握所有这些概念和技术需要一些时间。因此,在处理此功能时,你应该预留足够的时间。如果需要,也可以参考本课程计划中的其他资料。



让我们开始吧。
131:9_查询预订表 API 📡
在本节课中,我们将学习如何在小柠檬网站的用户进行餐桌预订时,使用 API 发送预订数据。为了正确实现此功能,我们需要回顾一些基础概念。
回顾 JavaScript 异步操作
首先,我们需要回顾 JavaScript 中的异步操作主题。显然,这是一个独立于 React 本身的主题,因为 React 是基于 JavaScript 构建的。这意味着你必须至少熟悉 JavaScript 中同步和异步的基本概念。
例如,你需要理解 JavaScript 作为一门同步、单线程的语言,是如何处理异步操作的一般原理。最简短的回答是:它并不真正直接处理。它只是利用其他内置的浏览器功能将工作委托给它们,然后接受这些工作的结果。这些功能被称为浏览器 API,有时也称为 Web API。
当然,这不应与你可以连接以从网络获取数据的第三方 API 混淆。为了成功完成本课任务,你需要在 React 中实现此功能之前,了解如何在纯 JavaScript 中处理对第三方数据的请求。


幸运的是,其机制大体相同,只是 React 坚持使用专门的 Hook(即 useEffect Hook)来处理副作用。

使用 Fetch 方法获取数据
在 JavaScript 中有几种方法可以请求第三方 JSON 数据,但更流行的方法之一是使用 fetch 方法,它是一个内置的 JavaScript 外观函数。
fetch 方法使用 XHR API(或称 XMLHttpRequest API)。这是一项内置在浏览器中的功能,但它不是浏览器 JavaScript 引擎的一部分。这就是为什么我们说 fetch 方法是一个外观函数。
当你调用 fetch 方法时,它会返回一个 Promise。这意味着你还需要至少从表面层面理解 Promise 在 JavaScript 以及延伸至 React 中是如何工作的。
理解 Promise


一个 Promise 是一个可能在稍后时间被履行的对象。实际上,JavaScript 中的每个 Promise 都可以存在于三种状态之一:等待中(Pending)、已履行(Fulfilled) 和 已拒绝(Rejected)。
如果 Promise 被履行,那么 JavaScript 引擎就可以自由执行所有作为 .then() 函数调用链接到原始 fetch 调用的方法。一旦收到 JSON 数据,你就可以用该数据更新本地组件的状态。
在 React 中处理副作用


因为调用 fetch 方法构成了一个副作用(即 React 之外的事情),获取这些数据需要使用 useEffect Hook 来更新状态。你可以使用 useState Hook 或 useReducer Hook 来管理状态。
那么,为什么 React 在获取第三方 JSON 数据的情况下坚持使用 useEffect Hook 呢?主要原因是为了将副作用与组件的渲染逻辑分离,并确保数据获取在正确的组件生命周期阶段(如组件挂载后)发生。
提交数据到 API
你还需要能够将数据提交到 API,就像在本课程之前的课程中所做的那样。你将获得一些练习,目标是完成 API 单元测试。

最后,像往常一样,你还需要使用 Git 来跟踪最新的更新。


本课中有相当多的任务需要完成。让我们开始吧。

本节课总结:在本节课中,我们一起学习了如何通过 API 处理数据发送。我们回顾了 JavaScript 异步操作和 Promise 的核心概念,介绍了使用 fetch 方法获取数据,并解释了在 React 中使用 useEffect Hook 处理数据获取这类副作用的必要性。这些知识是构建能够与后端服务交互的现代 React 应用程序的基础。
132:10_用户体验的重要性 😊
在本节课中,我们将要学习用户体验(UX)的核心概念及其在表单设计中的具体应用。我们将探讨表单验证、可访问性以及如何评估表单的UX/UI,目标是让表单尽可能地对用户友好。
概述:什么是用户体验? 😊
用户体验至关重要,因为它致力于满足用户的需求。其目标是提供令人满意的体验,从而维持用户对产品或服务的忠诚度。
每个人的用户体验都是独特的。在创建产品时,最重要的一点是:即使是你设计了它,你也可能不是它的用户。因此,你不能假设用户想要或需要什么。
你可能会问,什么构成了出色的体验?要找到答案,你应该贴近你的用户。与他们互动,观察他们如何使用你的产品,并思考他们为何做出某些选择。保持关注并提出问题,因为你可以从消费者和客户那里学到很多,从而帮助你为用户创造出色的体验。
表单设计的目标 😊
本节课将围绕“小柠檬餐厅”网页应用中的“预订餐桌”功能展开,具体涵盖以下三个核心方面:
- 表单验证
- 可访问性
- 表单的UX/UI评估
你的目标应该是让表单尽可能地对用户友好,因为填写表单对人们来说可能是一件繁琐的事情。

1. 表单验证 😊

上一节我们介绍了课程的整体目标,本节中我们来看看第一个核心环节:表单验证。
表单验证是一个互联网技术术语,用于检查用户在网页表单中提交的数据是否准确。但请注意,这个过程的情感层面比技术层面更重要。表单应该在用户出错时提醒他们,或者在他们输入正确时给予确认,以增强用户的信心。
在练习中,你将完成客户端表单验证,检查它是否能正确验证“小柠檬餐厅”的用户数据。
以下是表单验证的一个基本代码逻辑示例:
function validateForm(formData) {
if (formData.email.includes('@')) {
// 验证通过
return true;
} else {
// 验证失败,提示用户
alert('请输入有效的电子邮件地址。');
return false;
}
}
此外,你还将完成一个练习,为表单验证和提交功能实现单元测试。
2. 可访问性 😊
在确保了表单的功能性之后,我们需要确保所有用户都能使用它。这就引出了可访问性的概念。
除了遵守确保包容性的可访问性法规外,设计师应尽力在所有使用场景中涵盖所有潜在用户。因此,产品应该让每个人都能使用,无论他们以何种方式接触它。
你将通过完成一个练习来实践这一原则,确保“小柠檬餐厅预订餐桌”网页应用对所有用户都是可访问的。
一个简单的可访问性实践是为图片添加描述文本:
<img src="restaurant.jpg" alt="小柠檬餐厅明亮的室内就餐环境,配有木质桌椅和绿色植物装饰">
3. 评估UX/UI 😊
最后,在表单功能完备且易于访问的基础上,我们需要评估其用户体验的好坏。
你将回顾如何通过关注先前课程中涵盖的原则来评估UX/UI,即“UX/UI设计原则”。接着,你将执行一次启发式评估,将你的界面可用性与公认的可用性原则进行比较,以确定“小柠檬预订餐桌”界面的可用性程度。
本节课包含技术和概念两个方面,以及多种练习。内容相当丰富,让我们开始深入学习吧。😊



总结 😊

本节课中我们一起学习了:
- 用户体验(UX) 的核心是满足用户需求并创造满意体验。
- 设计表单验证时,需兼顾技术准确性与用户情感反馈。
- 遵循可访问性原则,确保产品能被所有用户平等使用。
- 运用启发式评估等方法,依据设计原则来评估和改进表单的UX/UI。

通过掌握这些知识并完成相关练习,你将能够创建出不仅功能正确、而且体验出色、包容性强的网页表单。
133:项目功能模块总结 🎉
在本节课中,我们将对课程第三模块“项目功能”进行总结。你已经完成了为Little Lemon餐厅创建前端应用的核心功能模块。本模块的每节课都聚焦于不同的概念、技术与方法。
模块内容概览
本模块主要涵盖三个核心部分:构建餐桌预订系统、与API交互以增强应用、以及通过多种方式提升用户体验。最终,完成这些课程后,你应该掌握如何搭建一个餐桌预订系统的前端,并利用HTML5验证和实时数据来优化用户体验。
现在,让我们简要回顾一下每节课的具体内容。
第一课:构建预订系统 🛠️
在第一课中,你构建了餐桌预订系统。为此,你需要重新回顾并应用以下核心概念。
以下是构建过程中涉及的关键技术点:
- React组件:使用组件化思想构建用户界面。
- React状态管理:在组件中使用
useState或useReducer来管理状态。 - 跨组件状态共享:在多个组件之间有效地传递和共享状态。
- 语义化代码:编写具有良好语义结构的HTML,提升可访问性与代码可读性。
- 单元测试:为你的功能编写测试用例,确保代码质量。
- Git版本控制:像在其他课程中一样,使用Git来跟踪和管理代码的更新。
第二课:与API交互 🌐
在第二课中,你通过与应用编程接口(API)交互,进一步改进了你的应用。
以下是实现API交互的主要步骤:
- 发起API请求:使用
fetch函数或类似方法查询餐桌预订API。 - 处理副作用:利用
useEffect钩子来处理数据获取等副作用操作。 - 更新应用状态:根据API调用返回的JSON数据,使用
useState或useReducer来更新应用的内部状态。 - 增强测试与跟踪:编写更多的单元测试,并继续使用Git跟踪项目进展。
第三课:提升用户体验 ✨
本模块的第三课专注于全方位提升你应用的用户体验。
你首先重新认识了表单验证的重要性,并为预订功能中的表单添加或改进了客户端验证,以优化用户体验。在完成客户端表单验证练习后,你亲身体会到填写一个简单的表单也可能让用户感到沮丧,从而更理解优化的重要性。
接着,你通过确保组件的可访问性,使你的Web应用能够服务于所有用户。




通过关注并融入可访问性设计,Little Lemon的“预订餐桌”网页应用现在应该能在所有使用场景下服务于所有潜在用户。
之后,你重新学习了可用性评估的方法与原则,并完成了一次启发式评估,以识别UI设计中的可用性问题,同时评估了这些问题的严重性与紧迫性,以便立即修复或将其纳入迭代设计流程。

与设计流程的所有阶段一样,你添加了额外的单元测试,并持续使用Git跟踪项目。





总结与展望 🚀
至此,你已经完成了涵盖“项目功能”的课程第三模块。你现在已经准备好进入最后一个模块,在那里你将进一步深化知识,并且截至目前的所有辛勤工作将通过自我评估和同行评审来检验。
本节课中,我们一起回顾了构建Little Lemon预订应用功能模块的全过程:从搭建系统基础、集成外部API到精细化打磨用户体验。你掌握了React状态管理、API交互、表单验证、可访问性设计及可用性评估等一系列核心前端开发技能。
134:12_毕业项目课程回顾 📚
在本节课中,我们将回顾毕业项目课程的全部内容,并了解最后一个模块——项目评估与课程总结的具体安排。
概述
欢迎来到毕业项目课程的第四个也是最后一个模块。本节课程将对整个项目历程进行回顾,并详细介绍最终评估阶段的任务,包括项目自评、同行评审以及课程总结。
课程回顾 🔄
上一节我们完成了项目的核心功能开发。现在,让我们快速回顾一下你在本课程中已涵盖的内容。

模块一:项目启动与设计
在第一个模块中,你被介绍了课程及其大纲,有机会认识你的同学,并学习了如何在本课程中取得成功。你设置了项目,包括:
- 创建初始的 React 应用。
- 使用 Git 跟踪你的更改。
- 规划项目的用户体验和用户界面。这涉及在 Figma 中使用线框图并应用设计基础。
模块二:项目基础
模块二涵盖了项目的基础。你完成了以下工作:
- 处理代码中的语义结构,确保尽可能使用语义化的 HTML5 标签。
- 为你的应用处理元标签和开放图谱协议设置。
- 重新使用 CSS Grid 来创建响应式布局。
模块三:项目功能
第三个模块“项目功能”涉及为 Little Lemon 网络应用编写餐桌预订系统并定义预订页面。你实现了:
- 单元测试。
- 与 API 协作。
- 进一步打磨了应用的 UI。
- 验证了用户数据。
- 为你的应用编写了更多测试。
模块四:项目评估 📋


以上内容将你带到了当前的模块——项目评估。既然你已经通过完成 Little Lemon 项目展示了前端开发技能,现在是时候进行评估了。
分级评估
在本模块的第一课,即恰如其分地命名为“分级评估”的课程中,你需要通过以下方式参与评估过程:
- 进行项目自评。
- 在可用性、可访问性、设计和代码方面,对另一位学习者的解决方案进行同行评审。
别担心,这并不难做,并且会有指导让你更容易完成。这是一个你应该享受并从中学习的趣味任务。
本模块的核心是与你的同伴协作,并检查已完成的 Little Lemon 项目,以便将你的设计和代码与其他项目进行比较。这种方法让你有机会:
- 进一步巩固学习。
- 增进知识。
- 深入了解其他人如何应对你在本课程中被要求完成的相同挑战和练习。
完成同行评审后,你将有机会检查一个已完成的解决方案,以便将其与你自己的实现以及你同伴的实现进行比较。这样做的目的是确保你能接触到解决同一问题的另一种方法。如前所述,有机会检查完成同一任务的多种方式应该能增进你的学习,让你更广泛地理解各种解决方案,并最终提高你作为 React 开发者的能力。


你将以一个分级评估测验来结束本课。



课程总结
第二课将以课程总结的形式进行。你将有机会反思完成项目所遵循的历程、获得的知识、沿途取得的成就,以及完成本课程后潜在的下一步计划。

总结


本节课中,我们一起回顾了整个毕业项目课程的四个模块:从项目启动与设计、打下基础、实现功能,到最后的评估与总结。现在,你已经准备好进入评估阶段,通过自评和同行评审来巩固所学,并从他人的解决方案中获得新的见解。
135:恭喜你完成了毕业项目 🎉
在本节课中,我们将回顾并总结你在毕业项目课程中的学习成果与旅程。你已经完成了这个顶点项目课程,这是对你整个前端开发学习路径的一次重要检验和综合展示。

课程概述
你已抵达这个顶点项目课程的终点。你付出了辛勤努力才走到这里,并在前端开发者的旅程中取得了令人难以置信的进步。这门课程以及你所取得的一切成就,实际上是你在这个前端开发项目中完成的所有先前课程的结晶。
具体来说,这些课程包括:Web开发入门、JavaScript编程、版本控制、深入HTML与CSS、React基础、高级React以及UX/UI设计。
项目成果与技能巩固
通过这门课程,你得以巩固并展示了在整个项目中学到的知识和实践开发技能。这是通过动手构建一个相对复杂的应用程序来实现的,该程序模拟了真实世界项目的一些用例和要求。
最终,你通过为“小柠檬餐厅”组合构建一个现代前端应用程序,展示了在整个项目中学到的多项技能。现在你已经完成了这个Web应用项目,这是一个停下来反思的好时机。





如何进行项目反思



你可以从多个角度反思已完成的课程。例如,你可以回顾之前完成的课程,并思考它们如何与你在这门课程中所做的事情联系起来。
除了思考完成课程所需的知识,你也可以反思完成这个特定项目的过程。你可以问自己一些问题,例如:
- 项目的哪些部分最难?
- 项目的哪些部分最容易?
- 我从构建这个项目中获得了什么经验?
- 我是否会从重新学习部分先前课程甚至再次学习它们中受益?
你甚至可能发现,项目中需要完成的大部分练习和任务都很容易,并且你已经准备好迎接更大的挑战。
展望下一步
这为你顺利进入本项目的下一门课程奠定了良好基础。在每一门前端开发课程中,你已经发展了一套广泛的技能。现在,你已有机会应用所学的一切,并使用React构建了自己的功能完整的网站,你已经为旅程的下一步做好了准备。
因此,何不注册下一门课程呢?一旦你完成了所有课程,你将获得前端开发证书。这些证书提供了全球认可且行业认可的技术技能证明,也可以根据你的目标,作为进阶其他课程和项目的凭证。你可以选择深入学习基于高级角色的项目,或学习基础课程。
总结与祝贺


再次祝贺你完成这个顶点项目课程。请记住,在本课程中完成的项目证明了你对前端开发价值和能力的理解。你现在拥有了一个使用React构建的、完全可运行的网站,可以将其纳入你的作品集进行展示。
这不仅展示了你的技能和知识,也充分说明了你是一个自我驱动、富有创新精神的个体。感谢你,很荣幸能与你一同踏上这段探索之旅。祝你在未来一切顺利。
136:编程面试准备课程简介
在本节课中,我们将要学习编程面试准备课程的概述。课程将帮助你为编程面试中独特且具有挑战性的方面做好准备,包括你需要了解或应用的一些问题解决方法和计算机科学基础知识。
现在,让我们预览一下你将学习的关键概念和技能。
第一模块:面试基础与计算机科学入门
上一节我们介绍了课程概览,本节中我们来看看第一模块的内容。
在第一模块中,你将首先了解什么是编程面试,它可能包含哪些内容,以及你可能会遇到哪些类型的编程面试。
你还会探索如何为编程面试做准备,重点包括沟通技巧,例如解释你的思考过程、处理错误以及STAR方法。你还将学习如何使用伪代码来展示你如何得出解决方案。
以下是一些可能有助于任何实际解决方案设计的重要技巧,以及如何测试你的解决方案。
接下来,你将学习计算机科学入门知识,从二进制的基本概念开始,了解二进制如何与现实生活中的硬件和计算相关联。
你将探索内存以及计算机内存的关键组件,包括随机存取存储器(RAM)和只读存储器(ROM),并了解你的计算机如何使用内存来执行任务、处理信息和存储数据。
接下来,你将深入学习时间复杂度的核心概念——大O表示法。
公式: O(n), O(log n), O(n²) 等。
你将了解一些大O表示法的类型,以及它如何应用于算法处理。

你将探索空间复杂度,这本质上是计算结果所需的空间。

第二模块:数据结构
上一节我们介绍了计算机科学基础,本节中我们来看看数据结构。
在第二模块中,你将学习数据结构,了解每种数据结构都有其特定的优点和局限性。因此,在设计解决方案时,理解每一种都至关重要。
你将从基本数据结构开始,了解不同编程语言之间数据结构的实现和能力,以及整体架构的相似模式。
你将探索主要的基本数据结构:字符串、整数、布尔值、数组和对象。
接下来,你将研究一些集合数据结构,从列表和集合开始。
然后,你将学习栈、队列和树。

代码示例(栈的抽象概念):
stack = []
stack.append('A') # 入栈
stack.pop() # 出栈

在继续学习一些高级数据结构之前,即哈希表、堆和图。
代码示例(哈希表/字典的抽象概念):
hash_table = {}
hash_table['key'] = 'value' # 插入
value = hash_table['key'] # 查找
第三模块:算法
上一节我们探讨了各种数据结构,本节中我们来看看算法的世界。
在第三模块中,你将学习算法入门,包括你可用的算法类型,以及如何最好地使用它们来排序和搜索数据。
你将首先探索排序算法,了解处理已排序数据或能够对自己的数据进行排序如何能显著节省时间。你将探索三种主要排序类型:选择排序、插入排序和快速排序,并了解到每种方法都有其权衡,在某些环境中比在其他环境中更有效。

接下来,你将发现搜索算法,以及每种类型如何为其自身的问题解决提供框架。
公式(二分查找时间复杂度): O(log n)
你还将深入了解搜索和排序算法中的时间和空间复杂度。
你将深入研究分治法、递归、动态规划和贪心算法所涉及的过程和底层机制。
课程总结与评估

最后,在最后一个模块中,你将有机会回顾在整个课程中学到的所有内容,然后参加分级课程测验,该测验将测试你在整个课程中学到的所有关键概念和技能。


总结图示: 课程涵盖从面试准备、计算机科学基础、数据结构到算法的完整路径。
本节课中,我们一起学习了编程面试准备课程的完整路线图。具体来说,你了解了本课程将如何帮助你为潜在的编程面试中独特且具有挑战性的方面做好准备,包括你需要了解或应用的一些问题解决方法和计算机科学基础知识。
现在,让我们开始学习吧。
137:介绍技术招聘流程 🚀
在本节课中,我们将一起了解大型科技公司(以Meta为例)完整的技术招聘流程。我们将从申请阶段开始,逐步深入到面试环节,最后讨论录用决策,帮助你全面理解如何准备和应对技术岗位的面试。
在美国的科技中心城市,寻找软件工程师和开发者的过程平均需要大约39个月。你将要经历的面试,有时会考察你在日常工作中不常使用的技能。面试不仅评估你的编程能力,还考察你的人际交往能力、项目推动能力以及与他人协作的能力。你无需担心穿着正装出席,完全可以穿着舒适,做你自己,专注于面试的技术环节。
面试官介绍 👥
大家好,我是Julie,是Meta纽约Instagram购物团队的软件工程师。
大家好,我是Maxxie Herrera,我的代词是They/Them。我是Meta社会影响力团队的软件工程师,在门洛帕克办公室工作。
大家好,我是Chanel Johnson,我在马里兰州远程为Meta工作,是Facebook应用核心架构团队的软件工程师,负责Facebook移动应用的基础设施。
大家好,我是Mariri Baallando,我是Meta内部Web3货币化团队的软件工程师,致力于探索创作者和网红如何通过NFT和加密货币等Web3技术在Facebook平台上谋生。
招聘流程的三个阶段 📊
上一节我们认识了面试官,本节中我们来看看招聘流程的整体框架。整个流程可以概括为三个主要阶段:申请流程、实际面试环节以及最终的校准与录用流程。
- 申请流程:此阶段主要由招聘人员筛选你的简历和工作经验。招聘人员会与你沟通,更好地了解你的技能、经验以及职业目标,以确保你与职位相匹配。
- 实际面试环节:这个阶段进一步细分为技术面试、架构面试和行为面试。
- 校准与录用流程:所有参与面试流程的人员会聚集在一起,讨论你在各阶段的表现,并决定是否向你发出录用通知。
深入面试环节 🔍
了解了整体框架后,本节我们将深入探讨实际面试环节的具体构成。面试环节通常包含多种类型的评估。
- 技术面试:可能会有1到4轮甚至更多。例如,编码面试(Coderpad Interview),你通过电话或语音与招聘人员或工程师一起解决技术挑战。
- 行为面试:面试官会通过交流了解与你共事的感觉,以及你解决问题的方式。
- 架构面试:你需要设计一个端到端的产品,并讨论其完整的系统架构。
如果你面试的是特定技术方向,如iOS、Android、机器学习或人工智能,你应该准备应对该领域相关的问题。例如,面试iOS岗位时,除了算法和数据结构问题,还会被问到iOS相关或日常工作中会遇到的问题。Android、AI或ML工程师的情况也类似。
常见挑战与建议 💡
在面试过程中,候选人常会遇到一些挑战。以下是面试官观察到的一些常见问题以及他们的建议。
- 申请阶段被筛选掉:许多候选人在此阶段被淘汰。精心打磨简历,聚焦于真实项目经验会很有帮助。如果没有正式工作经验,专注于个人项目同样可以展示你的实际能力和对目标职位的热情。
- 技术面试中的沟通问题:很多人在技术面试环节失利。一个主要原因是候选人编写代码时,没有解释自己的思考过程,没有提出澄清性问题,或者没有留意面试官给出的提示。沟通技巧是此环节的关键。
- 缺乏结构化解题方法:一些候选人在遇到陌生问题时没有结构化的解决流程,直接开始“蛮干编码”(cowboy coding),甚至不清楚要解决什么问题。因此,建立一个稳健的解题流程(robust problem-solving process)对于应对未知问题至关重要。
- 无法接受反馈:一个常见问题是候选人陷入困境后,过于纠结而无法接受反馈。面试官寻求的是对问题的全面解决思路,而不仅仅是答案本身,包括你如何解决问题以及与面试官的协作。
面试心态与公司选择 🧠
除了技术准备,心态和职业选择同样重要。本节我们来听听面试官们关于心态和如何选择公司的建议。
最大的心得是做真实的自己。我发现,毫无保留地展现真我,不仅成功率更高,压力也小得多。这意味着我作为候选人呈现的就是真实的自己,不试图隐藏任何东西。因为这就是我想在工作中成为的样子,也是我想在生活中成为的样子。我认为拥有这种心态会让整个过程轻松许多,无论结果如何。
认真思考你想要什么,并选择与你价值观产生共鸣的公司。除了薪酬和公司声望,还有其他因素会影响你在公司的幸福感。请确保列出这些因素,并在选择公司时加以考虑。

实用准备策略 🏋️
最后,我们来探讨最实用的准备策略。我的最大建议是进行尽可能多的练习。主要有两种方式:
- 在进行LeetCode练习或模拟面试时,即使你无法解决问题或卡住了,也要完整地走完全程,就像真正的面试一样。这能为你实际面试时提供很好的练习。
- 尽可能多地申请职位。这能增加你获得面试机会的概率。一旦进入真实的面试环境,如果你之前有过类似经验,就会容易得多。这有助于缓解紧张情绪,并让你获得更多练习。
在技术领域,我们寻找的并非某一类特定的人。我们需要不同的背景、视角和经验,因为只有这样,我们才能让产品变得更好。这是一个非常有价值的过程,你可以产生影响,结识他人,不断学习。在这份工作中,你永远不会感到无聊。
当你开始面试时,你会经历许多起伏。这些都是可以学习的经验。如果你坚持努力,不断学习,最终你将获得一个角色,开启一段精彩的技术职业生涯。你所做的准备将引导你在世界上产生巨大的影响,因为软件无处不在,被数百万乃至数十亿人使用。因此,我认为,为了在一家有影响力的大型科技公司获得职位所付出的努力是非常值得的。

本节课中,我们一起学习了技术招聘的完整流程,从申请到面试再到录用。我们了解了面试的不同类型、常见的挑战以及来自Meta工程师的宝贵建议。记住,充分准备、保持真实、有效沟通,并选择与你价值观相符的公司,是成功通过技术面试的关键。祝你面试顺利!
138:什么是编码面试 👨💻
在本节课中,我们将学习什么是技术面试,并掌握一套系统性的方法来应对编码面试。我们将从理解面试的目的开始,逐步介绍准备和解题的核心步骤,帮助你自信地展示技术能力。

概述

技术面试是你展示编码能力的场合。通常,你已经通过了初步的筛选电话面试,证明了你的软技能符合公司要求。软技能关乎你的社交行为能力,包括清晰的沟通、良好的职业道德,以及你的个人表现是否与公司价值观一致。技术面试的目的则是确定你从技术上能否胜任该职位的职责。
在本视频中,你将学习如何应对技术面试。参加编码面试时,牢记以下步骤会对你有所帮助。

面试核心步骤

以下是应对编码面试的四个关键步骤。深入理解这些概念将帮助你掌握如何应用这套方法。
第一步:充分准备,迎接成功
许多候选人可能会对技术面试感到些许畏惧。如果被问到问题而大脑一片空白怎么办?幸运的是,你可以采取一些步骤来为成功做好准备。未能准备就是在准备失败。
第二步:首先进行概念性解题
在实施具体解决方案之前,最好先对问题和答案的样貌有一个清晰的认识。花些时间确保你清楚被问及的内容。面试官不会介意你从一开始就寻求澄清。如果有白板,就使用它。在勾勒潜在解决方案之前,先记下问题的主要要点。
这是一个绝佳的机会,可以在编写任何代码之前,展示你使用伪代码推理问题的能力。展示你推理问题的能力,就等于成功了一半。记住,编码能力是可以被教授的,而解决问题的能力则是备受追捧的才能。在评估问题时,要大声说出你的思考过程,向面试官展示你如何参与问题解决,以及你为什么选择一种方法而非另一种。
如果你能将问题与你已知的问题联系起来,这会是一个很大的帮助。在本课程后面,有一个关于“分而治之”实践的视频。这是运用该策略的好机会。将大问题分解成小问题,有助于解决看似复杂的问题。如果存在额外的时间限制而你超出了允许的时间,你仍然能够展示出功能完整的代码块。
第三步:运用合适的工具
编码面试中提出的问题类型需要在面试时间内完成。因此,解决方案本质上不会过于复杂。它们旨在微观层面测试你的问题解决能力,以及你对可用工具的认知。
考虑经典的“数袜子”问题。你得到一个代表袜子颜色的数组:黄色袜子用1表示,蓝色用2,红色用3,绿色用4,橙色用5。袜子颜色对应数字,例如:[1, 2, 2, 1, 1, 3, 5, 1, 4, 4]。需要确定存在多少双相同颜色的袜子。
这里有四个1,相当于两双黄袜子。3和5代表单只袜子(一只红袜和一只橙袜),它们没有配对的袜子来组成一双。有两个2和两个4,分别代表一双蓝袜子和一双绿袜子。
为了简洁地解决这个问题,你可以利用合适的数据结构。在本课程后面,你将复习数据结构。有一个视频概述了字典如何存储键值对。一个解决方案是使用袜子颜色作为键,计数作为值,然后遍历字典并检索所有奇数,这表示存在单只袜子。
虽然有很多编程方法可以解决这个问题,但使用现有的结构可以最大限度地减少所需代码,并展示对基础构建模块的熟悉程度。只要可能,尽量利用现有的方法,而不是尝试手动实现解决方案。
除了熟悉常用的数据结构外,在进行任何技术面试之前,请复习常见的排序和搜索算法。

第四步:优化你的解决方案
优化代码是一种良好的实践;这意味着编写或重写代码,使程序使用尽可能少的内存或磁盘空间,并最小化CPU时间或网络带宽。编写出解决方案是迈向一个体面解决方案的好一步。确保你留出时间来优化你的代码。
本课程中你将遇到的另一个概念是时间和空间复杂度。你能向面试官展示你理解这些关键概念吗?简单来说,这是一种衡量你的解决方案运行速度和占用空间的方法。



在呈现答案时,概述你的解决方案的时间和空间复杂度,然后看看是否能改进。识别任何重复或重叠的代码,你可以将这些代码模块化为一个可重复调用的函数,并在可能时重用代码。良好编程的一个常被重复的原则是DRY(Don‘t Repeat Yourself)。其思想是在代码中只表达一次,并根据需要尽可能多地重用。此外,如果由于模块化或未完成的思路导致代码的某些部分不再需要,请将其删除。
避免过多的循环调用。如果你在数组中搜索一个值,在找到该项时终止循环。一个非常容易实现的代码优化是,在找到值时包含一个返回语句,或者使用依赖于布尔值的循环。一旦找到结果,循环就可以终止。这提高了整体效率并降低了时间复杂度。
空间复杂度完全是关于巧妙地使用内存。只要可能,避免创建超出需要的变量。
总结
在本视频中,你学习了一些无论面对何种挑战都可以使用的方法。即使你不熟悉某个问题,或者在规定时间内没有得出结果,也要始终努力展示你的推理过程和最佳实践方法。

通过在线解答练习题来为技术面试做准备,并在可能的情况下,对每个挑战采用类似的方法论。这样,无论面对何种挑战,你都能在一个熟悉的框架下工作。编码面试可能看起来是一项艰巨的任务,总会涉及未知因素,你对成功的渴望可能会带来一些面试前的紧张。保持冷静,逻辑思考。祝你好运。
139:沟通技巧
在本节课中,我们将学习面试中至关重要的沟通技巧。成功的面试几乎完全取决于你如何与面试官沟通。我们将探讨如何通过语言和非语言方式,有效地展示你与职位的匹配度。
第一印象的力量 💡
上一节我们介绍了沟通的重要性,本节中我们来看看如何建立良好的第一印象。永远不要低估第一印象的力量。作为潜在雇员,你与面试官的每一次互动都应反映出你能为组织带来的能力。
以下是建立良好第一印象的几个关键非语言信号:
- 守时:最好在预定会议开始前至少10分钟到达,尤其是在你不确定具体面试地点时。寻找地点需要时间,你应该以从容、准备就绪的姿态出现,而不是气喘吁吁、慌乱不安。
- 眼神交流与倾听:在面试过程中,确保保持眼神交流,并积极倾听被问到的问题。
- 着装得体:通常,工作面试要求穿着专业或商务装。确保衣服干净整洁,这既表示尊重,也为自己树立正面形象。
- 保持良好姿态:保持良好的姿势,避免坐立不安、不必要地触摸脸部或绞手。尽管在会议前和会议中感到紧张是可以理解的,但这些手势可能会无意中传达出你感觉无法胜任这项任务的信息。
缓解紧张的一个好方法是在会议前做好充分准备。确保你了解工作的内容、公司的业务及其价值观。
语言沟通的艺术 🗣️
上一节我们讨论了非语言沟通,本节中我们来看看同样重要的语言沟通。你需要能够与面试官有效交谈。
在面试中如何表现的一个良好指标是观察面试官。仔细倾听,他们会通过提问来考察你是否符合所需的技能和个性特征。通常,面试官会遵循 80/20法则:他们说话占20%的时间,让你展示自己占80%的时间。因此,在回答之前,要让面试官完全把问题引导给你。
以下是进行有效语言沟通的几个要点:
- 使用清晰简洁的语言:特别是在做了充分准备的情况下,很容易试图用你所知道的关于某个主题的一切来回答问题。这可能导致回答冗长散漫。更好的回答是紧扣主题,并为后续问题留出机会。一个好的面试官会提出相关后续问题,这能让对话自然流畅。
- 避免夸大或自我否定:注意不要使用可能传达对自己负面态度的情绪化词语。例如,与其说“我在那个任务上失败了”,不如说“那个任务很有挑战性,但它为我未来探索的研究领域提供了一些思路”。此外,应避免使用过多的俚语、脏话或不恰当的幽默。
运用STAR方法回答问题 ⭐
随着面试的进行,讨论将更多地集中在你的能力和对职位的适合度上。重要的是你要能传达出你为什么是合适的人选。通常,问题会集中在业务需求上,无论是正在使用的技术还是必须克服的问题。面试官想知道你在工作中遇到问题时将如何应对。
因此,尝试使用 STAR方法 来回答问题。在回答问题时,包含以下四个要点:情境、任务、行动 和 结果。
以下是更清晰地演示该方法的一些示例问题:
- 情境:当时的情况背景是什么?是什么项目,面临哪些挑战?
- 任务:你的职责和任务是什么?
- 行动:你采取了哪些行动来纠正或应对挑战?
- 结果:你的行动带来了什么结果或成果?采取这种方法对结果产生了什么影响?
使用这种方法作为回答问题的模板,将使你的回答更有深度。它提供了一个可行的回答框架,也让面试官有机会在你感觉舒适的讨论领域提出更多相关问题。
总结与回顾 📝

本节课中我们一起学习了面试沟通的核心要点。面试官会寻找能够清晰传达概念的候选人。你的首要任务是通过语言和非语言方式,沟通你为什么适合这个职位。最后,STAR方法 是一个应对面试中可能出现的各种技术性问题的非常高效的框架。

在任何公司的任何职位上,你可能都需要与利益相关者打交道,无论是处理角色中的复杂问题,还是解释为什么某个解决方案是最优路径。因此,请将你学到的关于沟通的知识自信地应用到实践中。
140:4_期望从技术面试获得什么
在本节课中,我们将了解Meta技术面试的构成、面试官的期望以及如何有效准备。课程内容基于Meta软件工程师的经验分享,旨在帮助你理解面试流程并提升表现。
根据一家知名招聘网站的数据,近一半的软件开发人员认为编码面试是所有技术面试环节中压力最大的部分。

面试中你会被问到很多技术问题、架构问题和组织问题。面试官真正想了解的是你的为人以及你所重视的价值观。
我面试时的一种练习方法是,对着一个真实的毛绒玩具讲话。这迫使我完整地练习整个面试过程,就像真的在出声思考一样。
我认为Meta的面试官都受过良好培训,能让面试者感到受欢迎,并能舒适地一起解决问题。

面试官介绍
- Marri Baallando:Meta软件工程师,在FB Web3货币化团队工作,帮助创作者和影响者通过Facebook产品谋生。
- Maxxie Herrera:Meta软件工程师,在Social Impact Award团队,于门洛帕克办公室工作。
- Julie:Meta软件工程师,在纽约的IG购物团队工作。
- Chanel Johnson:在马里兰州为Meta远程工作,是Facebook Acorps架构团队的软件工程师,负责Facebook移动应用的基础设施。
面试类型概述 🧩
上一部分我们认识了分享经验的工程师,接下来我们具体看看Meta技术面试通常包含哪些类型。
面试通常分为三种不同类型:技术面试、架构面试和行为面试。
以下是每种面试的详细说明:
- 技术面试:即常规的算法编码面试。在面试过程中你通常会有2到4轮这样的面试。其中一到两轮是初筛,通过初筛后还会有几轮。每道题的答题时间大约是20到30分钟,都是经典的算法题。
- 架构面试:这是一场约45分钟到1小时的面试。你会被问到如何构建一个端到端的功能。问题可能更偏向产品,例如“构建俄罗斯方块”;也可能更偏向后端,侧重于数据流以及如何为大量用户扩展系统。
- 行为面试:你会被问到关于你与他人合作的经验、曾面临的挑战以及参与过的激动人心的项目等问题。
我通常会问的一个问题是:“你在团队合作中,事情进展不顺利的一次经历是什么?”我问这个问题的主要原因是,在Meta,你需要与很多人合作,这不是一份孤立的工作。因此,你需要学习如何沟通,以及如何真正与他人一起学习和成长。
面试问题示例与准备 💡
了解了面试类型后,我们来看看具体的面试问题例子以及如何有针对性地准备。
当我面试时,我被问到了很多我作为iOS工程师预料之中的通用问题。例如,“我将如何构建Facebook应用中的新闻推送界面?”我需要说明我会创建哪些对象、这些对象如何相互通信、我期望的网络API是什么,并能够从高层次描述我将如何应对这些挑战,以及作为iOS工程师我将如何构建应用结构。这是一个非常经典的问题。
一个练习面试问题的技巧是,在白板编程时讲解你的解决方案。我会和朋友或同事一起做的是,当你写出解决方案时,解释你的思考过程:你在考虑时间复杂度吗?你在考虑如何让它更快、如何减少空间占用吗?这些也是我们作为面试官希望看到的,因为在实际工作中编码时,你需要思考这些事情。这是一个很好的练习,让你在梳理代码时解释:“我这样做是因为X、Y、Z,同时我也在考虑这一点。”
作为面试官,我实际上衷心希望你能表现出色。因为我希望的不是坐在那里盯着屏幕45分钟,看着你挣扎。我更愿意看到你成功,并和我们一起解决问题,这样我才能尽可能多地收集关于你的信息,以便我能做出你是否适合Meta的正确决定。
面试表现要点与常见误区 ⚠️
我们已经讨论了如何准备和回答问题,现在让我们关注面试当天的表现和需要避免的常见错误。
关于面试着装,关键是要做自己,穿你觉得舒服的衣服。没人指望你穿西装来。我见过很多面试者只是穿着T恤和牛仔裤。
我真正寻找的候选人是愿意解释他们的想法、愿意深入参与,并展现出高度自信和知识水平的人。
我见到人们最常犯的错误是,他们可能是一名非常有才华的iOS工程师,但他们的软件通用技能可能有所欠缺。因此,请确保你为那些通用的算法和数据结构类型的问题做好准备,因为无论你面试的是哪个职位方向,这些问题都会出现。
有时在技术挑战中你会卡住。而你能做的最糟糕的事情就是什么都不说。因为我无法读懂你的心思,也不知道你在做什么。所以你确实需要解释你在想什么、你是怎么卡住的、为什么卡住了。因为即使你没有答对问题,如果你能表现出你对问题有很好的理解,那也足以让你进入下一轮。
给面试官留下深刻印象的关键 🌟
避免错误是基础,那么如何才能脱颖而出,给面试官留下深刻印象呢?本节将揭示面试官看重的特质。
候选人真正让我印象深刻的是当他们为面试做好了准备。你显然应该为面试做好准备,但当他们研究过公司、了解公司的价值观、知道这些价值观如何应用到他们自己身上以及他们能为Meta带来什么时,这很加分。
在面试中,候选人真正让我印象深刻的是当他们能够接受我的反馈。即使他们已经走在正确的轨道上,有时如果我能看到这一点并稍微推动他们一下,这是一个非常好的迹象,表明他们能听取我的反馈并与我合作解决手头的任何问题。这很好地表明他们是我在实际工作中愿意与之共事的人。

面试实际上是展示你是谁,展示你知道什么、你如何成长、如何学习的过程,这没有什么单一的技巧。你真的需要稍微了解一下自己,以及你如何展示这些特质,并让它对你起作用。
一个非常热情、对面试感到兴奋并且态度非常积极的候选人,这真正显示出他是否是我想要共事的人,一个会对所做工作充满热情的人,我认为这正是通往成功的激情所在。
总结与鼓励 🎯
在本节课中,我们一起学习了Meta技术面试的主要类型,包括技术面试、架构面试和行为面试。我们了解了面试官的期望,例如希望候选人能够清晰地解释思路、积极协作并展现出扎实的基础知识。我们还探讨了有效的准备方法,如模拟练习和深入研究公司价值观,并指出了需要避免的常见错误,如沉默不语或忽视通用算法准备。
尽管面试过程可能很长,但最终能在像Meta这样的地方工作,处理每天被数百万人使用的产品,并与一些你见过的最聪明的人共事,是非常有回报的。这会帮助你在短时间内获得惊人的成长。
感谢观看,希望你能学到一些在Meta面试的好技巧,并祝你接下来的旅程好运。

141:二进制
在本节课中,我们将要学习二进制数,了解它们是什么,以及计算机如何使用它们来表示人类语言。我们将学习位置编码如何将有限的数字集合转化为无限大小的数值表示。最后,我们将学习如何通过计算一个数的幂,来确定这种简单表示法可以容纳多少种状态。
十进制系统基础

传统上,我们使用0到9这10个不同的数字进行计数。这源于数学的早期发展,是人类拥有10根手指和10根脚趾的自然结果。使用10个数字的计数方式被称为十进制。

十进制意味着在你必须添加另一个数字位并重复使用数字之前,你有10个不同的数字可用。每次你耗尽当前数字位的范围时,你会重置左边的数字,并在右边添加一个0。这个新的数字位必须比它右边的数字位大10倍。右边的数字位被重置,计数重新开始。

使用数字的位置来表示数值的渐进增长,这种方法被称为位置记数法。当你思考它时,这是一种允许记录无限数量值的早期算法实现。实现简单,但效果非常强大。

二进制系统原理

二进制使用相同的位置记数法。它是另一种常见的计数方式,被称为二进制。
二进制意味着所有数值都只用1或0来表示。计算机以字节为单位存储信息。每个字节由8个比特组成,每个比特可以是1或0。
正如你在十进制中学到的,计数会到9,然后你会添加另一个数字位并重置。在二进制中,同样的事情会发生。但在这种情况下,只有两个数字(0和1)被用来推进计数。你将数字向左移动,将1向左移动,直到所有1和0的组合都被使用完毕,此时你在末尾添加另一个0。在这个阶段,除了开头的一个1,所有数字都被重置为0。
让我们一步步探索它。从0开始计数,然后加1得到1。当达到1时,再次从0开始,但在左边添加一个1。一旦所有的1都被填满,再次从0开始,并向左边的数字加1,但如果那个数字已经是1,它也会回到0,并在左边的下一个位置添加一个1。

二进制在计算中的应用
二进制在计算中有许多用途。它是将电信号转换为计算机代码的一种非常方便的方式。如果有信号存在,就显示1,否则使用0。二进制计数系统允许这些基数为二的信号构成大量的信息、传输和存储。布尔值的存储方式与此相同。一个布尔值要么是1(代表真),要么是0(代表假)。使用这种简单的信息表示法可以构建一些强大的应用程序。

ASCII码(美国信息交换标准代码)是二进制到字符编码的映射,或者说从二进制到文本的映射。每个数字、字符以及许多特殊字符(如问号、括号、句点甚至空格键)都保留了一个二进制数。前面提到,一个字节由8个比特组成。每个比特可以取0或1的值。
计算可能的状态数

这就引出了一个问题:每个字节可以表示多少种不同的值?在这里,我们会用到指数运算,即计算一个数的幂。
一个例子是 2^3。即 2 * 2 * 2 = 8。
现在,假设你有一个有四个不同数字位的锁。每个数字位可以是0或1。这个锁有多少种可能的密码组合?
答案是 2^4 或 2 * 2 * 2 * 2 = 16。你正在处理一个二进制锁,因此每个数字位只能是0或1。所以你可以取四个数字位,每次乘以2,总数是16。每次你增加一个可能的数字位,你都会增加可能的排列组合。所以,同样的锁如果有五个数字位,就会有 2^5 或 32 种不同的组合。
现在,回到我们最初的问题:一个字节中可以有多少种不同的表示?前面提到,一个字节由8个比特组成,每个比特可以是0或1。8个比特会有 2^8 或 256 种不同的组合。
总结
本节课中,我们一起学习了二进制数,这是计算机的语言。虽然乍一看,它似乎仅限于0和1,但你了解到,通过使用位置编码,它可以用来表示更大的数字集合。你学习了计算机如何使用电来存储和读取数字,以及指数运算(计算一个数的幂)如何与计算唯一状态相关联,以及如何使用它来计算数字锁可能拥有的组合数量。

二进制是计算机的语言,理解它如何用于存储信息,将使你在讨论数据及其存储结构时拥有更深刻的理解。
142:6_内存
在本节课中,我们将要学习计算机内存的基础知识,特别是中央处理器(CPU)与不同类型内存(缓存、主存、辅存)的角色和功能。我们将了解数据如何被存储、访问和处理,以及不同内存类型如何协同工作以提升计算机性能。
在之前的视频中,我们介绍了字节(Byte)。每个字节由8个比特(Bit)组成。比特是计算机内存最简单的形式。
计算机如何工作:CPU的核心角色
为了更好地理解内存的各个层级,我们首先需要了解计算机是如何工作的。
计算机围绕中央处理器(CPU) 运行。CPU接收信息以及如何处理这些信息的指令。所有这些信息都以字节的形式存在,即由微小电流决定的一系列1和0。
CPU处理信息的速度通常比信息传输到它的速度更快。CPU常常会近乎同时地处理多个不同任务。在不同任务之间切换,可以让信息被传输到缓存中进行处理,并将结果存储到合适的位置。
内存层级与传输速率
内存单元与CPU的物理距离会影响信息加载所需的时间。因此,更快、更昂贵的内存总是更靠近CPU。
讨论内存时,一个重要的概念是传输速率。这关系到计算机将内存传输到缓存中进行处理的速度。
不同类型的内存
现在您对处理过程有了更好的理解,让我们来探索不同类型的内存,首先从缓存开始。
缓存内存
缓存内存是最昂贵的内存形式,位于CPU芯片附近。
当CPU收到处理某些信息的指令时,它首先检查缓存中是否存在该信息。如果信息在缓存中可用,则直接处理。
如果CPU在缓存中找不到所需信息,则不会处理。接着,CPU会查询更大、更慢的主内存,然后将该信息加载到缓存中进行处理。
将最近访问过的信息存储在缓存中,可以减少常用数据的搜索和传输时间,从而提高系统效率。
缓存内存的组织方式类似于大都市的地铁,按重要性分区。最急需的信息位于1区,随后的每个区域重要性递减,编号为2区、3区、4区等。
主内存
接下来,您将了解主内存。
计算机的主内存由随机存取存储器(RAM) 和只读存储器(ROM) 组成。主内存仅保存计算机当前正在处理的信息。它可以是易失性的或非易失性的。
- 易失性内存:主动存储信息,如果计算机断电,信息就会丢失。
- 非易失性内存:断电时仍能保留其信息。
ROM,顾名思义,是只读的,意味着信息不能被覆盖。这种内存通常在工厂编程一次,无法更改。通常,这里存放着对计算机功能至关重要的指令和数据。ROM在计算机启动并加载所需应用程序的信息时最为繁忙。
RAM是可编程的。它可以保留新的信息和指令。RAM保存当前正在使用的数据和指令。您计算机拥有的RAM量直接关系到它的运行速度,这归因于传输速率。大量的RAM意味着系统不需要不断地传输信息。相反,它可以利用RAM同时保存并运行多个应用程序。运行这些应用程序所需的所有内存都需要从您的RAM中获取。打开过多程序会耗尽您的RAM内存,从而影响系统性能。
有许多用于读取和存储这些内存地址的算法,这超出了本课程的范围。
辅助内存
现在,让我们更深入地探讨辅助内存。
辅助内存指的是可以外部插入、用于增加系统存储容量的外部内存。访问辅助内存速度较慢,并且需要将所有所需信息和指令传输到RAM中。
以下是辅助内存的例子:
- 云存储
- 外置硬盘
- U盘(内存棒)

总结
本节课中,我们一起学习了内存的各个组成部分。您了解了所有内存分配如何围绕CPU进行,CPU负责监督计算机上信息的读取、处理和存储。
您还学习了不同类型的内存,它们在速度和重要性上各不相同。这决定了它们与CPU的距离,更快、更昂贵的内存单元更靠近源头。

这些信息应该能帮助您更好地理解计算机的工作原理。
143:时间复杂度 ⏱️
在本节课中,我们将学习如何评估算法或代码的效率,特别是从时间消耗的角度。我们将介绍时间复杂度的概念,以及如何使用大O表示法来衡量不同算法在处理数据时所需的时间。
概述
开发应用程序或评估效率时,拥有一个度量标准或视角来评估功能适用性总是有用的。计算机科学中的评估通常会考虑两个方面,即时间和空间。在本视频中,你将学习如何通过完成任务所需的时间来评估时间效率或衡量性能。你可以将此称为任务的时间复杂度。
一个应用程序必须在可接受的时间范围内返回信息。如今,人们期望在点击网站时能获得即时响应。然而,根据用户的需求和期望,更复杂的查询可能会被允许有额外的处理时间。


大O表示法是确定算法效率的度量标准。简单来说,它估算了你的代码在不同输入集上运行所需的时间。本视频将考虑算法所需的时间量。
大O表示法简介

你将遇到的一些大O表示法包括以下内容:
- O(1)
- O(log log N)
- O(log N)
- O(N)
- 等等。
那么,如何衡量某事物可被计算的最快可能时间呢?你需要利用常数时间算法,该算法需要 O(1) 的时间来计算。简单来说,这意味着无论向系统输入什么,都只需要一次计算。
一个简单的例子是考虑打印数组中的第一项。在这种情况下,无论数组中存在多少个值,该方法的时间复杂度都是 O(1)。
线性时间复杂度 O(N)
如果你需要进行搜索,情况可能会变得更复杂。假设你有一个包含10个项目的数组,并且你想知道某个特定值是否在这个数组中。你可能会应用一个循环并检查每个项目,以查看该值是否存在。在这个例子中,复杂度被称为 O(n)。这被称为线性时间。
搜索所需的时间将等于数组的长度。数组越大,搜索所需的时间就越多。因此,如果数组有100个项目而不是10个,那么搜索将花费10倍的时间。

让我们探讨一个例子。每个操作在时间复杂度上都有一个时间代价。所以,O(1) 意味着它花费一次计算,而 O(n) 意味着它花费n次计算。例如,你不会说它花了45秒。你会说复杂度是n。因此,对于每一个n操作,我们的最终复杂度结果就加一。如果n等于100,那就是100次检查。复杂度仍然是 O(n)。只是n意味着它长了10倍。

这意味着你的应用程序速度取决于正在处理的数据大小。打印数组第N个位置是 O(1) 操作的一个例子。这意味着无论n是多少,都打印该位置的值。n有多大并不重要。代价始终是1。
对数时间复杂度 O(log N)
接下来,我们继续看 O(log N)。这种搜索的强度低于 O(n),但比 O(1) 差。O(log n) 是对数搜索,因此它会随着新输入的添加而增加,但这些输入只带来边际的增长。
一个很好的实际例子是二分查找。想象你正在玩一个猜数字游戏,提示是“太高”、“太低”、“正确”。给定一个从1到100的范围。你可能会决定系统地处理这个问题。首先,你猜50,结果太高。然后你猜25,仍然太高。接着你决定猜12或13,仍然太高。

这里发生的情况是,你每猜一次,搜索空间就减半。因此,虽然这个函数的输入是100,但使用二分查找方法,你应该在不到6或7次猜测内找到答案。

这个解决方案的时间复杂度可以说是 O(log N)。即使n(输入的数字范围)变大10倍,所需的猜测次数也不会增加10倍。
平方时间复杂度 O(N²)
O(N²) 对计算的要求很高。这是一种二次复杂度,意味着对于数组中的每个元素,工作量都会加倍。一个很好的可视化方法是考虑你有一个数组的数组。第一个循环将等于输入元素的数量,即N。第二个循环也会查看输入元素的数量N。因此,运行这种方法的总体复杂度可以说是n乘以N,即n²。
那么,如何直观地表示这个问题呢?下图展示了时间复杂度的算法。X轴与输入数量相关,Y轴与所用时间相关。
请注意,随着输入数量的增加,它对所有情况下的线条梯度都有不同的影响,除了 O(1)。在这个关于N如何与所采取的计算次数相关的图形表示中,最佳目标是 O(1)。O(log N) 仍然非常好。O(n) 可以接受,而 O(N²) 则不太好。
当然,并不总是能判断一个方法需要多长时间。让我们回到在循环中查找某物的例子。虽然你可以说搜索一个循环需要 O(n) 时间,但这可能并非总是如此。
最佳、最坏与平均情况

考虑被搜索的项目是数组中的第一个。那么返回将在 O(1) 时间内完成,这非常好。同样,元素可能缺失,因此必须搜索每个项目,这是 O(n) 时间。中间情况是它在循环中间附近被找到,即 O(N/2)。
在评估一种方法时,会使用三种定义:最佳情况、最坏情况和平均情况。
总结
在本节课中,我们介绍了与复杂度相关的时间概念。在实施问题解决方案时,你获得了一些需要考虑的因素。在开始之前,问自己一个好问题是:我的解决方案采用了多少次计算,是否有更好的方法?

现在你使用了一个度量标准来评估你对给定问题的解决方案,你可以开始思考其相对于时间复杂度的效率了。这并不是考虑解决方案的唯一方式,在下一个视频中,重点将放在空间复杂度上。
144:空间复杂度 📊
在本节课中,我们将学习算法分析中的另一个重要概念——空间复杂度。我们将了解如何衡量算法对内存的使用情况,以及它与时间复杂度的权衡关系。
空间复杂度概述
上一节我们介绍了时间复杂度,它衡量算法执行所需的时间。本节中我们来看看另一个评估算法适用性的关键因素——空间复杂度,即一个给定解决方案会占用多少内存。这通常与时间复杂度存在权衡关系。
选择数据结构时,你的优先级(速度或紧凑性)将决定最终的选择。例如,本课程后面将学到的哈希表算法,能在 O(1) 时间内提供非常快速的查找。然而,为了高效工作,它必须为存储的每个元素预留查找空间,这导致了 O(N) 的空间复杂度。
空间复杂度的大O表示法
空间复杂度的大O表示法与时间复杂度的相同,包括 O(1)、O(log N)、O(N) 等。在这些表示法中,N 都指输入数据的大小,通常以字节为单位衡量。
不同编程语言有不同的内存开销。例如,在Java中,一个整数需要4字节内存。一个空数组会消耗12字节用于头对象,外加4字节用于填充。因此,如果 n 指的是一个大小为4的整数数组,那么总内存需求是32字节。
讨论空间复杂度时,必须考虑输入大小的增加对整体内存使用的影响。
空间复杂度的构成
问题的空间复杂度可以分为两个部分:辅助空间和输入空间。
- 辅助空间 是解决该问题所需的所有数据占用的空间。它指的是计算给定解决方案时所需的临时空间。
- 输入空间 指的是向你正在评估的函数、算法、应用或系统添加数据所需的空间。
空间复杂度等于输入空间加上辅助空间,即计算一个结果所需的总空间。
空间复杂度计算示例
还记得我们之前计算一个整数数组空间复杂度的例子吗?我们计算了整数内存(4字节)、头对象(12字节)和填充(4字节),总数为32字节。
现在,考虑数组大小加倍到8个整数。用同样的方式计算空间复杂度,总数将是48字节。空间复杂度变高了。由于增加输入并没有增加辅助空间的大小,所以在计算大O表示法时,如果辅助空间不受输入大小增加的影响,可以忽略它。
影响内存使用的常见操作
了解在计算解决方案时所做的每个决策都需要内存,因此值得注意那些会增加内存使用的方面。
以下是可能增加内存使用的一些常见操作:
- 变量赋值:计算解决方案时可能会创建临时变量,就像之前长除法的类比一样。
- 创建新的数据结构:某些解决方案需要创建一个新数组来容纳值,或者一个保留索引位置的重复数组。创建一个新的数据结构实例会产生 O(N) 的辅助内存成本。
- 函数调用和分配:函数调用和内存分配也会产生额外的内存开销。
在设计应用程序时,值得牢记空间是如何被使用的。创建一个新变量来容纳一个值,而不是覆盖现有的变量,会影响你的空间效率。
空间使用效率的注意事项
如果你不必要地复制数组或具有高数据开销的复杂数据结构,这种影响会大大增加。

此外,在更简单、开销更小的结构就足够的情况下,编写使用复杂结构的函数可能会招致性能损失,特别是在计算解决方案时需要复制这些结构时。
总结

本节课中,我们将大O的概念从专注于时间考量扩展到了包含空间复杂度。我们强调了在速度和内存效率之间通常存在的权衡关系。此外,还提出了一些在设计解决方案时值得牢记的空间高效使用建议。
145:编码面试简介模块总结 🎯
在本节课中,我们将回顾“编码面试简介”模块的核心内容。我们将总结技术面试准备、沟通技巧、计算机科学基础(如二进制和内存)以及算法效率(时间与空间复杂度)等关键知识点。
技术编码面试 💻

上一节我们介绍了课程概述,本节中我们来看看技术编码面试的具体要求。技术面试主要用于评估你是否具备承担职位职责的技术能力。
以下是面试过程中必须牢记的步骤:
- 使用合适的工具始终很重要。
- 必须将时间限制考虑在内。
接着,我们学习了代码优化。你应该能够编写或重写代码,使程序使用尽可能少的内存或磁盘空间,并最小化CPU时间或网络带宽。
总而言之,你学习了一些无论面对何种挑战都可以使用的方法。即使你不熟悉问题或在规定时间内未能得出结果,也应始终努力展示你的推理过程和最佳实践方法。
通过练习解决在线问题来准备技术面试,并在可能的情况下,对每个挑战采用相似的方法论。这将有助于你在未来无论面对何种挑战时,都能在一个熟悉的框架下工作。
沟通与第一印象 🗣️
在了解了技术准备后,沟通的重要性同样不容忽视。我们介绍了言语和非言语沟通及其重要性。
你学习了STAR方法,以及如何在面试官沟通时利用它为你带来好处。你现在应该能够分析情境背景、面临的挑战、相关任务的责任、为应对挑战所需的行动,以及最终需要达成的结果。
总而言之,你现在应该能够在面试中清晰地传达一个概念。
- 以言语和非言语的方式沟通你为何适合该职位。
- 最后,使用STAR方法来应对面试过程中将出现的各种技术问题。
计算机科学基础:二进制与内存 💾
接下来,我们进入了计算机科学基础的学习。我们从二进制开始,了解了十进制(base 10)和二进制(base 2)的区别。
然后我们发现了位置记数法。这是利用数字的位置来表示数值的递增。
接着,我们介绍了计算机如何将数据存储为字节,以及每个字节由8个比特(bit)组成,每个比特可以是1或0。我们也给出了一些例子。你研究了指数运算的概念,即计算一个数的幂。
随后用了一个不同位数密码锁的例子来解释这个概念。你现在应该能够应用这些知识,并理解二进制是计算机的语言。
接下来,我们探讨了内存。你学到的第一个概念是内存容量,它指的是计算机可以容纳的字节数。你还学习了需要考虑的不同类型的内存,即缓存内存、主内存和辅助内存。



你现在应该知道,为了更好地理解内存的各个层级,停下来思考计算机的工作原理很重要。你学习了传输速率,即计算机将内存传输到缓存中进行处理的速度。

然后我们探讨了缓存和辅助内存,你应该能够描述它们之间的区别。接着,我们介绍了计算机主内存由随机存取存储器(RAM)和只读存储器(ROM)组成的概念。你应该能够描述主内存的作用,并区分RAM和ROM。你现在应该能更好地处理内存相关的问题了。
算法效率:时间与空间复杂度 ⚙️
在掌握了计算机基础后,我们转向评估代码性能。我们探索了时间复杂度,学习了如何通过完成任务所需的时间来评估时间效率或衡量性能。

我们发现了大O表示法,这是一种用于确定算法效率的度量标准。因此,它可以估算你的代码在不同输入集上运行所需的时间,或者说它考虑了算法将花费的时间量。

我们给出了一些例子,你现在应该对如何衡量时间复杂度有了扎实的理解。
接着,我们学习了空间复杂度。重要的不仅仅是算法的速度,还有给定解决方案需要占用多少内存。为了理解空间复杂度,我们引入了辅助空间的概念,即保存解决方案所需的所有数据所需的空间,也称为计算给定解决方案所需的临时空间。
另一个概念是输入空间,它指的是向你所评估的函数、算法、应用程序或系统添加数据所需的空间。

总而言之,空间复杂度 = 输入空间 + 辅助空间;这就是计算结果所需的空间。

总结 📝
本节课中我们一起学习了“编码面试简介”模块的全部内容。你对上述所有主题都进行了一些测验,这是你本课程学习之旅的良好开端。所有这些内容都将使你在未来的编码面试中表现出色。
146:基本数据结构 📚
在本节课中,我们将要学习数据结构的基础知识。数据结构是组织和存储数据的方式,对于任何编码面试和实际应用开发都至关重要。我们将了解数据结构的基本概念、主要类型以及在实际应用中选择合适结构时需要考虑的因素。
数据结构简介
数据结构是对现实世界中对象的建模,以便于在计算机内存中存储和组织。理解你正在处理的数据以及最适合使用的数据结构,会带来非常大的好处。

数据结构可以是简单的、创建后不再改变的不可变结构,也可以是便于对其内容执行操作的可变结构。这些操作可能包括对结构内容的更新和查询。

可变与不可变结构
表面上看,似乎应该始终使用可变结构。然而,可变结构需要时间和精力来建模,并且有些对象非常复杂,不易建模。其他因素,例如空间占用,也可能是一个考虑因素。

理解数据结构的底层机制是一个巨大的优势,因为使用特定数据结构的决定可能会对项目进展产生深远的影响。

数据结构的分类

虽然数据结构的实现和功能可能因编程语言而异,但总体架构通常遵循相似的模式。
以下是数据结构的通用分类,将不同类型的数据结构分为两个主要分支:线性和非线性。这关系到元素在数据结构中的存储方式。
线性结构
线性结构与信息的存储方式有关。结构的元素一个接一个地排列,或者说顺序排列,反映了它们被输入的顺序。
以下是线性结构的例子:
- 数组
- 队列
- 栈
- 列表
线性结构意味着每个元素都连接到它前面的元素。有些语言要求在同一结构中只能存储相似类型的数据,因此会有整数数组或字符串数组。其他语言则允许混合数组,这意味着在同一数组中存储整数和字符串并不被禁止。但这种简便的方法可能会在后续的错误处理中付出代价。

一旦创建了简单的结构(如列表或数组),它将包含一个索引。索引是一种访问元素的方式,这些元素不一定是第一个或最后一个实例。通常,索引的使用是通过附加方括号和项目的位置(整数)来完成的。
例如,array[4] 表示所需的元素是数组的第四项。然而,编程语言主要是0基的,这意味着计数从0开始。因此,array[4] 实际上是数组中的第五项。

如果请求索引位置8,但数组中只有7个元素,则通过索引访问数组可能会引发错误。这些结构的一个常见特性是,大多数语言都有一个内置的 length 方法,可以告知数组有多大。
数组与列表的特性
数组和列表通常是一等对象。这意味着所有可用于其他变量的功能也可用于它们。这个定义通常表明数据结构可以作为参数传递给函数、作为结果返回或赋值给变量。
将列表或数组传递给函数时,应注意传递的是结构本身,而不仅仅是结构的引用。这可以作为一种节省内存的机制,用于防止复制信息。然而,如果对结构的更改无意中影响了调用环境中的数组,这种情况可能会导致错误。
在这个例子中,一个字符串被添加到了一个整数列表中,因为新列表指向初始列表,所以初始列表也被更改了。因此,最好制作数组的副本,并将副本传递给函数。
另一个需要注意的内存相关问题是内存泄漏。如前所述,内存可以被任意分配。如果这块内存不再使用,那么良好的做法是释放该内存位置。由于粗心的编程或其他问题,程序可能会重复调用,导致分配了过多的内存而没有随后释放。随着时间的推移或通过重复调用,这可能导致应用程序内存耗尽并崩溃。大多数编译器都有复杂的算法来检测和释放内存,以避免此问题。
非线性结构
上一节我们介绍了线性结构,本节中我们来看看非线性结构。与线性结构相反,存在非线性实例,例如树或图。这些结构不允许你一次性平滑地遍历所有数据。相反,你可以探索特定的路径。这些结构的构成意味着它们可以包含自然排序,这使得查询特定数据非常快速。

你将在课程后面学习不同类型的排序。

总结
本节课中我们一起学习了数据结构。在本视频中,你对数据结构有了一个概括性的了解,包括它们的两种主要类型:线性和非线性。你还学习了在决定应使用的数据结构类型时应考虑的一些因素。随着你在这个模块中的深入学习,你将进一步探索这些结构,并了解它们各自的优点和缺点。
147:列表与集合 📚
在本节课中,我们将要学习两种重要的数据结构:列表和集合。你将了解它们各自的特点、实现方式以及适用场景,帮助你根据不同的存储需求做出合适的选择。

你是否曾需要存储一些数据,但不确定该使用哪种数据结构?这是一个常见的编程问题。本节视频将介绍两种可能用到的数据结构:列表和集合。两者都是非常有用的数据结构,各有其优缺点。列表和集合在许多编程语言中都很常见。
探索列表 📝
上一节我们引入了数据结构的概念,本节中我们首先来看看列表。
在大多数编程语言中,列表被表示为对象。这意味着除了存储数据,它们还拥有自己的内置方法。例如,可以使用内置的排序方法来排列列表中的数字。与数组类似,列表通常被声明为字符串、整数或浮点数类型。在某些编程语言中,列表可以包含混合类型的元素。
列表是一个抽象概念,指代一个元素的容器。列表的稳定实现通常使用数组或链表来完成。
基于数组的列表
基于数组的列表是使用数组作为底层数据结构构建的有序集合。因此,它们具有与数组相同的优势和局限性。基于数组的实现涉及到初始大小的设定,而不是像链表那样简单地指向另一个节点。
以下是关于数组列表初始化的一个关键点:
# 在某些语言中需要预先确定大小
my_list = [None] * initial_size
# 而在另一些语言中,结构可以动态增长
my_list = []
一些语言要求你最初确定结构的大小,而另一些语言则允许结构动态增长。需要注意的是,这种自由在某种程度上是表面上的。对于许多动态结构,在实例化时会自动配置一个初始大小。当达到此限制时,数组会将自己复制到一个分配了更大空间的新结构中。因此,选择不在开始时任意分配空间,可能会在运行时付出代价,因为此类数据结构可能需要在执行其他操作期间多次扩展。
考虑一个列表在循环中执行操作时动态增长的计算成本。在这种情况下,将初始列表大小设置得更大会更有帮助,而不是动态增长。动态增长可能成本高昂,因为必须创建并将值复制到越来越大的列表中。
链表

链表的工作方式不同。链表包含两部分信息:数据和指向下一个列表项的指针。链表从一个空列表开始,可以通过向列表引入新单元来动态增长。要扩展链表,你只需添加一个新节点并将列表指向其位置即可。
这使得链表在存储大量数据时非常快速。链表的灵活性是通过包含一些额外的存储需求来实现的,特别是在每个节点中,必须包含对其周围节点的引用。链表还有一个头节点和一个尾节点。头节点是一个独特的节点,表示它是列表的开始,而尾节点表示列表的结束。
这种增长数据结构大小的方法非常强大,可以产生非常大但易于管理的数据集。

理解集合 🔍

上一节我们介绍了列表,本节中我们来看看集合。
集合与列表非常相似。然而,集合会以无序的方式存储其元素。尽管有一些有序集合的可能实现,但集合有一些不寻常的特性:集合只保存唯一的元素。因此,向集合中添加一个已存在的元素不会对存储的数据产生任何影响。
集合存储信息的无序过程意味着,打印一个集合不一定能反映元素被添加到集合中的顺序。一旦一个值被添加到集合中,它就不能改变。相反,你必须删除它并添加一个新值。
集合的快速搜索

集合的搜索速度异常快。这是由于其内部机制。集合使用哈希表来确定存储集合元素的位置。因此,传递给集合的每个数字都会应用一个哈希函数。

哈希函数可以定义为一个算法,它接收一些数据并将其映射到一个固定大小的值。理论上,这个值是唯一的,并且每次对该数据应用该函数时,都会返回相同的值。

这意味着搜索集合可以在 O(1) 时间内完成。这是由于用于在集合中保存值的机制。你将在课程后面更详细地学习哈希函数。

搜索机制对比
以下是两种搜索方式的对比:
- O(N) 方法:遍历整个数据结构以检查值是否存在。
- 集合的方法:将映射函数应用于输入数据,并检查结果输出以查看值是否存在。

如果值存在,则返回该值。如果它不存在于集合中,则数据未存储在集合中,因此将返回 false。
集合的局限性
虽然集合可以执行异常快速的搜索,但在处理非常大的数据集时,性能会下降。这是由于哈希函数的性质。保留的值越多,发生冲突的风险就越大。冲突是指哈希函数为两个不同的值返回相同的唯一映射。使用的数据集越大,就越容易发生冲突。
总结 📋

本节课中我们一起学习了两种非常重要且有用的数据结构:列表和集合,并了解了它们各自固有的优缺点。你现在应该对何时使用每种结构有了更清晰的认识,这取决于解决方案的存储需求。列表适用于需要保持顺序或允许重复元素的场景,而集合则在需要确保元素唯一性或进行快速成员检查时表现出色。
148:栈和队列 🧱➡️📚
在本节课中,我们将要学习两种重要的数据结构:栈和队列。我们将探讨它们的定义、工作原理、核心方法以及它们之间的关键区别,并通过实例帮助你理解在何种场景下应选择使用哪一种。
栈和队列是两种抽象数据结构,它们在不同的编程语言中有多种实现方式。它们的共同核心原则在于元素添加和移除的特定方式。


与允许随机访问的列表和数组不同,栈和队列采用顺序访问。这种对数据访问方式的限制,在你希望控制数据如何被访问时非常有用。
栈:后进先出(LIFO)结构
上一节我们介绍了栈和队列的基本概念,本节中我们来看看栈的详细工作原理。
栈是一种线性数据结构,对项目的添加和移除有严格的规定。顾名思义,栈是元素相互堆叠的集合。这意味着你无法从中间抽取项目。栈遵循严格的 “先进后出” 原则,也可表述为 “后进先出”。这是一个简单而强大的概念,它规定项目只能从栈顶被检索,这决定了你检索它们的顺序。
一个生动的例子是在任何文本编辑器或代码环境中使用 Ctrl+Z 进行撤销操作。按一次 Ctrl+Z 会撤销最后一个操作,再按一次则撤销上一个操作,依此类推。类似地,Ctrl+Y 会重做操作,相当于将操作重新“推”回栈中。
栈通常只有少数几个核心方法。

以下是栈的主要方法:
push(item):将一个项目添加到栈顶。pop():从栈顶移除并返回一个项目。isEmpty():检查栈是否为空。isFull():检查栈是否已满(在固定容量栈中常用)。peek():查看栈顶项目但不移除它。

这些方法的功能与其名称相符。调用 pop() 或 push() 会永久改变栈的状态。而 peek() 方法则允许你查看栈顶元素而不改变栈的结构。有些实现可能包含搜索功能,但这并非必需。
现在,让我们探索一个例子。


想象一个应用程序生成了一副扑克牌。你可以创建一个包含52张牌的栈。每次发牌时,就像从真实的牌堆中一样,从栈顶移除一张牌。



以这种方式使用栈,可以简化维护牌堆状态所需的代码。
队列:先进先出(FIFO)结构
了解了栈之后,现在让我们来探索队列。队列与栈非常相似,通常具有类似的方法,如创建、插入、移除和检查状态。但与栈不同,队列基于 “先进先出” 原则工作。其名称很好地指示了该结构的工作方式。
举个例子,想象在快餐店有一排人等着买汉堡。第一个进入队列的人会先得到服务。随后的每位顾客都站在前一位后面,依次被处理。


与栈类似,队列也会从结构中“弹出”选定的项目,尽管不同语言对此有不同的实现。从队列中移除的元素是位于“底部”的那个,换句话说,是最早加入队列的项目。
用一个真实的IT例子来说明,服务器负载均衡系统通常使用队列来获取任务。该结构会按照插入顺序保存每个任务。当有服务器可用时,队列中第一个进入的任务会被移除并传递给该服务器处理。


总结
本节课中,我们一起学习了栈和队列以及它们之间的区别。栈遵循 LIFO 原则,而队列遵循 FIFO 原则。它们是编程工具包中非常有用的工具,了解它们对于处理需要结构化方式访问和插入数据的问题将是一个优势。
149:树结构入门 🌳
在本节课中,我们将要学习一种新的数据结构——树。树结构在添加和搜索数据时提供了极大的灵活性,其内在结构能帮助我们理解数据之间的关系,从而在提取信息时节省大量时间和代码。
什么是树结构?
在之前的课程中,我们已经学习了列表、栈和队列等数据结构。本节中,我们来看看另一种尚未接触过的数据结构——树。
在数据结构的语境中,树是一种非常复杂的数据结构,其设计类似于自然界中的树。它由相互连接的节点组成。一个节点可以是父节点或子节点。父节点可以拥有一组相连的子节点,没有子节点的节点被称为叶节点。就像一棵树一样,节点可以向不同方向分支,这赋予了它强大的搜索和存储特性。
总的来说,我们可以将树视为一种图状结构,其中包含存储数据的节点和模拟节点之间关系的边。
树结构术语解析
在讨论树结构时,理解一些术语非常重要。以下是关键术语的解释:
- 根节点:位于最顶层的节点。
- 子节点:连接到根节点或任何父节点的后续节点。
- 兄弟节点:拥有相同父节点的节点,它们被认为处于同一层级。
- 路径:一系列相连的节点。可以通过确定最短路径来假设两个节点之间的连接关系,即从一个节点移动到另一个节点的最快方式。直观上,路径较短的节点通常具有更多共同点。
- 节点深度:从该节点到根节点之间的边数,或最长路径的长度。
- 树的高度:从最顶层节点到结构中最深节点之间的边数。
- 树的大小:树中节点的总数。
树的类型与优势
树结构有许多变体和实现方式,例如二叉树、B树和B+树,还有四叉树和AVL树等。虽然它们都包含上述基本主题,但其用途和实现细节会根据所应用的树类型而略有不同。
将数据存储在树状结构中有许多优势。节点之间的连接表明了数据中固有的关系。它们可以以分层方式存储信息,最顶层的内容存储在上层节点中,而更深入的信息可以通过遍历树的特定分支来获取。由于其灵活的实现方式,树结构在插入和删除数据时也非常高效。
树的非线性特性意味着有多种遍历数据的方式。在二叉树中,这一特性在存储数据时非常有用。左节点的值较小,而右节点的值较大。
让我们用一些数据值来演示这一点。第一个数据值是23。然后添加4。因为4小于23,它被放到左边。接下来是1,它小于23,同时也小于4,所以也被放到左边。下一个数字是30,因为它大于23,所以被放到右边。添加24时,由于它小于30,被放到30的左边。而随后添加的56,则再次被放到30的右边。
遍历树可以采用深度优先或广度优先的方法。深度优先方法涉及按顺序从上到下访问每个节点。广度优先方法则是在上升到下一层级之前,先搜索同一层级上的每个节点,并重复此过程直到到达根节点。
树的更多好处包括:可以用来模拟笔记本电脑上的文件系统、Java中的类层次结构或组织中的层级模型。

总结

本节课中,我们一起探索了树结构提供的基本结构和内在特性。我们还学习了一些不同类型的树以及实现树数据结构的优势。树是一种强大且灵活的工具,能够高效地组织和检索具有层次关系的数据。
150:哈希表 🗂️
在本节课中,我们将要学习一种重要的数据结构——哈希表。我们将了解它的结构、工作原理、优势,以及在使用过程中可能遇到的“冲突”问题。
概述
在本课程的这个阶段,你已经接触了几种不同的数据结构。你会发现,存储信息并没有一种完美的方式。相反,存在多种多样的方法,每种方法都是特定问题的合适解决方案。在本视频中,你将学习什么是哈希表、它的结构和固有特性以及它是如何工作的。你还将探索使用哈希表的一些优势,并了解哈希中的“冲突”是什么意思。
哈希表的结构与哈希函数


哈希表包含多个槽位或桶来存放键值对。它需要一个哈希函数来确定将数据放入哪个正确的桶中。


哈希函数是应用于键以生成唯一数字的任何算法或公式。每个要存储的数据项都必须有一个键和一个值。获取键,并对其应用哈希函数,将其缩减为一个固定大小的值。
哈希函数的工作原理
有多种哈希函数可供使用。你可能在压缩方面对它们有所了解。当你想通过互联网发送信息时,可能会先将其大小压缩到可管理的字节数,然后通过互联网发送,最后在另一端解压。这就是哈希工作原理的一个例子。它将键缩减为一个小的、可管理的大小,然后作为索引指示器。
用于生成索引的信息取决于具体的应用场景。如果数据本身足够小,它可能就是数据本身;也可能是员工ID号的最后四位数字;或者是字典中的一个键。大多数编程语言都有内置的哈希函数,如MD5、SHA或CRC32。因此,实现哈希函数是一项直接的工作。
哈希表的性能优势
在讨论大O表示法时,我们曾介绍过速度和空间常常相互矛盾的观点。这意味着你可以减少检索项目所需的时间,但这样做会增加应用程序的开销。




哈希表优先考虑速度而非空间,可以在O(1)时间内检索一个项目。回想一下关于数组的讨论。当你想检查一个值是否存在时,必须执行一个搜索,检查列表中的每个元素并与目标值进行比较。在最坏的情况下,这将花费O(n)时间,换句话说,如果元素在数组的末尾,它必须进行n次检查。
哈希表提供了一种存储和搜索数据的替代方法。


这是通过使用索引来实现的。你必须实现一个算法,该算法接收一个键并将其映射到一个存储在索引中的值。


然后,当出现一个新键时,算法只需要运行相同的函数并确定值位于索引中的哪个位置。


就像书中的索引一样,这将大大加快识别某些数据位置所需的时间。你可能会在缓存、字典、数据库索引和集合中找到哈希表的使用。
一个简单的哈希表示例
考虑这样一个场景:你有一个包含10个键的数组,这些键是数字0到9。你将选择使用一个哈希函数来决定在内存中的哪个位置存储这些数字。
你选择了一种简单的方法,对数字应用模20运算。因此,对于从0到9的每个键,你应用哈希函数。从 0 mod 20 = 0 开始。
以下是计算过程:
1 mod 20 = 12 mod 20 = 23 mod 20 = 3- 依此类推。

通过这种方式,你将生成9个唯一的值,这些值用于表示内存中与这些键关联的数据的存放位置。这个例子很简单,但说明了创建哈希映射背后的机制。
哈希冲突

当要存储的键的数量增长超过20时,问题就出现了。记住,1 mod 20 = 1,但 21 mod 20 也等于 1。接下来,让我们看看哈希表中的冲突。

什么是哈希表中的冲突? 哈希函数将应用一个巧妙的算法,将键的大小缩减到一个可管理的大小。

有些方法比其他方法更复杂。那么,如果两次哈希的结果相同会发生什么?
为了扩展这个想法,值得思考一下冯·米塞斯的生日悖论。由于概率的原因,有时事件发生的可能性比我们想象的要大。在这种情况下,如果你调查一个只有23人的随机群体,实际上大约有50%的几率其中两人会有相同的生日。这被称为生日悖论。
假设一家公司有24名员工,并且应用了一个巧妙的哈希函数,该函数获取他们生日的日期和月份,并将其用作索引。只有24名员工和一个拥有365个索引槽的哈希表来存放对他们的引用,你可能会认为任何两名员工共享生日的概率很低。事实上,研究表明,这种情况发生的可能性超过50%。下次你参加聚会时,一定要检查是否有任何两位与会者的生日相同,亲自验证一下。
这说明,当哈希函数应用于键时,将会生成重复的哈希值,因此必须为此做出安排。

解决冲突的方法
针对这个问题有几种解决方案。
以下是两种常见的解决方案:
- 动态扩容:每次发生冲突时扩展表,然后增加哈希方法的复杂性,将值重新分配到新的地址。
![]()
通过这种方式,表将有机地增长以匹配所需数据的大小。 - 链地址法:在冲突点创建一个链表,简单地存储额外的值。因此,在发生冲突时,不是存储一个值,而是存储一个值的链表。
总结


在本视频中,你了解了什么是哈希表、它们的结构和特性以及它们如何工作。你还了解到,哈希是一种非常巧妙的方法,它使用哈希函数和索引来实现O(1)时间的搜索。




你探索了冲突以及如何利用它们来确定表的大小。你甚至还了解到,如果你参加的聚会客人超过24位,那么至少有两个人生日相同的可能性是很大的。
151:堆(Heap)😊
在本节课中,我们将要学习一种名为“堆”的数据结构。堆这个名字听起来可能不那么引人注目,但它是一种非常重要的组织工具,它结合了其他一些数据结构的特性和优点。
我们将了解堆的结构和特性,并探索如何使用堆来按重要性从低到高组织元素。同时,我们也会看到,通过限制堆的功能,反而可以提高其处理效率。
什么是堆?😊
堆是一种特殊的数据结构,其模型类似于树,但其行为方式与队列相似,不过有一个显著的区别:堆会为某些元素分配优先级。
堆中的每个元素都有一个键值,优先级可以是键值最小的元素,也可以是键值最大的元素。
- 最小堆:将优先级赋予键值最小的元素。
- 最大堆:将优先级赋予键值最大的元素。
堆最初是为了高效存储和搜索数据而引入的。但后来人们认识到,堆可以应用于许多非常有用的操作。
堆的核心操作
堆可以执行几个核心操作。对于最小堆,核心操作是 insert(插入)、find_min(查找最小值)和 delete_min(删除最小值)。对于最大堆,则是 insert、find_max 和 delete_max。
在本节接下来的讨论中,我们将围绕最小堆展开,但你可以将所讲的内容反过来理解,它们同样适用于最大堆。😊 两者唯一的区别在于优先级的放置位置。
与课程中讨论的许多数据结构一样,这些方法是构成堆的基本元素。不同语言的不同实现可能会添加额外的方法。😊 其中一个例子可能是 decrease_key(减小键值),即更改某个键的值。这样做的动机在于,现实世界中键的优先级可能会发生变化。
堆的底层结构



在讨论树时,我们提到过二叉搜索树会根据值的大小顺序来查找:如果值小于节点,则沿左路径向下;如果值大于节点,则沿右路径向下。
如果值 < 节点值: 向左子树查找
如果值 > 节点值: 向右子树查找
由于这种底层架构,堆通常使用二叉树来构建,不过另一种方法是让数组以模仿二叉树行为的方式工作。

在最小堆中,最小值被放置在根节点,随后的每个值都根据其值的大小被放置在层次结构中的相应位置。这意味着从堆中检索最小值的时间复杂度是 O(1),因为它总是存储在根节点。
与栈不同,检索一个值并不会导致它从树中被移除。相反,如果目的是在处理项目时将其移除,可以调用 delete_min 方法。
堆的设计哲学
通常,堆不支持删除优先级元素以外的操作。😊 原因在于,堆是为特定目的而构建的,即识别最重要的项目并在尽可能短的时间内返回它,然后排列下一个重要的项目。删除树中的项目需要重新构建树,这会导致性能下降。😊

如果你正在寻找一种可以以这种方式操作的数据结构,那么可能需要考虑堆以外的结构。
堆的插入过程

向最小堆中插入元素是通过“上浮”过程完成的。
每个新项目首先被插入到堆的底部(在数组表示中为末尾)。然后,将其与其父节点的值进行比较。如果新插入的项目值小于其父节点,则交换它们的位置。这个过程持续进行,直到新插入的项目上方没有比它更大的值,并且下方的值比它小。在堆中插入元素可以在 O(log N) 的时间内完成。
堆的应用场景
在了解了堆的底层机制之后,你现在可能对如何应用这种数据结构有了一些想法。
考虑到其固有结构能从一组元素中优先处理特定值,其自然的应用场景就是调度。这可以应用于CPU、路由器或数据包处理。

此外,可以想象这样的结构在优先处理某些任务时也很有用,例如面试安排,其中用于存储候选人的键可能与他们在面试流程中所处的阶段有关。😊 或者与职位在组织内的优先级有关?

拥有一个能根据重要性自动应用调度流程的系统,可以极大地节省时间。😊
总结
在本节课中,我们一起深入理解了堆,以及如何使用它来按重要性从低到高组织元素。我们了解到,通过限制功能,反而可以提高生产力。与选择任何数据结构一样,重要的是为正确的工作找到合适的工具。😊
152:图 🕸️
在本节课中,我们将要学习一种重要的数据结构——图。我们将了解图的基本概念、组成部分、不同类型以及如何遍历图。通过学习,你将理解图如何以一种灵活的方式建模数据,并基于数据的存储方式推断信息。
概述
在计算机科学中考虑一个给定问题时,始终需要思考解决该问题可能需要执行哪些操作。通过这种思考,可以选择一个合适的数据结构来存储数据。
假设你在一家大型互联网公司工作,需要存储一系列地点及其相互之间的连接关系。在这种场景下,图是一种非常有效的建模工具。
图的基本概念

上一节我们提到了使用图来建模地点关系。本节中,我们来看看图的具体构成。
图由节点和边组成。
- 节点:表示实体,例如城市、网页或人。
- 边:表示节点之间的关系或连接。


这种保存信息的方法就是基于图的方法。在接下来的内容中,将概述一些相关术语和这种方法的优势。
图的类型
了解了图的基本组成后,我们需要区分不同类型的图。


上图展示的图由表示目的地的节点和显示每个节点如何关联的边组成。节点之间存在数值,这意味着这是一个加权图。图中没有箭头,这意味着这是一个无向图。
与有向图相比,无向图没有优先级顺序。思考有向图和无向图的一种方式,是像双向街道和单向街道。
以下是图的主要分类:
- 有向图 vs 无向图:有向图的边有方向(如A->B),无向图的边没有方向(A-B)。
- 加权图 vs 无权图:加权图的边带有权重(如距离、成本),无权图的边没有权重。
有时,为了突出某种进展,在排列数据时给边赋予方向会很有帮助。而在其他情况下,边仅仅用来表示关联。

图的连接与路径



现在我们已经知道了图的类型,接下来探讨节点之间是如何连接的。
路径是由边连接的两个或更多节点的序列。
在有向图中,如果边只是单向的,那么这种连接被认为是弱连接。然而,如果两个节点之间有双向连接,则可以说是强连接。
此时,你可能会认为图类似于树。在某种程度上,可以说树是一种简单的图。值得注意的是,树有一个起点,并模拟了父节点和子节点的层次结构。而图是一种复杂得多的结构,没有开始或结束。

两个相邻的节点被称为邻居,通过一个邻居连接的节点被称为相邻节点。





图的遍历
和树一样,图也可以进行遍历,主要方法有广度优先搜索和深度优先搜索。

回忆一下,广度优先搜索涉及访问同一层的每个节点,然后再向下进行。而深度优先搜索则是在移动到下一个分支之前,先深入到每个分支的末端。


以下是两种遍历方法的简要说明:
-
广度优先搜索:选择一个给定的起始位置,遍历所有相邻节点。每个邻居都有一组连接的节点,这些节点可以被添加到另一个数据结构——队列中。通过这种方式,可以系统地访问每个节点。
![]()
![]()
-
深度优先搜索:为了实现深度优先搜索,可以使用栈。回忆一下,栈处理元素的方式与队列不同。队列遵循先进先出的原则,而栈则遵循后进先出的原则。因此,通过系统地将所有邻居节点放在栈上,可以确保进行深度优先遍历。
![]()
![]()
![]()
图的应用

图是一种被广泛研究的数据结构,是许多用于确定节点间重要性的算法的基础。

无论节点中存储的元素是什么,一个著名的算法是最短路径算法:找到从节点A到节点E的最快方式。边的权重会提示选择每条路径的成本。这种方法用于在互联网上路由数据包,或在谷歌地图上计算行程。

另一个常见的基于图的挑战是旅行商问题。


一个销售员需要访问几个特定的节点。规划一条在最短时间内覆盖所有节点的最佳路线是什么?这将用于包裹路由。

给定X个目的地和Y辆车,规划出最有效的路线,以便用最少的资源消耗交付所有包裹。


总结
本节课中,我们一起学习了图如何让你有机会以一种灵活的方式对数据进行建模,并通过数据的存储方式促进对信息的推断。


这种多功能的方法只保留最少的信息。从芝加哥到波士顿的距离没有存储在任何地方,但可以推断出来。很容易查询不同的问题,而无需改变数据的构成。计算步行时的最佳时间可以轻松替代驾驶时间,而无需大费周章。
有一个完整的统计学领域致力于从节点位置推断信息,这可以用来对存储在那里的任何数据进行推断。
153:模块总结 🎯
在本模块中,我们学习了数据结构的基础知识。从简单的字符串、布尔值到复杂的集合、图与堆,我们探讨了如何根据数据特性选择最合适的结构。理解数据结构是高效编程和问题解决的关键。
模块回顾 📚
上一节我们完成了数据结构入门模块的学习。本节中,我们来回顾一下本模块涵盖的核心内容。
你从一节关于基础数据结构的课程开始本模块。课程范围从字符串、布尔值或数组等基础结构,延伸到集合、图和堆等更高级的数据结构。理解你正在处理的数据以及最适合使用的结构非常有益。

你学习了简单的不可变结构(创建后不改变)和可变结构(便于对其内容执行操作)。


数据结构分类 🔍
接着,你深入研究了所有类型的数据结构以刷新记忆。以下是数据结构的通用分类,将不同类型分为两个主要分支:线性和非线性。


以下是线性结构的例子:
- 数组
- 队列
- 栈
- 列表
线性结构意味着每个元素都与其前一个元素相连。你详细学习了这些结构,现在应该能够描述它们中的每一个。


非线性结构 🌳
然后,你开始关注非线性数据结构。与线性结构相反,非线性结构的例子有树或图。这些结构不允许你以单一流畅的动作遍历数据,而是可以探索特定的路径。
接着,你进入了下一课,介绍了列表和集合。


你了解到,与数组类似,在某些编程语言中,列表通常被声明为字符串、整数或浮点数类型,而在另一些语言中,列表可以包含混合元素类型。你还了解到,有些语言要求你最初确定结构的大小,而另一些语言则允许动态增长的结构。

链表与集合 🔗

随后是关于链表及其工作原理的部分。记住,链表包含两条信息:数据和指向下一个列表项的指针。
# 链表节点示例
class Node:
def __init__(self, data):
self.data = data
self.next = None

接着,你学习了集合及其工作原理。集合与列表非常相似,但集合会以无序的方式存储其元素。

在集合之后,你了解了哈希函数及其在集合搜索中的作用。集合的搜索速度异常快。

栈与队列 📚
然后,你在同一课中进入了下一个关于栈和队列的视频。为了刷新记忆,栈和队列是抽象数据结构,根据编程语言有许多不同的实现。两者共有的独特原则是元素的添加和移除方式。
你学习了栈和队列采用顺序访问,并使用空栈、压入和弹出方法来移动和/或添加和移除项。你还了解了后进先出和先进先出原则。

LIFO:Last In, First Out(后进先出)
FIFO:First In, First Out(先进先出)
当你学习队列时,你了解到队列与栈非常相似,因为它往往具有相同的方法:创建、插入、移除和检查队列状态。与栈不同,队列基于先进先出的原则工作。同样,名称很好地指示了结构的工作原理。
树结构 🌲
本课的最后一个视频重点介绍了树。树是一种强大的数据结构,为添加和搜索值提供了极大的灵活性。树的固有结构可以让你理解存储数据之间的大量关系,这在从数据中提取信息时可以节省大量时间和代码。


你学习了树结构以及数据如何在树中移动。
高级数据结构 ⚙️

在下一课中,你被介绍了高级数据结构。首先,你学习了哈希表是什么、它的结构和固有特性以及它如何工作。你还探讨了使用哈希表的一些优点,并发现了哈希中“冲突”的含义。
让我们快速回顾一下这涉及的内容。你被介绍了哈希函数,并了解到获取键,并以将其减少为固定大小值的方式对其应用哈希函数。


你通过我们经验领域的一个例子学习了压缩。当你想通过互联网发送信息时,你可能首先将其大小压缩到可管理的字节数,通过互联网发送,然后在另一端解压缩。

随后解释了哈希表如何通过使用索引提供存储和搜索数据的替代方法。为了实现这一点,你必须实现一种算法,该算法接收一个键并将其映射到存储在索引中的值。

堆结构 📊
本课的下一个视频重点介绍了堆的结构和特性。你还发现了堆如何用于将元素从最不重要组织到最重要,以及如何通过限制堆的功能来提高生产力。
你了解到,堆可以优先处理值最小的键,然后称为最小堆;而将优先级放在最大值上的堆称为最大堆。堆可以执行一些选定的核心操作,即插入、查找和删除项。
随后,你了解到删除树中的项需要重新构建树,这会导致性能下降。总结关于堆的视频,你对堆以及如何使用它们将元素从最不重要组织到最重要有了更深入的理解。你已经了解到,通过限制功能,可以提高生产力。
与选择任何数据结构一样,重要的是为正确的工作找到合适的工具。
图结构 🗺️
最后,你重点学习了图,并设定了如下场景。在考虑计算机科学中的给定问题时,始终重要的是考虑解决你的问题可能需要什么执行,并通过这种反思选择适当的数据结构来保存你的数据。
假设你可能为一家大型互联网公司工作,该公司希望存储位置目录及其相互之间的连接性。使用了一个城市相对位置图来说明所有权重图、无向图等概念,并且与有向图相反,无向图没有优先顺序。
之后,你了解到,如果边只是单向的,则有向图中的连接被认为是弱连接。然而,如果两个节点之间有双向的两个连接,则称其为强连接。


总结 🏁
在本视频中,你学习了本模块涵盖的关键概念和主题。你已经对所有提到的主题进行了一些测验。你正为未来做好更充分的准备。祝你在下一个模块中好运。
154:排序算法 🧮
在本节课中,我们将要学习排序算法以及几种不同的排序方法。排序数据听起来可能很简单,但深入细节后会发现它颇具挑战性。我们将介绍选择排序、插入排序和快速排序这三种基本方法,并探讨每种方法的实现步骤及其优缺点。

排序的必要性
上一节我们介绍了排序算法的基本概念,本节中我们来看看为什么排序如此重要。

处理已排序的数据或具备自行排序数据的能力,可以显著节省时间。因此,一个能够被排序的元素数据集是基础且必要的。
这种顺序可以是按字母顺序、数字顺序、时间顺序、形状大小或颜色色调。具体使用何种度量标准并不重要,关键在于这些元素能够按升序或降序排列。

另一个需要考虑的因素是排序过程是置换(即重新排列原列表)还是通过创建副本同时保留原列表的顺序来实现。

选择排序
选择排序是一种早期的排序方法,它模仿了人类处理排序问题的方式。其基本原理非常直接。
以下是选择排序的核心步骤:
- 遍历列表,找到最小的元素。
- 将该最小元素与列表的第一个元素交换位置,使最小元素位于顶部。
- 此时,原顶部位置的元素被交换到了列表中空出的位置。
- 对列表中的每个元素重复此过程,直到列表按从小到大(从左到右)的顺序排列好。
让我们通过一个例子来探索这个过程。在下图中,索引位置0的元素是35。在选择排序中,会将索引0的元素与数组中的每个元素进行比较,直到找到最小值。同样地,下一个位置的元素46会与每个元素比较,在本例中与6交换。接着是索引2的元素36,你会发现索引3的元素9被认为是最小的,但必须搜索整个数组才能确认。这个过程持续进行,直到每个元素都按大小排序完毕。

插入排序
另一种直接的排序方法是插入排序。与遍历所有元素不同,这种方法从检查列表中的前两个元素开始。
以下是插入排序的核心步骤:
- 比较列表中的前两个元素,将较小的元素移到前面。
- 对之后的每个元素重复此过程:将每个元素与其左侧的元素进行比较。
- 如果发现它比左侧元素小,则交换到左侧,并继续向左比较,直到找到合适的位置。

所以,元素2与元素4比较,发现更小,于是发生交换。接着,元素2与元素1比较,发现更大,因此不再进行比较。然后,元素3首先与元素4比较,发现更小,发生交换。接着,元素3与元素2比较,发现更大,因此不再进行进一步比较。
让我们探索一个具体的例子。在屏幕上,你会注意到一个数字数组。第一个元素35左边没有更大的元素,所以它保持原位。然后元素46被比较,也留在原地。接下来你看到元素36,它与位置1的元素比较,比46小,所以它们交换。与位置0的检查表明该元素不需要进一步交换。在第3步,你会注意到元素9。它与46比较,因此交换到位置2。它进一步与位置0和1比较,并再次交换。接下来,位置4的元素与位置3比较并交换。它进一步与位置2和位置1交换,也与位置0比较,但由于更大,不再移动。这个过程持续进行,从右向左移动,直到整个数组排序完毕。
快速排序

插入排序和选择排序都是基于简单范式的直接方法。快速排序则是一种更复杂、实现难度更高但效率也高得多的方法。

快速排序基于枢轴的原则运作。该算法选择数组中的一个元素作为枢轴。
以下是快速排序的核心步骤:
- 选择数组中的一个元素作为枢轴。
- 将所有大于该值的元素移到枢轴的右侧,所有小于该值的元素移到枢轴的左侧。
- 对枢轴两侧的子数组重复此过程,直到所有项目排序完毕。
让我们通过一个例子来探索。这里,使用快速排序选择元素9作为枢轴点。所有小于9的项目交换到左边,所有大于9的项目交换到右边。因此,在这次首次分割后,较小的元素(在本例中是6和3)被移到了左边。对结果数组再次应用相同的过程,当发现3是唯一未被分割的元素时终止。现在,取大于原始选定枢轴的值,选择一个新的枢轴。在本例中,选择36,并执行进一步的元素交换。最后,剩余的未排序索引位置根据新的枢轴进行交换。一旦所有元素排序完毕,算法终止。
排序算法的选择与实践

除了上述方法,还有许多额外的排序方法,有些甚至融合了这些现有方法的特点。

在实践中,你可能不需要自己编写实现,因为每种编程语言都有优秀的现成实现。本节的目标是展示它们内部如何运作,以便你在面对特定问题时能选择最佳方案。

与数据结构一样,没有一种排序算法能在所有场景下都提供最佳结果。每种方法都有其权衡,在某些环境中比其他方法更有效。你很快将在大O表示法的学习中,更深入地了解这些方法的效率。
总结


本节课中,我们一起探索了排序算法以及几种可用的不同排序方法。我们学习了选择排序、插入排序和快速排序的实现步骤,并发现了每种排序方法的优缺点。理解这些基础算法的工作原理,将帮助你在未来的开发中做出更明智的技术决策。
155:搜索算法
在本节课中,我们将要学习计算机科学中的两种基础搜索算法:线性搜索和二分搜索。我们将了解它们的工作原理、实现步骤以及如何用大O表示法分析其效率。
概述
上一节我们介绍了排序算法,本节中我们来看看如何在一个已排序或未排序的数据集中查找特定元素。搜索是数据处理中的一项基本操作,理解不同的搜索方法及其适用场景至关重要。
什么是搜索?
在计算机科学中,当面对一个数据集合时,经常需要识别其中的特定元素。然而,对“特定元素”的定义可能存在不同的解读。
例如,给定一个哈希表,你可能需要查找是否存在一个键值对与给定的键匹配。这是一个简单的、一对一的比较操作,结果要么是返回唯一的键,要么是表示键不存在。
进行搜索时,还需要考虑一些其他情况,例如在数组中查找最大值、最小值或中位数。如果查找的值不存在,应该返回什么?返回一个空值可能会影响应用程序的后续运行能力。
因此,设计搜索时需要考虑:当没有返回值时应设置哪些安全措施?搜索是应该返回该值的第一个实例还是最后一个实例?
在本课末尾的补充阅读材料中,有一个链接指向空值发明者托尼·霍尔的一次演讲,他称空值为自己“价值十亿美元的错误”。
线性搜索
最简单的搜索实现是线性搜索。如果你有一个元素数组,线性搜索从索引起点开始,逐个遍历数组,直到找到目标元素或检查完所有元素。
在这种方法中,最好的情况是O(1)(目标元素正好在第一个位置),最坏的情况是O(n)(目标元素在最后一个位置或不存在,需要检查每个元素)。
以下是线性搜索的伪代码示例:
function linearSearch(array, target):
for i from 0 to length(array)-1:
if array[i] == target:
return i // 找到目标,返回索引
return -1 // 未找到目标
二分搜索
与数据结构相关,有些数据结构本身具有排序特性,例如堆或二叉树。你也可以对任何数据结构先应用排序算法,然后再应用搜索方法。
二分搜索在每次迭代中都会将搜索空间减半。假设有一个已排序的数据列表,二分搜索首先检查中间点,判断中间元素是大于还是小于目标元素。
如果中间元素小于目标值,则丢弃列表的左半部分,将右半部分作为新的搜索焦点。现在,只在列表的右半部分查询中间值。如果它仍然小于目标元素,则再次丢弃左半部分,检查过滤后列表的右半部分。通过这种方式,算法在每次迭代中都将搜索空间减半。
这种方法比线性搜索更快,但要求在开始搜索之前数据必须已排序。虽然这个要求看起来可能不太合理,但如果你的数据读取频率远高于更新频率,那么这种解决方案可能是合适的实现。
与之前介绍的线性搜索一样,这种方法最好的可能结果是第一次就找到元素,即O(1)。然而,最坏的情况则不那么乐观。第一次搜索后,列表被减半。如果这次迭代不成功,它再次被减半。第三次分割后,再次减半,依此类推。因此,可以说经过k次迭代后,剩余大小为 N / 2^k。换句话说,时间复杂度为 O(log n)。
这比线性方法要高效得多。但是,需要记住的是,任何感知到的时间增益都需要与排序列表所花费的时间进行权衡。如果列表经常更新,排序可能成为一个代价高昂的过程。
以下是二分搜索的伪代码示例(假设数组已升序排序):
function binarySearch(array, target):
left = 0
right = length(array) - 1
while left <= right:
middle = floor((left + right) / 2)
if array[middle] == target:
return middle // 找到目标,返回索引
else if array[middle] < target:
left = middle + 1 // 目标在右半部分
else:
right = middle - 1 // 目标在左半部分
return -1 // 未找到目标
总结
本节课中我们一起学习了线性和二分搜索算法。我们了解了完成这些搜索的步骤及其工作原理,也学习了如何应用大O表示法来估算两者的效率。我们还认识到,通过对标准方法进行一些巧妙的调整,可以显著提高性能。


在下一课中,你将开始学习如何使用算法。
156:分治算法范式
概述
在本节课中,我们将要学习“分治”算法范式。分治范式为解决特定问题提供了一个有用的思考框架。它包含了本模块中讨论的两个核心原则:递归和将问题分解为更小的问题。我们将了解分治范式如何运作,其涉及的必需与可选步骤,以及它为计算机带来的优势。
分治算法如何工作?
分治算法包含两个必需步骤和一个可选步骤,即“分”、“治”和可选的“合”。
在“分”的步骤中,输入被分割成更小的部分并单独处理。
在“治”的步骤中,与每个小部分相关的任务被逐一解决。
可选的最后一步“合”,则是将所有已解决的部分组合起来。这一步并非在所有情况下都会发生,但在我们提供的例子中会出现。
分治范式示例:归并排序
上一节我们介绍了分治算法的基本步骤,本节中我们来看看一个具体的应用实例。在讨论排序方法时,我们知道解决问题有多种方式。以排序为例,我们可以讨论另一种能够使用分治方法解决的排序方法。
归并排序是一种对数组进行排序的复杂方法。它首先将数组对半分开,然后将这两个半部分再次对半分,并持续重复这个过程,直到只剩下一个元素为止。接着,这个过程逆转,每个较小的列表在重新合并回其被分开的部分之前,先被排序。
这个解决方案基于一个理念:通过将问题分解为更小的问题,更容易完成整体任务。
为了更直观地理解分治如何应用于归并排序,让我们探讨一个现实世界的例子。
现实世界类比
假设你和三位室友决定一起购物。在列出一个长长的购物清单后,你们一同前往超市。
一种解决方案可能是大家一起在超市里走动,从清单上逐一拿取物品。
一个更好的方法可能是将清单分成四部分,每人负责一部分。这将减少在商店里的总耗时。


尽管这可能导致部分物品重复拿取,但任务的进一步优化可能是先对清单进行排序,使所有相似物品归类在一起。例如,所有饮料、所有水果、所有肉类等等,然后为每位成员分配超市的一个特定区域。这将是一种更高效的任务完成方式。
俗话说,问题分担,困难减半。那么这在计算机上是如何运作的呢?
分治在计算机上的优势
分治方法为计算机带来了两个直接的优势:并行化和内存管理。
并行化是指让不同的线程或计算机同时处理同一个问题,以更快地完成它。采用分治解决方案的一个好处是,你可以在编码时运用并行化。
现在,让我们探讨内存管理。以归并排序为例,可以考虑将每个数组发送到不同的核心或服务器进行处理(具体取决于你所在组织的架构),然后再返回结果。
有时,要处理的数据可能太大,无法全部装入内存,必须分块处理。此外,管理者可能提供了云计算资源的访问权限,因此解决方案可以涉及访问在线服务器,并将部分问题从公司服务器上分流出去处理。

所有这些都有助于管理你可用的内存资源。
总结
本节课中,我们通过归并排序的例子介绍了分治范式,以及它如何为解决问题提供一个框架。我们还学习了与之相关的一些术语,以及这种方法如何适用于现实世界的计算机优化方案。我们也了解了分治范式涉及的必需和可选步骤,以及这种范式为计算机带来的优势。
157:递归
在本节课中,我们将要学习递归的概念、实现递归解决方案的三个核心要求,以及递归与循环相比的优缺点。递归是函数调用自身的一种编程技巧,常用于解决可以分解为相似子问题的情况。

从分治到递归

在之前的视频中,我们学习了分治范式。本节中,我们来看看如何通过递归来实现分治思想。
计算机语言的一个基本能力是执行循环。循环使我们能够重复执行操作,直到达到期望的输出。与人类不同,计算机可以不知疲倦地重复执行相同的任务。
除了循环,解决问题的另一种方法是递归。函数调用自身的做法被称为递归,这也是本节视频的重点。
递归是指一个函数反复调用自身,每次处理问题的一个更小实例,直到满足某个退出条件。
实现递归的三个要求
以下是实现递归解决方案必须满足的三个核心要求。
- 基准情形:确保函数不会无限调用自身,最终能够终止。
- 递减结构:每次递归调用时,问题的规模都应减小。
- 递归调用:函数在其定义中调用自身。
让我们通过一个计算数字指数的例子来更好地说明这三个要求。计算一个数的指数,即确定从该数可以推导出多少种可能的排列组合,这在演示二进制如何表示一系列字符时讨论过。
function exponent(x, n) {
// 1. 基准情形
if (n === 0) {
return 1;
}
// 2. & 3. 递归调用(同时处理递减结构 n-1)
return x * exponent(x, n - 1);
}
在上面的代码中:
- 基准情形是
if (n === 0)。当n减至 0 时,递归停止。 - 递减结构体现在每次调用时,参数
n都会减 1 (n - 1)。这与建立基准情形同样重要,确保函数最终能到达基准情形并停止调用自身。 - 递归调用发生在
return x * exponent(x, n - 1);这一行,函数exponent调用了自身。
可以说结构是递减的,因为其规模在每次调用间都减小了。每次调用函数时,都会在调用栈上创建一个新的实例。例如,调用 exponent(2, 3) 将导致三个实例被创建并放入调用栈。

这会增加计算成本,因为调用函数需要资源。然而,每个调用实例的计算结果都会保留在调用栈上,这在计算层次性问题或需要了解哪些步骤导致特定结果的问题(如图的遍历)时非常有用。

递归的应用实例
让我们探索一个递归的使用例子。考虑二分查找算法。一个二分查找函数接收一个列表和一个目标值作为参数。
首先,检查列表的中点元素,以决定接下来要搜索列表的哪一半。重复此过程,直到找到目标元素或确定其不存在。你可能会考虑用循环或递归来解决这个问题。递归的输入将是一个列表和一个搜索元素,递归函数将不断调用自身,直到达到目标端点。
为何使用递归?
那么,既然简单的循环就能做到,为什么还要使用递归呢?有些问题非常适合用递归调用来解决。
考虑计算给定数字的斐波那契数列。斐波那契数列是一个数字序列,其中前两个数字是 0 和 1,之后的每个数字都是前两个数字之和。计算其结果涉及传入一个数字,计算输出,改变数字,然后用新的整数输入再次调用函数。
以递归方式编写代码意味着你可以简单地用不同的整数调用函数,它将返回所需步骤的分解。可读性是递归的一大优势。有时,当一个问题需要进行许多检查时,循环可能很快变得笨拙。递归解决方案减少了解决问题所需的代码量,并且可能更易于阅读和理解。
最后,递归可以作为分治解决方案的一部分来使用。在这里,问题被分解成更小的步骤并重复执行,以找到最优解。

总结
本节课中,我们一起学习了递归。你了解到,虽然递归可能会给问题增加一些计算开销,但它也能产生优雅、易读的代码。此外,递归体现了分治思想,将问题分解为其最小的组成部分并逐一解决。
158:动态规划概念与记忆化
在本节课中,我们将要学习动态规划的基本概念,包括其核心思想、记忆化技术以及它与分治法和递归的关系。我们将通过简单的例子来理解动态规划如何优化问题求解过程。
在接触动态规划之前,你已经学习了分治范式和递归。
本节中,我们将探讨记忆化和动态规划的概念。动态规划是一种编程范式,它提倡通过将问题分解为更小的子问题来解决它们。

这些子问题的解决方案随后被存储在适当的数据结构中,以备后用。

这样做的好处是,如果这些子问题需要再次计算,只需查找答案,而无需重新计算整个问题。

这种解决子问题并存储结果,以便在未来可能查找时节省时间的技术,被称为记忆化。


动态规划与前面视频中已经遇到的两个概念相关。
让我们快速回顾一下这些概念。第一个是分治法,即将一个大问题分解为一组较小的子问题,然后解决这些子问题。

第二个是分治法的一个子集,称为递归。递归是一种编码解决方案的实践,它避免运行循环,而是通过多次自我调用来得出解决方案。
动态规划是这些方法的扩展,它额外涉及记录每次新运行子问题时产生的结果。
在后续运行中,不再重新计算结果,而是查询上一次遇到相同问题时的答案。
如前所述,这种方法称为记忆化。为了强化这个概念,记忆化是指当编译器识别出某个计算已为之前的任务运行过时,存储并使用先前计算的结果,以替代重新运行计算。
为了举例说明,请考虑视频中提出的关于二进制数的问题:一个六位二进制锁有多少种可能的组合?
在之前的视频中,已经展示可以通过指数运算或求幂来发现这一点。因此,同一个六位锁将有 2^6 或 64 种组合。
所以 2^6 = 2 × 2 × 2 × 2 × 2 × 2。
或者,你可以将其分成两组,先计算 2 × 2 × 2,再计算 2 × 2 × 2,结果是 8 × 8,同样得到相同结果。
应用分治法,并利用记忆化高效计算,将首先计算 2^3,然后再次使用 2^3,从而减少所需的计算总量。
通过应用记忆化,第一个 2^3 将被计算出来,然后在第二个括号中重用,减少了整体所需的计算。

那么,什么样的问题适合用动态规划解决呢?
动态规划方法通常应用于组合或优化问题。


一个已经提到的组合问题的例子是斐波那契数列。另一个你可能在面试中遇到的例子是背包问题。
这既是一个组合问题,也是一个优化问题。

假设你为一次计划好的露营旅行做准备。你可以往背包里装必需品。每件物品都有重量成本:手电筒重 1 公斤,水重 2 公斤,帐篷重 3 公斤。此外,每件物品都有价值:手电筒价值 1,水价值 2,帐篷价值 3。
简而言之,背包问题列出了一系列重量不同、价值不同的物品。你的背包只能携带一定数量的物品。问题要求计算,如果你的背包能承受特定重量,你能携带的最佳物品组合是什么。目标是找到在背包重量容量限制下的最佳回报。
为了计算这个问题的解决方案,你必须选择所有加起来达到给定重量并包含给定价值的物品。
可携带的重量会发生变化。这个问题可以应用于资源分配,例如你拥有一定的 CPU 算力,需要运行 X 个任务,就像 CPU 完成任务的容量一样。有时重量可能是 7 公斤,其他时候可能是 10 公斤。
动态规划涉及保存用于得出给定解决方案的计算过程。
所以,如果你已经计算出了 7 公斤的最佳选择方案,当重量要求提高到 10 公斤时,你无需重新运行初始计算。这可以成为一种节省时间的度量方法。
在计算动态规划解决方案时,你必须首先确定目标函数,即对最佳结果是什么的描述。
接下来,你必须将问题分解为更小的步骤。实现这一点的一种已经讨论过的方法是使用递归函数,即反复调用自身直到得出解决方案的函数。
这些函数应该以这样的方式编写:你可以在不更改已编写方法代码的情况下改变结果。


总结
本节课中,我们一起学习了动态规划是一种旨在优化给定问题解决方案的方法。它利用记忆化和重叠子问题的原理,来识别何时可以快速实现目标函数,从而优化所需的计算步骤。
159:贪婪算法 🧠
在本节课中,我们将学习如何使用贪婪算法这一范式来解决复杂问题。我们将探讨其核心思想、与动态规划的对比,并通过具体例子理解其工作原理和优缺点。

概述
贪婪算法是一种遵循“奥卡姆剃刀”哲学原理的问题解决方法,该原理主张最简单的解决方案几乎总是最好的。在算法领域,贪婪算法通过一系列局部最优选择来寻求问题的整体解决方案,它注重即时回报,而非全局的、长远的规划。

上一节我们介绍了算法设计的不同范式,本节中我们来看看其中一种强调即时与局部最优的策略——贪婪算法。
贪婪算法与动态规划

这是一种有别于动态规划的替代方法。动态规划旨在通过解决所有子问题并选择最优子集来找到全局最优解。而贪婪方法则查看解决方案列表,并实施局部优化,通常选择当前回报最高的选项。
为了更清晰地说明,让我们举一个CPU需要完成一系列任务的例子。
- 动态规划方法:类似于“背包问题”,需要确定在给定时间内可以完成哪些任务的子集,以最大化总价值。这涉及计算所有可能的组合。
- 贪婪算法方法:总是选择当前最有价值的任务(例如运行时间最短的程序)放入“背包”,而不考虑这可能会排除其他哪些任务。

因此,在我们的CPU示例中,贪婪方法会先选择运行时间最短的程序,然后是次短的,依此类推。虽然这可能不会带来全局最优解,但它减少了计算最有效任务子集的开销。
为了更好地理解这两种方法的差异,让我们考虑最短路径问题。
实例分析:最短路径问题
下图展示了一张包含9个节点(A, B, C, D, E, F, G, H, S)的地图。每个节点通过带权重的路径连接,权重反映了选择该路径所需的成本(例如时间、距离)。
现在,你需要规划从节点E到节点F的最有效路线。

- 动态规划方法:会创建一个表格,从E开始计算每个潜在节点的累积成本,逐步引入后续节点集并计算总成本。这种方法无疑会得出最有效的解决方案。在初始计算后,利用记忆化技术保存结果,后续的路径计算将受益于更快的计算时间。这是一种自底向上的全局方法。
- 贪婪算法方法:其方法论则不同。它不会尝试寻找连接路线的最优子集,而是从节点E开始,查看每个可用的连接。
以下是贪婪算法的决策步骤:

- 在节点E,可选的连接及其权重为:C(5), B(3), D(2), G(4), H(12)。数组中最低的值是2,对应节点D。遵循贪婪原则,算法会选择这条路径,前进到节点D。
- 假设数据结构是有向图,在节点D,将面临另外三个节点:A(7), F(5), G(6)。由于F是最终目的地,算法会选择F并到达终点。
最终,算法累积的总成本(惩罚)为:从E到D的权重2,加上从D到F的权重5,总计7。

从视觉上看,这恰好是最优路径。它是在没有创建详尽的组合表和计算所有可能路径的情况下找到的。

然而,如果节点G和F之间的路径权重是2(而不是图中的6),那么贪婪算法可能会选择一条次优的路径(例如E->G->F,假设G到F的权重为2,则总成本为4+2=6,优于E->D->F的7)。但根据当前权重,算法会错误地选择E->D->F(成本7),而错过更优的E->G->F(成本4+2=6)。这就是选择贪婪算法而非动态规划时需要权衡的地方。

贪婪算法的特点

虽然贪婪算法的开销低,且编码解决方案相当直接,但它并不总能保证返回最佳选项。
- 优点:计算开销小,实现简单直接。
- 缺点:不能保证获得全局最优解。

总结
本节课中我们一起学习了贪婪算法。你不仅对贪婪算法方法有了更深入的理解,还看到了它与动态规划解决方案的对比,从而加深了对这种替代方法优缺点把握。
下次你在谷歌地图上规划路线时,可以思考一下所提供的路线选择是如何被计算出来的,其中可能就运用了类似的算法思想。
160:24_算法简介模块总结 🎯
在本节课中,我们将回顾“算法简介”模块的核心内容。我们将总结排序、搜索算法的基础知识,并探讨分治、递归、动态规划和贪心算法等关键算法范式。

模块回顾

恭喜你完成了“算法简介”模块的学习。现在,让我们花点时间回顾一下在这个模块中学到的知识。
我们首先从排序和搜索算法开始。排序是计算机科学中的基础操作,它对于高效的数据检索和处理至关重要。

排序算法

在本节中,我们学习了排序的重要性,并探讨了三种主要的排序方法:选择排序、插入排序和快速排序。
以下是这三种排序算法的核心思想:
- 选择排序:反复从未排序部分选择最小(或最大)元素,放到已排序序列的末尾。
- 插入排序:构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。
- 快速排序:采用分治策略,选取一个“基准”元素,将数组分为比基准小和比基准大的两个子数组,然后递归地对子数组进行排序。

我们分析了每种算法排序数据的具体步骤,并探讨了它们在解决特定问题时的优缺点。重要的是,没有一种排序算法能在所有场景下都提供最佳性能,选择哪种算法取决于具体的数据特性和需求。

搜索算法
上一节我们介绍了排序,本节中我们来看看搜索算法。搜索算法是计算机科学的另一个基本概念,用于在数据结构中查找特定元素。
以下是两种核心的搜索方法:
- 线性搜索:顺序遍历数据结构中的每一个元素,直到找到目标元素或遍历完所有元素。
- 二进制搜索(二分查找):要求数据已排序。在每次迭代中,将搜索空间对半分割,通过比较中间元素来缩小搜索范围。
我们还学习了实现这两种方法的步骤以及它们各自的优势。此外,我们深入探讨了搜索和排序算法的时间与空间复杂度,这是评估算法效率的关键指标。
算法范式
接下来,我们进入了关于算法范式的课程。这里我们学习了处理算法的不同高级策略。

首先,我们探讨了分治范式。在“分”的步骤中,将输入问题分解为更小的子问题;在“治”的步骤中,独立解决每个子问题;可选的最后一步“合”,则是将所有已解决的子问题结果合并。我们发现,分治技术为解决问题提供了一个有效的框架,并带来了诸多好处。

接着,我们探索了另一个重要的算法方法:递归。递归是指函数反复调用自身来解决规模更小的同类问题,直到满足某个退出条件。
实现递归解决方案需要满足三个要求:
- 基准情况:递归终止的条件。
- 递减结构:每次递归调用,问题规模都应向基准情况缩小。
- 递归调用:函数调用自身。
然后,我们介绍了动态规划。这是一种通过将问题分解为更小的重叠子问题来求解的编程范式。我们探讨了记忆化的概念,即解决子问题并将其结果存储起来,以便在未来的搜索中节省时间。
计算动态规划解决方案的过程可以概括为:
- 确定目标函数:描述最优结果是什么。
- 将问题分解为更小的步骤(定义状态和状态转移方程)。
- 决定应用哪种动态规划方法(如自顶向下的记忆化递归或自底向上的迭代)来实现目标。

最后,我们学习了贪心算法。与动态规划相比,贪心算法在每一步都做出当前看来最优的选择(局部最优),希望以此导致全局最优解。

我们探讨了如何实现贪心算法来解决问题,并认识到在选择贪心算法而非动态规划时存在权衡。虽然贪心算法的开销较低,编码实现也相对简单,但它并不总能保证返回全局最优解。

总结
本节课中,我们一起回顾了“算法简介”模块的全部内容。我们从基础的排序和搜索算法入手,理解了它们的工作原理和适用场景。随后,我们深入学习了分治、递归、动态规划和贪心算法这四种强大的算法范式,掌握了它们解决问题的核心思想和实现要点。
凭借你已获得的所有知识,在进入最后一个模块完成分级评估之前,只剩下完成本模块的最终测验了。你已经非常接近终点了。祝你好运,并享受接下来的学习旅程!🚀
161:课程回顾
在本节课中,我们将回顾整个课程所涵盖的一系列核心概念与技能,这些内容旨在帮助你为技术编码面试做好充分准备。我们将系统性地梳理从面试准备、计算机科学基础、数据结构到算法的关键知识点。

模块一:编码面试准备


上一节我们介绍了课程的整体目标,本节中我们来看看第一个模块,它专注于帮助你理解并准备技术编码面试。
你首先了解了什么是技术编码面试,它可能包含的形式以及你可能遇到的不同面试类型。第一课的重点是技术编码面试,其主要目的是确认你是否具备履行岗位职责的技术能力。
以下是你在技术面试中必须牢记的步骤:
- 理解问题。
- 提出澄清性问题。
- 构思解决方案。
- 编写代码。
- 测试你的解决方案。

你了解到,使用合适的工具始终很重要,并且必须时刻考虑时间限制。你还探索了如何为编码面试做准备,以及第一印象的重要性,包括专注于沟通技巧,例如解释你的思维过程和处理错误。

你学习了STAR方法(情境、任务、行动、结果),并了解了如何在面试交流中利用它来展示你的能力。你还学习了如何使用伪代码来演示你如何得出解决方案,以及一些实用的解决方案设计技巧和如何测试你的解决方案。
模块二:计算机科学基础
在了解了面试准备后,接下来我们进入计算机科学基础部分,这为理解后续的数据结构和算法奠定了基础。

你首先学习了二进制,了解了十进制(B10)和二进制(B2)之间的区别。然后你发现了位置记数法,即利用数字的位置来表示递增的数值。接着,你探索了计算机内存的关键组件及其工作原理。
你现在应该已经知道,为了更好地理解内存的各个层级,并能够描述它们之间的差异。你学习了传输速率,即计算机将内存转移到缓存进行处理的速度。

随后,你转向了时间复杂度,学习了如何通过完成任务所需的时间来评估时间效率或衡量性能。你发现了大O表示法,这是一种用于确定算法效率的度量标准。

你探索了空间复杂度,这本质上是计算结果所需的空间。任何关于空间复杂度的决策不仅基于算法的速度,还基于给定解决方案将使用多少内存容量。在速度与紧凑性之间,总是需要做出权衡选择。
模块三:数据结构
掌握了计算机科学基础后,我们进入数据结构模块,这是组织和管理数据的核心。
你学习了数据结构,范围从基本的数据结构如字符串、布尔值或数组,到更高级的数据结构如集合、图和堆,并了解了每种数据结构带来的特定优势和限制。你探索了所有类型的数据结构,以及它们如何被分类为两个主要分支:线性和非线性。
接下来,你被介绍了栈和队列,这两种抽象数据结构在元素的添加和移除方式上都有特定的特性。当你学习队列时,你了解到队列与栈非常相似,它们往往具有相同的方法(创建、插入、移除和检查状态)。但与栈不同,队列基于先进先出(FIFO) 的原则工作。
最后,你了解到树是一种强大的数据结构,它在添加和搜索值方面提供了极大的灵活性。随后,你继续研究了一些高级数据结构,即哈希表、堆和图。
你探索了堆,发现了堆如何用于将元素从最不重要到最重要进行组织,以及通过限制堆的功能,如何可以提高效率。

最后,你研究了图。这种结构图示了一个由节点(表示目的地)和边(显示每个节点如何与另一个节点相关联)组成的图。节点之间存在值,意味着这是一个加权图。没有箭头存在,意味着这是一个无向图,与有向图相对,无向图没有优先顺序。
你了解到,在有向图中,如果边是单向的,则连接被认为是弱连接。然而,如果两个节点之间有双向连接,则被称为强连接。


模块四:算法
在掌握了数据结构之后,我们进入最后一个模块:算法。这是解决问题的具体方法和步骤。
你首先探索了排序算法,并了解到使用已排序的数据或能够对自己的数据进行排序可以显著节省时间。你发现了排序的重要性,并探索了三种主要的排序方法:选择排序、插入排序和快速排序。
接下来,你继续探索搜索算法,并了解了每种类型如何为其解决问题提供自己的框架。你探索了两种核心的搜索方法:线性搜索和二分搜索。线性搜索遍历给定数据结构中的每一项,直到找到特定项;而二分搜索在每次迭代中将搜索空间减半。

你还深入了解了搜索和排序算法中的时间和空间复杂度。随后,你进入了最后一课,在那里你被介绍了如何使用算法。

在这里,你学习了处理算法的不同方法。首先,你探索了分治范式。你了解到,在“分”的步骤中,输入被分割成更小的段并单独处理。在“治”的步骤中,解决与给定段相关的每个任务。可选的最后一步“合”是组合所有已解决的段。

接下来,你探索了另一个重要的算法方法:递归。递归是指一个函数反复调用自身处理问题的较小实例,直到满足某个退出条件。你了解到实现递归解决方案有三个要求,即基本情况、递减结构和递归调用。

然后,你被介绍了动态规划,这是一种通过将问题分解为更小问题来解决问题的编程范式。你检查了计算动态规划解决方案所涉及的过程。本质上,这可以概括为:首先,确定目标函数,即描述最佳结果是什么。接下来,将问题分解为更小的步骤,然后决定你希望应用哪种方法来实现期望的结果。

最后,你学习了贪心算法。与动态规划方法相比,贪心方法会查看解决方案列表并实施局部优化。通常,会选择当前回报最高的选项。你查看了一个示例,了解如何实现贪心算法方法来达成解决方案。虽然贪心算法的开销低且编码解决方案相当简单,但它并不总是保证返回最佳选项。因此,在选择贪心方法而非动态方法时存在权衡。

总结
在本节课中,我们一起回顾了整个课程涵盖的众多重要概念和方法。这是一个真正的成就,它也应该为你可能参加的任何潜在编码面试做好准备。在结束课程之前,你只剩下完成最终的课程测验。祝你好运。
162:26_课程总结
在本节课中,我们将对编码面试准备课程进行全面的回顾与总结。我们将梳理你已掌握的核心技能,并为你未来的学习和职业发展提供方向。
你已经完成了这门编码面试准备课程。
你付出了巨大的努力,并在此过程中积累了丰富的知识。
你在开发者的成长道路上取得了显著的进步。
现在,你应该已经理解了编码面试独特且具有挑战性的方面。具体来说,你应该已经为面试做好了充分准备,掌握了一些面试软技能,这些技能将帮助你在参加编码面试时从容应对。你还掌握了计算机科学的基础知识以及一些解决问题的方法,可以应对面试中可能遇到的任何挑战。

课程核心收获
上一节我们回顾了你的整体进步,本节中我们来看看你在具体技能上的收获。


完成本课程后,你现在应该能够:
- 为整个面试流程做好准备。
- 提供成功面试的策略和技巧。
- 开放地讨论面试过程中的情绪因素。

评估所衡量的关键技能

在分级评估中,以下关键技能衡量了你对知识的理解和掌握程度:
以下是评估中涉及的核心能力领域:
- 编码面试背景下的数据结构:理解并应用各种数据结构。
- 算法的概念与使用:掌握核心算法思想及其实现。
- 算法的可视化:能够通过图示理解算法的执行过程。
- 结合新旧编码模式解决问题:灵活运用已知和新的编程模式来应对挑战。
成就与未来方向
恭喜你,你已经成功完成了本专业的所有课程。在此阶段,你可以考虑注册其他课程、专业或证书路径。
证书是全球公认且行业认可的、掌握技术技能的证明。无论你是刚起步的技术专业人士、学生还是商业用户,你所完成的课程以及作品集中的一系列实践项目都将证明你作为开发者的知识和能力。
这些成就可以用来向潜在雇主展示你的技能。它不仅向雇主表明你具有自我驱动力和创新精神,也充分体现了你个人的特质以及新获得的知识。
到目前为止,你做得非常出色,应该为自己的进步感到自豪。你迄今为止获得的经验将向潜在雇主证明,你积极主动、能力出众,并且不畏惧学习新事物。

再次祝贺你完成本课程,并祝你在接下来的学习旅程中一切顺利。
在本节课中,我们一起回顾了整个编码面试准备课程的要点,总结了你在软技能、计算机科学基础、问题解决方法以及具体技术能力上的成长。我们明确了课程评估的核心技能,并为你未来的持续学习和职业发展指明了方向。请带着这份收获与自信,继续你的开发者之旅。
1:0_简介
概述
在本节课中,我们将要学习Meta前端开发专业证书课程的总体介绍。课程将帮助你从零开始,在一年内掌握前端开发的核心技能,并为求职做好准备。
课程内容与目标
🎼 数字空间是一个充满连接与机遇的世界。以此刻为例,正是网络使你能够注册这个课程项目,并有机会学习Meta开发者的个人经验。
🎼 当你完成这个专业证书时,你将能成为数字体验的创造者。连接在进化,你也在成长。你可能完全没有技术背景,这没关系。即使毫无经验,这个项目也能让你在一年内做好求职准备。
那么,这个专业证书如何能让你为进入像Meta这样的组织工作做好准备呢?前端开发专业证书将帮助你构建胜任前端开发职位所需的技能,同时获得Meta颁发的认证。你将学习Meta开发者如何协作创建和测试响应式、高性能的网站与应用。你还将与其他有抱负的前端开发者讨论有趣的话题,并完成一系列编码练习来提升技能。按顺序完成证书中的所有课程非常重要,因为每门课程都将建立在你已掌握的技能之上。虽然我们为每门课程提供了建议的学习进度。
🎼 但整个项目完全是自定进度的,这意味着你可以自由管理自己的时间。在完成证书的前四门课程中,你将学习如何使用HTML、CSS和JavaScript编写交互式网页。你还将学习使用Bootstrap框架,然后深入React的世界。React是一个广泛使用的JavaScript库,由Meta创建。使用React构建的网站速度快、可扩展性强、安全性高,并能提供丰富的用户体验。难怪它成为了领先公司的选择。
项目实践与职业准备
🎼 在最终项目中,你将完成一个美观、专业的个人作品集网站,并具备交互功能,以便在求职时展示。通过学习使用Git和GitHub进行版本控制,你也将准备好与其他开发者协作。如果你希望独立工作或在较小的团队中工作,也不必担心。在用户体验基础方面,我们也会为你打好基础。你将学习如何研究用户需求,使用Figma等流行的行业工具创建线框图,并进行设计。在最后一门课程中,你将准备编码面试。你将练习面试技巧、完善简历,并解决一些通常在技术职位面试中出现的常见编码挑战。
完成后的机会
🎼 完成项目后,你将获得访问Meta职业项目求职板的权限。这是一个求职平台,连接了超过200家承诺通过Meta证书项目招募人才的雇主。谁知道你最终会走向何方。
无论未来的连接形式如何,你都将成为其创造的一部分。让我们开始吧。
总结
本节课中,我们一起学习了Meta前端开发专业证书课程的总体框架。课程从HTML、CSS、JavaScript基础开始,逐步深入到React、Git、用户体验设计和面试准备,旨在帮助初学者在一年内构建起完整的前端开发技能体系,并最终通过作品集和职业平台连接就业机会。
2:1_简介
概述
在本节课中,我们将要学习前端开发的基础知识,了解网络如何运作,并预览整个课程的学习路径。我们将从日常生活中的网络应用开始,逐步深入到构建网站和网络应用所需的核心技术与工具。
日常生活中的网络应用 🌐
你每天进行的许多活动都可以完全在线完成。你可以使用手机、电脑、平板或其他设备上的应用程序来访问网络,执行诸如购物、预订酒店以及与朋友和同事聊天等任务。
随着远程工作变得越来越流行,你可以在家中舒适地工作、与同事互动并保持高效。这一切的实现,都依赖于互联网基础设施、技术以及构建你所使用应用程序的专业人员的技能。

需要提及的是,当你遇到“应用”这个术语时,它可能指的是手机上的应用,也可能指在网站上运行或通过其他在线方式运行的网络应用。

课程内容与目标 🎯
上一节我们了解了网络应用的普遍性,本节中我们来看看本课程将具体涵盖哪些内容。
从模块一开始,你将学习网络如何运作,包括探索网页、网络服务器和网络浏览器。你将了解它们各自是什么,以及它们在将互联网带到你面前的过程中扮演什么角色。
你还将获得使用核心互联网技术(如HTML、CSS和JavaScript)的实践机会。你将学习开发者如何将这些技术结合起来,构建功能性和交互性的网站与网络应用。
随着课程的深入,你将探索专业开发者使用的一些工具。你将学习使用最佳实践和标准进行编码的基础知识。例如,你将学习如何使用网络浏览器内置的开发者工具,以及使用被称为集成开发环境(IDE)的行业标准软件进行编码。专业人士使用IDE来更高效地编写代码。
课程模块详解 📚
以下是本课程各个模块的详细介绍。
模块二:HTML与CSS入门
在模块二中,你将通过HTML 5和CSS的介绍开始你的编码之旅。你将学习这两种语言的基础知识,以及它们如何相互配合来布局和样式化网页上的元素。这包括文本、图像和视频等多媒体元素。
此外,为了确保你的网页对所有人都可访问,你将学习如何进行网页无障碍编码。
模块三:框架、库与响应式设计
在模块三中,你将学习开发者如何使用框架和库。本模块将重点介绍响应式设计。你将学习如何实现Bootstrap库,以便网页无论使用何种类型的设备都能提供出色的浏览体验。
你还将了解用户界面(UI)设计,以及如何使用常见的UI组件,并通过灵活的Bootstrap网格系统来定位它们。
接下来,你将接触React——一个免费开源的JavaScript库,开发者用它来基于UI组件构建用户界面。然后,你将了解静态内容与动态内容的区别,以及使用单页应用程序的好处。

模块四:实践项目
说到内容,在模块四中,你将有机会通过编辑你自己的个人传记网页来实践新学到的技能。

学习方法与建议 💡
本课程为你提供了网络开发的入门介绍。它是一个课程项目的一部分,旨在引导你走向软件开发职业。你的课程中有许多视频,将逐步引导你实现这个目标。
观看、暂停、回放并重新观看视频,直到你对自己的技能充满信心。然后,通过查阅课程读物来巩固你的知识,并在课程练习中实践你的技能。
在学习过程中,你会遇到几个知识测验,可以自我检查学习进度。
考虑成为一名网络开发者的不止你一人,课程讨论提示使你能够与同学建立联系。这是分享知识、讨论困难和结交新朋友的好方法。
为了在课程中取得成功,你应该尝试为你的学习制定一个时间表。理想情况下,为自己设定一个固定的学习时段和时长。
你可能在本视频中遇到了许多新的技术词汇和术语。如果你现在不能完全理解所有这些术语,请不要担心。随着课程的进行,一切都会变得更加清晰。

总结
本节课中,我们一起学习了前端开发课程的概览。我们了解了网络在日常生活中的应用,预览了课程将涵盖的从网络基础到HTML、CSS、JavaScript,再到Bootstrap、React等框架和库的核心内容,并明确了通过视频学习、实践练习、参与讨论和制定计划来成功完成课程的方法。现在,你已经准备好开启前端开发的学习之旅了。
3:前端、后端和全栈开发人员角色
在本节课中,我们将要学习网站和应用开发中的三种核心角色:前端开发人员、后端开发人员和全栈开发人员。我们将探讨他们的职责、所需技能以及彼此之间的区别。
当你在餐厅用餐时,通常有许多厨师在不同的区域准备你的餐点。同样地,对于你每天使用的网站和应用程序,也有许多角色参与将这些项目交付给用户。

如果你查阅一份高薪IT工作的列表,Web开发人员的角色肯定会占据显著位置,并且理由充分。如果没有开发人员在我们设备上创建、构建和维护我们每天使用的技术,我们生活的数字世界就不可能存在。

但对于有志成为开发人员的人来说,理解与Web开发相关的一些术语可能会令人困惑。为你找到合适的领域,将取决于你对Web开发人员的角色、职责和技术的更深入理解。
前端开发人员 👨💻
上一节我们介绍了Web开发的基本概念,本节中我们来看看前端开发人员。
前端开发人员负责处理网站或Web应用中用户将与之交互的所有部分。这可以包括样式、颜色、按钮、菜单,或者用户在点击、滑动和与网站交互时的用户体验。
前端开发人员的技能可能有所不同,但他们始终专注于三项核心技术:HTML、CSS和JavaScript。
例如,假设你是一名前端开发人员,被分配了在网站主页上添加新闻通讯注册选项的任务。在这种情况下,你将使用HTML来构建显示元素,例如供用户输入电子邮件地址的输入区域,以及点击发送的按钮。
然后,你可以使用CSS来定位、着色和设置页面上这些元素的样式。

最后,你可以使用JavaScript来处理用户点击按钮时的活动。这可能是检查电子邮件地址是否有效,然后将该电子邮件地址发送到网站,以便存储在新闻通讯成员列表中。

虽然HTML和CSS技能至关重要,但最关键的技能通常是JavaScript。它是前端技术的核心动力。这主要是因为它的多功能性,以及它与强大的库和框架(如Meta的React)结合使用的事实。这些工具可用于构建快速、安全且高度可扩展的、以丰富用户界面为驱动的企业网站和Web应用。
前端开发人员的薪资具有竞争力,并可能根据经验而有所不同。通常,前端开发人员的职位会面向初级、中级和高级专业人员开放。对于有志成为开发人员的人来说,这是一个很好的入门领域。通过展示一些核心概念和技能的基本演示,以及一个引人注目的作品集样本,就有可能进入初级职位的就业市场。
后端开发人员 🛠️
了解了前端开发人员的工作后,现在我们来探讨后端开发人员的角色。
后端开发人员负责处理网站或Web应用中最终用户看不到的部分。这些活动发生在幕后,特别是在Web服务器上、数据库中或在构建架构时。
后端开发人员负责在用户请求信息时,或当网站需要与Web架构的另一部分通信以进行处理时,创建和维护功能。例如,执行账户登录或使用信用卡完成在线购买。后端开发人员将促进网站与数据库中存储内容的交互。
因此,后端开发需要不同的语言、技能和工具。虽然这些可能有所不同,但它们通常包括与后端编程语言、数据库管理系统、API和Web服务器相关的知识。薪资与前端开发人员相似,并取决于经验。尽管如此,在某些情况下,薪资可能更高,特别是对于入门级和高级职位。这是因为开始学习后端技术需要更多的设置、配置、资源和一般的基础结构知识。这与前端形成对比,在前端,你仅使用Web浏览器就可以开始学习一些元素。
通往后端开发的道路通常较长,因为你必须精通前端技术的需求。这可能包括互联网、网络和服务器的内部工作原理。对于有志成为开发人员的人来说,首先从前端开始,然后在获得专业知识后再转向后端,这是相当常见的。

全栈开发人员 🌐
上一节我们分别介绍了前端和后端开发,本节中我们来看看结合两者的全栈开发人员。
全栈开发人员是指能够同样熟练地处理前端和后端技术的人。全栈开发人员在Web开发项目周期的所有领域都拥有技能和知识。例如,他们在网站或Web应用的规划、架构、设计、开发、部署和维护方面拥有相关的专业知识。
全栈开发人员的职位通常处于更高级别。需要一些时间来获得成为全栈开发人员所需的知识、专业经验和技能。因此,该领域的职位需求量大,并且是IT行业中薪酬最高的工作之一。
总结 📝

本节课中我们一起学习了Web开发中的三种主要角色。前端开发人员专注于用户直接看到和交互的部分,主要使用HTML、CSS和JavaScript。后端开发人员处理幕后的服务器、数据库和逻辑,确保功能正常运行。全栈开发人员则兼具前端和后端的技能,能够处理整个开发流程。理解这些角色的区别有助于你根据自己的兴趣和技能选择合适的发展方向。
4:前端开发人员的一天 👨💻
在本节课中,我们将跟随Meta的软件工程师Benedict Tobart(Benny),了解一名专业前端开发人员的日常工作、核心职责以及所需的技能与心态。这不仅仅是关于编写代码,更关乎协作、解决问题和为真实用户创造价值。
前端开发的独特之处 🤔
上一节我们介绍了前端开发的基本概念,本节中我们来看看它与其他开发角色的不同之处。

我认为前端开发的不同之处在于,你必须与设计师合作。你也需要与产品经理合作。它在根本上是非常需要协作的。
它既包含右脑思维,即算法思维以及如何让事物运行并表现良好。同时也包含左脑思维,即如何让事物变得易用且令人愉悦。
个人职业路径 🚶♂️
我算是偶然进入了前端工程领域。现在我已经无法想象离开这个行业了。

大家好,我是Benedict Tobart,大家叫我Benny,我是Meta的一名软件工程师。
我认为很多进入前端工程领域的人,他们并没有真正思考过。他们更多地将前端工程视为一个工程问题。你在构建东西,你在解决问题。而实际上,你所做的是为人们构建产品,你在帮助人们解决他们自己的问题。
因此,这带来了大量的协作,包括与设计师、产品经理的合作。你并非那种整天弯腰驼背坐在桌前敲代码的程序员。你踏入的是一个高度协作的环境。
我起步比较早,当时为一款叫《无尽的任务》的游戏建立了一个粉丝网站。所以我大概在那个时期开始制作网站,但我从未真正将其视为一条职业道路。实际上,我后来离开了网站建设,直到大学时期才重新回归。
我认为我真正开始将其视为职业是在大学二年级。我有一个很酷的表哥是程序员。他建议我应该学习这个方向。
最初吸引我的是,我正在构建一些让我感到兴奋的东西。我认为用软件解决问题的实用性真的非常吸引人。

典型的一天 📅
一个典型的工作日,无论是居家办公还是到办公室,我通常以代码审查开始,帮助同事,就如何更好地构建功能提供建议。
我经常与设计师和产品经理开会,讨论我们想要构建的功能。并且我支持很多工程师,帮助他们解决遇到的问题。工作内容广泛而多样。

我认为这恰恰是进入软件行业令人兴奋的地方。这里有广阔的空间让你成为不同类型的工程师。

工作的动力与挑战 ⚙️
你可以为解决那些非常困难、具有技术挑战性的问题而感到兴奋。但如果你做这件事没有目的,我就会失去动力。所以,知道在一天结束时,我所做的工作能帮助到某个人,这有助于我将精力集中在真正需要解决的问题上,而不是那些无关紧要的问题。
我一直在从事加密消息传递方面的工作,出于多种原因,当你构建这类产品时,你会非常严肃地对待它,因为它能让人们安全、私密地交流。
当你解决一个非常困难的问题时,很容易觉得自己没有取得任何进展。我认为行业里每个人都会谈到“冒名顶替综合征”。所以,如果你投入了大量时间却感觉没有进展,你就会开始怀疑自己是否应该在这里,是否能解决这个问题。这是很难克服的一点,这可能也是这份工作中最糟糕的部分。
核心技能要求 🛠️


从技术上讲,我认为你将在本课程中学到很多我所做的事情,包括如何编写代码、如何使其运行、如何使其易于访问,以及如何交付给用户。
以下是成为一名前端开发人员需要掌握的核心技术栈:
- JavaScript:实现网页交互逻辑的编程语言。
- HTML:构建网页内容和结构的标记语言。
- CSS:为网页添加样式和布局的样式表语言。
这些都是基础,你会在每一家网络开发公司找到它们的身影。
当谈到软技能时,同理心非常重要,要努力理解他人的观点。工程师关心性能、可访问性和正确性。通常工程师需要很长时间才能理解设计师或产品经理的视角,因为工程师的思维是算法式的,总想解决所有问题,而不是那80%的用户问题。
我与一位出色的设计师合作,他实际上会参与到代码讨论中,我可以问他关于代码的问题,他也能写一点代码。谈到同理心,他理解我的工作,我也理解他的工作。
归根结底,重要的是要认识到:你是一名软件工程师,你的工作是让他们的很多构想变为现实。同样,设计师不会进入你的代码告诉你应该如何最好地架构代码。你需要提供建议,并在那里提供支持。当你发现问题时,这是一个高度协作的过程。
成长心态与建议 🌱
一名优秀的前端工程师实际上也会涉足后端。解决问题始终是困难的。每当你在学习和挑战自己时,你都会遇到障碍。
最好的建议是:对自己宽容一些。抱着你的目标是成长的心态,无论是成为一名软件工程师还是一个更好的人,采用成长型思维进行开发将是至关重要的,为自己留出时间。我的意思是,谈论如何对自己宽容的心理学并非我的专长,但这与你学习的任何工程知识同等重要。
对我个人而言,这个角色会随着你构建东西和解决问题而成长。你会对其他问题有更深入的见解,这就是扩大你职责范围的概念。
你开始时作为一名工程师,解决诸如如何排序列表、如何在页面上放置列表、如何让按钮工作等问题。而到最后,你可能像我一样在Meta从事加密消息传递的工作。

总结与展望 ✨
本节课中,我们一起学习了前端开发人员的日常工作面貌。
我认为我们正处在行业一个非常令人兴奋的节点。未来几年将出现许多新的、令人兴奋的技术,以及许多将被构建的新方法和新产品。找到这些机会并解决新问题,是的,这让人深感满足。
成为一名前端开发人员,意味着你站在技术与用户体验的交汇点,通过代码将创意转化为现实,并在此过程中不断学习与成长。

浙公网安备 33010602011771号