You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Web-Dev-For-Beginners/translations/zh-CN/5-browser-extension/2-forms-browsers-local-storage/README.md

26 KiB

浏览器扩展项目第二部分:调用 API使用本地存储

journey
    title 你的 API 集成与存储旅程
    section 基础
      设置 DOM 引用: 3: Student
      添加事件监听器: 4: Student
      处理表单提交: 4: Student
    section 数据管理
      实现本地存储: 4: Student
      构建 API 调用: 5: Student
      处理异步操作: 5: Student
    section 用户体验
      添加错误处理: 5: Student
      创建加载状态: 4: Student
      优化交互: 5: Student

课前测验

课前测验

介绍

还记得你刚开始构建的浏览器扩展吗?现在你拥有一个看起来很不错的表单,但它本质上是静态的。今天我们将通过连接真实数据并赋予它记忆功能来让它变得生动。

想想阿波罗任务控制计算机——它们不仅仅展示固定信息。它们不断与航天器通信,更新遥测数据,并记住关键的任务参数。这就是我们今天要构建的动态行为。你的扩展将连接互联网,抓取真实的环境数据,并记住你下次的设置。

API 集成听起来可能很复杂,但实际上就是教你的代码如何与其他服务通信。无论你是在获取天气数据、社交媒体信息流,还是像今天一样获取碳足迹信息,关键都是建立这些数字连接。我们还将探讨浏览器如何持久保存信息——类似于图书馆如何使用卡片目录来记住书本位置。

完成本课后,你将拥有一个能够获取真实数据、保存用户偏好设置并提供流畅体验的浏览器扩展。让我们开始吧!

mindmap
  root((动态扩展))
    DOM Manipulation
      Element Selection
      Event Handling
      State Management
      UI Updates
    Local Storage
      Data Persistence
      Key-Value Pairs
      Session Management
      User Preferences
    API Integration
      HTTP Requests
      Authentication
      Data Parsing
      Error Handling
    Async Programming
      Promises
      Async/Await
      Error Catching
      Non-blocking Code
    User Experience
      Loading States
      Error Messages
      Smooth Transitions
      Data Validation

按照对应文件中的编号段落知道在哪里放置你的代码

设置扩展中要操作的元素

在你的 JavaScript 操作界面之前,需要先获取特定的 HTML 元素引用。就像望远镜需要对准特定的星星一样——伽利略研究木星的卫星前必须先定位并聚焦于木星。

在你的 index.js 文件中,我们将创建 const 变量来捕获每个重要表单元素的引用。这类似于科学家给他们的设备贴标签——这样他们不必每次都在整个实验室中搜索,就可以直接访问需要的东西。

flowchart LR
    A[JavaScript 代码] --> B[document.querySelector]
    B --> C[CSS 选择器]
    C --> D[HTML 元素]
    
    D --> E[".form-data"]
    D --> F[".region-name"]
    D --> G[".api-key"]
    D --> H[".loading"]
    D --> I[".errors"]
    D --> J[".result-container"]
    
    E --> K[表单元素]
    F --> L[输入字段]
    G --> M[输入字段]
    H --> N[UI 元素]
    I --> O[UI 元素]
    J --> P[UI 元素]
    
    style A fill:#e1f5fe
    style D fill:#e8f5e8
    style K fill:#fff3e0
    style L fill:#fff3e0
    style M fill:#fff3e0
// 表单字段
const form = document.querySelector('.form-data');
const region = document.querySelector('.region-name');
const apiKey = document.querySelector('.api-key');

// 结果
const errors = document.querySelector('.errors');
const loading = document.querySelector('.loading');
const results = document.querySelector('.result-container');
const usage = document.querySelector('.carbon-usage');
const fossilfuel = document.querySelector('.fossil-fuel');
const myregion = document.querySelector('.my-region');
const clearBtn = document.querySelector('.clear-btn');

这段代码的作用:

  • 捕获 表单元素,使用 document.querySelector() 结合 CSS 类选择器
  • 创建 了区域名称和 API 密钥输入字段的引用
  • 建立 结果显示元素的连接,用于展示碳使用相关数据
  • 设置 访问 UI 元素,如加载指示器和错误信息
  • 存储 每个元素引用在 const 变量中,便于代码中反复使用

