0
点赞
收藏
分享

微信扫一扫

鸿蒙应用开发从入门到入行 - 篇5:组件化开发思想开发鸿蒙案例(详解父子组件传值)

鸿蒙应用开发从入门到入行

第五天 - 组件化开发思想开发鸿蒙案例(详解父子组件传值)

导读:在本篇文章里,您将掌握组件化开发、组件传值等相关知识。并能彻底弄懂鸿蒙父子组件数据的同步机制。本篇干货很多,特别是有些关于组件的细节,需要好好掌握。

本次整体学习目标介绍

  • 最近比较忙,不过好在本文也是紧赶慢赶的弄出来了。
  • 话不多说,我们先回顾一下我们需要做的案例
  • 我们发现,这是一个综合性比较强的案例,涉及了布局、状态切换、列表渲染、数据新增、侧滑删除等功能。非常适合入门时的综合练手。
  • 接下来,我们分析一下这个案例布局大致的划分

Progress的使用

鸿蒙应用开发从入门到入行 - 篇5:组件化开发思想开发鸿蒙案例(详解父子组件传值)_移动开发

  • 咱们本次案例中需要用到一个进度缓,如上图
  • 这种效果ArkUI已为我们提供了内置组件,即 Progress
  • 使用方法

Progress( { value: 当前值, total: 总值 } )

Progress( { value: 4, total: 10 } ) // 总量10,已完成4

  • 默认情况下,Progress产生的是水平进度条,因此,上述代码会得到如下图效果
  • 一些细节:
  • 默认情况下,value为0,total为100
  • 当Progress的宽度大于或等于高度时水平显示(如上图)
  • 当Progress的宽度小于高度时(不包含等于,必须小于高度)垂直显示(如下图)
  • type参数
  • 用法

Progress( { value: 5, total: 10, type: ProgressType.Ring } )

  • 通过使用Progress传入type属性,可以修改Progress的样式
  • 该参数需要传入ProgressType类型的枚举,一共有5种样式,分别如下
  • ProgressType.Linear:线性样式,默认值。样式为上边两张图效果。
  • ProgressType.Ring:环形样式(无刻度),样式如下
  • ProgressType.ScaleRing:环形样式(有刻度),样式如下
  • 鸿蒙应用开发从入门到入行 - 篇5:组件化开发思想开发鸿蒙案例(详解父子组件传值)_前端_02

  • ProgressType.Eclipse:圆形样式
  • 和ProgressType.Capsule:胶囊样式(通过宽高设置水平或水平样式,参照ProgressType.Linear)
  • 修改颜色,默认情况下,进度条底色为灰色,前景色(进度值的色)为渐变蓝色。如果需要改,可以分别通过backgroundColor(背景色)与 color(前景)改

Progress({ value: 3, total: 10, type: ProgressType.Ring })
          .width(80)
          .height(80)
          .backgroundColor(Color.Red)
          .color(Color.Pink)

  • 这里是把背景改为红,进度值为粉色,如下图

组件化开发

  • 组件:组成页面的一部分
  • 组件化开发:把页面的每一部分当成一个组件,然后把这些组件像搭积木一样搭在一起即为组件化开发
  • 组件化开发优势:代码分门别类,页面与逻辑内聚,方便阅读与维护、方便复用,等等,总之好多好多优点,这就不说了。

项目准备

  • 我们来看看,这个年度待办案例,我们本次分几个区域(几个组件)
  • 通过上图发现,我们需要三大区域,分别对应头部区(统计)输入区列表展现区,因此新建三个组件,然后集中到Index.ets来用
  • 步骤
  • 新建项目,过程略
  • 把侧滑显示的删除图标放到entry/src/main/resources/base/media文件夹里(点我下载)
  • pages同级目录,新建view文件夹
  • view文件夹,新建三个ets文件,分别起名叫TodoHeaderTodoInputTodoMain,里面,每个组件里放一个Text作为暂时的显示,且导出这个组件,这里仅贴TodoHeader代码,其他两个组件类似

