跳到主要内容

节约使用cursor

· 阅读需 8 分钟
Quany
软件工程师

想让 Cursor 这个强大的 AI 编程助手既高效又省钱,核心在于理解其运作机制并掌握一些关键技巧。下面我为你整理了一份从基础到进阶的实用指南。

💸 理解 Cursor 的计费方式

首先,清楚钱花在哪里是节约的第一步。Cursor 的计费主要基于以下两种模式之一:

  • 按交互次数计费:在某些版本中,你与 AI 的一次问答(你提问,AI 一次回答)被视为一次交互。每个计费周期通常有免费的交互额度,用完后需要付费或使用慢速队列 。
  • 按 Token 计费:这是更本质的计费单位。Token 是 AI 处理文本的基本单位,可以理解为字符或单词。总消耗 = 输入 Token + 输出 Token + 上下文 Token。你提供给 AI 的提示、AI 生成的代码/文本、以及对话历史中所有需要被 AI“记住”的内容都会消耗 Token 。

基于以上计费原理,可以采取以下节约策略。

🧠 优化你的提问方式

高效的提问能显著减少来回沟通的次数和无效输出。

  • 需求明确具体:避免模糊的指令。与其说“帮我写个函数”,不如详细说明技术栈、具体功能、约束条件等 。
    • 不推荐帮我写代码
    • 推荐用React Hooks实现一个计数器组件,要求具备增加、减少、重置功能,使用TypeScript编写,状态通过useState管理
  • 分阶段处理复杂任务:对于大型功能,不要试图在一个对话中完成。将其拆分为设计、核心功能实现、异常处理、优化等阶段,分步进行。这比一次性请求能节省大量 Token 。
  • 先要方案,再要代码:对于复杂逻辑,可以先开启 Plan Mode 或直接提示 AI 先给出实现计划和方案。你确认方案可行后,再让它生成代码,避免在错误方向上浪费额度 。

🔧 善用工具与控制上下文

上下文管理是控制 Token 消耗的关键,因为对话历史越长,消耗的 Token 越多。

  • 精准引用上下文:使用 @ 符号精确地告诉 AI 需要关注哪些文件、文档或过去的对话 (@file, @docs, @past chats),而不是让 AI 自动加载可能不相关的全部上下文 。
  • 管理对话长度:避免所有对话都在一个窗口中进行。为不同的、复杂的任务开启新的独立对话框,以保持每个对话的上下文简洁,减少幻觉和 Token 浪费 。
  • 活用 NotePads:对于冗长的需求文档或接口说明,可以先将内容整理到 NotePads 中。在新对话中直接 @NotePads,就能让 AI 基于所有内容生成代码,避免在对话框中粘贴大量文本 。
  • 配置 .cursorignore 文件:将项目中不需要被 AI 索引的文件(如 node_modules, 构建产物、日志文件等)忽略掉,可以减少 AI 构建索引时的负担和无关上下文的干扰 。

⚙️ 精细配置与模型选择

  • 选择合适的模型:Cursor 提供多种 AI 模型。对于简单的代码补全、行内修改(Ctrl/Cmd + K),默认的 gpt-4o-mini 等较轻量模型通常足够且便宜。处理复杂架构设计时,再切换至 Claude 3.5/3.7 等更强但更贵的模型 。
  • 智能配置 Project Rules:Project Rules 是指导 AI 行为的项目规范,但它本身也会作为上下文消耗 Token 。
    • 模式选择:对于个人或小型项目,建议使用 Selective Apply(选择性应用)模式,仅在需要时通过 @rules 触发相关规则,这比 Always Apply(始终应用)模式更节省 Token 。
    • 规则优化:将规则分级,只将最关键的规定(如安全规则、基础命名规范)设为核心规则。同时,尽量使用简洁的语言编写规则 。

下面的表格对比了不同规则应用模式的优缺点 :

应用模式优点缺点适用场景
Always Apply保证代码一致性固定 Token 开销,可能包含不相关规则团队协作、企业级项目
Selective Apply按需使用,灵活性强,节省 Token可能忘记应用规则个人项目、预算有限时

💎 核心省钱口诀

总结一下,最核心的节约心法如下:

口诀具体做法
问题质量 > 问题数量花时间构思精准的提示词,一次性把需求说清楚。
手动控制 > 自动加载主动使用 @ 引用,而非依赖 AI 自动猜测上下文。
任务拆分 > 一蹴而就将大任务拆成小步骤,分而治之。
合适模型 > 最强模型根据任务难度匹配模型,好钢用在刀刃上。
人工校验 > 盲目信任始终检查 AI 生成的代码,避免错误修改导致更多成本。

希望这些技巧能帮助你更高效、更经济地使用 Cursor。如果你对某个特定功能(比如 Rules 的详细配置或 Agent 模式的高级用法)有进一步兴趣,我很乐意提供更具体的介绍。

编写高效的 Project Rules 确实是一门平衡艺术,既要让 AI 精准产出高质量代码,又要避免不必要的资源消耗。下面这个表格总结了高效 Rules 的四大核心原则,你可以先快速了解其精髓。

原则核心目标对代码质量的影响对 Token 节省的贡献
最小化 (Minimization)规则精炼、专注、可执行避免模糊指令,提高生成代码的准确性和一致性减少每次请求携带的冗余信息,直接降低 Token 消耗
结构化 (Structured)规则模块化、分层次、有边界让 AI 在特定场景下获得最相关的指导,减少“幻觉”和错误实现规则的“按需加载”,避免不相关的规则占用上下文
精准引用 (Explicitness)明确告诉 AI“何时用何规则”确保 AI 在正确的时机遵循正确的规范,输出稳定可靠通过 RuleType 等机制精确控制上下文,避免整个规则库被全部发送
一致性 (Consistency)保持代码风格和架构的统一提升代码的可维护性和可读性,便于团队协作和后续开发减少因风格不一致导致的返工和重复生成,间接节省 Token

🛠️ 编写高质量规则的具体方法

掌握了核心原则后,我们来看看如何将它们付诸实践。

  1. 使用命令式、否定式语言 规则的本质是指令,而非建议。使用果断、明确的语气,能显著提高 AI 的服从度 。

    • 不推荐 (模糊、解释型)“建议优先使用函数式组件。”
    • 推荐 (命令式)“使用 React 函数式组件和 Hooks。”
    • 推荐 (否定式)“禁止绕过 Repository 层直接操作数据库。”
  2. 采用新的 .cursor/rules/ 目录结构 放弃传统的单一 .cursorrules 文件。现在更推荐的做法是在项目根目录创建 .cursor/rules/ 文件夹,然后将规则分门别类地存放在不同的 .mdc 文件中 。这样做的好处是结构清晰,便于维护,并能更好地与 RuleType 配合实现精准引用。

  3. 善用 RuleType 实现精准控制 在每个 .mdc 文件的顶部,你可以通过 YAML front matter 定义 RuleType,这是实现 Token 节省的关键 。

    • Always:始终生效的通用规则,如项目核心行为、通用代码风格。
    • Apply to Specific Files:仅当编辑特定类型文件(如 *.py)时才生效,专用于语言或框架规则。
    • Apply Manual:仅在聊天中通过 @规则文件名 显式引用时才生效,适合不常用的特定规则。

💾 最大化节省 Token 的实战技巧

  1. 利用 Cache Read 机制 在同一对话中,AI 对已读过的内容(包括规则文件)的后续读取成本会大幅降低 。因此,应将相关任务放在同一个对话中完成,而不是每个小任务都开启新对话。

  2. 规则内容本身要精炼

    • 避免解释性语言:规则中不要写“为什么”,只写“做什么”和“不做什么” 。
    • 多用示例代码:对于复杂的模式或规范,一个简短的代码示例比大段文字描述更有效且更省 Token 。
  3. 将规则文件纳入版本控制 这不仅便于团队协作,还能确保所有成员和 AI 都基于同一套最新的规则进行开发,避免因规则不一致导致的重复修改和 Token 浪费 。

🔍 验证规则是否生效

编写规则后,需要进行测试。你可以尝试让 AI 做一个明显违反规则的操作(例如:“给这个函数添加功能,先不用写测试”)。如果 AI 拒绝执行并提醒你需要遵守测试规则,说明规则已成功生效 。

希望这份指南能帮助你打造出既高效又经济的 Project Rules。如果你在实践过程中遇到具体问题,比如针对某个特定框架的规则编写,我很乐意提供更进一步的探讨。

微信公众号

微信公众号

电脑智能化

· 阅读需 3 分钟
Quany
软件工程师

实现通过 Email 与 Claude Code 对话的核心方案是使用开源工具 Claude Code Remote。它通过邮件收发机制实现远程控制,非常适合长任务监控和移动办公场景。

核心实现原理

该工具采用 邮件中继 方式工作:

  1. 任务完成通知:本地 Claude Code 执行完任务后,自动发送结果邮件到你的邮箱
  2. 指令回复:你直接回复该邮件,在正文中写入新指令
  3. 自动执行:监控服务检测到白名单邮箱的回复后,自动将指令发送给 Claude Code
  4. 循环交互:重复上述流程,实现连续的远程对话

完整配置步骤

前期准备

  • Node.js 18+ 环境
  • 一个支持 IMAP/SMTP 的邮箱(如 Gmail、Outlook)
  • tmux 终端复用工具

安装配置

# 克隆项目
git clone https://github.com/JessyTsui/claude-code-remote
cd claude-code-remote

# 安装依赖
npm install

# 配置环境变量
cp .env.example .env

编辑 .env 文件,填入邮箱配置和白名单:

EMAIL_USER=your-email@gmail.com
EMAIL_PASS=your-app-password
ALLOWED_SENDERS=your-phone-email@gmail.com,your-colleague@company.com

启动服务

需要 两个终端窗口 配合运行:

终端 1:启动邮件监控服务

npm run relay:pty
# 显示 "🚀 Claude Code Remote is running! 📧 Monitoring emails..." 即成功

终端 2:在 tmux 中启动 Claude Code

# 创建新会话
tmux new-session -s my-claude-session

# 在 tmux 中正常启动 Claude
claude

使用流程演示

  1. 发起任务:在 tmux 的 Claude 界面输入指令

    分析当前目录下所有 Python 文件的复杂度
  2. 接收通知:任务完成后,邮箱收到标题为 "Claude Code Remote Task Complete [#ABC123]" 的邮件

  3. 回复指令:直接回复邮件,正文写:

    把结果生成一个 Markdown 报告,保存到 reports/ 目录
  4. 自动执行:回到 tmux 窗口,会看到 Claude 自动接收新指令并执行

  5. 持续交互:可无限循环回复邮件,直到任务完成


关键特性与优势

特性说明
客户端兼容支持所有邮件客户端(网页版、手机 App、桌面应用)
安全机制发送者白名单验证 + 会话隔离 + 自动超时
连续对话支持多轮指令,保持上下文连贯
网络要求无需公网 IP 或内网穿透,有邮箱即可控制
场景适配适合长任务监控、远程开发、团队协作

注意事项

  1. 邮箱安全:建议使用应用专用密码而非主密码
  2. Token 消耗:每次邮件交互都会消耗 Claude API 额度,重度使用月费约 $100-200
  3. 超时设置:长时间未操作的会话会自动失效,需重新启动
  4. 环境分离:生产环境建议单独配置 .env.production 文件

该方案已验证可行,多个开发者正在使用。如需更轻量级的实现,也可以基于 Python 编写简易邮件监控脚本调用 Claude API,但完整功能推荐使用 Claude Code Remote 项目。

微信公众号

微信公众号

电脑智能化

· 阅读需 1 分钟
Quany
软件工程师

ubuntu

网络配置:安装网卡驱动

sudo apt update
sudo apt install bcmwl-kernel-source

AI不间断运行

邮件往来审批 AI 执行

微信公众号

微信公众号

软件开发需求分类

· 阅读需 5 分钟
Quany
软件工程师

在中国软件开发实践中,需求分类已形成与国际接轨且符合本土管理规范的完整体系。以下是基于国内主流方法论(如软考、ASPICE标准、GB/T标准)及行业实践整理的需求分类框架:


一、按抽象级别和来源划分(三级需求体系)

这是中国软件工程领域最标准的分层方式,广泛应用于系统集成、政企项目及大型产品开发:

1. 业务需求(Business Requirements)

  • 定义:组织或业务部门的高层次目标,关注"为什么要做"
  • 来源:客户高层、业务战略、市场分析
  • 示例"提升订单处理效率30%""实现跨部门数据打通"
  • 文档形式:项目愿景与范围文档、商业论证

2. 用户需求(User Requirements)

  • 定义:最终用户对系统的期望,描述"用户想要什么"
  • 获取方式:访谈、问卷、用户故事、场景分析
  • 示例"用户能通过手机号快速注册""支持批量导入Excel数据"
  • 文档形式:用户故事(敏捷)、用例规约(传统)

3. 系统需求(System Requirements)

  • 定义:系统必须实现的具体技术特性和约束,说明"系统必须做什么"
  • 子类型
    • 功能需求:具体功能点(如"登录验证需支持短信验证码")
    • 非功能需求:性能、安全性、可靠性等质量属性(如"响应时间≤3秒")
  • 文档形式:软件需求规格说明书(SRS)

追溯关系:业务需求 → 用户需求 → 系统需求(双向可追溯性)


二、按性质划分(功能 vs 非功能)

这是开发团队执行层面的核心分类,直接影响技术方案:

功能需求(Functional Requirements)

  • 定义:系统必须完成的业务功能或服务
  • 占比:通常占需求总数的60-70%
  • 示例
    • 用户登录、权限管理
    • 订单创建、支付处理
    • 数据导入导出
  • 验证方法:功能测试、接口测试

非功能需求(Non-functional Requirements,NFR)

  • 定义:系统应满足的质量属性或约束条件
  • 关键类别
    类别说明示例
    性能需求响应时间、吞吐量"支持1000并发用户"
    安全性需求数据保护、访问控制"符合等保2.0三级要求"
    可靠性需求可用性、容错性"系统可用性≥99.9%"
    兼容性需求软硬件环境适配"支持Android 8.0+"
    可维护性代码规范、扩展性"采用微服务架构"
    合规性需求法律、行业标准"符合GDPR数据保护条例"

国内实践要点:非功能需求在合同验收中占比越来越高,尤其是政府项目对安全和性能有强制性要求。


三、按优先级划分(MoSCoW与Kano模型)

中国项目管理中常用的优先级划分方法:

1. MoSCoW方法

  • Must Have(必须有):核心功能,不满足则项目失败
  • Should Have(应有):重要功能,但可延期实现
  • Could Have(可以有):增值功能,资源允许时实现
  • Won't Have(暂不需要):明确排除在本次范围外

2. Kano模型

将需求分为三类:

  • 基本需求:必须满足,否则用户极度不满(如系统稳定性)
  • 期望需求:满足程度与用户满意度成正比(如界面美观度)
  • 兴奋需求:超出预期,带来惊喜(如AI智能推荐)

四、其他重要分类维度

1. 产品与过程需求

  • 产品需求:对软件本身的要求(如"验证用户输入合法性")
  • 过程需求:对开发过程的约束(如"必须使用Java开发"、"需通过CMMI三级评审")

2. 接口需求

  • 用户接口:UI/UX设计规范
  • 硬件接口:设备、传感器连接要求
  • 软件接口:第三方API、中间件集成
  • 通信接口:协议、数据格式(如RESTful API)

3. 派生需求 vs 基本需求

  • 基本需求:用户直接提出的原始需求
  • 派生需求:从技术实现或系统约束推导出的间接需求(如"因安全要求需增加日志审计功能")

五、中国本地化实践特点

1. 政策与合规驱动分类

  • 监管需求:如《数据安全法》《个人信息保护法》要求的数据存储、跨境传输限制
  • 信创适配需求:国产CPU(鲲鹏、飞腾)、操作系统(统信UOS、麒麟)、数据库适配
  • 等保/分保需求:明确的安全等级保护要求

2. 招投标导向分类

在政企项目中,需求常被分为:

  • 实质性需求:必须响应,否则废标(功能、性能指标)
  • 非实质性需求:评分项(界面美观、培训服务等)

3. 敏捷开发中的需求分层

国内互联网大厂普遍采用:

  • Epic(史诗):跨多个迭代的业务目标
  • Feature(特性):可交付的用户价值模块
  • User Story(用户故事):可在一个迭代内完成的细粒度需求
  • Task(任务):开发任务

六、需求分类的最佳实践

  1. 尽早建立可追溯性:使用需求管理工具(如Jira、PingCode、Ones)建立业务需求到代码的双向追溯
  2. 量化非功能需求:避免"快速响应"等模糊描述,必须量化(如"95%请求响应小于1秒")
  3. 分类评审机制:每类需求由不同角色评审(业务需求-客户签字,系统需求-架构师评审)
  4. 动态调整:采用敏捷模式,每迭代重新评估优先级,适应市场变化

通过清晰的需求分类,中国软件团队能有效应对需求蔓延评审效率低验收标准模糊等常见问题,也是通过软考高级系统分析师CMMI/ASPICE认证的关键能力要求。

微信公众号

微信公众号

todo

· 阅读需 1 分钟

状态管理

  • 页面回退的时候恢复状态

本周工作汇报

· 阅读需 6 分钟

时间范围

过去一周(基于 Git 提交记录)

主要工作内容

1. 品牌管理模块优化 ⭐

1.1 筛选功能实现

  • 文件: src/screens/crm/brand/filter.tsx
  • 功能: 实现了品牌列表的右侧抽屉筛选功能
    • 标签筛选(待完善)
    • 品牌编号筛选
    • 创建人筛选
    • 最后修改时间筛选(支持多个时间段选项)
    • 自定义时间段筛选
    • 筛选条件清除功能
  • 技术亮点:
    • 使用 Drawer Navigator 实现右侧抽屉
    • 集成 Jotai 进行状态管理
    • 使用 Tailwind CSS 进行样式设计

1.2 品牌列表组件化重构

  • PR: #14, #21
  • 改进内容:
    • 将大型组件拆分为多个子组件:
      • BrandCard.tsx - 品牌卡片组件
      • BrandFilterBar.tsx - 筛选栏组件
      • BrandFilterModal.tsx - 筛选模态框
      • BrandSearchAndActionBar.tsx - 搜索和操作栏
      • BrandSortModal.tsx - 排序模态框
      • BrandWatermark.tsx - 水印组件
    • 代码行数从 1315 行优化到更合理的结构
    • 提升了代码可维护性和可复用性

1.3 品牌详情页优化

  • PR: #20
  • 改进内容:
    • 修复类型定义问题
    • 优化 UI 展示逻辑
    • 改进数据加载和错误处理

2. 状态管理优化 🎯

2.1 Jotai Store 工具实现

  • 文件: src/utils/jotaiStore.ts (新增)
  • 功能: 实现了页面级别的 Jotai Store 管理工具
    • usePageStore - Hook 方式创建页面级 store
    • usePageBlur - 监听页面返回事件
    • createPageStore - 非 Hook 方式创建 store
  • 优势:
    • 自动在页面返回时重置状态
    • 避免状态污染
    • 提供更好的状态隔离

2.2 全局状态管理改进

  • PR: #15
  • 改进内容:
    • 添加全局 loading 引用
    • 改进导航类型定义
    • 优化状态管理结构

3. 存储系统迁移 💾

3.1 MMKV 迁移

  • PR: #17
  • 改进内容:
    • 移除 react-native-storageasync-storage
    • 全面迁移到 react-native-mmkv (v4)
    • 实现多实例架构优化
    • 提升存储性能(同步读写,高性能)

3.2 存储工具优化

  • 文件: src/utils/mmkv.ts
  • 改进内容:
    • 优化存储辅助函数
    • 改进错误处理
    • 添加类型安全

4. 导航系统优化 🧭

4.1 导航性能优化

  • PR: #16
  • 改进内容:
    • 实现懒加载(Lazy Loading)
    • 添加性能监控
    • 实现导航持久化
    • 优化导航结构
  • 新增文件:
    • src/navigation/performance-monitor.ts - 性能监控
    • src/navigation/persistence.ts - 持久化
    • src/navigation/helpers.ts - 辅助函数
    • src/navigation/exports.ts - 导出管理

4.2 导航安全性改进

  • PR: #22
  • 改进内容:
    • 改进错误处理机制
    • 添加导航安全检查
    • 优化加载状态管理

5. API 客户端优化 🔌

5.1 API 客户端重构

  • PR: #19
  • 改进内容:
    • 从 JavaScript 迁移到 TypeScript
    • 改进错误处理
    • 优化请求拦截器
    • 添加类型安全

5.2 请求头管理集中化

  • PR: #18
  • 改进内容:
    • 实现请求头集中管理
    • 自动设置 Organization-Idcorporation-id
    • 支持语言和时区请求头
    • 改进请求头缓存机制
  • 新增文件:
    • src/utils/requestHeaders.ts - 请求头管理
    • src/utils/language.ts - 语言工具

6. 代码结构优化 🏗️

6.1 组件化和模块化

  • PR: #21, #13
  • 改进内容:
    • 移除未使用的 API 定义和表单组件
    • 改进组件结构
    • 优化导入导出
    • 清理冗余代码

6.2 错误处理改进

  • PR: #22, #12
  • 改进内容:
    • 统一错误处理机制
    • 改进错误边界
    • 优化错误提示

7. UI/UX 优化 🎨

7.1 Tailwind CSS 迁移

  • PR: #8
  • 改进内容:
    • 将 UI 组件迁移到 Tailwind CSS
    • 使用 Tailwind 3.4.17 版本
    • 优化样式代码

7.2 @rneui/themed 集成

  • PR: #9
  • 改进内容:
    • 集成 @rneui/themed 组件库
    • 实现自定义主题
    • 优化 UI 组件使用

7.3 加载和认证上下文优化

  • PR: #11, #12
  • 改进内容:
    • 改进加载状态管理
    • 优化认证上下文
    • 改进应用加载流程

8. 工具和配置优化 ⚙️

8.1 环境配置优化

  • 改进内容:
    • 优化环境变量管理
    • 改进配置结构
    • 使用 react-native-config 管理配置

8.2 TypeScript 迁移

  • 改进内容:
    • 将多个 JavaScript 文件迁移到 TypeScript
    • 改进类型定义
    • 提升类型安全

9. 文档整理 📚

9.1 文档结构优化

  • 改进内容:
    • 整理文档到 doc 目录
    • 创建统一的 README
    • 整理技术文档

9.2 导航文档

  • 新增文档:
    • doc/src/navigation/README.md - 导航系统文档
    • doc/src/navigation/QUICK_START.md - 快速开始
    • doc/src/navigation/MIGRATION.md - 迁移指南
    • doc/src/navigation/INDEX.md - 索引文档

代码统计

提交统计

  • 总提交数: 80+ 次提交
  • PR 数量: 23 个 Pull Request
  • 文件变更: 110+ 个文件
  • 代码行数:
    • 新增: 10,349+ 行
    • 删除: 3,511+ 行
    • 净增: 6,838+ 行

主要文件变更

  • src/screens/crm/brand/ - 品牌管理模块重构
  • src/navigation/ - 导航系统优化
  • src/utils/ - 工具函数优化
  • src/api/ - API 客户端重构
  • src/services/ - 服务层优化

技术亮点 ✨

  1. 性能优化:

    • 实现懒加载和代码分割
    • 使用 MMKV 提升存储性能
    • 优化导航性能
  2. 类型安全:

    • 全面迁移到 TypeScript
    • 改进类型定义
    • 提升代码质量
  3. 状态管理:

    • 实现页面级状态管理
    • 避免状态污染
    • 提升状态管理效率
  4. 代码质量:

    • 组件化和模块化
    • 改进错误处理
    • 优化代码结构

待完成工作 🔄

  1. 品牌筛选功能:

    • 实现标签选择功能
    • 实现创建人选择功能
    • 实现自定义筛选项配置
    • 连接筛选逻辑到品牌列表
  2. 测试:

    • 添加单元测试
    • 添加集成测试
    • 性能测试
  3. 文档:

    • 完善 API 文档
    • 完善组件文档
    • 完善使用指南

下周计划 📅

  1. 完成品牌筛选功能的剩余部分
  2. 实现客户管理模块
  3. 优化性能和用户体验
  4. 添加测试覆盖
  5. 完善文档

总结

本周主要完成了品牌管理模块的重构和优化,实现了筛选功能的基础框架,优化了状态管理、存储系统、导航系统等多个核心模块。代码质量得到显著提升,类型安全性增强,性能得到优化。为后续功能开发奠定了良好的基础。

包大小

纷享销客的apk包大小为:885mb;

现在出的apk包大小为:39.6mb;iOS 的 ipa 大小为:9.7MB;

TODO List

· 阅读需 1 分钟
Quany
软件工程师

下拉选择 [ ] 返回头

微信公众号

微信公众号

React Native 中实现流畅的列表下拉刷新和上拉加载更多功能

· 阅读需 13 分钟
Quany
软件工程师

在 React Native 中实现流畅的列表下拉刷新和上拉加载更多功能,关键在于理解核心组件并合理管理状态。下面我将为你梳理最佳实践方案。

🔄 核心实现方案

实现下拉刷新和上拉加载主要有两种路径:使用 React Native 官方提供的组件,或者借助功能丰富的第三方库。

方案核心组件/库优点适用场景
官方方案FlatList + RefreshControl稳定性高,无需额外依赖,官方维护需要标准、轻量级的刷新加载功能,项目限制外部依赖
第三方库react-native-SmartRefreshLayout功能丰富,UI 定制性强,可能跨平台一致性更好需要更精美的动画效果或更复杂交互

使用官方 FlatList 方案

这是最直接、最常用的方法。FlatList 组件自身就提供了实现这些功能的关键属性:

  • 下拉刷新 (onRefresh & refreshing): 通过 refreshing 属性控制刷新状态指示器的显示,onRefresh 属性指定下拉时触发的函数。
  • 上拉加载更多 (onEndReached): 当列表滚动到底部阈值时触发 onEndReached 回调函数。

一个结合 Hooks 的基本示例代码如下:

import React, { useState, useCallback } from 'react';
import { FlatList, Text, View, ActivityIndicator, RefreshControl } from 'react-native';

const AdvancedList = () => {
const [data, setData] = useState(initialData);
const [refreshing, setRefreshing] = useState(false);
const [loadingMore, setLoadingMore] = useState(false);
const [hasMore, setHasMore] = useState(true);

// 下拉刷新
const onRefresh = useCallback(async () => {
setRefreshing(true);
// 模拟网络请求
await new Promise(resolve => setTimeout(resolve, 2000));
// 通常这里会获取最新数据并重置页码
setData(initialData);
setHasMore(true);
setRefreshing(false);
}, []);

// 上拉加载更多
const onEndReached = useCallback(async () => {
// 防止重复请求、无更多数据时不再请求
if (loadingMore || !hasMore) return;

setLoadingMore(true);
// 模拟网络请求
await new Promise(resolve => setTimeout(resolve, 2000));
// 模拟追加数据
const newData = [...data, ...moreData];
setData(newData);
setLoadingMore(false);
// 模拟数据加载完毕的情况
if (newData.length >= 30) {
setHasMore(false);
}
}, [loadingMore, hasMore, data]);

const renderFooter = () => {
if (!hasMore) {
return (
<View style={{ padding: 10, alignItems: 'center' }}>
<Text>没有更多数据了</Text>
</View>
);
}
return loadingMore ? <ActivityIndicator style={{ padding: 10 }} /> : null;
};

return (
<FlatList
data={data}
renderItem={({ item }) => <Text style={{ padding: 20 }}>{item.title}</Text>}
keyExtractor={item => item.id}
refreshControl={
<RefreshControl
refreshing={refreshing}
onRefresh={onRefresh}
// 可自定义样式,例如 tintColor (iOS), colors (Android) 等
/>
}
onEndReached={onEndReached}
onEndReachedThreshold={0.2} // 调整触发灵敏度,表示距离底部 20% 时触发
ListFooterComponent={renderFooter}
/>
);
};

// 模拟数据
const initialData = [{id: '1', title: '初始项目1'}, {id: '2', title: '初始项目2'}];
const moreData = [{id: '3', title: '更多项目3'}, {id: '4', title: '更多项目4'}];

export default AdvancedList;

探索第三方库

如果你需要更强大的下拉动画或更精细的控制,可以考虑像 react-native-SmartRefreshLayout 这样的第三方库。它们通常提供更丰富的内置样式和开箱即用的高级功能。使用时请参考其特定文档进行安装和配置。

⚠️ 关键细节与优化

实现基本功能后,以下几点能显著提升稳定性和用户体验:

  1. 状态管理是核心:务必妥善管理 refreshingloadingMorehasMore 等状态。确保在请求开始和结束时正确更新状态,避免重复请求或状态不一致。
  2. 防止 onEndReached 重复触发:这是常见问题。可以通过设置标志位(如 loadingMore)在请求期间阻止再次触发。注意 onEndReached 可能在滚动时多次调用,需要合理设计逻辑。
  3. 优化网络请求:在实际项目中,将数据获取逻辑封装成独立的函数或服务。使用 async/await 处理异步操作,并务必添加错误处理,在请求失败时给予用户反馈并提供重试机制。
  4. 自定义视觉反馈:除了默认的 ActivityIndicator,你可以通过 ListFooterComponentListHeaderComponent 完全自定义加载中和数据加载完的界面,使其更符合应用整体设计。

💎 方案选择建议

对于大多数应用,从官方 FlatList 方案入手是最佳选择。它稳定、可靠,能满足基本需求,且社区支持丰富。

如果你的项目对下拉刷新的动画效果有特殊要求,或者你希望减少自行处理细节状态的工作量,那么探索一款维护良好的第三方库(如 react-native-SmartRefreshLayout)会是更高效的选择。

希望这些实践建议能帮助你顺利实现功能!如果你在实现过程中遇到更具体的问题,比如如何与特定状态管理库结合,欢迎随时提出。

在 React Native 应用开发中,优化 FlatListonEndReached 回调以防止其重复触发,是确保列表分页加载性能和使用体验的关键。下面这张表格汇总了核心的解决方案,帮助你快速抓住要点。

优化维度具体方案说明
触发时机控制合理设置 onEndReachedThreshold将其设置为一个较小的值(如 0.10.01),表示滚动到距离列表底部 10%1% 时触发,避免过早或意外触发。
滚动动量管理使用 onMomentumScrollBegin 标志位利用滚动动量事件,确保一次完整的滚动动作只触发一次加载。
函数执行频率控制实现防抖(Debounce)在连续快速触发时,只执行最后一次调用,有效避免重复请求。

💡 核心方案详解与代码实现

1. 标志位控制法