添加事件监听器

现在我们让你的扩展响应用户操作。事件监听器是你的代码监控用户交互的方式。想象早期电话交换机的操作员——他们监听来电,当有人想要通话时连接正确的线路。

sequenceDiagram
    participant User
    participant Form
    participant JavaScript
    participant API
    participant Storage
    
    User->>Form: 填写区域/API密钥
    User->>Form: 点击提交
    Form->>JavaScript: 触发提交事件
    JavaScript->>JavaScript: handleSubmit(e)
    JavaScript->>Storage: 保存用户偏好
    JavaScript->>API: 获取碳数据
    API->>JavaScript: 返回数据
    JavaScript->>Form: 用结果更新界面
    
    User->>Form: 点击清除按钮
    Form->>JavaScript: 触发点击事件
    JavaScript->>Storage: 清除保存的数据
    JavaScript->>Form: 重置到初始状态
form.addEventListener('submit', (e) => handleSubmit(e));
clearBtn.addEventListener('click', (e) => reset(e));
init();

理解这些概念:

  • 绑定 表单提交事件监听器,当用户按下回车或点击提交时触发
  • 连接 清除按钮的点击监听器,用于重置表单
  • 传递 事件对象 (e) 给处理函数以实现额外控制
  • 立即调用 init() 函数,用于设置扩展的初始状态

注意这里使用的箭头函数简写语法。这种现代 JavaScript 写法比传统函数表达式更简洁,不过两者都有效!

🔄 教学检查点

事件处理理解:在进行初始化之前,请确保你能:

  • 解释 addEventListener 如何将用户操作连接到 JavaScript 函数
  • 理解为何要将事件对象 (e) 传给处理函数
  • 认识 submitclick 事件的区别
  • 描述 init() 函数何时运行及其原因

快速自测:如果忘记在表单提交时调用 e.preventDefault() 会怎么样? 答案:页面会重新加载,所有 JavaScript 状态丢失,用户体验中断

构建初始化和重置函数

让我们创建扩展的初始化逻辑。init() 函数就像船舶的导航系统检查仪器——它判断当前状态并相应调整界面。它会检查用户是否之前使用过你的扩展,并加载他们之前的设置。

reset() 函数则为用户提供一个重新开始的机会——类似科学家在实验间隙重置仪器以保证数据清洁。

function init() {
	// 检查用户是否之前保存过API凭证
	const storedApiKey = localStorage.getItem('apiKey');
	const storedRegion = localStorage.getItem('regionName');

	// 将扩展图标设置为通用绿色(未来课程的占位符)
	// 待办事项:在下一课实现图标更新

	if (storedApiKey === null || storedRegion === null) {
		// 初次使用者:显示设置表单
		form.style.display = 'block';
		results.style.display = 'none';
		loading.style.display = 'none';
		clearBtn.style.display = 'none';
		errors.textContent = '';
	} else {
		// 回访用户:自动加载他们保存的数据
		displayCarbonUsage(storedApiKey, storedRegion);
		results.style.display = 'none';
		form.style.display = 'none';
		clearBtn.style.display = 'block';
	}
}

function reset(e) {
	e.preventDefault();
	// 清除存储的区域以允许用户选择新位置
	localStorage.removeItem('regionName');
	// 重新启动初始化过程
	init();
}

这里发生的事情分解:

  • 从浏览器本地存储获取 保存的 API 密钥和区域数据
  • 检测 是否为首次使用(无存储凭据)或是回访用户
  • 为新用户显示 设置表单,并隐藏其他界面元素
  • 为回访用户自动加载 保存的数据并显示重置选项
  • 基于是否有数据 管理用户界面的状态

关于本地存储的关键概念:

  • 数据持久保存,跨浏览器会话有效(与会话存储不同)
  • 以键值对方式保存,使用 getItem()setItem()
  • 当指定键无数据时返回 null
  • 提供简单方式 存储用户偏好和设置