@Component 
export struct TodoHeader {
  build () {
    Text('TodoHeader')
  }
}

  • 然后来到pages/Index.ets,导入这三个组件,并依次写到Column里(因为这三个组件如上图所示,就是从上到下依次排列)
  • 技巧:上篇说过,不用导入,只要在组件写了export的情况下,直接写组件名出提示后按回车,会自动生成导入代码
  • Index.ets代码如下

import { TodoHeader } from '../view/TodoHeader'
import { TodoInput } from '../view/TodoInput'
import { TodoMain } from '../view/TodoMain'

@Entry
@Component
struct Index {
  build() {
    Column({ space: 20 }) {

      TodoHeader()
      TodoInput()
      TodoMain()
    }
    .height('100%')
    .width('100%')
    .backgroundColor('#f2f3f5')
  }
}

  • 注意:根据效果图发现整个页面背景是灰色的,因此记得给整个Column加背景色,每个区域之间有间距,因此加space
  • 至此,项目准备工作完成

TodoHeader - 布局实现

  • 分析布局如图
  • 根据上图很明显能发现,就是一个Row(水平布局),里面一个Text显示标题,一个Stack放层叠布局(里面再放Progress与Text,具体后面说)
  • 记得给Row宽度、高度与背景色、Text要加粗、内容居中等
  • 基于此,代码如下

@Component
export struct TodoHeader {
  build() {
    Row() {
      Text('今年目标')
        .fontSize(30)
        .fontWeight(FontWeight.Bold)
        .margin({ right: 20 })


      Stack() {
        Progress({ value: 0, total: 10, type: ProgressType.Ring })
          .width(80)
          .height(80)
        Text(`0 / 10`)
      }
    }
    .width('100%')
    .height(110)
    .justifyContent(FlexAlign.Center)
    .backgroundColor(Color.White)
  }
}

  • 这里其他属性都很容易,如果有不懂的可以评论区留言
  • 主要解释下Stack环形进度条那里
  • 如上图解释的:这里其实有两个组件Progress用来显示环形进度条,但是它没有文字,所以还要搭一个Text
  • 而如果不加堆叠,则他们会并排显示,如下图
  • 所以此时我们是期望能让 3 / 10 这个Text能叠在Progress上面,所以这种层叠效果,需要套一个Stack来实现(注意,默认情况下后面的在最上层,因此Text必须放在最后。除此外,默认都是居中堆叠,所以刚好Text就在环形中间了)
  • 注意:本代码里的宽高颜色都可以由各位根据预览效果自行设置,不必强求与猫林老师一致

TodoInput - 布局实现

鸿蒙应用开发从入门到入行 - 篇5:组件化开发思想开发鸿蒙案例(详解父子组件传值)_鸿蒙_03

  • 如上图所示,Row里放TextInput即可,记得让TextInput不用铺满Row。因为将来必然会需要拿到输入框里的内容,因此声明个变量,并与之双向绑定
  • 代码如下

@Component
export struct TodoInput {
  @State newFlag: string = ''

  build() {

    Row() {
      TextInput({ placeholder: '请输入新目标', text: $$this.newFlag })
        .width('90%')
    }
    .width('100%')
    .height(80)
    .backgroundColor(Color.White)
    .justifyContent(FlexAlign.Center)
  }
}

TodoMain - 布局实现

鸿蒙应用开发从入门到入行 - 篇5:组件化开发思想开发鸿蒙案例(详解父子组件传值)_鸿蒙_04

  • 发现这个区域就是一堆上一篇文章里教大家封装的组件,直接拿来用即可
  • 注:上一篇里猫林老师不小心起错名字。其实规范的名字应该叫TodoItem,上一篇写成了ToDoItem,这里正好勘误,用更规范的写法
  • 步骤
  • 来到view新建TodoItem.ets文件,然后写如下代码(分析过程略,见上篇)

@Component
export struct TodoItem {
  build() {
    Row() {
      Checkbox()
        .margin({ left: 20, right: 20 })
      Text('跟猫林学鸿蒙')
    }
    .width('100%')
    .height(40)
    .backgroundColor(Color.White)
    .borderRadius(20)
  }
}

  • 然后来到TodoMain组件,进行导入与使用,为了暂时能看到一堆内容,我们用ForEach先循环生成10个, TodoMain代码如下

import { TodoItem } from './TodoItem'

@Component
export struct TodoMain {
  @State todoList: number[] = [1, 2, 3, 4, 5, 6, 7]

  build() {
    Column({ space: 10 }) {
      ForEach(this.todoList, (item: number) => {
        TodoItem()
      })
    }
    .width('100%')
  }
}

  • 这里TodoMain的根容器是Column,因为它需要一行一行从上到下来显示

初始数据与说明

  • 初始数据如下

class TodoModel {
   text: string = ''
   finished: boolean = false
 }

 @State totalFlags: Array<TodoModel> = [
    { text: "月入5万", finished: false },
    { text: "中彩票500万", finished: false },
    { text: "找个富婆", finished: false },
    { text: "买套别墅", finished: false },
    { text: "改掉爱做梦的习惯", finished: false },
  ];

  • 解释
  • 每个新年目标都是个对象,有两种属性:text(目标文字)、finished(是否完成)
  • 以下是对语法的解释,会TS的可跳过下面这三段
  • 因为ArkTS是一种类型严谨的语言,因此需要对这种对象做一个类型定义,即声明一个类叫TodoModel,它里面有两个这样的属性
  • totalFlags即为这种对象类型的数组,例如Array代表数组每个元素都是数值类型的数组,所以上面写的Array代表数组每个元素都是TodoModel类型的数组
  • 数组也可以简写为 number[] 、本例中的Array<TodoModel>可简写为 TodoModel[]
  • 按照官方的编码规范指导,这种项目中用到的数据对应的类,要写到viewsmodel文件夹(与pages平级),我们新建好文件夹后,也在此文件夹再新建TodoModel.ets文件,声明这个类,并导出
  • 文件内代码如下

export class TodoModel {
  text: string = ''
  finished: boolean = false
}

  • 根据功能分析,本案例的年度目标必然是一个数组,且可对其增、删、改。但是这个数组在本案例中三大组件里都需要用:
  • TodoHeader 需要数组来统计总目标和已完成目标
  • TodoInput 需要给数组添加新内容
  • TodoMain 需要展示数组内容,并且将来侧滑时需要能删除数组内容
  • 基于上面需求,提问:数组应该放在哪?
  • 没错,应该放到他们共同的父组件,本案例即为Index.ets里。
  • 来到Index.ets,导入上面声明的类,并声明一个数组

......

import { TodoModel } from '../viewmodel/TodoModel'

@Entry
@Component
struct Index {
  
  @State totalFlags: Array<TodoModel> = [
    { text: "月入5万", finished: false },
    { text: "中彩票500万", finished: false },
    { text: "找个富婆", finished: false },
    { text: "买套别墅", finished: false },
    { text: "改掉爱做梦的习惯", finished: false },
  ];

  build() {
    ......
  }
}

组件传参 - 父传子

  • 此时数据有了,但还没交给TodoMain去显示,此时相当于要把Index里的数据给TodoMain,且因为Index是父组件,TodoMain是子组件,因此这种数据传递方式叫父传子,即把父的数据传递给子组件
  • 注:本章节主要是为了学习语法,最终部分代码不会出现于年度待办案例,因此此时先把TodoMainTodoItem代码做一个备份。然后再开始练下面语法
  • 语法步骤:
  • 子里声明一个成员变量
  • 父里使用组件时,在小括号里传入
  • 例如,本案例中我们有 TodoMainTodoItem,因为TodoMain包含了TodoItem。所以TodoMain是父TodoItem是子。我们就用这两个组件试试父传子
  • 代码步骤:
  • TodoItem里声明一个变量叫name,并在Text里使用,代码如下

export struct TodoItem {
  // 声明个成员变量,等待父传,注意:此时没加任意装饰器
  name: string = ''

  build() {
		.......
    Text(this.name)
  }
}

  • TodoMain里声明一个变量name,并传递给TodoItem

