ArkTS第二课: 状态管理与组件通信深度解析

您好,我是ID: 熊猫钓鱼!
十余年深耕技术一线,我始终相信:优秀的开发如同垂钓——既要对技术生态的「水域」有深邃理解,也要对问题本质的「鱼汛」保持敏锐直觉。从架构设计到性能调优,从技术选型到团队协作,我专注在恰当的时机,用最合适的技术钓起最优雅的解决方案。

目录

引言:为什么状态管理如此重要?

第一部分:状态管理基础概念

1.1 什么是状态?

1.2 状态驱动的核心理念

1.3 状态管理的核心装饰器

第二部分:@State 深度解析

2.1 @State 的基本用法

2.2 @State 的更新机制

2.3 @State 的最佳实践

第三部分:组件通信机制深度剖析

3.1 @Prop:单向数据流

3.2 @Link:双向数据绑定

3.3 @Provide 和 @Consume:跨层级通信

第四部分:@Watch 监听器

4.1 @Watch 的基本用法

4.2 @Watch 的高级用法

第五部分:实战案例 - 任务管理应用

总结

引言:为什么状态管理如此重要?

在鸿蒙应用开发中,状态管理是构建复杂应用的核心基石。想象一下,你正在开发一个电商应用:用户添加商品到购物车、切换商品分类、修改收货地址——所有这些交互本质上都是应用状态的变化。如何优雅地管理这些状态,确保界面及时更新,同时保持代码的可维护性,是每个 ArkTS 开发者必须掌握的技能。

状态管理不仅仅是技术问题,更是架构设计问题。一个好的状态管理方案能够让应用:

  • 数据流清晰可预测:任何时候都能知道状态如何变化、为什么变化

  • 组件职责明确:每个组件只关注自己的特定功能

  • 调试效率提升:快速定位状态相关的问题

  • 团队协作顺畅:新成员能够快速理解代码结构

本文将深入剖析 ArkTS 的状态管理体系和组件通信机制,通过实际案例帮助你构建坚实的状态管理基础。

第一部分:状态管理基础概念

1.1 什么是状态?

在 ArkTS 中,状态(State)是指那些能够影响组件渲染的数据。当状态发生变化时,组件会自动重新渲染以反映最新的数据。

状态的分类

  • 本地状态:只在单个组件内部使用的状态

  • 共享状态:在多个组件之间共享的状态

  • 全局状态:在整个应用范围内共享的状态

  • UI 状态:与界面显示相关的状态,如加载状态、选中状态

  • 业务状态:与业务逻辑相关的状态,如用户信息、商品数据

1.2 状态驱动的核心理念

ArkTS 采用响应式的状态驱动模型,其核心思想是:

UI = f(State)

这意味着界面是状态的函数,相同的状态总是产生相同的界面。当状态变化时,界面自动更新。这种单向数据流模式让应用的行为更加可预测。

1.3 状态管理的核心装饰器

ArkTS 提供了一系列装饰器来管理状态,每个装饰器都有特定的使用场景:

  • @State:组件内部的状态管理

  • @Prop:父子组件间的单向数据传递

  • @Link:父子组件间的双向数据绑定

  • @Provide/@Consume:跨组件层级的状态共享

  • @Watch:状态变化的监听回调

理解这些装饰器的区别和适用场景,是掌握 ArkTS 状态管理的关键。

第二部分:@State 深度解析

2.1 @State 的基本用法

@State 是用于组件内部状态管理的最基础装饰器。当 @State 修饰的变量发生变化时,组件会重新渲染。

@Component
struct CounterComponent {
  @State count: number = 0
  @State isActive: boolean = false
  @State userName: string = ''

  build() {
    Column({ space: 10 }) {
      Text(`计数: ${this.count}`)
        .fontSize(20)
      
      Text(`状态: ${this.isActive ? '活跃' : '非活跃'}`)
        .fontColor(this.isActive ? '#007DFF' : '#666666')
      
      TextInput({ placeholder: '请输入用户名' })
        .value(this.userName)
        .onChange((value: string) => {
          this.userName = value
        })
      
      Button('增加计数')
        .onClick(() => {
          this.count += 1
        })
        
      Button('切换状态')
        .onClick(() => {
          this.isActive = !this.isActive
        })
    }
    .padding(20)
  }
}

