密码存储

我们在开发网站或者APP时,首先要解决的问题,就是如何安全地传输和存储用户的密码。一些大公司的用户数据库泄露事件也时有发生,带来非常大的负面影响。因此,如何安全传输存储用户密码,是每位程序员必备的基础。本文将跟大家一起学习,如何安全传输存储用户的密码。 1. 如何安全地传输用户的密码 要拒绝用户密码在网络上裸奔,我们很容易就想到使用https协议,那先来回顾下https相关知识吧~ 1.1 https 协议 「http的三大风险」 为什么要使用https协议呢?http它不香吗? 因为http是明文信息传输的。如果在茫茫的网络海洋,使用http协议,有以下三大风险: 窃听/嗅探风险:第三方可以截获通信数据。 数据篡改风险:第三方获取到通信数据后,会进行恶意修改。 身份伪造风险:第三方可以冒充他人身份参与通信。 如果传输不重要的信息还好,但是传输用户密码这些敏感信息,那可不得了。所以一般都要使用https协议传输用户密码信息。 「https 原理」 https原理是什么呢?为什么它能解决http的三大风险呢? https = http + SSL/TLS, SSL/TLS 是传输层加密协议,它提供内容加密、身份认证、数据完整性校验,以解决数据传输的安全性问题。 为了加深https原理的理解,我们一起复习一下 一次完整https的请求流程吧~ 客户端发起https请求 服务器必须要有一套数字证书,可以自己制作,也可以向权威机构申请。这套证书其实就是一对公私钥。 服务器将自己的数字证书(含有公钥、证书的颁发机构等)发送给客户端。 客户端收到服务器端的数字证书之后,会对其进行验证,主要验证公钥是否有效,比如颁发机构,过期时间等等。如果不通过,则弹出警告框。如果证书没问题,则生成一个密钥(对称加密算法的密钥,其实是一个随机值),并且用证书的公钥对这个随机值加密。 客户端会发起https中的第二个请求,将加密之后的客户端密钥(随机值)发送给服务器。 服务器接收到客户端发来的密钥之后,会用自己的私钥对其进行非对称解密,解密之后得到客户端密钥,然后用客户端密钥对返回数据进行对称加密,这样数据就变成了密文。 服务器将加密后的密文返回给客户端。 客户端收到服务器发返回的密文,用自己的密钥(客户端密钥)对其进行对称解密,得到服务器返回的数据。 「https一定安全吗?」 https的数据传输过程,数据都是密文的,那么,使用了https协议传输密码信息,一定是安全的吗?其实不然 比如,https 完全就是建立在证书可信的基础上的呢。但是如果遇到中间人伪造证书,一旦客户端通过验证,安全性顿时就没了哦!平时各种钓鱼不可描述的网站,很可能就是黑客在诱导用户安装它们的伪造证书! 通过伪造证书,https也是可能被抓包的哦。 1.2 对称加密算法 既然使用了https协议传输用户密码,还是 「不一定安全」,那么,我们就给用户密码 「加密再传输」 呗~ 加密算法有 「对称加密」 和 「非对称加密」 两大类。用哪种类型的加密算法 「靠谱」 呢? 对称加密:加密和解密使用 「相同密钥」 的加密算法。 常用的对称加密算法主要有以下几种哈: 如果使用对称加密算法,需要考虑 「密钥如何给到对方」 ,如果密钥还是网络传输给对方,传输过程,被中间人拿到的话,也是有风险的哦。 1.3 非对称加密算法 再考虑一下非对称加密算法呢? 「非对称加密:」 非对称加密算法需要两个密钥(公开密钥和私有密钥)。公钥与私钥是成对存在的,如果用公钥对数据进行加密,只有对应的私钥才能解密。 ...

October 2, 2024

展开运算符