@Component
export struct TodoMain {
  .......
  @State name: string = 'abc'


  build() {
    Column({ space: 10 }) {
      ........
      ForEach(this.todoList, (item: number) => {
        // 这里是传参,把父的name传递给了子里的name
        TodoItem({ name: this.name })
      })
    }
    .width('100%')
  }
}

  • 此时会发现,正因为把父的name,也即数据为abc,传递给了子,所以此时TodoItem显示的即为abc,如下图
  • 注意,此时虽能成功父传子,但是父的数据一旦改变,子并不会跟着改变
  • 我们此时可以测试一下,在TodoMain里,添加一个Button,并在Button里修改掉name的值,如下代码

export struct TodoMain {
	....
  @State name: string = 'abc'

  build() {
    Column({ space: 10 }) {
      Button('我改').onClick((event: ClickEvent) => {
        this.name = 'Good猫林'
      })
       
     .........
     }
		......
  }
}

  • 点击按钮后会发现,子组件也即TodoItem上没有任何变化
  • 如果希望实现父的数据改变,子的数据能随着改变,需要在子的变量前加@Prop装饰器
  • 修改TodoItem里的name,前面加上@Prop,再来点击按钮看变化

export struct TodoItem {
  // 此时加了@Prop修饰
  @Prop name: string = ''

  build() {
    Row() {
      Checkbox()
        .margin({ left: 20, right: 20 })
      Text(this.name)
    }
   
   .......
  }
}

  • 此时点击按钮,让父的数据改变,子里的也跟着变
  • 到目前,我们已经学了两个用来修饰成员变量的装饰器,分别是@State@Prop,我们对它总结一下
  • @State: 主要是装饰给组件自己使用的数据,效果:能让成员变量的值改变后,界面也能刷新
  • @Prop: 主要是用在作为子组件时,用来装饰由父传递过来的数据,效果:能让父的数据改变子也能接收到
  • 注意:在ArkTS中,即使父传递的是引用类型的数据,若不加@Prop修饰,一样会导致父的数据改变子里不会变,同学们有兴趣可以自行测试

组件传参 - 父传子双向同步

  • 上面我们讲到,子里的成员变量加@Prop后,即可让父的数据改变,子随之改变,也即父的数据自动同步到子
  • 但是,目前无法实现子同步到父,也即子里改变了这个父传进来的数据,子里自身能改变,但是父的无法改变。也即Vue框架里的单向数据流
  • 例:在TodoMain里用一个Text显示name的值,并在TodoItem里给Row加点击事件并修改name的值,我们可观察效果
  • TodoMain代码

export struct TodoMain {
	....
  @State name: string = 'abc'

  build() {
    Column({ space: 10 }) {
      Button('我改').onClick((event: ClickEvent) => {
        this.name = 'Good猫林'
      })
        
      Row() {
        // 显示name
        Text(this.name)
      }
       
     .........
     }
		......
  }
}

  • TodoItem代码

export struct TodoItem {
  // 此时加了@Prop修饰
  @Prop name: string = ''

  build() {
    Row() {
     ......
    }
    .onClick(() => {
      this.name = '学鸿蒙'
    })
   
   .......
  }
}

  • 发现当我们点击TodoItem时,TodoItem自身的name数据了学鸿蒙,但是父里的还是abc,如图
  • 如果希望实现:父改了数据,自动同步到子。以及子改了数据,也同步到父,需要把子里的@Prop修改成@Link
  • 注意:如果用@Link修饰了成员变量,则成员变量不可初始化,例

export struct TodoItem {
  // 此时加了@Link,不用初始化
  @Link name: string

  build() {
    .......
  }
}

  • 虽然仅仅只是把@Prop改成了@Link,且把初始化值的部分删了,但此时已经完成了双向同步,我们来点击一下Row测试一下效果,会发现如下图所示,子和父都变成了学鸿蒙
  • 总结@Prop@Link
  • 相同点:
  • 都是用在子组件,用来接收父传递过来的数据,
  • 都可以实现父改变数据后同步给子
  • 不同点:
  • 初始化值不同:
  • @Prop需要初始化值,相当于给默认值。可以实现,父如果传了就用父的数据,如果没传则用默认值
  • @Link不能初始化,相当于必须要父传递数据了才有数据
  • 同步给父不同
  • @Prop修饰的数据,子里改变了不会同步给父
  • @Link修饰的数据,子里改变了会同步给父