2.2 @State 的更新机制

理解 @State 的更新机制至关重要:

不可变更新:对于对象和数组,必须采用不可变更新方式

@Component
struct StateUpdateExample {
  @State user: { name: string; age: number } = { name: '小明', age: 20 }
  @State items: string[] = ['项目1', '项目2']

  build() {
    Column({ space: 10 }) {
      // 错误:直接修改对象属性不会触发更新
      Button('错误更新年龄')
        .onClick(() => {
          this.user.age = 25 // 不会触发重新渲染!
        })
      
      // 正确:创建新对象
      Button('正确更新年龄')
        .onClick(() => {
          this.user = { ...this.user, age: 25 }
        })
      
      // 错误:直接修改数组
      Button('错误添加项目')
        .onClick(() => {
          this.items.push('新项目') // 不会触发重新渲染!
        })
      
      // 正确:创建新数组
      Button('正确添加项目')
        .onClick(() => {
          this.items = [...this.items, '新项目']
        })
    }
  }
}

更新批处理:ArkTS 会对连续的状态更新进行批处理,优化性能

@Component
struct BatchUpdateExample {
  @State count: number = 0
  @State message: string = ''

  build() {
    Column({ space: 10 }) {
      Button('批量更新')
        .onClick(() => {
          // 这三个更新会被合并,组件只会重新渲染一次
          this.count += 1
          this.count += 1
          this.count += 1
          this.message = `当前计数: ${this.count}`
        })
    }
  }
}

2.3 @State 的最佳实践

状态最小化原则:只将真正影响渲染的数据定义为状态

@Component
struct GoodExample {
  @State user: User = { name: '小明', age: 20 }
  
  // 计算属性,不需要定义为 @State
  get displayName(): string {
    return `用户: ${this.user.name}`
  }
  
  // 常量,不需要定义为 @State
  readonly appVersion: string = '1.0.0'
}

@Component
struct BadExample {
  // 不需要定义为状态
  @State displayName: string = ''
  @State appVersion: string = '1.0.0'
}

状态组织:合理组织相关状态

// 好的组织方式
@State userInfo: { name: string; age: number; email: string } = {
  name: '',
  age: 0,
  email: ''
}

// 不好的组织方式 - 状态分散
@State userName: string = ''
@State userAge: number = 0
@State userEmail: string = ''

第三部分:组件通信机制深度剖析

3.1 @Prop:单向数据流

@Prop 用于父子组件之间的单向数据传递。子组件接收父组件的数据,但不能直接修改。

// 子组件
@Component
struct UserCard {
  @Prop userName: string
  @Prop userAge: number
  @Prop isVip: boolean = false  // 默认值

  build() {
    Column({ space: 8 }) {
      Text(this.userName)
        .fontSize(18)
        .fontWeight(FontWeight.Bold)
      
      Text(`年龄: ${this.userAge}`)
        .fontSize(14)
      
      if (this.isVip) {
        Text('VIP会员')
          .fontColor('#FF6A00')
          .fontSize(12)
      }
    }
    .padding(15)
    .backgroundColor(Color.White)
    .borderRadius(8)
  }
}

// 父组件
@Entry
@Component
struct UserList {
  @State users: User[] = [
    { name: '小明', age: 20, isVip: true },
    { name: '小红', age: 22, isVip: false },
    { name: '小刚', age: 19, isVip: true }
  ]

  build() {
    Column({ space: 15 }) {
      ForEach(this.users, (user: User) => {
        UserCard({
          userName: user.name,
          userAge: user.age,
          isVip: user.isVip
        })
      })
    }
    .padding(20)
  }
}

@Prop 的特点

  • 数据从父组件流向子组件

  • 子组件不能直接修改 @Prop 变量

  • 当父组件的状态更新时,子组件会自动更新

  • 支持默认值

3.2 @Link:双向数据绑定

@Link 实现了父子组件之间的双向数据绑定。子组件可以修改父组件传递的状态。

// 子组件 - 购物车商品项
@Component
struct CartItem {
  @Link item: CartItemModel
  @Link totalPrice: number