ES6 新增了很多特性,扩展运算符就是其中一个。简单的三个点号,让原本繁琐的代码瞬间变得简洁、优雅。就让我们看看它有哪些常见用法吧。 让函数的 Apply 调用方式成为过去 有时候我们需要通过 Function.prototype.apply 来调用函数,参数就放在一个数组里。 function doStuff (x, y, z) { } var args = [0, 1, 2]; // 调用函数并传参 doStuff.apply(null, args); 现在有了展开运算符,我们可以完全避免使用 apply,只需在调用函数时在数组前加上展开运算符: doStuff(...args); 代码变得更短、更简洁,也不需要一个毫无用处的 null! 合并数组 合并数组有各种方式,但是展开运算符为我们提供了一种新思路: arr1.push(...arr2) // 在数组后面添加 arr2 里的元素 arr1.unshift(...arr2) //在数组前面添加 arr2 里的元素 如果你想合并两个数组,并且想把元素放到数组中的任何位置,你可以这么做: var arr1 = ['two', 'three']; var arr2 = ['one', ...arr1, 'four', 'five']; // ["one", "two", "three", "four", "five"] 代码少了很多,还可以随意控制位置! 拷贝数组 拷贝数组是个常见的任务,过去我们使用 Array.prototype.slice 来完成,但现在我们可以通过展开运算符得到数组的副本: var arr = [1,2,3]; var arr2 = [...arr]; // 等同于 arr.slice() arr2.push(4) 这里要注意,数组里的对象仍然是引用类型的,因此实际上并不是所有内容都被“拷贝”了。 将参数或 NodeList 转成数组 跟拷贝数组类似,我们用 Array.Prototype.slice 将 NodeList和 arguments 对象转成真正的数组,但是现在我们可以使用展开运算符完成这个任务: ...

October 2, 2024

表单控件

01、<form>表单 表单是比较重要的HTML元素,主要作用是向服务端提交数据。比如结合表单元素input使用,通过内部的button按钮提交(type=“submit”)表单数据。 元素/属性 描述 值/备注 <form> 表单元素 action 提交表单的目标(服务端)地址url method 提交数据的方式,就是数据传输的方式 get:通过URL提交数据 url?uname=1&age=2;post,通过HTTP表单数据提交,键值格式。 target 提交数据时打开action url的方式 _self:当前窗口(默认值);_blank:新窗口 enctype 编码类型(encode type),规定了form表单在发送到服务器时候编码方式,不常用。 application/x-www-form-urlencoded:编码所有字符(默认);multipart/form-data :混合类型, 表单中有文件上传时使用;text/plain:纯文体,空格转换为 “+” 加号,不对特殊字符编码 submit() 提交表单数据,通过js代码调用 <fieldset> 表单分组,默认样式:一个框 便于表单样式管理的语义化元素 form from的id,当<fieldset>不在form中时 disabled 整个分组都不可用 <legend> 作为<fieldset>的标题,显示在框上 (legend /ˈledʒənd/ 铭文、图例) <style> #form fieldset { border: 1px solid skyblue; padding: 20px 10px; border-radius: 5px; text-align: center; margin: 10px 0px; } #form fieldset legend { font-size: 1em; border: 1px solid rgb(236, 175, 43); border-radius: 1em; padding: 3px 15px; } </style> <form id="form" action="#" target="_self" method="post"> <fieldset> <legend>登录</legend> <input type="text" name="uname" placeholder="请输入用户名" required maxlength="36"> <input type="password" name="upwd" required maxlength="12" placeholder="输入密码"> <input type="submit" value="submit-登录"> </fieldset> </form> 📢注意:<form>提交数据时参数名为表单元素的name,因此表单控件须设置name属性。 ...

September 25, 2024

Objective-C与Swift区别

语言设计 Objective-C 是C的超集,在C的基础上添加了Smalltalk风格的消息传递机制 对象间交互不通过调用方法而是发送消息,以实现面向对象编程和超高的动态性 动态性由 Objective-C Runtime 系统支持,可以在运行时修改类结构、方法交换等,详见 Runtime 弱类型语言,核心类型id可以指向任何Objective-C对象 Swift 从很多现代化语言中汲取精华,是一门偏重于安全、性能的现代化语言 Swift 也有自己的运行时系统(Swift Runtime),但设计哲学完全不同,更强调编译期优化 强类型语言,编译器在编译阶段进行严格的类型检查 语法特性 Objective-C 语法冗长,需要显式声明类型 命名通常较长且需要添加前缀避免冲突 需要单独的.h和.m文件声明接口和实现 引用其他文件/模块必须要import(Objective-C中import详解) 协议不支持默认实现 泛型是"轻量级泛型",仅用于编译时类型检查,运行时类型信息会被擦除 Swift 语法简洁,强大的类型推断减少冗余的类型声明 有命名空间(模块)的存在避免命名冲突 不需要单独的头文件声明接口 默认情况下变量必须初始化 引用同模块的其他文件不需要import(Swift中import详解) 协议支持默认实现、支持拓展,支持关联类型以实现泛型协议 值类型也可以遵守协议 泛型在编译时进行类型特化优化,支持完整的泛型元编程 内存管理 Objective-C 大部分都是引用类型,依赖引用计数管理内存 ARC在编译时自动插入retain、release、autorelease调用 64位系统使用优化的isa(Non-pointer isa),通过位域在isa中嵌入引用计数(extra_rc字段),溢出时使用SideTable存储 实例变量(ivar)会被Runtime自动初始化为0/nil Swift 引用类型也是依赖引用计数管理内存 存在大量的值类型,例如结构体、枚举、数组、字典等 非逃逸的值类型的内存存在栈区由系统进行自动管理 纯Swift类的引用计数直接存储在对象头部的RefCount字段中,无需SideTable,访问更高效 编译器在编译时就确保了初始化,不需要运行时统一清零 拓展:值类型和引用类型的区别 拓展:iOS中的内存管理 性能对比 Objective-C 基于消息传递机制本质上是动态派发,运行时查找方法实现,有一定开销 ARC管理引用计数也有开销 引用类型内存存在堆区,内存管理的性能开销会比栈区多很多 Swift 方法派发尽可能使用静态派发(如对final类/方法、私有方法、值类型方法的调用) 编译器可以内联优化,速度接近C 内联优化:比如A方法调用B方法,B方法调用C方法,代码编写调用A方法时编译器通过内联直接调用C方法的实现 值类型内存分配在栈区,性能开销相对较小 虽然值类型是深拷贝,Swift为了优化值类型性能引入写时拷贝(Copy-on-Write)机制,值类型只在写操作发生深拷贝,其他时候都是浅拷贝 安全性 Objective-C 弱类型(动态类型)占主导 核心类型id可以指向任何Objective-C对象 编译器几乎不做类型检查,会有运行时崩溃的风险(如向不响应某消息的对象发送该消息) nil消息发送不会崩溃但可能导致逻辑错误,不容易感知 局部变量不会自动初始化,可能包含栈上的垃圾数据(未定义行为) Swift 可选类型强制处理空值,编译器强制解包检查 强类型和编译时检查阻止了大量常见错误 变量必须初始化后才能使用,编译器在编译阶段强制检查,消除未定义行为 强制初始化使编译器能更精确地追踪变量生命周期,带来更多优化机会 数组越界等操作会在运行时触发明确的崩溃,便于定位问题 编程范式 Objective-C 主要是面向对象编程(OOP) 通过继承实现代码复用 依赖Runtime实现面向切面编程(AOP),如Method Swizzling Swift 拥抱多范式,对函数式编程、面向协议编程(POP)、声明式编程很友好 通过协议扩展提供默认实现,协议组合替代多继承 值类型(struct/enum)也可以遵循协议,不再局限于类 拓展:OOP、POP与AOP ...

May 8, 2026

iOS音视频原理

音视频开发是iOS中一个庞大且核心的技术领域,涵盖采集、编码、传输、解码、渲染等完整链路。本文从基础概念出发,深入iOS平台提供的音视频框架和底层原理。 音视频处理全链路 一个完整的音视频系统(如直播、视频通话、短视频)包含以下环节: flowchart LR A["采集Camera/Mic"] --> B["前处理美颜/降噪"] B --> C["编码H.264/AAC"] C --> D["封装FLV/MP4"] D --> E["传输RTMP/HLS"] E --> F["解封装解析容器"] F --> G["解码VideoToolbox"] G --> H["后处理滤镜/混音"] H --> I["渲染Metal/Speaker"] 环节 做什么 iOS中对应的技术 采集 从摄像头/麦克风获取原始数据 AVCaptureSession 前处理 美颜、滤镜、降噪、回声消除 CoreImage、vDSP 编码 将原始数据压缩为体积更小的格式 VideoToolbox、AudioToolbox 封装 将编码后的音视频数据打包到容器中 AVAssetWriter 传输 通过网络发送到服务器或其他端 URLSession、第三方推流SDK 解封装 从容器中分离出音频流和视频流 AVAssetReader 解码 将压缩数据还原为原始像素/采样数据 VideoToolbox、AudioToolbox 后处理 滤镜、水印、混音等 CoreImage、Metal 渲染 将画面显示到屏幕、声音输出到扬声器 AVSampleBufferDisplayLayer、Metal、AudioUnit 核心术语 在深入细节之前,先了解几个贯穿全文的核心概念: 什么是编解码器(Codec) Codec = Coder + Decoder,即编码器+解码器的合称。编码器负责将原始音视频数据压缩为更小的格式,解码器负责将压缩数据还原。 音视频中有两类完全不同的"格式"概念,初学者极易混淆: 编码格式(Codec):定义数据怎么压缩 视频:H.264、H.265、VP9、AV1 音频:AAC、MP3、Opus、FLAC 封装格式(Container):定义压缩后的数据怎么组织存储 MP4、MOV、FLV、MKV、TS 两者是独立的,可以自由组合: MP4容器 + H.264视频 + AAC音频 (最常见的组合) MOV容器 + H.265视频 + AAC音频 (iPhone录制默认) FLV容器 + H.264视频 + AAC音频 (直播推流常用) MKV容器 + AV1视频 + Opus音频 (新一代免费组合) 什么是H.264和H.265 H.264(也叫AVC,Advanced Video Coding)是目前使用最广泛的视频编码标准,由ITU-T和ISO/IEC联合制定,2003年发布。它定义了一套将视频帧压缩为比特流的算法规范——包括如何划分图像块、如何预测、如何变换量化等。几乎所有设备和平台都支持H.264硬件解码。 ...

May 2, 2026

LLM Wiki:Karpathy 提出的"后 RAG 时代"个人知识库范式

2026 年 4 月,Andrej Karpathy 在 GitHub Gist 上发布了一份名为 llm-wiki.md 的"想法文档"(idea file),短短几天收获了 5000+ 星和 5000+ fork。这篇文档篇幅不长,却被评价为"后 RAG 时代"个人知识管理的一个**分类定义性(category-defining)**框架。 原文地址:https://gist.github.com/karpathy/442a6bf555914893e9891c11519de94f Karpathy 写这篇文档的风格非常有意思:它不是一份实现规范,而是一份"可以复制粘贴给你自己的 LLM Agent(Claude Code、Codex、OpenCode/Pi 等)“的模式描述。它告诉 Agent 模式的核心是什么,剩下的细节(目录结构、schema 约定、页面格式、工具链)交给用户和 Agent 一起协同演化。 本文将详细拆解这份文档的核心思想,并结合社区讨论(scale 极限、Zettelkasten 替代方案、反对派观点等)给出一个完整解读。 一、问题起点:RAG 的"每次从零开始” 大多数人今天使用 LLM 处理文档的方式是 RAG(检索增强生成): 把一堆文档上传到系统 查询时 LLM 从原文中检索相关片段 基于片段合成答案 NotebookLM、ChatGPT 文件上传、绝大多数 RAG 系统都是这个模式。 1.1 RAG 的根本局限:没有累积 Karpathy 点出了 RAG 的一个关键问题:LLM 每次都在从零开始重新发现知识。 问一个需要综合 5 篇文档的微妙问题 → LLM 每次都要重新找出相关片段,重新拼接,重新推理 上一次你问过的关键问题的答案,下一次再问时不会被保留 跨源的矛盾、交叉引用、细微的关联 —— 每次都要重新建立 什么都没有被"沉淀"下来。 这就像每次考试都重新读一遍教科书,却从不做笔记、从不整理知识卡片、从不画思维导图。 1.2 LLM Wiki 的核心差异 LLM Wiki 提出了一个根本性的反转: ...

May 2, 2026

生产者消费者模式