年度目标案例 - 数据展示

  • 回到我们之前最初的需求:要把Index里的数据给TodoMain,经历了上面组件传参的学习后,我们能很快完成。
  • 步骤:
  • 注意:先把之前备份的TodoMain与TodoItem恢复
  • TodoMain里,声明成员变量接收Index传递过来的数组,之前备份的文件里其实已经声明过,只不过是number类型的,改成TodoModel即可,代码如下

@Link todoList: TodoModel[] = []

  • 解释为什么用 @Link修饰符
  • 因为在TodoMain里将来需要改变数组(例如侧滑删除等),需要同步给父组件(即Index),因此用@Link
  • Index里传递(这里Index的数据在上面初始数据与说明章节已声明过)

TodoMain({ todoList: this.totalFlags })

  • 此时已经把数组从Index传递到TodoMain,但是TodoMain里又需要遍历这个数组去产生TodoItem,并且需要把数组每一项交给TodoItem去渲染,所以这里又要父传子,思路示意图如下
  • 来到TodoItem,声明一个TodoModel的对象,用来接收数组每一项,并且把item里的finished属性绑定给Checkboxtext属性绑定给Text去显示

import { TodoModel } from '../viewmodel/TodoModel'

@Component
export struct TodoItem {

  @Prop item: TodoModel = new TodoModel() // 本次代码

  build() {
    Row() {
      Checkbox()
        .select(this.item.finished) // 本次代码
        .margin({ left: 20, right: 20 })
      Text(this.item.text) // 本次代码
    }
    .width('100%')
    .height(40)
    .backgroundColor(Color.White)
    .borderRadius(20)
  }
}

  • TodoMain里使用ForEach渲染

import { TodoItem } from './TodoItem'
import { TodoModel } from '../viewmodel/TodoModel'

@Component
export struct TodoMain {
  @Link todoList: TodoModel[]

  build() {
    Column({ space: 10 }) {
     
        // 本次代码
        ForEach(this.todoList, (item: TodoModel, index: number) => {
            // 这里用了ES6简写,完整写法是 TodoItem({ item: item })代表把ForEach里的item传递给TodoItem需要的item
            TodoItem({ item })
        })
      // 以上是本次代码改动
    }
    .width('100%')
  }
}

  • 此时便完成了数组的渲染

年度目标案例 - 目标完成打勾

  • 需求如下:
  • 根据上面数据说明,打勾与否需要与每一项的finished属性绑定,因此先来到TodoItemCheckboxselect属性做双向绑定

Checkbox()
        .select($$this.item.finished)

  • 注:
  • Checkbox的select属性方法是用来设置它是否打勾的。传true代表打勾,传false代表不打勾
  • 这里我们除了要数据能影响Checkbox以外,也需要当我们操作组件后结果能影响到数据,因此加$$做双向绑定
  • 然后根据finished的值,做不同的处理
  • 如果是true,给文字加删除线,否则不加任何线
  • 如果是true,给文字加opacity为半透明,否则不透明
  • 解释:通过需求图可以看到,要让文字和删除线都变灰,最快的方式是给整个Text加透明度,配合白色底就会呈现灰色。而如果单独知识改文字颜色也即fontColor只会让文字变灰,删除线不变,还得在decoration里加color才有用,不方便
  • 代码如下

Text(this.item.text)// 本次代码
  .opacity(this.item.finished ? 0.4 : 1)
  .decoration({ type: this.item.finished ? TextDecorationType.LineThrough : TextDecorationType.None })

  • 注:decoration就是Text设置文字线条的样式,可以设置删除线、下划线等,具体可查官方文档

