收集官网faq:文档中心
一. ArkTS&ArkUI
1. 基础理论
1. 鸿蒙相关的生命周期都有哪些?
UIAbility生命周期:onCreate、onWindowStageCreate、onForeground、onBackground、onWindowStageDestroy、onDestroy。
-
onCreate:Create状态为在应用加载过程中,UIAbility实例创建完成时触发,系统会调用onCreate()回调。可以在该回调中进行页面初始化操作,例如变量定义资源加载等,用于后续的UI展示。
-
onWindowStageCreate():UIAbility实例创建完成之后,在进入Foreground之前,系统会创建一个WindowStage。WindowStage创建完成后会进入onWindowStageCreate()回调,可以在该回调中设置UI加载、设置WindowStage的事件订阅。事件订阅代码
-
onForegound():在UIAbility的UI可见之前,如UIAbility切换至前台时触发。可以在onForeground()回调中申请系统需要的资源,或者重新申请在onBackground()中释放的资源。
-
onWindowStageDestory():在UIAbility实例销毁之前,则会先进入onWindowStageDestroy()回调,可以在该回调中释放UI资源。
-
onBackground():在UIAbility的UI完全不可见之后,如UIAbility切换至后台时候触发。可以在onBackground()回调中释放UI不可见时无用的资源,或者在此回调中执行较为耗时的操作,例如状态保存等。
-
onWindowStageDestory():在UIAbility实例销毁之前,则会先进入onWindowStageDestroy()回调,可以在该回调中释放UI资源。
-
onDestroy():Destroy状态在UIAbility实例销毁时触发。可以在onDestroy()回调中进行系统资源的释放、数据的保存等操作。
页面生命周期:onPageShow、onPageHide、onBackPress。
-
页面生命周期,说白了就是@Entry修饰的组件,才称之为页面。
-
onPageShow:页面每次显示时触发一次,包括路由过程、应用进入前台等场景,仅@Entry装饰的自定义组件生效。
-
onPageHide:页面每次隐藏时触发一次,包括路由过程、应用进入后台等场景,仅@Entry装饰的自定义组件生效。
-
onBackPress:当用户点击返回按钮时触发,仅@Entry装饰的自定义组件生效。
组件生命周期:aboutToAppear(发起网络请求)、aboutToDisappear。
-
aboutToAppear:在创建自定义组件的新实例后,在执行其build()函数之前执行。允许在aboutToAppear函数中改变状态变量,更改将在后续执行build()函数中生效。
-
aboutToDisappear:函数在自定义组件销毁之前执行。不允许在aboutToDisappear函数中改变状态变量,特别是@Link变量的修改可能会导致应用程序行为不稳定。
按返回键页面执行生命周期方法:
-
打开第一个页面:
-
Index:aboutToAppear
-
Index:onPageShow
-
-
跳转第二个页面:
-
Index:onPageHide
-
Second:aboutToAppear
-
Second:onPageShow
-
-
点击back: 如果是在第二个页面跳转到第一个页面:
-
Second:onBackPress Second:onPageHide
-
Second:onPageHide Index:aboutToAppear
-
Index:onPageShow Index:onPageShow
-
Second:aboutToDisappear
-
返回页面不走aboutToAppear:
-
aboutToAppear函数在创建自定义组件的新实例后,在执行其build()函数之前执行。
-
返回页面时==不需要走重新创建==,不会执行aboutToAppear,只会执行onPageShow。
aboutToAppear和onAppear的区别?
-
aboutToAppear:是组件的生命周期方法,当组件实例创建后,执行build函数之前执行aboutToAppear
-
onAppear:是组件的属性方法,在该组件显示时触发此回调
Text() .onAppear(()=>{}
2. ArkUI的两大开发范式是什么,区别是什么 ***
-
ArkUI推荐使用声明式开发范式 , 其他的框架有参考类Web开发范式
-
类Web开发范式:采用经典的HML、CSS、JavaScript三段式开发方式,即使用HML标签文件搭建布局、使用CSS文件描述样式、使用JavaScript文件处理逻辑。该范式更符合于Web前端开发者的使用习惯,便于快速将已有的Web应用改造成方舟UI框架应用。
-
声明式开发范式:采用基于TypeScript声明式UI语法扩展而来的ArkTS语言,从组件、动画和状态管理三个维度提供UI绘制能力。
-
==延伸问题==:有听过命令式编程么,命令式编程与声明式编程的区别是什么?
-
其实这里的命令式编程,就相当于是类Web开发范式
-
-
我们来对比一下命令式和声明式
-
声明式开发范式 : 只需要描述/声明 , 你要做什么 (通过封装好的组件以及相关熟悉方法 , 快速实现目的)
-
命令式开发范式 : 不仅需要知道做什么 , 更需要知道如何做 (需要通过最原始的方式 , 一步一步实现)
-
左侧是纯前端实现的按钮点击 , 也就是命令式
-
命令式需要自己一点一点实现 , 如何做必须清楚
-
-
右侧是ArkTs实现的按钮点击 , 也就是声明式
-
声明式只需要知道做什么 , 调用对应api即可 , 不需要知道内部如何实现
-
3. 项目使用的是harmoneyos还是openharmoney,区别是啥
我们公司的项目使用的是HarmonyOS。
HarmonyOS:OpenHarmony+闭源应用和华为移动服务HMS(比如应用市场,视频,音乐等app)
Andoird:aosp(android open source project) + GMS(Google Mobile Service)
3. 关于context相关得内容有哪些 , 他们的区别?
各类Context的继承关系:
使用:
-
page中获取上下文:通过函数getContext获取
import common from '@ohos.app.ability.common'; private context:common.UIAbilityContext = getContext(this) as common.UIAbilityContext
-
Ability中获取上下文: 直接通过this获取context
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void { let uiAbilityContext = this.context; ... }
理论:
基类Context提供了获取应用文件路径的能力,ApplicationContext、AbilityStageContext、UIAbilityContext和ExtensionContext均继承该能力。应用文件路径属于应用沙箱路径,具体请参见应用沙箱目录。
上述各类Context获取的应用文件路径有所不同。
通过ApplicationContext==获取应用级别的应用文件路径==,此路径是应用全局信息推荐的存放路径,这些文件会跟随应用的卸载而删除。
import common from '@ohos.app.ability.common'; @Entry @Component struct Page_Context { private context = getContext(this) as common.UIAbilityContext; build() { ... Button() .onClick(() => { let applicationContext = this.context.getApplicationContext(); let cacheDir = applicationContext.cacheDir;//<路径前缀>/<加密等级>/base/cache let tempDir = applicationContext.tempDir; let filesDir = applicationContext.filesDir; let databaseDir = applicationContext.databaseDir; let bundleCodeDir = applicationContext.bundleCodeDir; let distributedFilesDir = applicationContext.distributedFilesDir; let preferencesDir = applicationContext.preferencesDir; // 获取应用文件路径 let filePath = tempDir + 'test.txt'; hilog.info(DOMAIN_NUMBER, TAG, `filePath: ${filePath}`); if (filePath !== null) { promptAction.showToast({ message: filePath }); } }) } }
通过AbilityStageContext、UIAbilityContext、ExtensionContext==获取HAP级别的应用文件路径==。此路径是HAP相关信息推荐的存放路径,这些文件会跟随HAP的卸载而删除,但不会影响应用级别路径的文件,除非该应用的HAP已全部卸载。
import common from '@ohos.app.ability.common'; @Entry @Component struct Page_Context { private context = getContext(this) as common.UIAbilityContext; build() { ... Button() .onClick(() => { let cacheDir = this.context.cacheDir;//<路径前缀>/<加密等级>/base/haps/<module-name>/cache let tempDir = this.context.tempDir; let filesDir = this.context.filesDir; let databaseDir = this.context.databaseDir; let bundleCodeDir = this.context.bundleCodeDir; let distributedFilesDir = this.context.distributedFilesDir; let preferencesDir = this.context.preferencesDir; // 获取应用文件路径 let filePath = tempDir + 'test.txt'; }) } }
4. arkts中哪些类不能被继承, 面试官关注点是组件是否可以继承?(组件是否可以被继承)
组件不能被继承,被@compent修饰的自定义组件不能被继承,只能引用,或者对外暴露方法。
官网解释:
-
struct:自定义组件基于struct实现,struct + 自定义组件名 + {...}的组合构成自定义组件,==不能有继承关系==。对于struct的实例化,可以省略new。
-
@Component:@Component装饰器仅能装饰struct关键字声明的数据结构。==struct被@Component装饰后具备组件化的能力==,需要实现build方法描述UI,一个struct只能被一个@Component装饰。@Component可以接受一个可选的bool类型参数。
@Component struct MyComponent { }
5.介绍Stage模型和FA模型
-
Stage模型 : HarmonyOS 3.1推出 也就是API9 , 是目前==主推==且会长期演进的模型
-
由于提供了AbilityStage、WindowStage等类作为应用组件和Window窗口的“舞台”,因此称这种应用模型为Stage模型
-
stage: 舞台 /steɪdʒ/
-
-
FA模型: FA(Feature Ability)模型:HarmonyOS早期版本开始支持的模型,已经不再主推
-
feature: 特点 /ˈfiːtʃə(r)/
-
-
区别: Stage模型与FA模型最大的区别在于
-
Stage模型中,多个应用组件共享同一个ArkTS引擎实例;
-
而FA模型中,每个应用组件独享一个ArkTS引擎实例。
-
因此在Stage模型中,应用组件之间可以方便的共享对象和状态,同时减少复杂应用运行对内存的占用。
-
Stage模型作为主推的应用模型,开发者通过它能够更加便利地开发出分布式场景下的复杂应用。
-
2. 装饰器
1. 你使用过哪些装饰器,分别阐述一下他们得作用 ***
-
@State装饰器,使得变量变为状态变量,影响UI(数据变化,UI变化)
-
@Prop装饰的变量和父组件建立单向的同步关系:
-
父组件的@State数据变化,会同步到子组件@Prop
-
具体用法
//父组件:Parent @State num:number = 0 build(){ Son({num:this.num}) } //子组件:Son @Prop num:number
-
@Prop修饰的变量,api9不能初始化,api11能初始化
-
-
@Link装饰的变量与其父组件中的数据源共享相同的值。
-
父组件的@State数据变化,会同步到子组件@Link数据
-
子组件@link数据变化,会同步到父组件@State数据
-
具体用法
//父组件:Parent @State num:number = 0 build(){ Son({num:$num})//api9必须使用$,api11开始也可以使用this了 } //子组件:Son @Link num:number
-
@Link修饰的变量,api9不能初始化,api11不能初始化
-
2. 有用过@Styles,@Extend,@Builder装饰器么?
-
@Styles装饰器:定义组件重用样式 (多个组件通用的样式)
@Styles装饰器,用于封装重复的通用样式代码。
如果多个不同类型的组件,有着相同的样式,例如宽高,背景色,字体大小。那么就可以将这下相同的样式代码抽取到一个@Styles装饰器修饰的方法中,供大家复用。
支持全局和局部定义:
// 全局 @Styles function functionName() { ... } //styles方法不能调用另一个styles方法*** // 在组件内 @Component struct FancyUse { @Styles fancy() { .height(100) } }
-
@Extend装饰器:定义扩展组件样式 (某一种组件自己的样式,私有属性)
@Extend,用于扩展原生组件样式。
如果同一类型的组件,有着很多相同的样式,例如按钮的类型,点击事件等。那么就可以将这些重复代码,抽过去到一个@Extend装饰器修饰的方法中,供此组件使用。
==仅支持全局定义==:(因为它相当于是给所有的此类组件使用)
// @Extend(Text)可以支持Text的私有属性fontColor @Extend(Text) function fancy () { .fontColor(Color.Red) } // superFancyText可以调用预定义的fancy @Extend(Text) function superFancyText(size:number) { //Extend方法可以调用另一个Extend方法 .fontSize(size) .fancy() }
-
@Builder装饰器:自定义构建函数
@Builder装饰器,用于封装重复的,复杂UI结构代码,例如List中的ListItem的布局结构,一般比较复杂就可以抽取到@Builder装饰的函数中
@Builder所装饰的函数遵循build()函数语法规则,开发者可以将重复使用的UI元素抽象成一个方法,在build方法里调用。
支持全局定义和局部定义:
//既然调用是通过this调用,那么说明是在组件内部定义 //组件内部定义不需要关键字function @Builder MyBuilderFunction() { ... } //全局定义 MyGlobalBuilderFunction()
-
@BuilderParam装饰器:引用@Builder函数
当开发者创建了自定义组件,并想对该组件添加特定功能时,例如在自定义组件中添加一个点击跳转操作。若直接在组件内嵌入事件方法,将会导致所有引入该自定义组件的地方均增加了该功能。
为解决此问题,ArkUI引入了@BuilderParam装饰器,该装饰器用于声明任意UI描述的一个元素,==类似slot占位符==。
-
使得自定义组件更加灵活
代码:
@Component struct Child { @Builder customBuilder() {} // 使用父组件@Builder装饰的方法初始化子组件@BuilderParam @BuilderParam customBuilderParam: () => void = this.customBuilder; build() { Column() { this.customBuilderParam() } } } @Entry @Component struct Parent { @Builder componentBuilder() { Text(`Parent builder `) } build() { Column() { Child({ customBuilderParam: this.componentBuilder }) } } }
-
3. 还用过其他装饰器么?
1. Provide和Consume
-
@Provide和@Consume,用于祖先与后代组件的双向数据同步,实现跨层级传递
-
==理解==:@Provide装饰器的变量是在祖先组件中,可以理解为被“提供”给后代的状态变量。@Consume装饰的变量是在后代组件中,去“消费”数据
-
-
语法特点:
-
@Provide和@Consume可以通过相同的变量名或者相同的变量别名绑定
// 通过相同的变量名绑定 @Provide a: number = 0; //祖先组件中定义 @Consume a: number; //子孙组件中定义 // 通过相同的变量别名绑定 @Provide('a') b: number = 0;//参数即为别名 @Consume('a') c: number;
-
2. ObjectLink和Observed
-
@ObjectLink和@Observed类装饰器用于在涉及嵌套对象或数组的场景中进行双向数据同步:
-
被@Observed装饰的类,可以被观察到属性的变化;
-
子组件中@ObjectLink装饰器装饰的状态变量用于接收@Observed装饰的类的实例,和父组件中对应的状态变量建立双向数据绑定。这个实例可以是数组中的被@Observed装饰的项,或者是class object中的属性,这个属性同样也需要被@Observed装饰。
-
单独使用@Observed是没有任何作用的,需要搭配@ObjectLink或者@Prop使用。
-
-
详情参考: 3.状态管理.md --> 2.5章节
3. Watch
概述
-
@Watch应用于==对状态变量的监听==。如果开发者需要关注某个状态变量的值是否改变,可以使用@Watch为状态变量设置回调函数。
-
@Watch用于监听状态变量的变化,当状态变量变化时,@Watch的回调方法将被调用
代码:
@Component struct TotalView { @Prop @Watch('onCountUpdated') count: number = 0; @State total: number = 0; // 该函数是自定义组件的成员函数 // @Watch 回调 // propName是被watch的属性名 // 多个状态绑定同一个@Watch回调时,通过propName区分到底是哪个状态改变了 onCountUpdated(propName: string): void { this.total += this.count; } build() { Text(`Total: ${this.total}`) } } @Entry @Component struct CountModifier { @State count: number = 0; build() { Column() { Button('add to basket') .onClick(() => { this.count++ }) TotalView({ count: this.count }) } } }
3. 数据存储
1. LocalStorage和AppStorage的区别,和对应的装饰器以及PersistentStorage ***
LocalStorage
页面级UI状态存储,通常用于UIAbility内、页面间的状态共享。
localStorage是页面级数据存储,在页面中创建实例,组件中使用@LocalStorageLink和@LocalStorageProp装饰器修饰对应的状态变量,绑定对应的组件使用比状态属性更灵活
//应用逻辑使用LocalStorage let para: Record<string,number> = { 'PropA': 47 }; let storage: LocalStorage = new LocalStorage(para); // 创建新实例并使用给定对象初始化 , 创建实例存储数据 let propA: number | undefined = storage.get('PropA') // propA == 47 ,get()获取数据 //link():如果给定的propName在LocalStorage实例中存在,则返回与LocalStorage中propName对应属性的双向绑定数据。 (双向同步) let link1: SubscribedAbstractProperty<number> = storage.link('PropA'); // link1.get() == 47 let link2: SubscribedAbstractProperty<number> = storage.link('PropA'); // link2.get() == 47 //prop():如果给定的propName在LocalStorage中存在,则返回与LocalStorage中propName对应属性的单向绑定数据。 (单向同步) let prop: SubscribedAbstractProperty<number> = storage.prop('PropA'); // prop.get() == 47 link1.set(48); // two-way sync: link1.get() == link2.get() == prop.get() == 48 //双向同步 prop.set(1); // one-way sync: prop.get() == 1; but link1.get() == link2.get() == 48 //单向同步 link1.set(49); // two-way sync: link1.get() == link2.get() == prop.get() == 49
-
new LocalStorage(数据Object)
创建实例并存储数据 -
set方法,设置数据
-
get方法,获取数据
-
link方法,返回一个双向同步的变量
-
与UI逻辑中@LocalStorageLink装饰器类似
-
-
prop方法,返回单向同步的变量
-
与UI逻辑中@LocalStorageProp装饰器类似
-
//UI使用LocalStorage //除了应用程序逻辑使用LocalStorage,还可以借助LocalStorage相关的两个装饰器@LocalStorageProp和@LocalStorageLink,在UI组件内部获取到LocalStorage实例中存储的状态变量。 // 创建新实例并使用给定对象初始化 let para: Record<string, number> = { 'PropA': 47 }; //这个变量一般就定义在某个@Entry组件的上方** let storage: LocalStorage = new LocalStorage(para); @Component struct Child { // @LocalStorageLink变量装饰器与LocalStorage中的'PropA'属性建立双向绑定 @LocalStorageLink('PropA') storageLink2: number = 1; build() { Button(`Child from LocalStorage ${this.storageLink2}`) // 更改将同步至LocalStorage中的'PropA'以及Parent.storageLink1 .onClick(() => { this.storageLink2 += 1 }) } } // 使LocalStorage可从@Component组件访问 @Entry(storage)//storage是上边定义的变量** @Component struct Parent { // @LocalStorageLink变量装饰器与LocalStorage中的'PropA'属性建立双向绑定 @LocalStorageLink('PropA') storageLink1: number = 1; build() { Column({ space: 15 }) { Button(`Parent from LocalStorage ${this.storageLink1}`) // initial value from LocalStorage will be 47, because 'PropA' initialized already .onClick(() => { this.storageLink1 += 1 &