  build() {
    Row({ space: 15 }) {
      Text(this.item.name)
        .fontSize(16)
        .layoutWeight(1)
      
      Row({ space: 10 }) {
        Button('-')
          .width(30)
          .height(30)
          .onClick(() => {
            if (this.item.quantity > 1) {
              this.item.quantity -= 1
              this.totalPrice -= this.item.price
            }
          })
        
        Text(`${this.item.quantity}`)
          .width(40)
          .textAlign(TextAlign.Center)
        
        Button('+')
          .width(30)
          .height(30)
          .onClick(() => {
            this.item.quantity += 1
            this.totalPrice += this.item.price
          })
      }
      
      Text(`¥${(this.item.price * this.item.quantity).toFixed(2)}`)
        .width(80)
        .fontColor('#FF6A00')
    }
    .width('100%')
    .padding(10)
  }
}

// 父组件 - 购物车
@Entry
@Component
struct ShoppingCart {
  @State cartItems: CartItemModel[] = [
    { name: '商品A', price: 25.5, quantity: 1 },
    { name: '商品B', price: 38.0, quantity: 2 },
    { name: '商品C', price: 15.8, quantity: 1 }
  ]
  
  @State totalPrice: number = 0

  aboutToAppear() {
    // 计算初始总价
    this.totalPrice = this.cartItems.reduce((total, item) => {
      return total + item.price * item.quantity
    }, 0)
  }

  build() {
    Column({ space: 20 }) {
      Text('购物车')
        .fontSize(24)
        .fontWeight(FontWeight.Bold)
      
      Column({ space: 10 }) {
        ForEach(this.cartItems, (item: CartItemModel, index: number) => {
          CartItem({
            item: $cartItems[index],
            totalPrice: $totalPrice
          })
        })
      }
      .width('100%')
      
      Divider()
      
      Row() {
        Text('总计:')
          .fontSize(18)
        Text(`¥${this.totalPrice.toFixed(2)}`)
          .fontSize(20)
          .fontColor('#FF6A00')
          .layoutWeight(1)
          .textAlign(TextAlign.End)
      }
      .width('100%')
      .padding(10)
    }
    .padding(20)
  }
}

@Link 的关键点

  • 使用 $ 符号传递引用:$cartItems[index]

  • 子组件的修改会直接更新父组件的状态

  • 适合需要双向交互的场景

  • 要小心循环更新的问题

3.3 @Provide 和 @Consume:跨层级通信

当组件层级很深时,使用 @Prop 逐层传递数据会很繁琐。@Provide 和 @Consume 提供了跨层级的通信方案。

// 主题颜色提供者
@Entry
@Component
struct ThemeProvider {
  @Provide themeColor: string = '#007DFF'
  @Provide isDarkMode: boolean = false

  build() {
    Column({ space: 20 }) {
      Text('主题设置')
        .fontSize(24)
        .fontColor(this.themeColor)
      
      ThemeColorPicker()
      
      ThemeSwitch()
      
      // 深层嵌套的组件
      SomeDeeplyNestedComponent()
    }
    .width('100%')
    .height('100%')
    .padding(20)
    .backgroundColor(this.isDarkMode ? '#333333' : '#FFFFFF')
  }
}

// 主题颜色选择器 - 中间层组件
@Component
struct ThemeColorPicker {
  @Consume themeColor: string

  build() {
    Column({ space: 10 }) {
      Text('选择主题颜色')
        .fontSize(16)
      
      Row({ space: 10 }) {
        Button('蓝色')
          .backgroundColor('#007DFF')
          .onClick(() => { this.themeColor = '#007DFF' })
        
        Button('绿色')
          .backgroundColor('#00B96B')
          .onClick(() => { this.themeColor = '#00B96B' })
        
        Button('橙色')
          .backgroundColor('#FF6A00')
          .onClick(() => { this.themeColor = '#FF6A00' })
      }
    }
  }
}

// 主题切换器 - 另一个中间层组件
@Component
struct ThemeSwitch {
  @Consume isDarkMode: boolean
  @Consume themeColor: string

  build() {
    Row({ space: 10 }) {
      Text('深色模式')
        .fontColor(this.themeColor)
      
      Toggle({ type: ToggleType.Switch, isOn: this.isDarkMode })
        .onChange((value: boolean) => {
          this.isDarkMode = value
        })
    }
  }
}

// 深层嵌套的组件 - 可以直接消费主题
@Component
struct SomeDeeplyNestedComponent {
  @Consume themeColor: string
  @Consume isDarkMode: boolean