💡 理解浏览器存储LocalStorage 就像赋予你的扩展持久记忆。想象古代亚历山大图书馆如何保存卷轴——信息在学者离开再回来时依然可用。

关键特点:

  • 关闭浏览器后依然保存数据
  • 重启计算机和浏览器崩溃后数据依然存在
  • 提供丰富存储空间保存用户偏好
  • 无需网络延迟即可即时访问

重要提示:你的浏览器扩展有自己的独立本地存储,独立于普通网页。这保证了安全且避免与其他网站冲突。

你可以通过打开浏览器开发者工具F12进入 Application 选项卡,展开 Local Storage 部分查看存储的数据。

stateDiagram-v2
    [*] --> CheckStorage: 扩展启动
    CheckStorage --> FirstTime: 无存储数据
    CheckStorage --> Returning: 发现数据
    
    FirstTime --> ShowForm: 显示设置表单
    ShowForm --> UserInput: 用户输入数据
    UserInput --> SaveData: 存储到 localStorage
    SaveData --> FetchAPI: 获取碳数据
    
    Returning --> LoadData: 从 localStorage 读取
    LoadData --> FetchAPI: 获取碳数据
    
    FetchAPI --> ShowResults: 显示数据
    ShowResults --> UserAction: 用户交互
    
    UserAction --> Reset: 点击清除按钮
    UserAction --> ShowResults: 查看数据
    
    Reset --> ClearStorage: 删除保存的数据
    ClearStorage --> FirstTime: 返回设置

本地存储面板

⚠️ 安全注意事项:在生产环境中,将 API 密钥存储在 LocalStorage 存在安全风险,因为 JavaScript 代码可以访问这些数据。学习用途这样做可以,但实际应用应使用安全的服务器端存储敏感凭证。

处理表单提交

接下来我们处理用户提交表单时的行为。默认情况下,浏览器表单提交会重新加载页面,但我们要拦截这种行为,使体验更加流畅。

这种做法类似任务控制对航天器通信的处理——不是每次传输都重置整个系统,而是持续运行同时处理新信息。

创建一个函数,捕获表单提交事件并提取用户输入内容:

function handleSubmit(e) {
	e.preventDefault();
	setUpUser(apiKey.value, region.value);
}

以上我们做了:

  • 阻止 表单默认提交行为避免刷新页面
  • 提取 用户在 API 密钥和区域字段中的输入值
  • 表单数据传递给 setUpUser() 函数进行处理
  • 保持 单页面应用行为,避免页面重新加载

请记得你的 HTML 表单字段带有 required 属性,所以浏览器会自动验证用户必须填写 API 密钥和区域后,才会执行此函数。

设置用户偏好

setUpUser 函数负责保存用户凭据并启动第一次 API 调用。这样实现从设置到展示结果的平滑过渡。

function setUpUser(apiKey, regionName) {
	// 保存用户凭证以供将来会话使用
	localStorage.setItem('apiKey', apiKey);
	localStorage.setItem('regionName', regionName);
	
	// 更新界面以显示加载状态
	loading.style.display = 'block';
	errors.textContent = '';
	clearBtn.style.display = 'block';
	
	// 使用用户凭证获取碳排放使用数据
	displayCarbonUsage(apiKey, regionName);
}

一步步过程为:

  • 保存 API 密钥和区域名到本地存储以便未来使用
  • 显示 加载指示器告知用户正在获取数据
  • 清除 任何先前显示的错误信息
  • 显示 清除按钮,供用户以后重置设置
  • 发起 API 调用获取真实的碳使用数据

这个函数通过协调数据持久化和界面更新,创造了无缝的用户体验。

显示碳使用数据

现在我们将扩展连接到外部数据源,通过 API 获取数据。这使你的扩展从独立工具变成可以访问互联网上实时信息的应用。

理解 API

API 是不同应用间通信的方式。可以把它想象成 19 世纪连接远距离城市的电报系统——操作员会向远方电报站发送请求,并收取返回的信息。每次你查看社交媒体、问语音助手问题、或用送货应用时,都离不开 API 帮助完成数据交换。

flowchart TD
    A[您的扩展] --> B[HTTP 请求]
    B --> C[CO2 信号 API]
    C --> D{请求有效吗?}
    D -->|是| E[查询数据库]
    D -->|否| F[返回错误]
    E --> G[碳排放数据]
    G --> H[JSON 响应]
    H --> I[您的扩展]
    F --> I
    I --> J[更新界面]
    
    subgraph "API 请求"
        K[请求头: auth-token]
        L[参数: countryCode]
        M[方法: GET]
    end
    
    subgraph "API 响应"
        N[碳强度]
        O[化石燃料百分比]
        P[时间戳]
    end
    
    style C fill:#e8f5e8
    style G fill:#fff3e0
    style I fill:#e1f5fe

关于 REST API 的关键概念:

  • REST 表示“表述性状态转移”
  • 使用 标准 HTTP 方法 GET、POST、PUT、DELETE来操作数据
  • 返回 通常是 JSON 这类可预测格式的数据
  • 提供 对不同请求的一致 URL 端点

我们使用的 CO2 Signal API 提供全球电网的实时碳强度数据。帮助用户了解用电对环境的影响!

💡 理解异步 JavaScriptasync 关键字 让你的代码同时处理多个操作。当你向服务器请求数据时,不希望整个扩展卡住——那就像空中交通管制在等待一架飞机响应时停止所有操作。

主要好处:

  • 保持 扩展在数据加载期间依然响应
  • 允许 在网络请求时执行其他代码
  • 提升 代码可读性,相较传统回调模式更易懂
  • 支持 网络异常时优雅错误处理

这里有个关于 async 的视频:

管理 Promise 的 Async 和 Await

🎥 点击上图观看 async/await 相关视频。

🔄 教学检查点

异步编程理解:开始写 API 函数之前,请确认你理解:

  • 为什么使用 async/await,而非阻塞整个扩展
  • try/catch 块如何优雅处理网络错误
  • 同步与异步操作的区别
  • API 调用失败时会发生什么,如何应对

现实生活的异步连接:

  • 点外卖:你不会一直待在厨房旁边,会拿到收据后继续其他活动
  • 发邮件:发送时应用不会卡死,你可以继续写邮件
  • 网页加载:图片边加载边显示,你已经可以看文本了

API 认证流程

sequenceDiagram
    participant Ext as 扩展
    participant API as CO2 信号 API
    participant DB as 数据库
    
    Ext->>API: 使用认证令牌请求
    API->>API: 验证令牌
    API->>DB: 查询碳数据
    DB->>API: 返回数据
    API->>Ext: JSON 响应
    Ext->>Ext: 更新界面

创建获取并展示碳使用数据的函数:

// 现代的 fetch API 方法(不需要外部依赖)
async function displayCarbonUsage(apiKey, region) {
	try {
		// 从 CO2 Signal API 获取碳强度数据
		const response = await fetch('https://api.co2signal.com/v1/latest', {
			method: 'GET',
			headers: {
				'auth-token': apiKey,
				'Content-Type': 'application/json'
			},
			// 添加特定区域的查询参数
			...new URLSearchParams({ countryCode: region }) && {
				url: `https://api.co2signal.com/v1/latest?countryCode=${region}`
			}
		});

		// 检查 API 请求是否成功
		if (!response.ok) {
			throw new Error(`API request failed: ${response.status}`);
		}

		const data = await response.json();
		const carbonData = data.data;

		// 计算四舍五入的碳强度值
		const carbonIntensity = Math.round(carbonData.carbonIntensity);

		// 使用获取到的数据更新用户界面
		loading.style.display = 'none';
		form.style.display = 'none';
		myregion.textContent = region.toUpperCase();
		usage.textContent = `${carbonIntensity} grams (grams CO₂ emitted per kilowatt hour)`;
		fossilfuel.textContent = `${carbonData.fossilFuelPercentage.toFixed(2)}% (percentage of fossil fuels used to generate electricity)`;
		results.style.display = 'block';

		// 待办calculateColor(carbonIntensity) - 在下一节课中实现

	} catch (error) {
		console.error('Error fetching carbon data:', error);
		
		// 显示用户友好的错误信息
		loading.style.display = 'none';
		results.style.display = 'none';
		errors.textContent = 'Sorry, we couldn\'t fetch data for that region. Please check your API key and region code.';
	}
}