插播 - 目前鸿蒙开发的缺陷

  • 目前虽然实现了数据展现与目标完成的打勾,但此时存在数据传递的问题
  • 通过最终效果图我们知道,我们需要统计出已完成的目标数量展现到进度条里。但此时,我们在TodoItem里打勾了后,finished的值已经改变(通过界面有变化能证明),但是没有传入到父,导致父里的finished还是false
  • 我们在父里(TodoMain)里写一个Row输出数组里下标0的元素的finished

.......

  build() {
    Column({ space: 10 }) {

      Row() {
        Text(this.todoList[0].finished + '')
      }

      ..........
    }
  }
.....

  • 注:这里的+ '' 是为了把finished转为字符串类型,因为Text只能用字符串
  • 然后给第一项打勾,会发现如下图效果
  • 大家思考一下:为什么会这样呢?
  • 请思考
  • 请思考
  • 请思考
  • 请思考
  • 请思考
  • ..........
  • 没错,就是因为子里用的是@Prop,我们上面说过,@Prop只能实现父数据同步到子,子里变了无法同步到父,此时就是这原因导致
  • 如何解决?
  • 相信同学们都能想到用@Link,父子双向同步。但此时,鸿蒙的缺陷体现出来了!当你把TodoItem里的item改成@Link装饰后,惊讶地发现:在TodoMain里报错了!如下图
  • 原因:语法限制,@Link只能接收父组件里声明的第一层成员变量
  • 什么叫第一层?
  • 就好比一个数组,数组里全是对象。对于数组而言即为第一层,数组里的每个对象称之为第二层,以此类推
  • 再好比一个嵌套对象。即对象里有个属性又是对象,那么外层的称之为第一层,里面的属性即为第二层,以此类推
  • 所以上述报错里写的item相当于就是数组里的对象,也即第二层,所以报错
  • 出现这个语法限制的根本原因是:目前的鸿蒙开发中,默认情况下无法监听到第二层的改动。而@Link又要实现双向同步,你都无法监听到改动,又如何完成双向同步呢?
  • 所以鸿蒙也给了解决方案:使用@Observed@ObjectLink来解决。但是,猫林老师这里不打算讲它。因为这个解决方案其实用起来也很麻烦繁琐,非常不人性化。
  • 有兴趣的同学可以自行去学习Observed与ObjectLink,猫林老师这里只讲最实用的技术,帮助你更快开发鸿蒙!
  • 题外话,从API12开始,鸿蒙提供了@ObservedV2,力求解决监听这种监听属性的问题。但猫林老师尝鲜过,也不是很方便,这里赞不推荐
  • 题外话2:不要一听猫林老师吐槽一句鸿蒙开发中存在这种不太方便的点,就觉得鸿蒙不行。任何语言刚推出时总有些坑或者不太方便的地方需要等待后续更新。当初苹果的Swift刚开始推出时也有许多地方有待完善,苹果也是慢慢更新使之越来越好。所以大家要保持好信心,未来可期。而且,华为官方也确实不断的对鸿蒙开发做着改进,就像上面提到的@ObservedV2。所以鸿蒙开发未来必然会越来越方便,越来越容易好用。可是到那时越容易就会越多人涌进,但是不要怕。你们在此刻开始进入鸿蒙的人,都算得上是鸿蒙的元老级程序员。(毕竟现在连纯血鸿蒙的正式版都还没发布,仅仅是测试版呢),所以对你们,更是抓住风口的弄潮儿,必将成为吃到红利的一波人!

待办列表 - 解决缺陷 - 实现打勾改动同步给父

  • 那,猫林老师这里怎么解决上述缺陷呢?首先,因为@Link目前不能用,那咱们就把它换回@Prop

.....
@Component
export struct TodoItem {
  @Prop item: TodoModel
  .....
}

  • 可是@Prop又确实无法让父的数据同步改变,该怎么办呢?
  • 既然子里无法改动到父,那就换个思路。让父,自己改!!
  • 整体思路是:
  • 让父提供一个修改数据的方法, 子里要修改时调用父的方法即可修改!
  • 如图
  • 实现:
  • 来到TodoMain,声明一个方法如下