  build() {
    Column({ space: 10 }) {
      Text('这个组件深度嵌套')
        .fontColor(this.themeColor)
      
      Text(`当前主题: ${this.isDarkMode ? '深色' : '浅色'}`)
        .fontColor(this.isDarkMode ? '#FFFFFF' : '#333333')
    }
    .padding(15)
    .backgroundColor(this.isDarkMode ? '#444444' : '#F5F5F5')
    .borderRadius(8)
  }
}

@Provide/@Consume 的优势

  • 避免 prop drilling(属性钻取)

  • 任意层级的子组件都可以直接消费数据

  • 提供者更新时,所有消费者自动更新

  • 适合全局主题、用户信息等共享数据

第四部分:@Watch 监听器

4.1 @Watch 的基本用法

@Watch 装饰器用于监听状态变化,并在变化时执行特定的回调函数。

@Component
struct WatchExample {
  @State count: number = 0
  @State message: string = '初始消息'
  
  @Watch('onCountChange')
  @State watchedCount: number = 0

  // 监听回调函数
  onCountChange(): void {
    console.log(`计数发生变化: ${this.watchedCount}`)
    this.message = `当前计数: ${this.watchedCount}`
    
    // 可以在这里执行副作用操作
    if (this.watchedCount > 10) {
      console.log('计数超过10了!')
    }
  }

  build() {
    Column({ space: 15 }) {
      Text(this.message)
        .fontSize(18)
      
      Text(`监听计数: ${this.watchedCount}`)
        .fontSize(16)
        .fontColor('#007DFF')
      
      Button('增加监听计数')
        .onClick(() => {
          this.watchedCount += 1
        })
        
      Button('直接修改计数(不触发监听)')
        .onClick(() => {
          this.count += 1
        })
    }
    .padding(20)
  }
}

4.2 @Watch 的高级用法

@Watch 可以监听多个状态,并执行复杂的业务逻辑。

@Component
struct AdvancedWatchExample {
  @State user: User = { name: '', age: 0, email: '' }
  @State formValid: boolean = false
  
  @Watch('onUserChange')
  @State watchedUser: User = { name: '', age: 0, email: '' }

  // 监听用户信息变化
  onUserChange(): void {
    console.log('用户信息发生变化:', this.watchedUser)
    
    // 表单验证逻辑
    const isValid = this.validateForm()
    this.formValid = isValid
    
    if (isValid) {
      console.log('表单验证通过')
      // 可以在这里触发自动保存等操作
    }
  }

  // 表单验证
  validateForm(): boolean {
    return this.watchedUser.name.length > 0 &&
           this.watchedUser.age > 0 &&
           this.watchedUser.email.includes('@')
  }

  build() {
    Column({ space: 15 }) {
      Text('用户注册')
        .fontSize(20)
        .fontWeight(FontWeight.Bold)
      
      TextInput({ placeholder: '姓名' })
        .value(this.watchedUser.name)
        .onChange((value: string) => {
          this.watchedUser = { ...this.watchedUser, name: value }
        })
      
      TextInput({ placeholder: '年龄' })
        .value(this.watchedUser.age.toString())
        .onChange((value: string) => {
          this.watchedUser = { ...this.watchedUser, age: parseInt(value) || 0 }
        })
      
      TextInput({ placeholder: '邮箱' })
        .value(this.watchedUser.email)
        .onChange((value: string) => {
          this.watchedUser = { ...this.watchedUser, email: value }
        })
      
      Button('提交')
        .width('100%')
        .enabled(this.formValid)
        .onClick(() => {
          console.log('提交用户信息:', this.watchedUser)
        })
      
      Text(this.formValid ? '表单验证通过' : '请填写完整信息')
        .fontColor(this.formValid ? '#00B96B' : '#FF6A00')
    }
    .padding(20)
  }
}

第五部分:实战案例 - 任务管理应用

让我们通过一个完整的任务管理应用,综合运用所学的状态管理和组件通信知识。

// 数据类型定义
interface Task {
  id: string
  title: string
  description: string
  completed: boolean
  priority: 'low' | 'medium' | 'high'
  createTime: number
}

// 任务项组件
@Component
struct TaskItem {
  @Link task: Task
  @Link selectedTask: Task
  