详细解析:

  • 使用 现代的 fetch() API替代外部库如 Axios代码更简洁且无依赖
  • 实现 使用 response.ok 做适当错误检查,及早捕捉 API 失败
  • 通过 async/await 处理异步操作,使代码流程更易读
  • 使用 auth-token 头部进行 CO2 Signal API 认证
  • 解析 JSON 响应数据并提取碳强度信息
  • 更新 多个 UI 元素,格式化显示环保数据
  • 提供 当 API 调用失败时易理解的错误提示

示例中展示的现代 JavaScript 技巧:

  • 模板字符串${} 语法,更清晰字符串格式化
  • try/catch 错误处理 让应用更健壮
  • async/await 异步模式 优雅调用网络接口
  • 对象解构 提取 API 响应中需要的数据
  • 链式方法调用 实现多步 DOM 操作

这个函数展示了多个重要的网页开发概念——与外部服务器通信,认证处理,数据解析,界面更新,以及错误管理。这些是专业开发者日常使用的基础技能。

flowchart TD
    A[开始 API 调用] --> B[获取请求]
    B --> C{网络成功?}
    C -->|否| D[网络错误]
    C -->|是| E{响应正常?}
    E -->|否| F[API 错误]
    E -->|是| G[解析 JSON]
    G --> H{数据有效?}
    H -->|否| I[数据错误]
    H -->|是| J[更新界面]
    
    D --> K[显示错误信息]
    F --> K
    I --> K
    J --> L[隐藏加载]
    K --> L
    
    style A fill:#e1f5fe
    style J fill:#e8f5e8
    style K fill:#ffebee
    style L fill:#f3e5f5

🔄 教学检查点

完整系统理解:确认你掌握整个流程:

  • DOM 引用为什么能让 JavaScript 控控界面
  • 本地存储如何实现跨浏览器会话的数据持久化
  • async/await 如何让 API 调用不冻结扩展
  • API 请求失败时会怎样,以及如何处理异常
  • 用户体验为何包含加载状态和错误提示

🎉 你已经完成了: 一个浏览器扩展,它:

  • 连接 互联网,获取真实环保数据
  • 持久保存 用户设置跨会话
  • 优雅处理 错误,避免崩溃
  • 提供 流畅且专业的用户体验

通过运行 npm run build 并刷新浏览器扩展测试你的成果。你现在拥有一个功能齐全的碳足迹追踪器。下一课将添加动态图标功能,完善此扩展。


GitHub Copilot Agent 挑战 🚀

使用 Agent 模式完成以下挑战: 描述: 通过添加错误处理改进和用户体验功能来增强浏览器扩展。本挑战将帮助你练习使用现代 JavaScript 模式进行 API 调用、本地存储和 DOM 操作。

提示: 创建一个增强版的 displayCarbonUsage 函数包含1) 失败 API 调用的指数退避重试机制2) 调用 API 前对区域代码的输入验证3) 带有进度指示器的加载动画4) 在 localStorage 中缓存 API 响应及过期时间戳缓存时长30分钟及 5) 显示先前 API 调用历史数据的功能。同时为所有函数参数和返回类型添加适当的 TypeScript 风格 JSDoc 注释。

此处了解更多关于 agent 模式的信息。

🚀 挑战

通过探索大量浏览器 API扩展你对 API 的理解,选择以下浏览器 API 之一并构建一个小示范:

研究问题:

  • 该 API 解决了哪些现实世界的问题?
  • API 如何处理错误和边界情况?
  • 使用该 API 有哪些安全考虑?
  • 该 API 在不同浏览器中的支持程度如何?

研究后,确定一个 API 具备哪些特性才是开发者友好且可靠的。

课后测验

课后测验

复习与自学