这种方法通过一个标志变量来记录是否已经开始加载,从而避免在一次滚动中重复触发。

import React, { useRef, useState, useCallback } from 'react';
import { FlatList, View, Text } from 'react-native';

const OptimizedList = () => {
const [data, setData] = useState([...]); // 初始数据
const [isLoadingMore, setIsLoadingMore] = useState(false);
const [hasMore, setHasMore] = useState(true);
// 使用 useRef 创建在渲染间保持不变的标志位
const isOnEndReachedCalled = useRef(false);

const loadMoreData = useCallback(async () => {
// 如果正在加载或已无更多数据,则直接返回
if (isLoadingMore || !hasMore) return;

setIsLoadingMore(true);
// 模拟网络请求
const newData = await fetchMoreData();
setData(prevData => [...prevData, ...newData]);
setIsLoadingMore(false);
// 根据返回数据判断是否还有更多
setHasMore(newData.length > 0);
// 加载完成后,重置标志位,允许下一次触发
isOnEndReachedCalled.current = false;
}, [isLoadingMore, hasMore]);

const handleOnEndReached = useCallback(() => {
// 关键:如果本次滚动还未触发加载,则执行
if (!isOnEndReachedCalled.current) {
loadMoreData();
isOnEndReachedCalled.current = true; // 设置标志位,阻止重复触发
}
}, [loadMoreData]);

return (
<FlatList
data={data}
renderItem={({ item }) => <Text>{item.title}</Text>}
onEndReached={handleOnEndReached}
onEndReachedThreshold={0.1} // 设置为一个较小的阈值
onMomentumScrollBegin={() => {
// 当用户开始滚动时,重置标志位,为下一次触发做准备
isOnEndReachedCalled.current = false;
}}
ListFooterComponent={() => (
isLoadingMore ? <Text>加载中...</Text> : null
)}
// 其他优化属性
maxToRenderPerBatch={5}
windowSize={5}
/>
);
};

原理:通过 onMomentumScrollBeginisOnEndReachedCalled 标志位协同工作,确保一次“滚动手势”只触发一次 onEndReached

2. 防抖函数优化

防抖技术可以确保在频繁触发事件时,只执行最后一次操作。

import { debounce } from 'lodash'; // 或者自己实现一个简单的防抖函数

// 自定义一个简单的防抖Hook
function useDebounce(callback, delay) {
const debouncedFn = useRef(debounce(callback, delay)).current;
return debouncedFn;
}

// 在组件中使用
const MyListComponent = () => {
// ... 状态定义

const loadMoreData = useCallback(async () => {
// 加载数据的逻辑
}, [/* 依赖 */]);

// 创建一个防抖版本的加载函数,300毫秒内连续触发只会执行一次
const debouncedLoadMore = useDebounce(loadMoreData, 300);

const handleOnEndReached = useCallback(() => {
if (!isLoadingMore && hasMore) {
debouncedLoadMore();
}
}, [isLoadingMore, hasMore, debouncedLoadMore]);

return (
<FlatList
// ... 其他属性
onEndReached={handleOnEndReached}
onEndReachedThreshold={0.2}
/>
);
};

原理:当 onEndReached 被连续快速调用时,防抖函数会延迟执行 loadMoreData。如果在延迟时间内再次被调用,会取消之前的计时并重新计时,从而确保只有最后一次滚动操作会真正加载数据。

⚙️ 其他重要优化配置

除了防止重复触发,合理配置 FlatList 的其他属性对整体性能也至关重要。

  • 优化渲染性能:通过 maxToRenderPerBatch(控制每批渲染的项目数)和 windowSize(控制渲染窗口大小)等属性,可以减少JS线程的负担,使滚动更加流畅。例如,可以设置 maxToRenderPerBatch={5}windowSize={5}
  • 确保外层容器高度:有时 FlatList 的父容器高度计算异常会导致 onEndReached 在初始化时就被触发。确保父容器有明确的高度(例如设置 style={{ flex: 1 }}{height: '100%'})可以解决此问题。
  • 使用稳定的 Key:为 keyExtractor 提供一个唯一且稳定的键(如数据项的 id),而不是数组的 index,这有助于 FlatList 更高效地识别项目变化,减少不必要的重渲染。
  • 考虑使用性能更好的列表组件:对于超长列表或极其复杂的列表项,可以考虑使用社区提供的性能更佳的组件,如 Shopify 的 FlashList,它在底层做了大量优化。

💎 总结与建议

你可以根据项目复杂度和需求,选择最适合的方案。对于大多数场景,“标志位控制法” 因其简单有效通常是首选。如果列表滚动非常频繁,可以结合防抖函数进一步优化。同时,别忘了配置好 FlatList 的其他优化属性,并从整体上保证组件设计的合理性。

希望这些具体的方案能帮助你有效解决 onEndReached 重复触发的问题!如果你在实现过程中遇到其他难题,欢迎随时提出。

在复杂列表场景下,单纯的基础虚拟化可能仍会遇到性能瓶颈。结合虚拟化技术进行深度优化,需要像指挥一场交响乐一样,让数据加载、渲染和内存管理各个环节精准协同。下面我将从核心策略、关键技术到实践方案,为你系统梳理提升长列表刷新加载性能的方法。

📊 复杂列表的额外挑战与优化方向

首先,我们来看看复杂列表相比简单列表带来了哪些特定的性能挑战,这决定了我们的优化方向。

挑战维度对性能的影响基础虚拟化的不足
数据量极大网络传输、解析耗时过长,阻塞渲染仅优化了渲染,未优化数据到达速度
列表项结构复杂单个项渲染成本高,即使数量不多也会卡顿仍会尝试渲染视口内所有复杂项
交互频繁与数据实时性频繁的状态更新导致大量组件重渲染缺乏对更新粒度的控制
内存占用高尤其是图片、多媒体内容易引发内存泄漏虚拟化主要减少DOM节点,需额外内存管理

🚀 提升性能的核心策略