定义 生产者消费者模式(Producer-Consumer Pattern)是一种并发设计模式,它通过一个共享的缓冲区来解耦生产者和消费者,使得生产者和消费者可以以不同的速度工作,而不需要直接相互依赖。 生产者负责产生数据并放入缓冲区,消费者从缓冲区中取出数据进行处理。这种模式是多线程编程中最经典的设计模式之一。 为什么需要生产者消费者模式 生产者消费者模式要解决的核心问题是:如何在生产者和消费者处理速度不一致的情况下,实现高效、安全的数据传递。 问题场景:假设我们正在开发一个图片处理App,用户选择多张图片后需要进行滤镜处理。图片加载速度快,但滤镜处理速度慢。 最直接的方式可能是这样: class ImageProcessor { func processImages(_ urls: [URL]) { for url in urls { // 加载图片(快) let image = loadImage(from: url) // 应用滤镜(慢) let filtered = applyFilter(to: image) // 保存结果 saveImage(filtered) } } private func loadImage(from url: URL) -> UIImage { /* ... */ } private func applyFilter(to image: UIImage) -> UIImage { /* ... */ } private func saveImage(_ image: UIImage) { /* ... */ } } 这种方式有什么问题? ...

May 2, 2026

版本控制

什么是版本控制 版本控制是一种系统性的方法,用于管理和跟踪软件开发项目中的代码、文件和资源。它的核心目标是在不同时间点的开发中保持代码的一致性、可追溯性和可维护性。 为什么要做版本控制 这个问题可以从定义中的关键词可以看出端倪:不同时间点下的代码一致性、可追溯性、可维护性。这里额外补充一点:当今的互联网开发早已步入敏捷迭代,各种动态化方案层出不穷都有着顺应时代潮流的意思,因此软件开发一定会越来越多地涉及到团队开发,团队之间的协作也一定需要这样一个机制去保障稳定性。 代码一致性 版本控制系统允许开发人员有效地管理和组织项目中的代码。它跟踪每个文件的历史记录,记录了每个版本的变更,包括谁做了什么修改、何时以及为什么。 可追溯性 版本控制系统允许为重要的里程碑或发布创建标签(tag),以便轻松识别和回滚到特定版本。 并且当某个版本的代码出现问题,版本控制系统允许开发人员轻松地回滚到之前的稳定版本,并在不破坏其余代码的情况下进行修复。 可维护性 开发人员可以创建分支(branch),这是一个独立的代码副本,用于开发新功能、修复错误或进行实验性工作。然后,这些分支可以与主代码库合并,以实现功能集成。 团队协作 在多人协作的环境中,不同开发者可能同时修改代码。版本控制系统确保不同的修改不会互相冲突,以及如何解决冲突。 版本控制系统促进了团队之间的协作,可以通过代码审查工具进行代码审查,以确保代码质量和一致性。 版本控制工具Git Git的安装 WIndows 访问 Git官网,选择对应的操作系统下载即可。 这里下载独立安装包即可 点install就行,会自动安装一个git GUI工具,但通常不会直接用它 下载完成安装即可,输入以下命令验证安装是否成功 git version 图形化工具 安装 这里推荐TortoiseGit这个工具,非常方便。但我觉得Git本身学习成本并不高,GUI工具更多是为了提升效率,基本的Git知识点还是需要掌握的。 下载本体 然后是语言包 安装时一路next即可,安装完本体后再安装语言包。 配置好Github的账户、邮箱、秘钥等,注意配置好了密钥也不会在下述部分显示,可以打开编辑全局进行查看 使用 在使用之前先提前补充一个点(后续会详细介绍),Git有本地仓库和远端仓库的概念,通常支持HTTPS和SSH两种协议进行关联,两种方式优缺点也十分明显: HTTPS:无需额外配置,但是在每次连接远端仓库时需要输入账号密码进行校验。 SSH:生成一组密钥对,需要本地进行配置,好处是配置好后每次连接远端仓库时会自动比较密钥对,无需额外校验。关于SSH详细可以参考这篇文章ssh-远程登录协议 为了一劳永逸,我们这里配置下SSH,首先打开Git bash # 输入下列命令,最后一个参数是您的邮箱 $ ssh-keygen -t rsa -C yourEmail.com 可以看到密钥对已经生成了 进入上述目录,注意.ssh文件夹默认是隐藏的,直接访问路径就行 以Github为例,配置公钥 创建一个Demo项目,平时学习的话就创建公有项目就行 复制一下我们的项目链接 选取一个你喜欢的目录作为本地工作区,然后按鼠标右键 ...

August 7, 2025

跨域相关

一、什么是跨域? 1.什么是同源策略及其限制内容? 同源策略是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,浏览器很容易受到XSS、CSRF等攻击。所谓同源是指"协议+域名+端口"三者相同,即便两个不同的域名指向同一个ip地址,也非同源。 同源策略限制内容有: Cookie、LocalStorage、IndexedDB 等存储性内容 DOM 节点 AJAX 请求发送后,结果被浏览器拦截了 但是有三个标签是允许跨域加载资源: <img src=XXX> <link href=XXX> <script src=XXX> 2.常见跨域场景 当协议、子域名、主域名、端口号中任意一个不相同时,都算作不同域。不同域之间相互请求资源,就算作“跨域”。常见跨域场景如下图所示: 特别说明两点: 第一:如果是协议和端口造成的跨域问题“前台”是无能为力的。 第二:在跨域问题上,仅仅是通过“URL的首部”来识别而不会根据域名对应的IP地址是否相同来判断。“URL的首部”可以理解为“协议, 域名和端口必须匹配”。 这里你或许有个疑问:请求跨域了,那么请求到底发出去没有? 跨域并不是请求发不出去,请求能发出去,服务端能收到请求并正常返回结果,只是结果被浏览器拦截了。你可能会疑问明明通过表单的方式可以发起跨域请求,为什么 Ajax 就不会?因为归根结底,跨域是为了阻止用户读取到另一个域名下的内容,Ajax 可以获取响应,浏览器认为这不安全,所以拦截了响应。但是表单并不会获取新的内容,所以可以发起跨域请求。同时也说明了跨域并不能完全阻止 CSRF,因为请求毕竟是发出去了。 二、跨域解决方案 1.jsonp 1) JSONP原理 利用 <script> 标签没有跨域限制的漏洞,网页可以得到从其他来源动态产生的 JSON 数据。JSONP请求一定需要对方的服务器做支持才可以。 2) JSONP和AJAX对比 JSONP和AJAX相同,都是客户端向服务器端发送请求,从服务器端获取数据的方式。但AJAX属于同源策略,JSONP属于非同源策略(跨域请求) 3) JSONP优缺点 JSONP优点是简单兼容性好,可用于解决主流浏览器的跨域数据访问的问题。缺点是仅支持get方法具有局限性,不安全可能会遭受XSS攻击。 4) JSONP的实现流程 声明一个回调函数,其函数名(如show)当做参数值,要传递给跨域请求数据的服务器,函数形参为要获取目标数据(服务器返回的data)。 创建一个<script>标签,把那个跨域的API数据接口地址,赋值给script的src,还要在这个地址中向服务器传递该函数名(可以通过问号传参:?callback=show)。 服务器接收到请求后,需要进行特殊的处理:把传递进来的函数名和它需要给你的数据拼接成一个字符串,例如:传递进去的函数名是show,它准备好的数据是show('我不爱你')。 最后服务器把准备的数据通过HTTP协议返回给客户端,客户端再调用执行之前声明的回调函数(show),对返回的数据进行操作。 在开发中可能会遇到多个 JSONP 请求的回调函数名是相同的,这时候就需要自己封装一个 JSONP函数。 // index.html function jsonp({ url, params, callback }) { return new Promise((resolve, reject) => { let script = document.createElement('script') window[callback] = function(data) { resolve(data) document.body.removeChild(script) } params = { ...params, callback } // wd=b&callback=show let arrs = [] for (let key in params) { arrs.push(`${key}=${params[key]}`) } script.src = `${url}?${arrs.join('&')}` document.body.appendChild(script) }) } jsonp({ url: 'http://localhost:3000/say', params: { wd: 'Iloveyou' }, callback: 'show' }).then(data => { console.log(data) }) 上面这段代码相当于向http://localhost:3000/say?wd=Iloveyou&callback=show这个地址请求数据,然后后台返回show('我不爱你'),最后会运行show()这个函数,打印出’我不爱你' ...