本课你学习了 LocalStorage 和 API这两者对专业 web 开发人员都非常有用。你能想到这两者是如何协同工作的吗?思考如何架构一个网站,该网站会存储供 API 使用的项目。

你在接下来的 5 分钟内可以做什么

  • 打开开发者工具中的 Application 标签,探索任意网站的 localStorage
  • 创建一个简单的 HTML 表单,测试浏览器的表单验证
  • 尝试在浏览器控制台中存储和检索 localStorage 数据
  • 使用 Network 标签检查提交的表单数据

🎯 你在本小时内可以完成的任务

  • 完成课后测验并理解表单处理概念
  • 构建一个保存用户偏好的浏览器扩展表单
  • 实现带有友好错误提示的客户端表单验证
  • 练习使用 chrome.storage API 持久化扩展数据
  • 创建响应已保存用户设置的用户界面

📅 你一周的扩展开发计划

  • 完成一个具备表单功能的完整浏览器扩展
  • 掌握不同存储选项:本地、同步和会话存储
  • 实现高级表单功能,如自动完成和验证
  • 添加用户数据的导入/导出功能
  • 跨不同浏览器彻底测试你的扩展
  • 优化扩展的用户体验和错误处理机制

🌟 你一个月的 Web API 掌握计划

  • 使用多种浏览器存储 API 构建复杂应用
  • 学习离线优先开发模式
  • 参与开源项目,推动数据持久化
  • 掌握隐私保护开发和 GDPR 合规
  • 创建用于表单处理和数据管理的可重用库
  • 分享关于 Web API 和扩展开发的知识

🎯 你的扩展开发大师时间线

timeline
    title API 集成与存储学习进度
    
    section DOM 基础 (15 分钟)
        元素引用: querySelector 掌握
                  : 事件监听器设置
                  : 状态管理基础
        
    section 本地存储 (20 分钟)
        数据持久化: 键值存储
                  : 会话管理
                  : 用户偏好处理
                  : 存储检查工具
        
    section 表单处理 (25 分钟)
        用户输入: 表单验证
                : 事件阻止
                : 数据提取
                : UI 状态切换
        
    section API 集成 (35 分钟)
        外部通信: HTTP 请求
                : 认证模式
                : JSON 数据解析
                : 响应处理
        
    section 异步编程 (40 分钟)
        现代 JavaScript: Promise 处理
                       : Async/await 模式
                       : 错误管理
                       : 非阻塞操作
        
    section 错误处理 (30 分钟)
        稳健应用: Try/catch 块
                 : 用户友好信息
                 : 优雅降级
                 : 调试技术
        
    section 高级模式 (1 周)
        专业发展: 缓存策略
                : 速率限制
                : 重试机制
                : 性能优化
        
    section 生产技能 (1 个月)
        企业功能: 安全最佳实践
                 : API 版本管理
                 : 监控与日志
                 : 可扩展架构

🛠️ 你的全栈开发工具包总结

完成本课后,你已经具备:

  • DOM 精通:精确定位和操作元素
  • 存储专长:使用 localStorage 进行持久化数据管理
  • API 集成:实时数据获取和认证
  • 异步编程:使用现代 JavaScript 实现非阻塞操作
  • 错误处理:构建稳健且能优雅处理失败的应用
  • 用户体验:加载状态、验证及流畅交互
  • 现代模式fetch API、async/await 和 ES6+ 特性

获得的专业技能:你已实现以下模式:

  • Web 应用:单页应用并连接外部数据源
  • 移动开发:具备离线能力的 API 驱动应用
  • 桌面软件:带持久存储的 Electron 应用
  • 企业系统:认证、缓存和错误处理
  • 现代框架React/Vue/Angular 数据管理模式

下一个阶段:你已准备好探索高级主题,如缓存策略、实时 WebSocket 连接或复杂状态管理!

任务

采用一个 API


免责声明 本文件由人工智能翻译服务Co-op Translator翻译完成。虽然我们力求准确,但请注意自动翻译可能包含错误或不准确之处。原文文件的母语版本应被视为权威来源。对于重要信息,建议采用专业人工翻译。对于因使用本翻译而产生的任何误解或误释,我们概不负责。