changeStatus(item: TodoModel, index: number) {
    this.todoList[index] = {
      text: item.text,
      finished: !item.finished
    }
  }

  • 解释:
  • 本方法需要传入被点的item以及被点的item的索引
  • 通过索引的方式改掉数组中这一项,文字不变,但是完成状态取反即可
  • 这时候可能有老铁有疑问:
  • 为什么不直接 item = !item.finished
  • 还是那个问题:目前不支持监听第二层数据改变,直接改item还是第二层。
  • 但数组是第一层,因此你用数组[索引]的方式,就是在改第一层数据,这是能被监听到的
  • 此时需要把这个方法传递给TodoItem,因此TodoItem需要声明一个成员方法来接收

export struct TodoItem {
  ......
  onChange: () => void = () => {}
  .....
}

  • 解释:
  • 方法名叫onChange,它的类型是一个无参数无返回值的函数,初始化值是一个空函数
  • 一般情况下,这种由外界传入的方法不需要加装饰器
  • 然后给Select组件加onChange 事件,这个事件是当Select发生勾选状态改变就会调用的事件,在里面调用传入的onChange方法

Checkbox()
        .select(this.item.finished)
        .margin({ left: 20, right: 20 })
        .onChange(() => {
          // 本次代码
          this.onChange()
        })

  • 回到TodoMain做方法传递:此时调用TodoItem除了之前要传入的item,现在还要多一个onChange

ForEach(this.todoList, (item: TodoModel, index: number) => {
        // 此时调用TodoItem
        TodoItem({
          item, onChange: () => {
            this.changeStatus(item, index)
          }
        })
      })

  • 解释参数:item即为当前变动的数据,index即为当前数据对应的下标(都是changeStatus需要用到的内容)
  • 注意看:这里我没有直接把this.changeStatus 这个方法传递给onChange,而是声明了一个新的箭头函数,只不过在箭头函数里的函数体里调用了this.changeStatus,这么做的原因是this.changeStatus方法里有this.todoList这样的代码,我们都知道this是指当前环境上下文,它在TodoMain里,就代表TodoMain这个上下文,所以它修改它的todoList没毛病。但如果你是直接把this.changeStatus传递给onChange,那它相当于是在todoItem里调用,同样的this也会变成todoItem上下文,此时它是没有todoList数组的,所以这里利用箭头函数保留当前上下文的特点,在todoMain里用箭头函数再包一层,即可保证this依然指向todoMain ------- 这段请可能理解,因为文字有其局限性,实在不理解等以后猫林老师出视频教程再来看
  • 附:有懂JS的同学可能会问:那可以 onChange: this.changeStatus.bind(.....)这样的方式传递吗?似乎也能绑定当前this指向呀?
  • 答:不能!ArkTS中不允许使用callapplybind
  • 具体可查看当前最新文档,点我进入,打开页面后搜:不支持Function.bind
  • 至此,状态改变同步给父已完成。
  • 鉴于篇幅关系以及一篇文章颗粒度太大也影响大家阅读。所以年度目标这个案例的剩余部分我在下一篇文章讲完,其中下一篇也会以需求驱动的方式为大家讲到很多干货新知识,请拭目以待!

总结今日内容

  • 组件
  • 组件化开发思想
  • 父子组件数据传递与同步
  • 本篇干活与细节略多,需要多认真思考学习。
  • 忠告:我们是以需求驱动学习知识点。特别是本篇含有部分需要理解与实操的内容。鸿蒙零基础的同学,一定要好好的跟着敲本案例代码才能理解

课后练习

  • 判断题
  • 一个Progress的宽度是100,高度是150,此时环形图一定是垂直方向的线性样式滚动条

互动简答题

  • 思考:本案例中,最终TodoItem里的数据打勾变化后(完成状态变化),TodoMain已经能成功收到改动了。那么它的父组件,最早持有数组的Index有收到改动吗?请说出你的理由,打在评论区

判断题答案

  • 错。原因自己思考,实在不懂可以评论区留言找一起学的同学帮助
举报

相关推荐

0 条评论