July 28, 2025

条件渲染的常见方法和注意事项

条件渲染方式 1. if 语句 先从 React 最基本的条件类型来看。如果有数据就显示组件,如果没有数据就不显示任何内容。posts 为需要渲染的列表: export default function App() { const { posts } = usePosts(); if (!posts) return null; return ( <div> <PostList posts={posts} /> </div> ); } 这种形式会生效的原因就是我们会提前返回,如果满足条件(posts 值不存在),就通过return null 在组件中不显示任何内容。 如果有多个要检查的条件时,也可以使用 if 语句。例如,在显示数据之前检查加载和错误状态: export default function App() { const { isLoading, isError, posts } = usePosts(); if (isLoading) return <div>Loading...</div>; if (isError) return <div>Error!</div>; return ( <div> <PostList posts={posts} /> </div> ); } 这里我们可以多次使用 if 语句,不需要再使用 else 或者 if-eles 语句,这样就减少了需要编写的代码,并且可读性更强。 2. 三元运算符 当我们想提前退出或者什么都不显示时,if 语句会很有用。但是,如果我们不想写一个与返回的 JSX 分开的条件,而是直接在其中写呢?那就可以使用三元表达式来编写条件。 在 React 中,我们必须在 JSX 中包含表达式,而不是语句。这就是为什么我们在 JSX 中只能使用三元表达式,而不是 if 语句来编写条件。 ...

December 17, 2024