  build() {
    Row({ space: 15 }) {
      // 完成状态复选框
      If(this.task.completed)
        .then(Image($r('app.media.checked'))
          .width(20)
          .height(20)
          .onClick(() => {
            this.task.completed = false
          }))
        .else(Image($r('app.media.unchecked'))
          .width(20)
          .height(20)
          .onClick(() => {
            this.task.completed = true
          }))
      
      // 任务内容
      Column({ space: 5 }) {
        Text(this.task.title)
          .fontSize(16)
          .fontColor(this.task.completed ? '#999999' : '#333333')
          .decoration({ type: this.task.completed ? TextDecorationType.LineThrough : TextDecorationType.None })
        
        Text(this.task.description)
          .fontSize(12)
          .fontColor('#666666')
          .maxLines(1)
          .textOverflow({ overflow: TextOverflow.Ellipsis })
      }
      .layoutWeight(1)
      .onClick(() => {
        this.selectedTask = this.task
      })
      
      // 优先级标识
      Text(this.getPriorityText())
        .fontSize(10)
        .fontColor(this.getPriorityColor())
        .padding(4)
        .backgroundColor(this.getPriorityBgColor())
        .borderRadius(4)
    }
    .width('100%')
    .padding(10)
    .backgroundColor(this.selectedTask?.id === this.task.id ? '#F0F8FF' : Color.White)
    .borderRadius(8)
  }

  getPriorityText(): string {
    switch (this.task.priority) {
      case 'high': return '高'
      case 'medium': return '中'
      case 'low': return '低'
      default: return ''
    }
  }

  getPriorityColor(): string {
    switch (this.task.priority) {
      case 'high': return '#FF6A00'
      case 'medium': return '#1890FF'
      case 'low': return '#52C41A'
      default: return '#666666'
    }
  }

  getPriorityBgColor(): string {
    switch (this.task.priority) {
      case 'high': return '#FFF2E8'
      case 'medium': return '#E6F7FF'
      case 'low': return '#F6FFED'
      default: return '#F5F5F5'
    }
  }
}

// 任务详情组件
@Component
struct TaskDetail {
  @Link task: Task
  
  build() {
    Column({ space: 20 }) {
      if (this.task) {
        Text('任务详情')
          .fontSize(20)
          .fontWeight(FontWeight.Bold)
        
        Column({ space: 15 }) {
          Row() {
            Text('标题:')
              .fontSize(16)
              .fontColor('#666666')
              .width(80)
            Text(this.task.title)
              .fontSize(16)
              .layoutWeight(1)
          }
          
          Row() {
            Text('描述:')
              .fontSize(16)
              .fontColor('#666666')
              .width(80)
            Text(this.task.description)
              .fontSize(16)
              .layoutWeight(1)
          }
          
          Row() {
            Text('优先级:')
              .fontSize(16)
              .fontColor('#666666')
              .width(80)
            Text(this.getPriorityText())
              .fontSize(16)
              .fontColor(this.getPriorityColor())
          }
          
          Row() {
            Text('状态:')
              .fontSize(16)
              .fontColor('#666666')
              .width(80)
            Text(this.task.completed ? '已完成' : '未完成')
              .fontSize(16)
              .fontColor(this.task.completed ? '#00B96B' : '#FF6A00')
          }
        }
        .width('100%')
        
        Button(this.task.completed ? '标记为未完成' : '标记为已完成')
          .width('100%')
          .onClick(() => {
            this.task.completed = !this.task.completed
          })
      } else {
        Text('请选择一个任务查看详情')
          .fontSize(16)
          .fontColor('#999999')
      }
    }
    .padding(20)
    .backgroundColor(Color.White)
    .borderRadius(8)
  }

  getPriorityText(): string {
    switch (this.task.priority) {
      case 'high': return '高优先级'
      case 'medium': return '中优先级'
      case 'low': return '低优先级'
      default: return ''
    }
  }

  getPriorityColor(): string {
    switch (this.task.priority) {
      case 'high': return '#FF6A00'
      case 'medium': return '#1890FF'
      case 'low': return '#52C41A'
      default: return '#666666'
    }
  }
}

