2026/7/29 16:49:31

ArkTS 进阶之道(11):build() 重渲边界——状态变为啥只刷依赖组件

ArkTS 进阶之道(11):build() 重渲边界——状态变为啥只刷依赖组件 ArkTS 进阶之道11build() 重渲边界——状态变为啥只刷依赖组件本文是「ArkTS 进阶之道」系列第 11 篇开「ArkUI 渎染哲学」新阶段。上一阶段讲状态哲学篇 56-59数据流绑定State/Prop/Link/Provide/Consume 状态变回调Watch——都是状态绑追踪。本文换角度讲渲染build() 重渲边界——根因在依赖追踪的局部重渲机制State 变只刷依赖 UI 不刷全 build不是全组件树重渲。能力系列篇 17 讲过条件渲染怎么用本文讲为哈只刷依赖不刷全 build——根因在局部重渲边界。一、开篇build() 不是全重渲是依赖追踪的局部重渲边界你写 TypeScript/React 时状态变重渲是「魔法」React 谰 render 全函数重渲要 useMemo/useCallback 手写 memo 避免全刷// React 全函数重渲 手写 memo 避免全刷 function Component() { const [count, setCount] useState(0) const staticText 我不依赖 count // 不依赖 count 但全函数重渲时也重渲 return ( View Text{count}/Text ← 依赖 countcount 变刷 Text{staticText}/Text ← 不依赖 count但 React 全函数重渲时也重渲要 useMemo /View ) } setCount(1) // React 谰 render 全函数重渲staticText 也重渲要手写 memo 避免你写鸿蒙 ArkTS 时build()依赖追踪的局部重渲——不用手写 memo 避免全刷// ArkTS build() 依赖追踪局部重渲边界 Entry Component struct Index { State count: number 0 staticText: string 我不依赖 countcount 变我不刷 // 不依赖 countcount 变不刷 build() { Column() { Text(${this.count}) ← 依赖 countcount 变刷局部重渲 Text(this.staticText) ← 不依赖 countcount 变不刷重渲边界 } } } // count 变只刷依赖 count 的 TextstaticText 不刷局部重渲边界不用手写 memo魔法 vs 边界的区别React 把重渲当「魔法」全函数重渲你手写 memo 避免全刷ArkTS 把 build() 当「依赖追踪的局部重渲边界」装饰器编译期记依赖状态变只刷依赖 UI 不刷全 build。根因不是魔法是边界——State 变触发的是局部重渲只刷依赖 UI不是全 build 重渲。二、根因build() 的依赖追踪局部重渲边界机制鸿蒙 ArkUI 的 build() 是依赖追踪局部重渲边界——编译期记 UI 哪里依赖哪个 StateState 变只刷依赖 UI 不刷全 build来自三重边界机制。机制 1编译期记 UI 依赖——build 里哪行 UI 用了哪个 Statebuild() 编译期记 UI 依赖——扫描 build() 里 UI 哪行用了哪个 State记成依赖关系Entry Component struct Index { State count: number 0 // State 荬饰器编译期加追踪 staticText: string 我不依赖 count // 普通字段不记依赖 build() { Column() { Text(${this.count}) ← 编译期记Text1 依赖 count Text(this.staticText) ← 编译期记Text2 不依赖 count依赖 staticText } } }编译期记依赖build() 编译期扫描每行 UI——Text(${this.count})依赖 Statecount记成count → Text1Text(this.staticText)依赖普通字段staticText记成staticText → Text2。记依赖不用手写 memo装饰器编译期扫描记。根因不是全重渲是编译期记的局部依赖关系。机制 2状态变只刷依赖 UI——State 参触发局部重渲不刷全 buildState 变只刷依赖 UI——赋值触发只重渲编译期记的依赖 UI不刷全 buildEntry Component struct Index { State count: number 0 staticText: string 我不依赖 countcount 变我不刷 build() { Column() { Text(${this.count}) ← 依赖 countcount 变刷 Text1局部重渲 Text(this.staticText) ← 不依赖 countcount 变不刷 Text2重渲边界 Button(点我) .onClick(() { this.count }) ← count 反 反只刷 Text1Text2 不刷 } } }局部重渲边界Statecount赋值this.count触发只重渲编译期记的依赖 UIText1显示新 count不刷不依赖 count 的Text2staticText 不刷。赋值只刷依赖 UI 是局部重渲边界——不是全 build 重渲是编译期记的依赖关系触发局部刷。根因不是全刷是依赖追踪的局部重渲边界。机制 3边界证据——不依赖变 UI 不刷 / 普通字段不刷build() 重渲边界证据——不依赖变 UI 不刷普通字段不刷 UIEntry Component struct Index { State count: number 0 State staticText: string 我不依赖 countcount 变我不刷重渲边界证据 renderCount: number 0 // 普通字段不刷 UI重渲边界对比 build() { Column() { Text(依赖 count ${this.count}) ← 依赖 countcount 反 刷 Text(this.staticText) ← 不依赖 countcount 反 反不刷边界证据 Text(普通字段 ${this.renderCount}) ← 普通字段赋值不刷 UI边界对比 Button(改 count) .onClick(() { this.count }) ← count 反 反只刷依赖 count UI Button(改 staticText) .onClick(() { this.staticText 改了 }) ← staticText 反只刷 Text2 } } }边界证据①改 count 只刷依赖 count 的 Text1不依赖 count 的 Text2 不刷局部重渲边界②改 staticText 只刷依赖 staticText 的 Text2依赖 count 的 Text1 不刷边界反向证据③普通字段 renderCount 赋值不刷 UI不是 State 没追踪不刷。三个证据证明 build() 是依赖追踪的局部重渲边界不是全 build 重渲。三、真机配图build() 重渲边界——状态变只刷依赖组件不刷全 build初始态count0、staticText 显示「我不依赖 count」、Watch 回调次数0 域初始值点调两按钮后count1 刷依赖 UI、staticText 改了刷自己不刷依赖 count UI、Watch 回调次数1 普通字段不刷 UI 域重渲边界对比证据齐对比证据点改 State count 按钮后只刷依赖 count 的 UIcount1 刷了不依赖 count 的 staticText 没刷仍显示原值是重渲边界证据。点改 staticText 按钮后只刷 staticText 自己依赖 count 的 UI 没刷边界反向证据。Watch 回调次数1 是普通字段赋值不刷 UI边界对比。build() 不是全重渲是依赖追踪的局部重渲边界——State 反只刷依赖 UI 不刷全 build根因在编译期记的依赖关系触发局部刷。四、真解法build() 重渲边界的三个场景场景 1状态变只刷依赖 UI90% 场景局部重渲边界首选Entry Component struct Index { State count: number 0 // State 编译期记依赖 build() { Column() { Text(count ${this.count}) ← 依赖 countcount 反刷 Text(我不依赖 count) ← 不依赖 countcount 反不刷边界 Button(点我) .onClick(() { this.count }) ← 只刷依赖 count 的 Text } } }为哈能跑Statecount编译期记依赖赋值只刷依赖 UI 不刷全 build。首选这个90% 的场景状态变只刷依赖 UI 用局部重渲边界就够。要写「状态变只刷相关 UI 不刷全 build」时用这个——不用手写 memo 避免全刷装饰器编译期记依赖触发局部刷。场景 2多状态各刷各依赖 UI每个 State 只刷自己的依赖 UIEntry Component struct Index { State count: number 0 // count 只刷依赖 count UI State name: string 初始 // name 只刷依赖 name UI State list: string[] [] // list 只刷依赖 list UI build() { Column() { Text(count ${this.count}) ← 依赖 countcount 反只刷这行 Text(name ${this.name}) ← 依赖 namename 反只刷这行 Text(list ${this.list.length}) ← 依赖 listlist 反只刷这行 Button(改 count).onClick(() { this.count }) // 只刷依赖 count UI Button(改 name).onClick(() { this.name 新名 }) // 只刷依赖 name UI Button(改 list).onClick(() { this.list.push(新) }) // 只刷依赖 list UI } } }为哈能跑每个 State 只刷自己的依赖 UI——count 变只刷依赖 count 的 Textname 变只刷依赖 name 的 Textlist 变只刷依赖 list 的 Text。要写「多状态各自只刷自己 UI 不互刷」时用这个——每个 State 编译期记自己依赖赋值只刷自己依赖 UI 不刷别的。场景 3Watch 改普通字段不刷 UI副作用边界 vs 数据流边界Entry Component struct Index { State Watch(onCountChange) count: number 0 // State Watch renderCount: number 0 // 普通字段Watch 回调里改不刷 UI onCountChange(): void { this.renderCount // Watch 回调里改普通字段不刷 UI副作用边界 } build() { Column() { Text(count ${this.count}) ← 依赖 countcount 反刷数据流边界 Text(renderCount ${this.renderCount}) ← 依赖普通字段普通字段不刷 UI边界对比 Button(点我) .onClick(() { this.count }) // count 反刷依赖 UI Watch 改 renderCount 不刷 } } }为哈能跑Watch 回调里改普通字段renderCount不刷 UI——普通字段不是 State 没追踪不刷副作用边界。要写「状态变要执行副作用但不刷 UI」时用这个——Watch 回调改普通字段执行副作用计数/日志不触发 UI 重渲副作用边界 vs 数据流边界分离。五、一句话哲学build() 不是全重渲是依赖追踪的局部重渲边界。ArkUI 的 build() 编译期记 UI 哪行依赖哪个 StateState 反只刷依赖 UI 不刷全 build。根因不是全重渲是局部重渲边界——build() 编译期记依赖扫描记依赖关系 状态变只刷依赖 UI局部重渲不刷全 build 边界证据不依赖变 UI 不刷/普通字段不刷。对比 React 全函数重渲要手写 memo 避免全刷ArkUI 荬饰器编译期记依赖触发局部刷。状态哲学→渲染哲学过渡状态哲学篇 56-59讲数据流绑定 状态变回调——都是「状态绑追踪触发」。渲染哲学篇 60-62讲 build() 重渲边界——状态变触发的局部重渲是「渲染绑依赖」。State 赋值触发的两个边界①数据流边界篇 56赋值就刷 UI 依赖追踪②渲染边界篇 60只刷依赖 UI 不刷全 build——渲染边界是数据流边界的细化不只刷 UI是只刷依赖 UI。下一篇ArkTS 进阶之道12—— if/条件渲染边界为啥 build 里不能写 if 语句对应能力系列篇 17讲根因——续「ArkUI 渎染哲学」阶段。能力系列回链能力系列篇本文进阶点篇 17 条件渲染用法下篇预告if 条件渲染边界根因篇 13 State 基础用法状态哲学State 赋值就刷 UI 依赖追踪篇 16 Watch 用法状态哲学Watch 响应式回调钩子真机 demo 完整代码// 篇 60 demobuild() 重渲边界——状态变只刷依赖组件不刷全 build // 对比State 反只刷依赖 UI vs 普通字段变不刷 UI Entry Component struct Index { // ✅ State 依赖追踪变只刷依赖 UI 不刷全 build State Watch(onCountChange) count: number 0 State staticText: string 我不依赖 countcount 反我不刷重渲边界证据 State log: string (未操作) renderCount: number 0 // 普通字段不刷 UI重渲边界对比 onCountChange(): void { this.renderCount // Watch 回调里改普通字段不刷 UI this.log count 反${this.count}Watch 回调调了 ${this.renderCount} 次staticText 不刷是重渲边界证据 } build() { Column({ space: 12 }) { Text(篇 60 配图build() 重渲边界——状态变只刷依赖组件) .fontSize(18).fontWeight(FontWeight.Bold).margin({ top: 20, bottom: 8 }) Text(State count 反只刷依赖 UI 不刷全 build重渲边界证据) .fontSize(12).fontColor(#888).margin({ bottom: 16 }) Column({ space: 6 }) { // 依赖 count 的 UIcount 反就刷 Text(依赖 count ${this.count}).fontSize(16).fontWeight(FontWeight.Bold).fontColor(#2563eb) // 不依赖 count 的 UIcount 反不刷重渲边界证据 Text(this.staticText).fontSize(13).fontColor(#999) Text(Watch 回调次数 ${this.renderCount}普通字段不刷 UI).fontSize(13).fontColor(#999) Text(日志${this.log}).fontSize(12).fontColor(#333).margin({ top: 4 }) } .width(92%).padding(12).backgroundColor(#f5f5f5).borderRadius(8) // 依赖 count 的按钮count 反刷依赖 UI Button(改 State count只刷依赖 UI) .width(92%).height(44).fontSize(14) .onClick(() { this.count // ✅ count 反只刷依赖 UIstaticText 不刷重渲边界 }) // 改不依赖 count 的 staticText只刷 staticText 不刷依赖 count 的 UI Button(改 staticText只刷 staticText 不刷依赖 count UI) .width(92%).height(44).fontSize(14) .onClick(() { this.staticText 改了count${this.count}我没刷因 count 没反 this.log 改 staticText只刷 staticText 不刷依赖 count UI重渲边界 }) } .width(100%).height(100%).alignItems(HorizontalAlign.Center) } }写鸿蒙 ArkUI 记住build() 不是全重渲是依赖追踪的局部重渲边界——build() 编译期记 UI 哪行依赖哪个 StateState 反只刷依赖 UI 不刷全 build。根因不是全重渲是局部重渲边界——build() 编译期记依赖扫描记依赖关系 状态变只刷依赖 UI局部重渲不刷全 build 边界证据不依赖变 UI 不刷/普通字段不刷。状态变只刷依赖 UI 用局部重渲边界首选多状态各刷各依赖 UI 不互刷Watch 改普通字段执行副作用不刷 UI副作用边界 vs 数据流边界分离。只刷依赖 UI 不刷全 build是 ArkUI 渎染哲学核心