针对上述挑战,可以采取以下关键策略,它们共同构成了高性能虚拟列表的支柱。

  1. 数据层面的异步与分片加载 虚拟化解决了渲染量,但数据本身的加载速度也需要优化。

    • 增量加载:不要等待所有数据都获取完毕再渲染。采用“无限滚动”模式,滚动到底部时自动加载下一页数据。
    • 数据分片与优先级:对超大规模数据(如10万条),可在首屏优先加载前几百条,同时利用 Web Worker 在后台线程静默加载和预处理剩余数据,避免阻塞主线程。
    • 智能预加载:根据滚动速度预测用户行为。快速滚动时,预加载更多数据;慢速浏览时,减少预加载以节省资源。
  2. 渲染层面的精细控制与复用 这是降低每个列表项渲染开销的关键。

    • 极致复用与防重渲染:对列表项组件使用 React.memoPureComponent,并结合自定义的 arePropsEqual 函数,确保只有当前项依赖的数据真正变化时才会重渲染。
    • 降低组件复杂度:简化列表项组件结构,避免深层次的嵌套。对于复杂项,可考虑将交互状态提升到父组件或使用状态管理库,减少内部状态导致的更新。
    • 占位符与骨架屏:数据加载过程中,先使用骨架屏占位,保持布局稳定,提升用户体验。
  3. 内存与资源管理 尤其在移动端或图片、视频多的场景,内存管理至关重要。

    • 图片优化与卸载:使用专门的图片库(如 react-native-fast-image),它们能更好地处理缓存和内存。确保图片滚出视口时,其内存能被正确释放。
    • 主动清理资源:在组件卸载或数据更新时,清理所有副作用,如事件监听器、定时器等,防止内存泄漏。

🛠️ 关键技术方案与代码实践

以下是一些可以直接应用的技术方案和代码思路。

  1. 虚拟化库的高级用法

    • 动态高度处理:使用支持动态高度的虚拟化库(如 react-windowVariableSizeListreact-virtuoso)。关键在于提前测量或预估项高度,并缓存结果以避免重复计算。
    • ** Overscan(缓冲区)配置**:合理设置 overscanCount 或类似参数。在可视区域外多渲染几行,快速滚动时避免出现空白,但设置过大会增加渲染负担。
  2. 数据获取与状态更新优化

    • 防抖与节流:对搜索过滤等操作进行防抖,避免频繁发起请求和重新渲染虚拟列表。
    • 不可变数据与结构共享:更新列表数据时,使用不可变数据更新方式。这样可以快速通过引用比较判断数据是否变化,优化 React.memo 的效果。
  3. 针对React Native的特定优化 如果使用 React Native 的 FlatList,以下配置尤为关键:

    <FlatList
    data={data}
    keyExtractor={(item) => item.id} // 必须提供稳定的key
    initialNumToRender={10} // 初始渲染数量,不宜过大
    maxToRenderPerBatch={5} // 每批增量渲染数量,控制卡顿
    windowSize={5} // 渲染区域高度是可视区域的倍数,调小可节省内存
    removeClippedSubviews={true} // 卸载屏幕外视图(Android尤其需要)
    getItemLayout={(data, index) => ( // 提前告知项尺寸,避免测量开销
    {length: ITEM_HEIGHT, offset: ITEM_HEIGHT * index, index}
    )}
    // 结合你的异步加载逻辑
    onEndReached={loadMoreData}
    onEndReachedThreshold={0.5} // 触发加载的阈值
    />

💎 总结与进阶建议

将虚拟化技术与上述策略结合,能显著提升复杂长列表的性能。关键在于理解一个核心思想:虚拟化不是一颗银弹,而是一个性能基础。在此之上,需要数据、渲染、内存多方位的协同优化。

对于追求极致性能的场景,还可以探索:

  • 更先进的虚拟化库:如 react-virtuoso,它内置了对分组列表、自动高度测量等复杂场景的良好支持。
  • 并发渲染:在支持 React 18+ 的环境中,考虑使用 useTransitionuseDeferredValue 将列表渲染标记为可中断的非紧急更新,避免交互卡顿。

希望这些策略和方案能为你优化复杂列表提供清晰的路径。如果你能分享更多关于你遇到的具体场景(例如,列表项的主要复杂度来源是图片、计算还是复杂的交互?),我可以提供更有针对性的建议。

微信公众号

微信公众号

想通过命令行安装特定版本的 Android NDK

· 阅读需 3 分钟
Quany
软件工程师

您想通过命令行安装特定版本的 Android NDK,但需要先澄清一个关键点:adb 命令主要用于与已连接的 Android 设备通信,而安装 NDK 是在您的本地开发电脑上进行的操作。正确的工具是 Android SDK 的 sdkmanager

下面为您详细介绍两种方案来满足您的需求。

🔧 方案一:使用 sdkmanager 安装指定版本(推荐)

这是最直接、由 Android 官方推荐的方法。sdkmanager 是一个专门用于管理 Android SDK 包(包括 NDK)的命令行工具。

  1. 定位 sdkmanager:首先,您需要找到您电脑上 Android SDK 目录下的 sdkmanager 工具。它通常位于 android-sdk/tools/bin/ 目录下。如果您设置了 ANDROID_SDK_ROOTANDROID_HOME 环境变量,会更容易定位。

  2. 执行安装命令:打开终端(或命令提示符),使用以下命令格式进行安装。请注意,sdkmanager 期望的版本字符串格式可能与您提供的略有不同,它通常遵循 major.minor.revision 的模式。

    sdkmanager "ndk;21.0.6113669"

    在命令执行过程中,系统会显示许可协议,您需要接受才能继续安装。您也可以在命令中直接加上 --accept_licenses 参数来自动接受。

  3. 验证安装:安装完成后,您可以在 $ANDROID_SDK_ROOT/ndk/ 目录下找到一个名为 21.0.6113669 的文件夹,这就是安装好的 NDK。

⚙️ 方案二:配置项目使用已安装的 NDK

如果您的开发环境中已经安装了多个版本的 NDK(您的错误信息显示本地有众多版本),另一个高效的策略是直接修改项目配置,使其使用一个已有的、兼容的版本,而不是重新安装。

  • 修改项目配置:在您项目模块(通常是 app)级的 build.gradle 文件中,指定一个您本地已有的 NDK 版本。从您的错误日志看,21.3.6528147 是一个很接近的选择。

    android {
    ndkVersion "21.3.6528147" // 指定一个本地已有的兼容版本
    }

💎 方案对比与建议

为了让您更清晰地选择,这里有一个简单的对比表格:

方案核心命令/操作优点适用场景
方案一sdkmanager "ndk;21.0.6113669"官方推荐,直接满足版本要求需要精确匹配特定 NDK 版本时
方案二build.gradle 中设置 ndkVersion快速,无需下载,利用现有资源项目有一定灵活性,允许使用兼容版本

综合建议

  • 如果您确定项目必须使用 21.0.6113669 这个特定版本,请使用方案一
  • 如果希望快速解决问题,并且经过测试后确认使用一个已有的高版本(如 21.3.6528147)也能成功编译,那么方案二是更高效的选择。

⚠️ 重要注意事项

  • 确保 sdkmanager 可用:在执行方案一的命令前,请确认 sdkmanager 在您的系统环境变量 PATH 中,或者您需要切换到其所在目录执行。
  • 版本可用性:极老的或非常规的 NDK 版本可能已从官方仓库中移除。如果 sdkmanager 提示找不到该版本,您可能需要从其他渠道手动下载并配置。
  • AGP 版本:您遇到这个问题的原因可能是项目使用的 Android Gradle Plugin (AGP) 版本较老,其默认的 NDK 版本恰好是 21.0.6113669。长远考虑,升级 AGP 版本是一个更好的选择,因为它会自动管理并使用更新的、兼容性更好的 NDK。

希望这些方案能帮助您解决问题!如果您在操作中遇到任何其他情况,欢迎随时提出。

微信公众号

微信公众号

React Native 0.83

· 阅读需 4 分钟
Quany
软件工程师

React 19.2 的新特性确实为 React Native 开发带来了更优雅的解决方案,特别是 useEffectEvent,它能显著改善副作用管理和状态处理。下面的表格总结了这些特性的核心用途和兼容性,方便你快速了解。

特性 核心用途 React Native 兼容性

useEffectEvent 从 Effect 中分离“事件逻辑”,解决闭包陷阱,简化依赖数组 需 RN 0.83+(已集成 React 19.2)

Activity 组件 条件渲染时隐藏而非卸载组件,以保留状态(如表单输入、滚动位置) 需 RN 0.83+(已集成 React 19.2)

React Compiler 1.0 构建时自动优化重渲染,减少手动useMemo, useCallback使用 需项目配置启用(目前仍为实验性)

性能工具增强 新的 DevTools 提供更强大的性能追踪和网络请求调试能力 需 RN 0.83+

💡 核心特性详解与应用

  1. useEffectEvent:告别闭包陷阱

useEffectEvent 是 React 19.2 中一个解决常见副作用的 Hook,它允许你将一个包含“非响应式”逻辑(即那些不应该导致副作用重新执行的逻辑)的函数标记为 “Effect 事件”。

• 解决的问题:在以往的 React 开发中,如果一个 Effect 内部使用的函数引用了组件的 props 或 state,你必须将该函数放入 Effect 的依赖数组中。但这常常会导致 Effect 过度重复执行。使用 useRef 手动管理最新值虽然可行,但增加了代码的复杂度和出错风险。

• React Native 使用示例:一个典型的场景是处理 WebSocket 消息。你希望 WebSocket 连接只在 roomId 改变时重建,但消息处理函数又需要能访问到最新的 messages 状态。

    // 注意:此示例基于 React 19.2 的 Canary 版本,使用时请确认你的 React 版本
import { useState, useEffect, useEffectEvent } from 'react';
import { View, Text } from 'react-native';

function ChatRoom({ roomId }) {
const [messages, setMessages] = useState([]);

// 使用 useEffectEvent 包裹消息处理逻辑
const handleNewMessage = useEffectEvent((newMessage) => {
// 此函数总能访问到最新的 messages 状态
setMessages((currentMessages) => [...currentMessages, newMessage]);
});

useEffect(() => {
const socket = new WebSocket(`wss://myapi.com/chat/${roomId}`);

socket.onmessage = (event) => {
const messageData = JSON.parse(event.data);
handleNewMessage(messageData); // 在 Effect 内部调用
};

return () => socket.close();
}, [roomId]); // 依赖数组只需包含 roomId,handleNewMessage 不需要加入

return (
<View>
{messages.map(msg => <Text key={msg.id}>{msg.text}</Text>)}
</View>
);
}

• 重要限制:useEffectEvent 返回的函数必须仅在 Effect 内部调用,而不能直接作为如 onPress 这类事件处理函数使用。

  1. Activity 组件:优雅的状态保持

Activity 组件提供了一种更优雅的方式来条件性地渲染组件,其核心优势在于当组件被隐藏时,它不会被卸载,从而完美保留所有状态。

• 解决的问题:在 React Native 中,我们常用条件渲染 。但当 isVisible 变为 false 时,组件会被卸载,其内部状态(如输入框的内容、滚动位置)会完全丢失。再次显示时,组件会重新挂载,状态被重置。

• React Native 使用示例:在具有多个标签页的界面中,使用 Activity 可以确保切换标签时各个标签页的状态得以保留。

    // 注意:此组件在 React 19.2 中引入
import { Activity } from 'react';
import { View, Text, TextInput } from 'react-native';

function TabScreen({ isActive }) {
const [inputValue, setInputValue] = useState(''); // 这个状态在标签隐藏时不会丢失

return (
<Activity mode={isActive ? 'visible' : 'hidden'}>
<View>
<Text>这个标签页的状态会被保留</Text>
<TextInput
value={inputValue}
onChangeText={setInputValue}
placeholder="尝试输入后切换标签..."
/>
</View>
</Activity>
);
}

🔐 使用前的重要检查

在将 React 19.2 的新特性用于 React Native 项目前,请务必确认以下两点:

  1. React Native 版本:确保你的项目使用的是 React Native 0.83 或更高版本,因为该版本才开始集成 React 19.2。你可以通过查看 package.json 文件中的 react-native 依赖版本来确认。
  2. React 版本:确认项目的 react 和 react-native 包版本兼容。React Native 0.83 内置了兼容的 React 版本。如果你想在更早的 RN 版本中尝试,需要手动升级 React 到 19.2,但这可能会带来风险,强烈建议通过升级 RN 版本来获取稳定支持。

希望这些具体的示例能帮助你在 React Native 项目中更好地运用 React 19.2 的新特性。如果你在升级版本或使用特定特性时遇到具体问题,我很乐意提供进一步的探讨。

微信公众号

微信公众号