// 主应用组件
@Entry
@Component
struct TaskManagerApp {
  @State tasks: Task[] = [
    {
      id: '1',
      title: '学习ArkTS状态管理',
      description: '深入学习@State、@Prop、@Link等装饰器的使用方法',
      completed: false,
      priority: 'high',
      createTime: Date.now()
    },
    {
      id: '2',
      title: '编写示例代码',
      description: '通过实际案例巩固所学知识',
      completed: true,
      priority: 'medium',
      createTime: Date.now() - 86400000
    },
    {
      id: '3', 
      title: '复习组件通信',
      description: '理解父子组件、兄弟组件之间的通信方式',
      completed: false,
      priority: 'low',
      createTime: Date.now() - 172800000
    }
  ]
  
  @State selectedTask: Task = null
  @State newTask: Task = {
    id: '',
    title: '',
    description: '',
    completed: false,
    priority: 'medium',
    createTime: 0
  }

  build() {
    Row({ space: 20 }) {
      // 左侧 - 任务列表
      Column({ space: 15 }) {
        Text('任务列表')
          .fontSize(24)
          .fontWeight(FontWeight.Bold)
        
        // 添加新任务区域
        this.buildAddTaskForm()
        
        // 任务列表
        Column({ space: 10 }) {
          ForEach(this.tasks, (task: Task, index: number) => {
            TaskItem({
              task: $tasks[index],
              selectedTask: $selectedTask
            })
          })
        }
        .width('100%')
        .layoutWeight(1)
        .scrollable(ScrollDirection.Vertical)
      }
      .width('50%')
      .height('100%')
      .padding(20)
      
      // 右侧 - 任务详情
      Column({ space: 15 }) {
        Text('任务详情')
          .fontSize(24)
          .fontWeight(FontWeight.Bold)
        
        TaskDetail({ task: $selectedTask })
      }
      .width('50%')
      .height('100%')
      .padding(20)
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#F5F5F5')
  }

  @Builder buildAddTaskForm() {
    Column({ space: 10 }) {
      Text('添加新任务')
        .fontSize(18)
        .fontWeight(FontWeight.Medium)
      
      TextInput({ placeholder: '任务标题' })
        .value(this.newTask.title)
        .onChange((value: string) => {
          this.newTask = { ...this.newTask, title: value }
        })
      
      TextInput({ placeholder: '任务描述' })
        .value(this.newTask.description)
        .onChange((value: string) => {
          this.newTask = { ...this.newTask, description: value }
        })
      
      Row({ space: 10 }) {
        Text('优先级:')
          .fontSize(14)
        
        ForEach(['low', 'medium', 'high'] as const, (priority: 'low' | 'medium' | 'high') => {
          Button(this.getPriorityText(priority))
            .padding(8)
            .backgroundColor(this.newTask.priority === priority ? '#007DFF' : '#F0F0F0')
            .fontColor(this.newTask.priority === priority ? Color.White : '#666666')
            .onClick(() => {
              this.newTask = { ...this.newTask, priority }
            })
        })
      }
      
      Button('添加任务')
        .width('100%')
        .enabled(this.newTask.title.length > 0)
        .onClick(() => {
          const task: Task = {
            ...this.newTask,
            id: Date.now().toString(),
            createTime: Date.now()
          }
          this.tasks = [...this.tasks, task]
          this.newTask = {
            id: '',
            title: '',
            description: '',
            completed: false,
            priority: 'medium',
            createTime: 0
          }
        })
    }
    .padding(15)
    .backgroundColor(Color.White)
    .borderRadius(8)
  }

  getPriorityText(priority: 'low' | 'medium' | 'high'): string {
    switch (priority) {
      case 'high': return '高'
      case 'medium': return '中' 
      case 'low': return '低'
      default: return ''
    }
  }
}

总结

ArkTS 的状态管理和组件通信体系提供了强大而灵活的工具,帮助开发者构建复杂的鸿蒙应用。关键要点总结:

  1. 理解装饰器差异:掌握 @State、@Prop、@Link、@Provide/@Consume 的适用场景

  2. 遵循单向数据流:保持数据流动的可预测性

  3. 合理组织状态:根据组件关系选择通信方式

  4. 注意性能优化:避免不必要的重新渲染

  5. 实践最佳实践:单一数据源、状态提升等原则

通过本文学到的知识,你应该能够设计出清晰、可维护的状态管理方案,为构建复杂的鸿蒙应用打下坚实基础。记住,良好的状态管理是高质量应用的核心!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值