HarmonyOS @Local 完全指南:V2 组件私有状态的不可变更新模式
前言Local是 HarmonyOS 状态管理 V2 中替代State的私有状态装饰器。与State相比Local有两项关键改进严格私有性父组件无任何途径读写它和更清晰的更新语义明确要求不可变更新模式让数据流方向一目了然。本文用一个完整的 TodoList 演示Local操作数组、对象和基本类型的全套模式以及 ArkTS 严格模式下的常见编译陷阱。核心 APILocal 声明语法Entry ComponentV2 struct MyPage { Local count: number 0 // 基本类型 Local title: string 待办清单 // 字符串 Local todos: Todo[] [] // 数组 Local filter: string all // 枚举字符串 }触发 UI 更新的规则数据类型触发更新的写法不触发更新的写法基本类型this.count✅—字符串this.title 新标题✅—数组this.todos [...this.todos, item]✅this.todos.push(item)❌对象this.profile { ...fields }✅this.profile.name 新❌核心规则Local检测的是引用变化不是内容变化。数组和对象必须生成新引用才能触发重渲染基本类型赋值本身就是新值天然触发。Local vs State 关键差异// V1State 可被父组件通过 ObjectLink 访问 State items: Todo[] [] // 父组件可通过 ObjectLink 读写 // V2Local 严格私有外部无法读写 Local items: Todo[] [] // 只有本组件内部能修改实现思路用一个 TodoList 覆盖Local的三种数据类型Local todos: Todo[]展示数组的不可变增删改Local filter: string展示字符串赋值Local nextId: number展示基本类型自增。getStats()返回派生统计数据后续 article68 中Computed会自动缓存此类计算。ArkTS 不允许对象展开{ ...obj }所有对象更新须逐字段构造在第 3、4 步中重点展示。逐步实现第 1 步定义接口和 Local 状态interface Todo { id: number text: string done: boolean } Entry ComponentV2 struct Index { Local todos: Todo[] [] // 任务列表 Local nextId: number 1 // 自增 ID基本类型 Local filter: string all // 过滤器字符串 Local inputText: string // 输入框绑定三种类型一次覆盖数组、基本类型、字符串触发方式各不相同。第 2 步添加任务——数组不可变更新private addTodo(): void { const text this.inputText.trim() if (!text) return // [...旧数组, 新元素] 产生新数组引用触发 Local 更新 this.todos [...this.todos, { id: this.nextId, text, done: false }] this.nextId // 基本类型直接赋值 this.inputText // 清空输入框 }[...this.todos, newItem]是数组的标准不可变添加模式等价于 V1 的this.todos this.todos.concat([newItem])。第 3 步切换完成状态——map 返回新数组private toggleDone(id: number): void { // ArkTS 禁止对象展开 { ...t }须逐字段构造新对象 this.todos this.todos.map(t t.id id ? { id: t.id, text: t.text, done: !t.done } // 新对象 : t ) }map()天然返回新数组配合逐字段构造对象一行完成不可变更新。注意 ArkTS 严格模式禁止{ ...t, done: !t.done }arkts-no-spread错误必须显式列出每个字段。第 4 步删除任务——filter 返回新数组private removeTodo(id: number): void { this.todos this.todos.filter(t t.id ! id) }filter()同样返回新数组是最简洁的不可变删除写法。第 5 步过滤显示与统计private filteredTodos(): Todo[] { if (this.filter active) return this.todos.filter(t !t.done) if (this.filter done) return this.todos.filter(t t.done) return this.todos } private getStats(): Stats { const done this.todos.filter(t t.done).length return { total: this.todos.length, done, active: this.todos.length - done } }切换this.filter字符串值即触发重渲染filteredTodos()返回对应子集。完整代码// Article 62: Local 私有状态深度解析 interface Todo { id: number text: string done: boolean } interface Stats { total: number done: number active: number } Entry ComponentV2 struct Index { Local todos: Todo[] [] Local nextId: number 1 Local filter: string all Local inputText: string private addTodo(): void { const text this.inputText.trim() if (!text) return this.todos [...this.todos, { id: this.nextId, text, done: false }] this.nextId this.inputText } private toggleDone(id: number): void { this.todos this.todos.map(t t.id id ? { id: t.id, text: t.text, done: !t.done } : t ) } private removeTodo(id: number): void { this.todos this.todos.filter(t t.id ! id) } private getStats(): Stats { const done this.todos.filter(t t.done).length return { total: this.todos.length, done, active: this.todos.length - done } } private filteredTodos(): Todo[] { if (this.filter active) return this.todos.filter(t !t.done) if (this.filter done) return this.todos.filter(t t.done) return this.todos } private filterLabel(f: string): string { if (f active) return 待完成 if (f done) return 已完成 return 全部 } build() { Column({ space: 0 }) { Row() { Text(Local 私有状态).fontSize(18).fontWeight(FontWeight.Bold).fontColor(#1a1a1a) } .width(100%).height(56).backgroundColor(#ffffff).padding({ left: 16 }) .border({ width: { bottom: 1 }, color: #f0f0f0 }) Scroll() { Column({ space: 12 }) { Row({ space: 0 }) { Column({ space: 4 }) { Text(${this.getStats().total}).fontSize(28).fontWeight(FontWeight.Bold).fontColor(#1a1a1a) Text(全部).fontSize(11).fontColor(#aaa) } .layoutWeight(1).alignItems(HorizontalAlign.Center) Column({ space: 4 }) { Text(${this.getStats().active}).fontSize(28).fontWeight(FontWeight.Bold).fontColor(#0066ff) Text(待完成).fontSize(11).fontColor(#aaa) } .layoutWeight(1).alignItems(HorizontalAlign.Center) Column({ space: 4 }) { Text(${this.getStats().done}).fontSize(28).fontWeight(FontWeight.Bold).fontColor(#07c160) Text(已完成).fontSize(11).fontColor(#aaa) } .layoutWeight(1).alignItems(HorizontalAlign.Center) } .width(100%).backgroundColor(#ffffff).borderRadius(12).padding(16) .margin({ left: 12, right: 12 }) Column({ space: 10 }) { Text(添加任务).fontSize(13).fontColor(#888).fontWeight(FontWeight.Medium).width(100%) Row({ space: 10 }) { TextInput({ placeholder: 输入任务内容…, text: this.inputText }) .layoutWeight(1).height(44).borderRadius(22).fontSize(14) .backgroundColor(#f5f5f5).padding({ left: 16, right: 16 }) .onChange((v: string) { this.inputText v }) Button(添加) .height(44).borderRadius(22).fontSize(14).padding({ left: 20, right: 20 }) .backgroundColor(this.inputText.trim() ? #0066ff : #e0e0e0).fontColor(#fff) .onClick(() this.addTodo()) } } .width(100%).backgroundColor(#ffffff).borderRadius(12).padding(16) .margin({ left: 12, right: 12 }) Row({ space: 8 }) { ForEach([all, active, done] as string[], (f: string) { Text(this.filterLabel(f)) .fontSize(13).padding({ left: 16, right: 16, top: 6, bottom: 6 }).borderRadius(16) .fontColor(this.filter f ? #fff : #666) .backgroundColor(this.filter f ? #0066ff : #f0f0f0) .onClick(() { this.filter f }) }) } .width(100%).padding({ left: 16, right: 16 }) Column({ space: 8 }) { if (this.filteredTodos().length 0) { Text(暂无任务).fontSize(14).fontColor(#ccc).margin({ top: 20 }) } else { ForEach(this.filteredTodos(), (todo: Todo) { Row({ space: 12 }) { Text(todo.done ? ✅ : ⬜).fontSize(20) .onClick(() this.toggleDone(todo.id)) Text(todo.text) .fontSize(14).layoutWeight(1).fontColor(todo.done ? #bbb : #1a1a1a) .decoration({ type: todo.done ? TextDecorationType.LineThrough : TextDecorationType.None }) Text(✕).fontSize(16).fontColor(#ddd) .onClick(() this.removeTodo(todo.id)) } .width(100%).backgroundColor(#ffffff).borderRadius(12) .padding({ left: 16, right: 16, top: 14, bottom: 14 }) }) } } .width(100%).padding({ left: 12, right: 12, bottom: 24 }) } .width(100%).padding({ top: 12 }) } .layoutWeight(1).backgroundColor(#f8f8f8) } .width(100%).height(100%).backgroundColor(#f8f8f8) } }运行效果初始态列表为空三项统计均为 0添加 4 条任务后全部 4 / 待完成 2 / 已完成 2前两条显示删除线注意事项ArkTS 禁止对象展开{ ...obj, key: val }会触发arkts-no-spread编译错误必须逐字段构造新对象{ id: t.id, text: t.text, done: !t.done }。这是 ArkTS 与标准 TypeScript 的重要区别迁移代码时需全面替换。数组方法的可变/不可变区别push/pop/splice/sort原地变更→ 不触发Local更新map/filter/concat/[...arr]返回新数组→ 触发更新 始终使用返回新数组的方法。build() 内不能声明变量ForEach回调或其他 UI 构建函数内不允许const/let声明Only UI component syntax can be written here需提取为组件方法如filterLabel()。Local 严格私有父组件不能通过任何方式读取或修改子组件的Local状态。如果需要父写子用Paramarticle63子写父用Eventarticle64。派生计算的性能getStats()和filteredTodos()每次渲染都重新计算。当列表较大时1000 项应改用Computed缓存article68避免不必要的重复遍历。

相关新闻

B站评论数据采集系统:基于Selenium的完整评论层级爬取方案

B站评论数据采集系统:基于Selenium的完整评论层级爬取方案

B站评论数据采集系统:基于Selenium的完整评论层级爬取方案 【免费下载链接】BilibiliCommentScraper B站视频评论爬虫 Bilibili完整爬取评论数据,包括一级评论、二级评论、昵称、用户ID、发布时间、点赞数 项目地址: https://gitcode.com/gh_mirrors/b…

2026/8/26 17:59:19 阅读更多 →
阿里云百炼大模型API调用实战指南

阿里云百炼大模型API调用实战指南

1. 从零开始调用大模型API:完整指南 作为一名长期从事AI应用开发的工程师,我深知初学者在接触大模型API时的困惑。第一次调用API时,我也曾对着文档发愣,不知从何下手。本文将带你完整走通阿里云百炼大模型API的调用全流程&#x…

2026/8/18 11:45:47 阅读更多 →
ComfyUI-WD14-Tagger:AI绘画必备的智能图像标签识别工具

ComfyUI-WD14-Tagger:AI绘画必备的智能图像标签识别工具

ComfyUI-WD14-Tagger:AI绘画必备的智能图像标签识别工具 【免费下载链接】ComfyUI-WD14-Tagger A ComfyUI extension allowing for the interrogation of booru tags from images. 项目地址: https://gitcode.com/gh_mirrors/co/ComfyUI-WD14-Tagger 你是否曾…

2026/8/24 12:15:06 阅读更多 →

最新新闻

VS Code C/C++ 插件 cpptools 内存占用异常

VS Code C/C++ 插件 cpptools 内存占用异常

前期概要 使用VS code连接WSL经常性出现终端和IDE卡住无法使用,关闭VS后即正常。 根本原因 cpptools 吃内存 "全量符号索引常驻" "多文件夹多实例" "可能的宏/symlink 泄漏" "没用 compile_commands 限制范围" 内存…

2026/8/26 17:58:32 阅读更多 →
SQL编码基础知识

SQL编码基础知识

文章目录 📌 核心概念:字符集与编码📌 常见编码类型详解ASCIILatin1(ISO-8859-1)GBKUTF-8UTF-8mb4编码类型对比速查 📌 SQL 中的编码实践创建数据库时指定编码创建表时指定编码为特定列指定编码查看编码信息…

2026/8/26 17:58:32 阅读更多 →
windows 驱动实例分析系列: wireguard-nt驱动分析-api篇(四)

windows 驱动实例分析系列: wireguard-nt驱动分析-api篇(四)

WireGuard-NT API 模块分析 - 第四部分:辅助机制、错误处理与构建系统 1. 原子操作与内存屏障 API 模块在多线程环境中使用无锁原子操作来管理状态,主要应用于日志系统的状态控制。 1.1 无栅栏原子操作 在 logger.c 中使用了 ReadULongNoFence 和 WriteU…

2026/8/26 17:58:32 阅读更多 →
2026 年 MCP 协议彻底火了:用 Python 从零搭建你的第一个 AI Agent 工具链

2026 年 MCP 协议彻底火了:用 Python 从零搭建你的第一个 AI Agent 工具链

2026 年 MCP 协议彻底火了:用 Python 从零搭建你的第一个 AI Agent 工具链如果 2024 年是 RAG 的元年,2025 年是 Function Calling 的普及年,那 2026 年毫无疑问属于 MCP(Model Context Protocol,模型上下文协议&#…

2026/8/26 17:57:31 阅读更多 →
嵌入式CAN通信学习记录(万字解析):从STM32到Linux双机联调实战

嵌入式CAN通信学习记录(万字解析):从STM32到Linux双机联调实战

1. 引言 CAN(Controller Area Network)总线是嵌入式系统中广泛使用的一种高可靠性、多主机的串行通信协议,尤其在汽车电子、工业控制等领域应用广泛。本文旨在记录我学习嵌入式CAN通信的完整过程,涵盖从STM32开发板的环回/静默模…

2026/8/26 17:57:31 阅读更多 →
当“问小白怎么复制表格”成为日常:从格式崩塌到一键归档的技术突围

当“问小白怎么复制表格”成为日常:从格式崩塌到一键归档的技术突围

当“问小白怎么复制表格”成为日常:从格式崩塌到一键归档的技术突围 问小白用户想必都经历过同一个噩梦:让AI生成了一份逻辑清晰的对比表格,对话框内完美渲染,复制到Word或Excel后,却变成了一堆挤在单元格里的竖线符号…

2026/8/26 17:57:31 阅读更多 →

日新闻

Python random 模块常用函数详解:从入门到实战

Python random 模块常用函数详解:从入门到实战

目录 1. 引言2. 准备工作3. 基础随机函数4. 序列相关函数5. 随机种子与复现6. 实战案例7. 注意事项8. 常见问题与排查9. 总结 1. 引言 摘要: 本文系统介绍 Python 标准库 random 模块中最常用的随机数生成函数。内容涵盖基础随机函数(random()、unifor…

2026/8/26 0:00:40 阅读更多 →
《Microsoft Sql server 2008 Internals》读书笔记--第三章Databases and Database Files(2)

《Microsoft Sql server 2008 Internals》读书笔记--第三章Databases and Database Files(2)

《Microsoft Sql server 2008 Internals》索引目录: 《Microsoft Sql server 2008 Internals》读书笔记--目录索引 在上篇文章中,主要介绍了创建数据库的基本语法和FileGroup的初步知识。需要注意的是: 关于FileGroup 如果你的系统是用Raid设备直接存…

2026/8/26 1:18:18 阅读更多 →
政务AI智能体怎么建?三种模式、三步路径与四个误区

政务AI智能体怎么建?三种模式、三步路径与四个误区

政务AI智能体已经从概念试点阶段,转入了政务服务的常态化落地应用;在实际使用过程中,它能自主理解办事需求、辅助完成填报申报、开展材料预审,并联动多个系统协同作业,真正嵌入到政务办理的全流程当中。但在落地推进过…

2026/8/26 1:18:18 阅读更多 →

周新闻

[光学原理与应用-521]:对光的错误理解与纠偏

[光学原理与应用-521]:对光的错误理解与纠偏

首先光是一种能量的载体和形态,宏观上观察到的光是由无数个微观的光量子组成的,每个光子在产生的瞬间,其在真空的空间中以确定不变的速度沿着一个初始的方向一直向前,在微观层面,每个光量子的运动轨迹是以波函数所展现…

2026/8/26 14:45:33 阅读更多 →
SIP通话转接原理与REFER方法实战解析

SIP通话转接原理与REFER方法实战解析

1. 通话转接不是“挂断再拨号”,而是SIP会话的动态重定向你有没有遇到过这样的场景:客服坐席A正在和客户通电话,突然需要把这通对话无缝转给专家坐席B,客户完全感知不到中间的断连——既没听到忙音,也没被要求重新拨号…

2026/8/26 17:46:43 阅读更多 →
Kolla-ansible单节点OpenStack部署实战:从环境准备到排坑指南

Kolla-ansible单节点OpenStack部署实战:从环境准备到排坑指南

1. 为什么选择Kolla-ansible来部署单节点OpenStack?如果你正在寻找一种能把OpenStack从“概念”快速变成“可用的实验环境”的方法,那么Kolla-ansible几乎是当前最主流、最省心的选择。我见过太多人卡在手动编译依赖、配置服务、处理版本冲突的泥潭里&am…

2026/8/26 14:46:37 阅读更多 →

月新闻

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南 【免费下载链接】BaiduNetdiskPlugin-macOS For macOS.百度网盘 破解SVIP、下载速度限制~ 项目地址: https://gitcode.com/gh_mirrors/ba/BaiduNetdiskPlugin-macOS 还在为百度网盘macOS版的龟速下…

2026/8/26 3:50:20 阅读更多 →
终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换

终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换

终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换 【免费下载链接】ncmdump 项目地址: https://gitcode.com/gh_mirrors/ncmd/ncmdump 还在为网易云音乐下载的NCM格式文件无法在其他播放器播放而烦恼吗?ncmdump解密工具帮你轻松解决这个困…

2026/8/26 17:46:39 阅读更多 →
HarmonyOS 应用开发《掌上英语》第81篇: 智能体卡片:为英语学习 App 打造桌面级学习助手

HarmonyOS 应用开发《掌上英语》第81篇: 智能体卡片:为英语学习 App 打造桌面级学习助手

AgentCard 智能体卡片:为英语学习 App 打造桌面级学习助手适用平台:HarmonyOS 7.0 (API 26 Beta)一、引言 HarmonyOS 7.0(API 26 Beta)新增了 AgentCard 智能体卡片能力,这是继 HMAF(鸿蒙智能体框架&#x…

2026/8/26 1:24:05 阅读更多 →