ForEach接口基于数组类型数据来举行循环渲染,必要与容器组件配合使用,且接口返回的组件应当是允许包含在ForEach父容器组件中的子组件。比方,ListItem组件要求ForEach的父容器组件必须为List组件。
说明:
从API version 9开始,该接口支持在ArkTS卡片中使用。
接口形貌
- ForEach(
- arr: Array,
- itemGenerator: (item: any, index?: number) => void,
- keyGenerator?: (item: any, index?: number): string => string
- )
复制代码 以下是参数的详细说明:
参数名参数类型是否必填参数形貌arrArray是数据源,为类型的数组。
说明:
- 可以设置为空数组,此时不会创建子组件。
- 可以设置返回值为数组类型的函数,比方,但设置的函数不应改变包括数组本身在内的任何状态变量,比方不应使用,或这些会改变原数组的函数。Arrayarr.slice(1, 3)Array.splice()Array.sort()Array.reverse()itemGenerator(item: any, index?: number) => void是组件天生函数。
- 为数组中的每个元素创建对应的组件。
- 参数:数组中的数据项。
- 参数(可选):数组中的数据项索引。
说明:
- 组件的类型必须是的父容器所允许的。比方,组件要求的父容器组件必须为组件。itemarrindexarrForEachListItemForEachListkeyGenerator(item: any, index?: number) => string否键值天生函数。
- 为数据源的每个数组项天生唯一且持久的键值。函数返回值为开辟者自定义的键值天生规则。
- 参数:数组中的数据项。
- 参数(可选):数组中的数据项索引。
说明:
- 如果函数缺省,框架默认的键值天生函数为
- 键值天生函数不应改变任何组件状态。arritemarrindexarr(item: T, index: number) => { return index + '__' + JSON.stringify(item); } 说明:
- ForEach的函数可以包含条件渲染逻辑。另外,也可以在条件渲染语句中使用组件。itemGeneratorif/elseif/elseForEach
- 在初始化渲染时,会加载数据源的全部数据,并为每个数据项创建对应的组件,然后将其挂载到渲染树上。如果数据源非常大或有特定的性能需求,建议使用组件。ForEachLazyForEach
键值天生规则
在循环渲染过程中,系统会为每个数组元素天生一个唯一且持久的键值,用于标识对应的组件。当这个键值变革时,ArkUI框架将视为该数组元素已被更换或修改,并会基于新的键值创建一个新的组件。ForEach
ForEach提供了一个名为的参数,这是一个函数,开辟者可以通过它自定义键值的天生规则。如果开辟者没有定义函数,则ArkUI框架会使用默认的键值天生函数,即。keyGeneratorkeyGenerator(item: any, index: number) => { return index + '__' + JSON.stringify(item); }
ArkUI框架对于的键值天生有一套特定的判断规则,这主要与函数的第二个参数以及函数的返回值有关。总的来说,只有当开辟者在函数中声明了参数,并且自定义的函数返回值中不包含参数时,ArkUI框架才会在开辟者自定义的函数返回值前添加参数,作为终极的键值。在其他情况下,系统将直接使用开辟者自定义的函数返回值作为终极的键值。如果函数未定义,系统将使用上述默认的键值天生函数。详细的键值天生规则判断逻辑如下图所示。ForEachitemGeneratorindexkeyGeneratoritemGeneratorindexkeyGeneratorindexkeyGeneratorindexkeyGeneratorkeyGenerator
图1 ForEach键值天生规则
说明:
ArkUI框架会对重复的键值发出告诫。在UI更新的场景下,如果出现重复的键值,框架可能无法正常工作,详细请参见渲染结果非预期。
组件创建规则
在确定键值天生规则后,ForEach的第二个参数函数会根据键值天生规则为数据源的每个数组项创建组件。组件的创建包括两种情况:ForEach首次渲染和ForEach非首次渲染。itemGenerator
首次渲染
在ForEach首次渲染时,会根据前述键值天生规则为数据源的每个数组项天生唯一键值,并创建相应的组件。
- @Entry
- @Component
- struct Parent {
- @State simpleList: Array<string> = ['one', 'two', 'three'];
- build() {
- Row() {
- Column() {
- ForEach(this.simpleList, (item: string) => {
- ChildItem({ item: item })
- }, (item: string) => item)
- }
- .width('100%')
- .height('100%')
- }
- .height('100%')
- .backgroundColor(0xF1F3F5)
- }
- }
- @Component
- struct ChildItem {
- @Prop item: string;
- build() {
- Text(this.item)
- .fontSize(50)
- }
- }
复制代码 运行结果如下图所示。
图2 ForEach数据源不存在雷同值案例首次渲染运行结果图
在上述代码中,键值天生规则是函数的返回值。在ForEach渲染循环时,为数据源数组项依次天生键值、和,并创建对应的组件渲染到界面上。keyGeneratoritemonetwothreeChildItem
当不同数组项按照键值天生规则天生的键值雷同时,框架的行为是未定义的。比方,在以下代码中,ForEach渲染雷同的数据项时,只创建了一个组件,而没有创建多个具有雷同键值的组件。twoChildItem
- @Entry
- @Component
- struct Parent {
- @State simpleList: Array<string> = ['one', 'two', 'two', 'three'];
- build() {
- Row() {
- Column() {
- ForEach(this.simpleList, (item: string) => {
- ChildItem({ item: item })
- }, (item: string) => item)
- }
- .width('100%')
- .height('100%')
- }
- .height('100%')
- .backgroundColor(0xF1F3F5)
- }
- }
- @Component
- struct ChildItem {
- @Prop item: string;
- build() {
- Text(this.item)
- .fontSize(50)
- }
- }
复制代码 运行结果如下图所示。
图3 ForEach数据源存在雷同值案例首次渲染运行结果图
在该示例中,终极键值天生规则为。当ForEach遍历数据源,遍历到索引为1的时,按照终极键值天生规则天生键值为的组件并举行标记。当遍历到索引为2的时,按照终极键值天生规则当前项的键值也为,此时不再创建新的组件。itemsimpleListtwotwotwotwo
非首次渲染
在ForEach组件举行非首次渲染时,它会查抄新天生的键值是否在上次渲染中已经存在。如果键值不存在,则会创建一个新的组件;如果键值存在,则不会创建新的组件,而是直接渲染该键值所对应的组件。比方,在以下的代码示例中,通过点击事件修改了数组的第三项值为"new three",这将触发ForEach组件举行非首次渲染。
- @Entry
- @Component
- struct Parent {
- @State simpleList: Array<string> = ['one', 'two', 'three'];
- build() {
- Row() {
- Column() {
- Text('点击修改第3个数组项的值')
- .fontSize(24)
- .fontColor(Color.Red)
- .onClick(() => {
- this.simpleList[2] = 'new three';
- })
- ForEach(this.simpleList, (item: string) => {
- ChildItem({ item: item })
- .margin({ top: 20 })
- }, (item: string) => item)
- }
- .justifyContent(FlexAlign.Center)
- .width('100%')
- .height('100%')
- }
- .height('100%')
- .backgroundColor(0xF1F3F5)
- }
- }
- @Component
- struct ChildItem {
- @Prop item: string;
- build() {
- Text(this.item)
- .fontSize(30)
- }
- }
复制代码 运行结果如下图所示。
图4 ForEach非首次渲染案例运行结果图
从本例可以看出 能够监听到简单数据类型数组数据源 数组项的变革。@StatesimpleList
- 当 数组项发生变革时,会触发 举行重新渲染。simpleListForEach
- ForEach 遍历新的数据源 ,并天生对应的键值、和。['one', 'two', 'new three']onetwonew three
- 其中,键值和在上次渲染中已经存在,所以 复用了对应的组件并举行了渲染。对于第三个数组项 "new three",由于其通过键值天生规则 天生的键值在上次渲染中不存在,因此 为该数组项创建了一个新的组件。onetwoForEachitemnew threeForEach
使用场景
ForEach组件在开辟过程中的主要应用场景包括:数据源稳定、数据源数组项发生变革(如插入、删除利用)、数据源数组项子属性变革。
数据源稳定
在数据源保持稳定的场景中,数据源可以直接采用根本数据类型。比方,在页面加载状态时,可以使用骨架屏列表举行渲染展示。
- @Entry
- @Component
- struct ArticleList {
- @State simpleList: Array<number> = [1, 2, 3, 4, 5];
- build() {
- Column() {
- ForEach(this.simpleList, (item: string) => {
- ArticleSkeletonView()
- .margin({ top: 20 })
- }, (item: string) => item)
- }
- .padding(20)
- .width('100%')
- .height('100%')
- }
- }
- @Builder
- function textArea(width: number | Resource | string = '100%', height: number | Resource | string = '100%') {
- Row()
- .width(width)
- .height(height)
- .backgroundColor('#FFF2F3F4')
- }
- @Component
- struct ArticleSkeletonView {
- build() {
- Row() {
- Column() {
- textArea(80, 80)
- }
- .margin({ right: 20 })
- Column() {
- textArea('60%', 20)
- textArea('50%', 20)
- }
- .alignItems(HorizontalAlign.Start)
- .justifyContent(FlexAlign.SpaceAround)
- .height('100%')
- }
- .padding(20)
- .borderRadius(12)
- .backgroundColor('#FFECECEC')
- .height(120)
- .width('100%')
- .justifyContent(FlexAlign.SpaceBetween)
- }
- }
复制代码 运行结果如下图所示。
图5 骨架屏运行结果图
在本示例中,采用数据项item作为键值天生规则,由于数据源simpleList的数组项各不雷同,因此能够保证键值的唯一性。
数据源数组项发生变革
在数据源数组项发生变革的场景下,比方举行数组插入、删除利用大概数组项索引位置发生互换时,数据源应为对象数组类型,并使用对象的唯一ID作为终极键值。比方,当在页面上通过手势上滑加载下一页数据时,会在数据源数组尾部新增新获取的数据项,从而使得数据源数组长度增大。
- class Article {
- id: string;
- title: string;
- brief: string;
- constructor(id: string, title: string, brief: string) {
- this.id = id;
- this.title = title;
- this.brief = brief;
- }
- }
- @Entry
- @Component
- struct ArticleListView {
- @State isListReachEnd: boolean = false;
- @State articleList: Array<Article> = [
- new Article('001', '第1篇文章', '文章简介内容'),
- new Article('002', '第2篇文章', '文章简介内容'),
- new Article('003', '第3篇文章', '文章简介内容'),
- new Article('004', '第4篇文章', '文章简介内容'),
- new Article('005', '第5篇文章', '文章简介内容'),
- new Article('006', '第6篇文章', '文章简介内容')
- ]
- loadMoreArticles() {
- this.articleList.push(new Article('007', '加载的新文章', '文章简介内容'));
- }
- build() {
- Column({ space: 5 }) {
- List() {
- ForEach(this.articleList, (item: Article) => {
- ListItem() {
- ArticleCard({ article: item })
- .margin({ top: 20 })
- }
- }, (item: Article) => item.id)
- }
- .onReachEnd(() => {
- this.isListReachEnd = true;
- })
- .parallelGesture(
- PanGesture({ direction: PanDirection.Up, distance: 80 })
- .onActionStart(() => {
- if (this.isListReachEnd) {
- this.loadMoreArticles();
- this.isListReachEnd = false;
- }
- })
- )
- .padding(20)
- .scrollBar(BarState.Off)
- }
- .width('100%')
- .height('100%')
- .backgroundColor(0xF1F3F5)
- }
- }
- @Component
- struct ArticleCard {
- @Prop article: Article;
- build() {
- Row() {
- Image($r('app.media.icon'))
- .width(80)
- .height(80)
- .margin({ right: 20 })
- Column() {
- Text(this.article.title)
- .fontSize(20)
- .margin({ bottom: 8 })
- Text(this.article.brief)
- .fontSize(16)
- .fontColor(Color.Gray)
- .margin({ bottom: 8 })
- }
- .alignItems(HorizontalAlign.Start)
- .width('80%')
- .height('100%')
- }
- .padding(20)
- .borderRadius(12)
- .backgroundColor('#FFECECEC')
- .height(120)
- .width('100%')
- .justifyContent(FlexAlign.SpaceBetween)
- }
- }
复制代码 初始运行结果(左图)和手势上滑加载后结果(右图)如下图所示。
图6 数据源数组项变革案例运行结果图
在本示例中,组件作为组件的子组件,通过装饰器接收一个对象,用于渲染文章卡片。ArticleCardArticleListView@PropArticle
- 当列表滚动到底部时,如果手势滑动距离凌驾指定的80,将触发函数。此函数会在数据源的尾部添加一个新的数据项,从而增加数据源的长度。loadMoreArticle()articleList
- 数据源被装饰器修饰,ArkUI框架能够感知到数据源长度的变革,并触发举行重新渲染。@StateForEach
数据源数组项子属性变革
当数据源的数组项为对象数据类型,并且只修改某个数组项的属性值时,由于数据源为复杂数据类型,ArkUI框架无法监听到装饰器修饰的数据源数组项的属性变革,从而无法触发的重新渲染。为实现重新渲染,必要结合和装饰器使用。比方,在文章列表卡片上点击“点赞”按钮,从而修改文章的点赞数目。@StateForEachForEach@Observed@ObjectLink
- @Observed
- class Article {
- id: string;
- title: string;
- brief: string;
- isLiked: boolean;
- likesCount: number;
- constructor(id: string, title: string, brief: string, isLiked: boolean, likesCount: number) {
- this.id = id;
- this.title = title;
- this.brief = brief;
- this.isLiked = isLiked;
- this.likesCount = likesCount;
- }
- }
- @Entry
- @Component
- struct ArticleListView {
- @State articleList: Array<Article> = [
- new Article('001', '第0篇文章', '文章简介内容', false, 100),
- new Article('002', '第1篇文章', '文章简介内容', false, 100),
- new Article('003', '第2篇文章', '文章简介内容', false, 100),
- new Article('004', '第4篇文章', '文章简介内容', false, 100),
- new Article('005', '第5篇文章', '文章简介内容', false, 100),
- new Article('006', '第6篇文章', '文章简介内容', false, 100),
- ];
- build() {
- List() {
- ForEach(this.articleList, (item: Article) => {
- ListItem() {
- ArticleCard({
- article: item
- })
- .margin({ top: 20 })
- }
- }, (item: Article) => item.id)
- }
- .padding(20)
- .scrollBar(BarState.Off)
- .backgroundColor(0xF1F3F5)
- }
- }
- @Component
- struct ArticleCard {
- @ObjectLink article: Article;
- handleLiked() {
- this.article.isLiked = !this.article.isLiked;
- this.article.likesCount = this.article.isLiked ? this.article.likesCount + 1 : this.article.likesCount - 1;
- }
- build() {
- Row() {
- Image($r('app.media.icon'))
- .width(80)
- .height(80)
- .margin({ right: 20 })
- Column() {
- Text(this.article.title)
- .fontSize(20)
- .margin({ bottom: 8 })
- Text(this.article.brief)
- .fontSize(16)
- .fontColor(Color.Gray)
- .margin({ bottom: 8 })
- Row() {
- Image(this.article.isLiked ? $r('app.media.iconLiked') : $r('app.media.iconUnLiked'))
- .width(24)
- .height(24)
- .margin({ right: 8 })
- Text(this.article.likesCount.toString())
- .fontSize(16)
- }
- .onClick(() => this.handleLiked())
- .justifyContent(FlexAlign.Center)
- }
- .alignItems(HorizontalAlign.Start)
- .width('80%')
- .height('100%')
- }
- .padding(20)
- .borderRadius(12)
- .backgroundColor('#FFECECEC')
- .height(120)
- .width('100%')
- .justifyContent(FlexAlign.SpaceBetween)
- }
- }
复制代码 上述代码的初始运行结果(左图)和点击第1个文章卡片上的点赞图标后的运行结果(右图)如下图所示。
图7 数据源数组项子属性变革案例运行结果图
在本示例中,类被装饰器修饰。父组件传入对象实例给子组件,子组件使用装饰器接收该实例。Article@ObservedArticleListViewArticleArticleCard@ObjectLink
- 当点击第1个文章卡片上的点赞图标时,会触发组件的函数。该函数修改第1个卡片对应组件里实例的和属性值。ArticleCardhandleLikedarticleisLikedlikesCount
- 由于子组件中的使用了装饰器,父子组件共享同一份数据。因此,父组件中的第1个数组项的和数值也会同步修改。ArticleCardarticle@ObjectLinkarticlearticleListisLikedlikedCounts
- 当父组件监听到数据源数组项属性值变革时,会触发重新渲染。ForEach
- 在此处,键值天生规则为数组项的属性值。当遍历新数据源时,数组项的均没有变革,不会新建组件。ForEachidForEachid
- 渲染第1个数组项对应的组件时,读取到的和为修改后的新值。ArticleCardisLikedlikesCount
使用建议
- 只管制止在终极的键值天生规则中包含数据项索引,以防止出现渲染结果非预期和渲染性能降低。如果业务确实必要使用,比方列表必要通过举行条件渲染,开辟者必要接受在改变数据源后重新创建组件所带来的性能损耗。indexindexindexForEach
- 为满足键值的唯一性,对于对象数据类型,建议使用对象数据中的唯一作为键值。id
- 根本数据类型的数据项没有唯一属性。如果使用根本数据类型本身作为键值,必须确保数组项无重复。因此,对于数据源会发生变革的场景,建议将根本数据类型数组转化为具备唯一属性的对象数据类型数组,再使用属性作为键值天生规则。IDIDID
不保举案例
开辟者在使用ForEach的过程中,若对于键值天生规则的理解不够充分,可能会出现错误的使用方式。错误使用一方面会导致功能层面问题,比方渲染结果非预期,另一方面会导致性能层面问题,比方渲染性能降低。
渲染结果非预期
在本示例中,通过设置的第三个参数函数,自定义键值天生规则为数据源的索引的字符串类型值。当点击父组件中“在第1项后插入新项”文本组件后,界面会出现非预期的结果。ForEachKeyGeneratorindexParent
- @Entry
- @Component
- struct Parent {
- @State simpleList: Array<string> = ['one', 'two', 'three'];
- build() {
- Column() {
- Button() {
- Text('在第1项后插入新项').fontSize(30)
- }
- .onClick(() => {
- this.simpleList.splice(1, 0, 'new item');
- })
- ForEach(this.simpleList, (item: string) => {
- ChildItem({ item: item })
- }, (item: string, index: number) => index.toString())
- }
- .justifyContent(FlexAlign.Center)
- .width('100%')
- .height('100%')
- .backgroundColor(0xF1F3F5)
- }
- }
- @Component
- struct ChildItem {
- @Prop item: string;
- build() {
- Text(this.item)
- .fontSize(30)
- }
- }
复制代码 上述代码的初始渲染结果(左图)和点击“在第1项后插入新项”文本组件后的渲染结果(右图)如下图所示。
图8 渲染结果非预期运行结果图
ForEach在首次渲染时,创建的键值依次为"0"、"1"、"2"。
插入新项后,数据源变为,框架监听到装饰的数据源长度变革触发重新渲染。simpleList['one', 'new item', 'two', 'three']@StateForEach
ForEach依次遍历新数据源,遍历数据项"one"时天生键值"0",存在雷同键值,因此不创建新组件。继续遍历数据项"new item"时天生键值"1",存在雷同键值,因此不创建新组件。继续遍历数据项"two"天生键值"2",存在雷同键值,因此不创建新组件。最后遍历数据项"three"时天生键值"3",不存在雷同键值,创建内容为"three"的新组件并渲染。
从以上可以看出,当终极键值天生规则包含时,渴望的界面渲染结果为,而现实的渲染结果为,渲染结果不符合开辟者预期。因此,开辟者在使用时应只管制止终极键值天生规则中包含。index['one', 'new item', 'two', 'three']['one', 'two', 'three', 'three']ForEachindex
渲染性能降低
在本示例中,的第三个参数函数处于缺省状态。根据上述键值天生规则,此例使用框架默认的键值天生规则,即终极键值为字符串。当点击“在第1项后插入新项”文本组件后,将必要为第2个数组项以及其后的全部项重新创建组件。ForEachKeyGeneratorindex + '__' + JSON.stringify(item)ForEach
- @Entry
- @Component
- struct Parent {
- @State simpleList: Array<string> = ['one', 'two', 'three'];
- build() {
- Column() {
- Button() {
- Text('在第1项后插入新项').fontSize(30)
- }
- .onClick(() => {
- this.simpleList.splice(1, 0, 'new item');
- console.log(`[onClick]: simpleList is ${JSON.stringify(this.simpleList)}`);
- })
- ForEach(this.simpleList, (item: string) => {
- ChildItem({ item: item })
- })
- }
- .justifyContent(FlexAlign.Center)
- .width('100%')
- .height('100%')
- .backgroundColor(0xF1F3F5)
- }
- }
- @Component
- struct ChildItem {
- @Prop item: string;
- aboutToAppear() {
- console.log(`[aboutToAppear]: item is ${this.item}`);
- }
- build() {
- Text(this.item)
- .fontSize(50)
- }
- }
复制代码 以上代码的初始渲染结果(左图)和点击"在第1项后插入新项"文本组件后的渲染结果(右图)如下所示。
图9 渲染性能降低案例运行结果图
点击“在第1项后插入新项”文本组件后,IDE的日记打印结果如下所示。
图10 渲染性能降低案例日记打印图
插入新项后,为、 、 三个数组项创建了对应的组件,并执行了组件的aboutToAppear()生命周期函数。这是因为:ForEachnew itemtwothreeChildItem
- 在首次渲染时,创建的键值依次为、、。ForEach0__one1__two2__three
- 插入新项后,数据源变为,ArkUI框架监听到装饰的数据源长度变革触发重新渲染。simpleList['one', 'new item', 'two', 'three']@StateForEach
- ForEach依次遍历新数据源,遍历数据项时天生键值,键值已存在,因此不创建新组件。继续遍历数据项时天生键值,不存在雷同键值,创建内容为的新组件并渲染。继续遍历数据项天生键值,不存在雷同键值,创建内容为的新组件并渲染。最后遍历数据项时天生键值,不存在雷同键值,创建内容为的新组件并渲染。one0__onenew item1__new itemnew itemtwo2__twotwothree3__threethree
只管此示例中界面渲染的结果符合预期,但每次插入一条新数组项时,都会为从该数组项起背面的全部数组项全部重新创建组件。当数据源数据量较大或组件结构复杂时,由于组件无法得到复用,将导致性能体验不佳。因此,除非必要,否则不保举将第三个参数函数处于缺省状态,以及在键值天生规则中包含数据项索引。ForEachKeyGeneratorindex
最后
有许多小同伴不知道学习哪些鸿蒙开辟技术?不知道必要重点把握哪些鸿蒙应用开辟知识点?而且学习时频繁踩坑,终极浪费大量时间。所以有一份实用的鸿蒙(HarmonyOS NEXT)资料用来跟着学习是非常有必要的。
这份鸿蒙(HarmonyOS NEXT)资料包含了鸿蒙开辟必把握的核心知识要点,内容包含了(ArkTS、ArkUI开辟组件、Stage模子、多端摆设、分布式应用开辟、音频、视频、WebGL、OpenHarmony多媒体技术、Napi组件、OpenHarmony内核、Harmony南向开辟、鸿蒙项目实战等等)鸿蒙(HarmonyOS NEXT)技术知识点。
希望这一份鸿蒙学习资料能够给各人带来帮助,有必要的小同伴自行领取,限时开源,先到先得~无套路领取!!
获取这份完备版高清学习路线,请点击→纯血版全套鸿蒙HarmonyOS学习资料
鸿蒙(HarmonyOS NEXT)最新学习路线
- HarmonOS就业必备技能
- HarmonOS多媒体技术
有了路线图,怎么能没有学习资料呢,小编也准备了一份团结鸿蒙官方发布笔记整理收纳的一套系统性的鸿蒙(OpenHarmony )学习手册(共计1236页)与鸿蒙(OpenHarmony )开辟入门教学视频,内容包含:ArkTS、ArkUI、Web开辟、应用模子、资源分类…等知识点。
获取以上完备版高清学习路线,请点击→纯血版全套鸿蒙HarmonyOS学习资料
《鸿蒙 (OpenHarmony)开辟入门教学视频》
《鸿蒙生态应用开辟V2.0白皮书》
《鸿蒙 (OpenHarmony)开辟基础到实战手册》
OpenHarmony北向、南向开辟环境搭建
《鸿蒙开辟基础》
- ArkTS语言
- 安装DevEco Studio
- 运用你的第一个ArkTS应用
- ArkUI声明式UI开辟
- .……
《鸿蒙开辟进阶》
- Stage模子入门
- 网络管理
- 数据管理
- 电话服务
- 分布式应用开辟
- 关照与窗口管理
- 多媒体技术
- 安全技能
- 任务管理
- WebGL
- 国际化开辟
- 应用测试
- DFX面向将来计划
- 鸿蒙系统移植和裁剪定制
- ……
《鸿蒙进阶实战》
- ArkTS实践
- UIAbility应用
- 网络案例
- ……
获取以上完备鸿蒙HarmonyOS学习资料,请点击→纯血版全套鸿蒙HarmonyOS学习资料
总结
总的来说,华为鸿蒙不再兼容安卓,对中年程序员来说是一个挑战,也是一个机会。只有积极应对变革,不断学习和提升自己,他们才能在这个变革的期间中立于不败之地。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |