鸿蒙
基础
你现在用的是鸿蒙几的api和编辑器?
我现在用的是Harmony Next的API12,编译器是DevEco Studio Next Bate5版本。
HAP、HSP、HAR区别
HAP:应用安装和运行的基本单元。支持在配置文件中声明abilities、extensionAbilities组件,支持在配置文件中声明pages页面。
主要使用场景:
Entry:应用的主模块,用于实现应用的入口界面、入口图标、主特性功能等。
Feature:应用的特性模块,用于实现应用的特性功能。
HAR:静态共享包。编译态复用,不支持在配置文件中声明abilities、extensionAbilities组件,不支持在配置文件中声明pages页面,支持Navigation组件导航。
主要使用场景:
作为二方库,发布到OHPM私仓,供公司内部其他应用依赖使用。
作为三方库,发布到OHPM中心仓,供其他应用依赖使用。
HSP:动态共享包。运行时复用,不支持在配置文件中声明abilities、extensionAbilities组件,支持在配置文件中声明pages页面。
主要使用场景:
多模块共用的代码、资源可以使用HSP,提高代码的可重用性和可维护性。
元服务分包预加载。
ArkTS和TS有什么区别?
ArkTs 基于 Ts 做了扩展,并且强化了静态检查和分析
一、扩展了 UI:
- 定义了声明式 UI 描述、自定义组件,事件方法、属性方法
- 提供了多维度的状态管理机制
- 提供了控制渲染、循环渲染的能力
二、强化了检查
- 不支持 var、any、unknown、Symbol
- 不支持解构赋值
- 不支持使用对象字面量进行类型声明
- 不支持在运行时动态增删对象的属性
- 不支持在函数内声明函数
- 不支持使用 typeof 作为类型
- 不支持使用 # 符号开头声明的私有字段,改用 private 关键字
- 不支持把 function 定义函数赋值给变量,改为使用箭头函数
| 对比项 | ArkTS | TypeScript (TS) |
|---|---|---|
| 设计目标 | 为 OpenHarmony(鸿蒙)设计的扩展语言,强调声明式 UI 和跨端开发能力。 | JavaScript 的超集,增强类型安全,适用于通用全栈开发。 |
| 类型系统 | 强制静态类型,必须显式声明类型(更严格)。 | 支持静态类型,但允许隐式推断或使用 any 绕过类型检查。 |
| 开发范式 | 声明式 UI + 响应式编程,内置状态管理(如 @State、@Link 装饰器)。 | 无固定范式,需依赖第三方框架(如 React、Vue)实现 UI 逻辑。 |
| 组件系统 | 内置 OpenHarmony 组件(如 @Component、@Entry),直接调用系统能力。 | 无内置组件,需结合框架(如 Angular、React)实现组件化。 |
| 运行环境 | 依赖 OpenHarmony 的 ArkUI 框架和方舟编译器,运行于鸿蒙设备。 | 转译为 JavaScript 后运行在浏览器、Node.js 等 JS 引擎环境。 |
| 生态系统 | 绑定 OpenHarmony SDK,专注于鸿蒙设备(手机、IoT、智能硬件等)。 | 庞大的 JS/TS 生态,支持 Web、服务端、跨平台框架(如 Flutter)。 |
| 工具链 | 需使用 DevEco Studio 和鸿蒙 SDK 进行开发、编译和调试。 | 通用工具链(如 VS Code、Webpack、Babel)即可开发。 |
| 语法扩展 | 新增鸿蒙专属装饰器和 API(如 @Preview、@Watch、系统接口调用)。 | 支持通用装饰器和类型特性,无平台特定语法扩展。 |
关键备注
- 兼容性:TS 代码可在 ArkTS 中运行,但 ArkTS 的鸿蒙专属语法(如装饰器、组件)无法直接在 TS 中使用。
- 定位:
- ArkTS 是 鸿蒙生态专用语言,深度集成 OpenHarmony 能力(如跨端协同、硬件直连)。
- TS 是 通用开发语言,服务于 Web、移动端、后端等多场景。
ArkTS 在很大程度上借鉴了 TS 的语法,并且延用了诸多 ES6 的方法和属性。像常用的 Object.keys,还有字符串以及数组相关的方法等都有所延用。
然而,ArkTS 有着更为严苛的规范:
a. 在动态类型方面,它几乎进行了全面禁止。例如解构赋值在 ArkTS 中是被禁用的;
b. 延展运算符只能应用于数组,不能用于其他场景;
c. 禁止使用 call/apply/bind 这些方法来改变 this 的指向;
d. 字面量类型在 ArkTS 中也无法使用;
e. any 和 unknown 类型同样被禁用。
f. 要求必须明确声明一些数据的类型,比如用 State 修饰的变量就需要明确其类型。
总的来说,ArkTS 虽然借鉴了 TS 的一些优势之处,但出于对性能的考虑,屏蔽了 TS 在类型方面可能带来的一些短板,它在继承与创新中找到了一个独特的平衡点,以满足鸿蒙系统开发等场景下对于高效、稳定、安全编程的要求。
ArkTS常见的数据类型有哪些?
基本数据类型
- number: 整数和浮点数。如
let num = 10; - bigint: 任意大整数,加
n标识。如let big = 123n; - string: 文本,单 / 双 / 反引号定义。如
let str = 'hi'; - boolean:
true或false。如let isOk = true; - void: 函数无返回值。如
function f(): void {} - null: 空对象指针,值为
null。 - undefined: 变量声明未赋值时值为
undefined。
引用数据类型
- Object: 任意对象。如
let obj = {key: 'val'}; - Array: 存储相同类型值。如
let arr: number[] = [1, 2]; - Tuple: 已知数量和类型的数组,元素类型可不同。如
let t: [string, number] = ['a', 1]; - enum: 命名常量集合。如
enum E {A, B};
ArkTS常见的数据类型有哪些? 什么地方用到了枚举?
ArkTs常见的数据类型
①基本数据类型②复合数据类型③对象类型④函数类型⑤高级类型
什么地方用到了枚举?
①表示固定的离散状态或取值范围②自定义常量列表的时候提高代码的可维护性③作为参数或者返回值
ArkTS(基于 TypeScript 的 HarmonyOS 开发语言)支持多种数据类型,主要包括:
- 基本数据类型 • number:整数或浮点数
• string:字符串
• boolean:布尔值(true/false)
• bigint:大整数
• undefined:未定义的值
• null:空值
- 复杂数据类型 • Array:数组
• Tuple:元组(固定长度和类型的数组)
• Object:对象
• Function:函数
• Date:日期对象
• RegExp:正则表达式
- 特殊数据类型 • any:任意类型 ArkTS中禁止使用any类型。(不推荐广泛使用)
// 不支持: let res: any = some_api_function('hello', 'world'); //
res是什么?错误代码的数字?字符串?对象? // 该如何处理它? // 支持: class CallResult { public succeeded(): boolean { ... } public errorMessage(): string { ... } }let res: CallResult = some_api_function('hello', 'world'); if (!res.succeeded()) { console.log('Call failed: ' + res.errorMessage()); }
• unknown:未知类型(比 any 更安全)
• void:无返回值
• never:永不返回的值(如抛出异常的函数)
- HarmonyOS 特有类型 • ResourceStr:资源字符串类型
• PixelMap:图片像素图类型
• Want:用于进程间通信的数据结构
• ElementName:组件名称类型
枚举(Enum)的应用场景
枚举在 ArkTS 中用于定义一组命名的常量,主要应用在以下场景:
- 状态管理
enum UserStatus {
ACTIVE = 'active',
INACTIVE = 'inactive',
SUSPENDED = 'suspended'
}
let currentUserStatus: UserStatus = UserStatus.ACTIVE;
- 错误码定义
enum ErrorCode {
SUCCESS = 0,
NOT_FOUND = 404,
UNAUTHORIZED = 401,
SERVER_ERROR = 500
}
function handleError(code: ErrorCode) {
switch(code) {
case ErrorCode.NOT_FOUND:
console.log('Resource not found');
break;
// ...
}
}
- 配置选项
enum LogLevel {
DEBUG = 'debug',
INFO = 'info',
WARN = 'warn',
ERROR = 'error'
}
const appConfig = {
logLevel: LogLevel.INFO
};
- 方向控制
enum Direction {
UP,
DOWN,
LEFT,
RIGHT
}
function move(direction: Direction) {
// 根据方向值执行不同操作
}
- UI 组件类型
enum ComponentType {
BUTTON,
TEXT,
IMAGE,
LIST
}
function createComponent(type: ComponentType) {
// 创建不同类型的组件
}
枚举的高级用法
- 常量枚举(Const Enums)
const enum Direction {
UP,
DOWN,
LEFT,
RIGHT
}
const dir = Direction.UP; // 编译为 dir = 0
- 异构枚举(Heterogeneous Enums)
enum BooleanLikeHeterogeneousEnum {
No = 0,
Yes = "YES"
}
注意:不推荐使用,可能导致类型不安全
- 枚举成员类型
enum ShapeKind {
Circle,
Square,
}
interface Circle {
kind: ShapeKind.Circle;
radius: number;
}
interface Square {
kind: ShapeKind.Square;
sideLength: number;
}
type Shape = Circle | Square;
最佳实践建议
- 优先使用枚举而非魔法数字/字符串,提高代码可读性和可维护性
- 为枚举添加明确的类型注释,便于类型检查
- 考虑使用常量枚举优化性能(减少运行时开销)
- 避免过度使用枚举,简单场景可用联合类型替代
- 注意枚举的序列化问题,某些场景可能需要自定义序列化逻辑
ArkTS 的枚举机制与 TypeScript 基本一致,但在 HarmonyOS 开发中,合理使用枚举可以显著提升代码质量和开发效率。
生命周期
ArkTS中应用、页面、组件的生命周期有哪些?
应用生命周期 :
- onCreate:应用首次创建时触发,用于初始化全局资源(如读取系统配置)。
- onWindowStageCreate :窗口阶段创建时触发,通过
loadContent加载页面(如设置首页)。 - onForeground :应用从后台切换到前台时触发,可重新申请资源(如恢复定位功能)。
- onBackground :应用进入后台时触发,需释放非必要资源(如停止后台服务)。
- onWindowStageDestroy:窗口阶段销毁时触发,释放UI相关资源(如关闭页面动画)。
- onDestroy:应用完全销毁前触发,用于保存数据或清理内存。
页面级生命周期(下边都是需注意区分)(仅 @Entry 修饰的组件)因为@Entry 也是@Component组件,所以页面组件同时拥有自定义组件的生命周期
页面生命周期,即被@Entry装饰的组件生命周期,提供以下生命周期接口:
- onPageShow:页面每次显示时触发一次,包括路由过程、应用进入前台等场景。
- onPageHide:页面每次隐藏时触发一次,包括路由过程、应用进入后台等场景。
- onBackPress:当用户点击返回按钮时触发。
组件生命周期,即一般用@Component装饰的自定义组件的生命周期,提供以下生命周期接口:
- aboutToAppear:组件即将出现时回调该接口,具体时机为在创建自定义组件的新实例后,在执行其build()函数之前执行。
- onDidBuild:组件build()函数执行完成之后回调该接口,开发者可以在这个阶段进行埋点数据上报等不影响实际UI的功能。不建议在onDidBuild函数中更改状态变量、使用animateTo等功能,这可能会导致不稳定的UI表现。
- aboutToDisappear:aboutToDisappear函数在自定义组件析构销毁之前执行。不允许在aboutToDisappear函数中改变状态变量,特别是@Link变量的修改可能会导致应用程序行为不稳定。
自定义组件有哪些生命周期
组件生命周期,即一般用@Component装饰的自定义组件的生命周期,提供以下生命周期接口:
aboutToAppear build()函数之前执行。
onDidBuild build()函数执行完成之后回调该接口,不建议在onDidBuild函数中更改状态变量、使用animateTo等功能,这可能会导致不稳定的UI表现。
aboutToDisappear :aboutToDisappear函数在自定义组件析构销毁之前执行。不允许在aboutToDisappear函数中改变状态变量,特别是@Link变量的修改可能会导致应用程序行为不稳定。
说说ability生命周期?
Ability 的生命周期包括以下主要阶段:
1.onCreate()
作用: 初始化应用的核心组件和数据,准备好 UI 或者其他相关内容。
2.onStart()
作用: 在界面显示之前的最后一步准备工作,可以处理一些界面相关的初始化。
3.onActive()
作用: Ability 进入活动状态,用户可以与之进行交互。
4.onInactive()
作用: 暂停与用户的交互,但 Ability 仍然可见。
5.onBackground()
作用: Ability 进入后台,可以释放一些不必要的资源来节省内存或电量。
6.onStop()
作用: Ability 彻底停止,清理所有资源。
7.onDestroy()
作用: 完全清理 Ability,通常在应用被终止时调用。
UIAbility 组件有哪些生命周期
UIAbility组件的生命周期包括四个状态:Create、Foreground、Background、Destroy,
在不同状态之间转换时,系统会调用相应的生命周期回调函数。
这些状态的具体描述如下
- Create状态:组件的创建阶段,此时组件刚刚被创建,但还未显示给用户。
- Foreground状态:组件已经显示在用户界面上,用户可以与该组件进行交互。
- Background状态:组件被切换到后台,不再显示在用户界面上,但仍保留在内存中。
- Destroy状态:组件被销毁,释放了所有相关资源。
此外,对于页面和自定义组件,还描述了额外的生命周期状态,如aboutToAppear、onPageShow、onPageHide、onBackPress、aboutToDisappear等,这些状态用于处理页面显示、隐藏、以及用户交互等场景。
UIAbility是包含UI界面的应用组件,提供组件创建、销毁、前后台切换等生命周期回调,同时也具备组件协同的能力。窗口模块用于在同一块物理屏幕上提供多个应用界面显示、交互的机制。
组件的生命周期?
aboutToAppear,build,onPageShow,Componentisvisiable,onPageHide,aboutToDisappear
说说ability生命周期?
onCreate(want, launchParam): 这个方法在Ability被创建时调用。want参数是一个Intent-like对象,包含了启动Ability所需的信息;launchParam则提供了启动Ability时的附加参数。此方法是初始化Ability的最佳位置,例如设置布局、初始化成员变量等。
onStart(want): 当Ability变为可见但尚未获得焦点时调用。如果Ability正在运行,则此方法不会被调用。
onResume(): 当Ability获得焦点并准备好接收用户输入时调用。这是Ability最活跃的状态。
onPause(): 当Ability失去焦点但仍对用户可见时调用。此时应该保存状态,但不阻塞主线程。
onStop(): 当Ability不再对用户可见时调用。此时可以释放一些资源,因为用户无法看到Ability的状态。
onDestroy(): 当Ability被销毁时调用。这是释放所有资源的最后机会,如关闭数据库连接、取消网络请求等。
装饰器
什么是装饰器,有什么作用,如何在ArkTS中使用类装饰器?
装饰器是一种特殊类型的声明,可以附加到类、方法、访问符、属性或参数上,以修改其行为。 ArkTS 中,装饰器提供了一种在声明时定义如何处理类的方法、属性或参数的机制。
function classDecorator<T extends { new(...args: any[]): {} }>(constructor: T) {
return class extends constructor {
newProperty = "new property";
hello = "override";
};
}
@classDecorator
class Greeter {
property = "property";
hello: string;
constructor(m: string) {
this.hello = m;
}
}
console.log(new Greeter("world")); // 输出 { property: 'property', hello: 'override', newProperty: 'new property' }
用过哪些装饰器,分别介绍一下?枚举的使用场景
好的,我们再详细梳理一下 ArkTS 中常用的装饰器及其使用场景,以及枚举(Enum)的使用场景。
一、 ArkTS 常用装饰器及其使用场景
装饰器是 ArkTS 中用于给类、结构体、方法、属性等添加额外功能或元数据的特殊语法。以下是一些核心的装饰器:
@Component:- 介绍: 标记一个
struct为自定义组件。这是构建可复用 UI 单元的基础。 - 使用场景: 当你需要将一部分 UI 和相关逻辑封装成一个独立的、可复用的单元时使用。例如,创建一个自定义按钮、卡片、列表项等。
- 示例:
@Component struct MyCustomButton { // ... component definition ... }
- 介绍: 标记一个
@Entry:- 介绍: 标记一个
@Component为页面的入口。一个应用可以有多个入口,例如不同的 Ability 或页面。 - 使用场景: 指定哪个组件作为用户能直接看到和交互的起始页面。
- 示例:
@Entry @Component struct IndexPage { // ... page definition ... }
- 介绍: 标记一个
@State:- 介绍: 定义组件内部的可变状态。当
@State变量的值改变时,框架会自动重新渲染该组件及其子组件中依赖此状态的部分。 - 使用场景: 管理那些只影响当前组件自身显示或行为的数据。例如,计数器、输入框的内容、开关的状态等。
- 示例:
@Component struct Counter { @State count: number = 0; // ... build function uses this.count ... }
- 介绍: 定义组件内部的可变状态。当
@Prop:- 介绍: 用于父组件向子组件单向传递数据。子组件接收
@Prop变量,但不能直接修改它。父组件的数据源变化会自动同步到子组件。 - 使用场景: 将父组件的数据传递给子组件进行展示或基于该数据进行计算。例如,传递用户名给一个欢迎组件,传递列表项数据给列表项组件。
- 示例:
// Parent @State userName: string = 'Alice'; MyChildComponent({ user: this.userName }) // Child @Component struct MyChildComponent { @Prop user: string; // ... build function uses this.user ... }
- 介绍: 用于父组件向子组件单向传递数据。子组件接收
@Link:- 介绍: 实现父子组件间的双向数据绑定。父组件传递
@State变量,子组件用@Link接收。任何一方的修改都会同步到另一方。 - 使用场景: 当子组件需要修改父组件传递过来的状态时。例如,一个自定义输入框组件需要更新父组件中的文本状态,或者一个设置项开关需要改变父组件中的配置状态。
- 示例:
// Parent @State inputText: string = ''; MyInputComponent({ textValue: $inputText }) // Use $ for linking // Child @Component struct MyInputComponent { @Link textValue: string; // ... TextInput updates this.textValue ... }
- 介绍: 实现父子组件间的双向数据绑定。父组件传递
@Provide/@Consume:- 介绍: 用于跨越多层级的组件进行状态共享。祖先组件使用
@Provide提供数据,任何后代组件都可以使用@Consume来获取和监听这个数据,无需逐层传递。 - 使用场景: 全局主题、用户信息、应用配置等需要在应用中多个不直接相关的组件间共享的状态。
- 示例:
// Ancestor @Provide('themeColor') theme: string = '#FFFFFF'; // Descendant (can be many levels deep) @Consume('themeColor') providedTheme: string;
- 介绍: 用于跨越多层级的组件进行状态共享。祖先组件使用
@Observed/@ObjectLink:- 介绍:
@Observed标记一个类,使其属性变化可被观察。@ObjectLink在组件中持有@Observed类对象的引用,当对象的属性变化时,会触发组件刷新。 - 使用场景: 管理复杂的数据模型对象,当对象的内部属性变化需要驱动 UI 更新时。例如,一个包含多个属性的用户信息对象。
- 示例:
@Observed class UserProfile { name: string = ''; age: number = 0; } @Component struct ProfileView { @ObjectLink profile: UserProfile; // ... UI displays profile.name, profile.age ... }
- 介绍:
@Builder:- 介绍: 定义一个返回 UI 描述的函数,用于创建可复用的UI 结构片段。
- 使用场景: 在组件内部或外部定义可复用的 UI 块,特别适合列表项、网格项、卡片内容等重复性结构。可以接受参数,包括
@BuilderParam来传递 UI 内容。 - 示例:
@Builder function ListItemBuilder(item: MyItemData) { Row() { /* ... UI for list item ... */ } }
@Styles:- 介绍: 定义一组可复用的样式属性集合。
- 使用场景: 统一应用内的视觉风格,避免在多个组件中重复编写相同的样式代码。例如,定义通用的按钮样式、文本样式、边框样式等。
- 示例:
@Styles function primaryButtonStyle() { .width(100).height(40).backgroundColor(Color.Blue) } // Usage: Button().primaryButtonStyle()
@Extend:
- 介绍: 继承一个已有的
@Styles定义,并可以添加或覆盖其中的样式。 - 使用场景: 在基础样式上创建变体。例如,基于
primaryButtonStyle创建一个disabledPrimaryButtonStyle。 - 示例:
@Extend(primaryButtonStyle) function disabledPrimaryButtonStyle() { .opacity(0.5) }
@Watch:- 介绍: 监听某个状态变量的变化,并在其值改变时执行一个回调函数。
- 使用场景: 当某个状态变化时,需要执行一些副作用操作,如数据验证、发送网络请求、调用其他方法等。
- 示例:
@State value: string = ''; @Watch('onValueChanged') valueWatcher: string = this.value; onValueChanged(propName: string): void { console.log(`${propName} changed to ${this.value}`); // Perform side effect }
二、 枚举 (Enum) 的使用场景
枚举 (enum) 用于定义一组命名的常量集合。它能增强代码的可读性和可维护性。
表示有限的状态集合: 当一个变量或属性只能从一组预定义的、有限的选项中取值时。
- 场景: 网络请求状态 (
Loading,Success,Error,Idle)、订单状态 (Pending,Paid,Shipped,Completed)、用户角色 (Admin,Editor,Viewer)、方向 (Up,Down,Left,Right)。 - 好处: 代码更清晰,避免使用难以理解的“魔法数字”或字符串。
- 场景: 网络请求状态 (
定义配置或选项: 用于表示一组固定的配置选项。
- 场景: 主题模式 (
Light,Dark,System)、对齐方式 (Start,Center,End)、日志级别 (Debug,Info,Warn,Error)。 - 好处: 提供了类型安全的方式来处理这些选项。
- 场景: 主题模式 (
替代布尔值进行更清晰的表达: 当一个布尔值不足以清晰表达所有状态时。
- 场景: 一个操作的结果可能有多种状态,如
Success,Failure,Cancelled,而不是简单的true/false。
- 场景: 一个操作的结果可能有多种状态,如
作为函数参数或返回值的类型: 限制函数可以接受或返回的值的范围。
- 场景: 一个函数根据传入的
SortOrder(Ascending,Descending) 来排序数据。
- 场景: 一个函数根据传入的
示例:
enum NetworkStatus {
Idle,
Loading,
Success,
Error
}
@Component
struct DataFetcher {
@State status: NetworkStatus = NetworkStatus.Idle;
fetchData() {
this.status = NetworkStatus.Loading;
// ... perform network request ...
// On success: this.status = NetworkStatus.Success;
// On failure: this.status = NetworkStatus.Error;
}
build() {
Column() {
if (this.status === NetworkStatus.Loading) {
LoadingProgress()
} else if (this.status === NetworkStatus.Success) {
// Display data
} else if (this.status === NetworkStatus.Error) {
Text('Failed to load data.')
} else {
Button('Load Data').onClick(() => this.fetchData())
}
}
}
}
希望这次的解释更加清晰和全面!
| 装饰器 | 描述 |
|---|---|
@Component | 将结构体标记为可复用的UI组件,使其具有组件化能力,能够成为一个独立的组件。同时支持组件的生命周期管理和UI绘制。 |
@Entry | 将结构体标记为页面组件,代表一个完整的页面。被 @Entry装饰的组件会被视为当前页面的默认入口组件,支持页面预览和组件树结构的根节点。 |
@State | 标记一个类或对象的属性为状态信息,表示该属性会随着时间的推移而发生变化。当状态发生变化时,组件的UI会相应地更新。 |
@Prop | 在子组件中定义需要从父组件接收的属性,实现父组件到子组件的数据单向传递。 |
@Link | 达成父组件与子组件之间的数据双向传递,允许子组件修改并同步数据回父组件。 |
@Provide /@Consume | 跨组件传值,通过提供方(@Provide)和消费方(@Consume)的标记来共享和更新数据。 |
@StorageLink / @StorageProp | 作用于应用全局的UI状态存储,允许在应用的多个部分之间共享和更新状态。 |
@LocalStorageLink / @LocalStorageProp | 针对页面级别的UI状态存储,方便页面间数据共享和更新。 |
@Builder | 用于创建自定义构建函数,对重复的UI元素进行抽象,提高代码复用性。 |
@BuilderParam | 引用自定义构建函数,为组件添加特定功能或参数。 |
@Styles | 定义组件的样式集合,允许将多条样式设置提炼成一个方法,便于复用。 |
@Extend | 在@Styles的基础上,用于扩展组件的样式或行为,可抽离公共的样式。 |
@Preview | 允许组件在开发过程中进行预览,但注意它本身并不直接作为装饰器使用,而是通过与其他装饰器(如@Entry和@Component)结合来实现预览功能。 |
@State 是什么原理?怎么实现的?
原理是
1.封装状态:state 装饰器可以将状态封装到组件中,使得状态管理更加集中和简洁
2.增强功能:通过装饰器,可以在类定义时自动添加状态相关的方法,例如更新状态、初始化状态等。
反应式更新:状态变化通常会引起组件重新渲染,这种机制利用了框架的生命周期方法
3.或响应式编程的特性。
实现是
1.创建装饰器:定义一个装饰器函数,接受一个组件类作为参数。
通常使用this.state。
2.初始化状态:在构造函数中初始化状态,
3.添加更新方法:添加一个方法用于更新状态,并确保可以触发组件的重新渲染。
4.返回增强后的组件:返回一个新的组件,包含原始组件的功能和新的状态管理逻辑
@Entry修饰符 有跟没有是什么区别,加了@Entry修饰符和不加的生命周期有什么区别
@Entry 修饰符标记的应用组件会在应用启动时自动加载和初始化,是应用的入口。未标记 @Entry 的组件则不会自动加载,需通过其他组件调用或事件触发来实例化
有用过哪些装饰器?
@Link, @ObjectLink
以下是ArkTS中一些常见的装饰器(Decorators):
- @Component:用于装饰struct,使结构体具有基于组件的能力,需要实现build方法来更新UI。
- @Entry:用于装饰struct,表明该组件作为页面的入口组件,页面加载时将被渲染显示。
- @State:装饰基本数据类型、类、数组等,使状态数据被修改时触发组件的build方法进行UI界面更新。它不具有同步能力,但可以用于父子组件之间的单向数据传递。
- @Prop:装饰基本数据类型,用于在父组件和子组件之间建立单向数据依赖关系。父组件的@State数据变化时,会同步到子组件的@Prop。
- @Link:装饰基本数据类型、类、数组等,在父子组件之间进行双向数据绑定。任何一方所做的修改都会反映给另一方。
- @Provide 和 @Consume:这两个装饰器搭配使用,用于祖先与后代组件的双向数据同步,实现跨层级传递。@Provide装饰的变量是在祖先组件中,被“提供”给后代的状态变量;@Consume装饰的变量是在后代组件中,去“消费”数据。
- @Observed 和 @ObjectLink:这两个装饰器用于在涉及嵌套对象或数组的场景中进行双向数据同步。@Observed装饰class,改变class的原型链,使其数据变更被UI页面管理;@ObjectLink用于装饰已被@Observed装饰的类的对象,实现数据双向绑定@Builder:装饰方法,用于封装重复的、复杂的UI结构代码,可以在自定义组件内快速生成多个布局内容。 @Extend:用于装饰方法,将新的属性函数添加到内置组件上,快速定义并复用组件的自定义样式。
- @Preview:装饰struct,如果自定义的组件被@Preview装饰,则可以在开发环境(如DevEco Studio)的预览器中进行预览
- @CustomDialog:装饰struct,用于装饰自定义弹窗。
- @Watch:装饰已经被其他状态装饰器(如@State、@Prop、@Link等)装饰的变量,用于监听状态变量的变化,并注册回调方法。
- @Style 和 @Styles:用于定义组件的样式。@Style通常用于定义组件的私有样式,而@Styles则用于定义可复用的样式,供多个组件共享。
- @BuilderParam:用于声明任意UI描述的一个元素,类似于slot占位符,使得自定义组件更加灵活。 由于ArkTS是在不断发展的,具体的装饰器可能会有所增加或更新,请参考最新的官方文档以获得最准确的信息。
@Link和@Prop的区别
@Link
双向同步:@Link用于实现父子组件之间的双向数据同步。它通过数据源的引用来初始化,因此能够实时同步父组件和子组件的状态变化。
性能:由于@Link是通过引用进行数据同步,而不是深拷贝,所以在性能上比@Prop更优。
@Prop
单向同步:@Prop用于实现父子组件之间的单向数据同步。它通过深拷贝数据源来初始化,因此子组件的数据变化不会影响父组件的数据。
性能:由于@Prop需要深拷贝数据源,这会带来一定的性能开销,特别是在处理复杂数据结构时。
使用场景
@Link:适用于需要实时同步父子组件状态的场景,特别是在子组件需要修改父组件数据的情况下。
@Prop:适用于不需要实时同步,但需要保证父组件数据独立于子组件的场景。如果子组件的数据变化不会影响父组件,可以使用@Prop来减少性能开销 。
@prop和@link在使用的有什么需要注意的,有什么区别
在鸿蒙(HarmonyOS)应用开发中,@Prop 和 @Link 是用于实现父子组件数据同步的关键装饰器,它们的核心区别在于数据流方向和应用场景。以下是详细对比和使用注意事项:
1. 核心区别
| 特性 | @Prop | @Link |
|---|---|---|
| 数据同步方向 | 单向(父 → 子) | 双向(父 ↔ 子) |
| 子组件修改数据 | 子组件修改不会同步到父组件 | 子组件修改会同步到父组件 |
| 初始化要求 | 必须由父组件初始化(无默认值) | 必须由父组件初始化(通过$符号绑定) |
| 适用场景 | 父组件控制数据,子组件只读或独立修改 | 父子组件需共同维护同一数据 |
| 性能影响 | 较低(单向绑定) | 略高(需监听双向变化) |
2. 使用注意事项
(1)@Prop 的注意事项
单向数据流
父组件的数据变化会同步到子组件,但子组件内部修改@Prop变量不会影响父组件。如果子组件需要本地修改数据,需额外使用@State:@Component struct ChildComponent { @Prop propData: string; // 父组件传递的数据 @State localData: string = this.propData; // 子组件本地副本 build() { Button(`Local: ${this.localData}`) .onClick(() => { this.localData = "Updated by Child"; // 仅修改本地副本 }) } }不可默认初始化
@Prop必须由父组件传递初始值,不能直接赋值:// 错误写法!@Prop不能默认初始化 @Prop propData: string = "Default"; // 编译报错
(2)@Link 的注意事项
必须使用
$绑定父组件状态
父组件通过$符号传递@State或@Link变量,表示双向绑定:// 父组件 @Component struct ParentComponent { @State parentData: string = "Hello"; build() { ChildComponent({ linkData: $parentData }) // 使用$符号 } } // 子组件 @Component struct ChildComponent { @Link linkData: string; // 自动双向同步 }避免循环更新
双向绑定可能导致父子组件无限循环更新(例如父子组件都在onClick中修改数据)。需通过条件判断或防抖逻辑避免:@Component struct ChildComponent { @Link linkData: string; build() { Button(`Child: ${this.linkData}`) .onClick(() => { if (this.linkData !== "Updated by Child") { this.linkData = "Updated by Child"; // 条件判断 } }) } }复杂对象需使用
@Observed和@ObjectLink
如果传递的是对象(非基本类型),需用@Observed装饰类,子组件使用@ObjectLink:@Observed class User { name: string; constructor(name: string) { this.name = name; } } @Component struct ParentComponent { @State user: User = new User("Alice"); build() { ChildComponent({ user: $user }) // 传递对象引用 } } @Component struct ChildComponent { @ObjectLink user: User; // 双向绑定对象 build() { Button(`User: ${this.user.name}`) .onClick(() => { this.user.name = "Bob"; // 修改会同步到父组件 }) } }
3. 如何选择?
用
@Prop当:- 子组件只需展示父组件数据,无需修改。
- 子组件需要基于父数据生成本地状态(如表单的临时编辑值)。
用
@Link当:- 父子组件需要实时同步数据(如开关状态、实时输入框)。
- 子组件需要直接修改父组件状态(如购物车数量增减)。
4. 常见问题
Q1:为什么 @Link 子组件修改数据后父组件不更新?
检查父组件是否用
$符号传递了@State变量:// 错误写法!未使用$符号 ChildComponent({ linkData: parentData }) // 父组件不会更新 // 正确写法 ChildComponent({ linkData: $parentData })
Q2:@Prop 和 @Link 能传递函数吗?
不能直接传递函数。若需子组件触发父组件逻辑,应通过回调事件实现:
// 父组件 @Component struct ParentComponent { handleEvent() { console.log("Event from child"); } build() { ChildComponent({ onEvent: this.handleEvent.bind(this) }) } } // 子组件 @Component struct ChildComponent { private onEvent: () => void; build() { Button('Trigger Event').onClick(() => this.onEvent()) } }
Q3:@Link 能否绑定常量?
不能。
@Link必须绑定可变的@State或@Link变量:// 错误写法!常量无法双向绑定 const fixedValue: string = "Constant"; ChildComponent({ linkData: fixedValue }) // 编译报错
总结
@Prop:轻量级单向同步,适合数据从父到子的只读场景。@Link:双向同步,适合父子组件共同维护数据的场景,需严格使用$绑定。- 复杂对象:结合
@Observed和@ObjectLink实现双向绑定。
正确使用这两种装饰器可以高效管理组件间数据流,避免不必要的渲染和逻辑混乱。
如何在ArkTS中实现应用级状态管理?
- AppStorage :
- 全局单例对象,用于跨页面、跨UIAbility的状态共享
- 通过
@StorageLink和@StorageProp实现双向/单向同步。
- LocalStorage
- 页面级状态管理,适用于同一页面内多组件共享状态
- 通过
@LocalStorageLink和@LocalStorageProp绑定。
localStorage和appStorage的区别,和对应的装饰器?
localStorage是页面级数据存储,在页面中创建实例,组件中使用@LocalStorageLink和@LocalStorageProp装饰器修饰对应的状态变量,绑定对应的组件使用比状态属性更灵活。
appStorage是进程级数据存储,进程启动时自动创建了唯一实例,在各个页面组件中@StorageProp和@StorageLink装饰器修饰对应的状态变量。
localStorage和appStorage数据存取都是在主线程进行的,且api只提供了同步接口,存取数据时要注意数据的大小。
LocalStorage&AppStorage&PersistentStorage分别介绍一下?
LocalStorage 是页面级的UI状态存储,通过 @Entry 装饰器接收的参数可以在页面内共享同一个 LocalStorage 实例。 LocalStorage 也可以在 UIAbility 内,页面间共享状态。LocalStorage是一个局部的状态管理器,它修饰的变量保存在内存中,是非持久化状态,退出应用程序后会消失。
一个应用可能有若干个UIAbility,如果要在多个 UIAbility 共享数据,就可以使用 AppStorage。AppStorage是一个全局的状态管理器,它修饰的变量保存在内存中,是非持久化状态,退出应用程序后会消失。应用退出再次启动后,依然能保存选定的结果,是应用开发中十分常见的现象,这就需要用到PersistentStorage。它修饰的变量保存在磁盘中,是持久化状态,退出应用程序后依然存在。PersistentStorage是应用程序中的可选单例对象。此对象的作用是持久化存储选定的AppStorage属性,以确保这些属性在应用程序重新启动时的值与应用程序关闭时的值相同.
简述 ArkTS 中 @State 装饰器的作用
- 重点:
@State用于声明响应式数据,数据变化时会触发 UI 更新。 - 业务场景:在开发一个计数器应用时,用
@State声明计数变量,点击按钮改变计数时,UI 自动更新显示新的计数。
追问:@State和@Link的区别是什么?
- @State:组件私有状态,仅影响自身及子组件的UI刷新。
- @Link:与父组件状态双向绑定,修改会同步到父组件,适用于父子组件数据联动场景。
- 示例:父组件通过
@Link传递变量给子组件,子组件修改该变量会直接更新父组件状态。
列举常见的ArkTS装饰器及其作用。
组件级装饰器
@State:组件内状态管理,数据变化触发UI刷新。@Prop:单向同步父组件的状态,子组件修改不影响父组件。@Link:与父组件双向同步,修改会同步到父组件。@Provide/@Consume:跨层级组件状态同步,无需逐层传递。
- 应用级装饰器:
@StorageLink/@StorageProp:与AppStorage(应用级数据库)双向/单向同步。@LocalStorageLink/@LocalStorageProp:与页面级LocalStorage同步。
@Watch装饰器的作用和使用场景?
- 作用:监听状态变量变化,触发回调函数。
- 场景:
- 数据变化时执行副作用(如日志记录、网络请求)。
- 与
@Link或@State配合,实现复杂状态逻辑(如购物车总价计算)。
监听数据变化,可以使用什么装饰器?
可以使用@Watch装饰器来监听数据的变化,@Watch必须配合其他装饰器(比如@State,@Prop,@Link等)一起使用。
使用方法:
@State
@Watch('回调函数名')
info: string = ''
回调函数名() {
}
说一说@observed和@ObjectLink的应用场景?
@Observed 和 @ObjectLink 是 ArkTS 中用于处理类对象状态管理的一对重要装饰器。它们通常一起使用,以实现当类对象的属性发生变化时,能够自动触发相关 UI 组件的更新。
@Observed
- 作用: 标记一个类 (Class)。被
@Observed标记的类,其属性的赋值操作会被框架所“观察”。当这些属性的值发生改变时,框架能够感知到这一变化。 - 应用场景:
- 定义数据模型类: 当你需要创建一个包含多个相关属性的数据模型类,并且希望这个模型对象的变化能够驱动 UI 更新时。例如,用户信息类 (
UserProfile)、商品详情类 (ProductDetails)、设置项集合类 (SettingsModel) 等。 - 封装业务逻辑与状态: 将相关的业务状态和操作封装在一个类中,使得状态管理更加结构化。
- 定义数据模型类: 当你需要创建一个包含多个相关属性的数据模型类,并且希望这个模型对象的变化能够驱动 UI 更新时。例如,用户信息类 (
- 关键点:
@Observed本身只让类的属性变化变得“可观察”,它不直接导致 UI 更新。它需要配合@ObjectLink或其他状态管理装饰器(如@State中包含@Observed对象)才能触发 UI 刷新。
// 定义一个可观察的数据模型类
@Observed
class UserProfile {
id: number;
name: string;
email: string;
isActive: boolean;
constructor(id: number, name: string, email: string) {
this.id = id;
this.name = name;
this.email = email;
this.isActive = true;
}
updateEmail(newEmail: string) {
this.email = newEmail; // 属性赋值会被观察到
}
toggleStatus() {
this.isActive = !this.isActive; // 属性赋值会被观察到
}
}
@ObjectLink
- 作用: 在自定义组件 (
@Component) 中,用于持有一个由@Observed标记的类对象的引用。当@ObjectLink装饰的变量所引用的@Observed对象的任何被观察属性发生变化时,持有该@ObjectLink变量的组件将会被触发重新渲染。 - 应用场景:
- 在 UI 组件中展示和响应类对象的状态: 当一个组件需要显示来自某个复杂数据模型对象的信息,并且当该对象内部属性变化时需要自动更新 UI。
- 将类对象传递给子组件: 可以将
@ObjectLink变量像普通变量一样传递给子组件(通常子组件用@ObjectLink或@Prop接收,取决于是否需要修改)。
- 关键点:
@ObjectLink建立起了@Observed对象属性变化与 UI 组件刷新之间的桥梁。
import { UserProfile } from './UserProfile'; // 假设 UserProfile 定义在单独文件
@Component
struct UserProfileCard {
// 使用 @ObjectLink 持有 UserProfile 对象的引用
@ObjectLink profile: UserProfile;
build() {
Column() {
Text(`ID: ${this.profile.id}`)
Text(`Name: ${this.profile.name}`)
Text(`Email: ${this.profile.email}`) // 当 profile.email 改变时,这里会自动更新
Text(`Status: ${this.profile.isActive ? 'Active' : 'Inactive'}`) // 当 profile.isActive 改变时,这里会自动更新
Button('Update Email')
.onClick(() => {
// 直接调用对象的方法来修改属性
this.profile.updateEmail(`new_email_${Date.now()}@example.com`);
// 由于 profile 是 @Observed 对象,其 email 属性变化会被观察到
// 进而触发 UserProfileCard 组件的刷新
})
Button('Toggle Status')
.onClick(() => {
this.profile.toggleStatus(); // 同样会触发刷新
})
}
.padding(10)
.border({ width: 1, color: Color.Gray })
}
}
// 在父组件中使用
@Entry
@Component
struct UserPage {
// 创建 UserProfile 实例,通常这个实例可能来自数据请求或其他地方
// 注意:如果 userProfileInstance 本身需要被替换(而不是内部属性改变),
// 则需要将它放在 @State 中。
@State userProfileInstance: UserProfile = new UserProfile(1, 'Alice', 'alice@example.com');
build() {
Column() {
// 将 UserProfile 实例传递给子组件的 @ObjectLink
UserProfileCard({ profile: this.userProfileInstance })
// 如果父组件需要替换整个对象实例
Button('Load Another User')
.onClick(() => {
this.userProfileInstance = new UserProfile(2, 'Bob', 'bob@example.com');
// 替换整个对象也会触发子组件刷新(因为 @ObjectLink 引用的对象变了)
})
}
}
}
总结应用场景:
当你需要管理的数据结构比较复杂(包含多个属性),并且这些数据需要封装一定的行为(方法),同时数据的变化需要实时反映在 UI 上时,就应该考虑使用 @Observed 来定义数据模型类,并使用 @ObjectLink 在相关的 UI 组件中持有和观察这个类的实例。
这提供了一种比使用多个分散的 @State 变量更结构化、更面向对象的方式来管理复杂状态。 @Observed和@ObjectLink主要用于在涉及嵌套对象或数组元素为对象的场景中进行双向数据同步
实际应用开发中,@Observed和@ObjectLink经常结合使用,在父组件中,使用@Observed装饰一个包含多个子对象的类,然后在子组件中使用@ObjectLink接收这些子对象的实例,从而建立起跨组件的双向数据绑定。
Builder和BuilderParams的区别?
鸿蒙应用开发中,@Builder 和 @BuilderParam 是两个不同的概念,主要用于构建用户界面(UI)组件。
Builder 是一个用来定义和构建 UI 组件的对象。通过 Builder,开发者可以链式调用各种方法来配置和创建 UI 组件,并将这些组件加入到应用程序的界面中。
BuilderParams 是用于传递配置参数的对象,通常用来初始化或配置 Builder。这些参数可以在构建 UI 组件之前设置,并且在创建组件时应用。
区别是Builder 更关注于构建组件的过程,而 BuilderParams 更侧重于配置这些组件的属性。
Builder和BuildParams的区别
鸿蒙应用开发中,@Builder 和 @BuilderParam 是两个不同的概念,主要用于构建用户界面(UI)组件。
@Builder:这是一个装饰器(decorator),用于定义一个UI组件的构建器模式。它允许你以一种流畅的方式来创建复杂的UI组件实例。当你有一个具有多个属性的组件时,使用@Builder可以帮助你更方便地设置这些属性,而不需要记住每个构造函数参数的顺序。@BuilderParam:这是另一个装饰器,它用于标记那些需要传递给@Builder方法的参数。当你要初始化一个自定义组件时,可以使用@BuilderParam来指定哪些属性应该被传递给@Builder方法。这样,在每次调用带有@Builder装饰的方法时,都可以通过@BuilderParam来传递特定的参数,从而定制组件的行为。
组件通讯
数据通信的方案?
- @Prop父子组件通讯 @Provide、@Consume祖先和后代同步 @State:修饰之后,数据改变,页面改变
- @Link父子双向同步 通过导入router包,调用 router.pushUrl进行跳转并且可以通过params进行数据通讯。
在多对多跨应用数据共享的场景下,需要提供一条数据通路能够接入多个不同应用的数据并共享给其他应用进行读取。
UDMF针对多对多跨应用数据共享的不同业务场景提供了标准化的数据通路,提供了标准化的数据接入与读取接口。
IPC(进程间通信):在不同进程或设备间传输数据,适用于复杂的应用场景。
使用EventHub进行数据通信:在基类Context中提供了EventHub对象,可以通过发布订阅方式来实现事件的传递。在事件传递前,订阅者需要先进行订阅,当发布者发布事件时,订阅者将接收到事件并进行相应处理。EventHub提供了一种基于发布订阅模式的事件机制,通过订阅和发布自定义事件,实现UIAbilitv组件/ExtensionAbilitv组件与UI之间的数据同步
聊一聊组件如何通讯?
在鸿蒙(HarmonyOS)应用开发中,组件通信是核心功能之一,涉及UI组件之间的数据传递、跨页面通信、Ability与UI的交互等场景。根据鸿蒙的架构(基于ArkUI的声明式开发范式),以下是组件通信的完整方案:
1. 父子组件通信
适用于同一页面内父子组件之间的数据传递。
(1)父组件 → 子组件:@Prop 和 @Link
@Prop:单向同步,父组件修改数据会同步到子组件,但子组件修改不会影响父组件。@Link:双向同步,父子组件的数据修改会相互影响。
示例代码
// 父组件
@Component
struct ParentComponent {
@State parentData: string = "Hello";
build() {
Column() {
Text(`Parent Data: ${this.parentData}`)
Button('Change Parent Data')
.onClick(() => {
this.parentData = "Updated by Parent";
})
// 子组件
ChildComponent({
propData: this.parentData, // @Prop
linkData: $parentData // @Link ($表示双向绑定)
})
}
}
}
// 子组件
@Component
struct ChildComponent {
@Prop propData: string; // 单向同步
@Link linkData: string; // 双向同步
build() {
Column() {
Text(`Prop Data: ${this.propData}`)
Text(`Link Data: ${this.linkData}`)
Button('Change Link Data')
.onClick(() => {
this.linkData = "Updated by Child"; // 会同步到父组件
})
}
}
}
(2)子组件 → 父组件:@Watch 和自定义事件
通过回调函数或 @Watch 监听子组件的变化。
示例代码
// 父组件
@Component
struct ParentComponent {
@State dataFromChild: string = "";
build() {
Column() {
Text(`Child Said: ${this.dataFromChild}`)
ChildComponent({
onChildEvent: (msg: string) => {
this.dataFromChild = msg; // 接收子组件消息
}
})
}
}
}
// 子组件
@Component
struct ChildComponent {
private onChildEvent: (msg: string) => void;
build() {
Button('Notify Parent')
.onClick(() => {
this.onChildEvent("Hello from Child!"); // 触发父组件回调
})
}
}
2. 兄弟组件通信
兄弟组件之间需要通过共同的父组件或全局状态管理实现通信。
(1)通过父组件中转
// 父组件
@Component
struct ParentComponent {
@State sharedData: string = "";
build() {
Column() {
ChildAComponent({
onDataChange: (data: string) => {
this.sharedData = data; // 接收ChildA的数据
}
})
ChildBComponent({
data: this.sharedData // 将数据传递给ChildB
})
}
}
}
(2)使用全局状态管理(AppStorage)
适用于跨页面、跨组件的状态共享。
// 存储全局数据
AppStorage.SetOrCreate<string>('globalData', 'Initial Value');
// 组件A(修改数据)
@Component
struct ComponentA {
@StorageLink('globalData') globalData: string;
build() {
Button('Update Global Data')
.onClick(() => {
this.globalData = "Updated by A";
})
}
}
// 组件B(读取数据)
@Component
struct ComponentB {
@StorageLink('globalData') globalData: string;
build() {
Text(`Global Data: ${this.globalData}`)
}
}
3. 跨页面通信
(1)页面跳转传参(router.push)
import router from '@ohos.router';
// 页面A跳转并传参
router.pushUrl({
url: 'pages/PageB',
params: { key: 'value' }
});
// 页面B接收参数
@Component
struct PageB {
@State paramValue: string = router.getParams()?.['key'] || '';
}
(2)EventHub(事件总线)
适用于任意组件/页面间的消息发布订阅。
// 订阅事件(在onPageShow中注册)
import emitter from '@ohos.events.emitter';
emitter.on('customEvent', (data) => {
console.log('Received:', data);
});
// 发布事件(触发其他页面的监听)
emitter.emit('customEvent', { msg: 'Hello EventHub!' });
4. Ability与UI的通信
(1)Ability调用UI组件方法
通过 AbilityContext 或 GlobalContext 共享数据。
// UI组件注册回调
GlobalContext.getInstance().set('updateUI', (data: string) => {
// 更新UI逻辑
});
// Ability触发UI更新
GlobalContext.getInstance().get('updateUI')('New Data');
(2)UI组件调用Ability方法
通过 Feature Ability 或 Particle Ability 的调用机制。
import featureAbility from '@ohos.ability.featureAbility';
// UI组件调用Ability
featureAbility.startAbility({
bundleName: 'com.example.ability',
abilityName: 'ServiceAbility'
});
5. WebView与ETS通信
参考之前的方案:
- ETS调用H5:
webController.runJavaScript()。 - H5调用ETS:
onMessage监听 +window.ohosWebView.postMessage()。
总结:鸿蒙组件通信方式对比
| 场景 | 推荐方案 | 特点 |
|---|---|---|
| 父子组件 | @Prop / @Link / 回调 | 简单直接,适合紧密耦合的组件 |
| 兄弟组件 | 父组件中转 / AppStorage | 需通过共同父组件或全局状态管理 |
| 跨页面 | router.push / EventHub | 路由传参或事件总线解耦 |
| 全局状态 | AppStorage / LocalStorage | 适合多页面共享数据 |
| Ability与UI | GlobalContext / FeatureAbility | 跨Ability通信需依赖上下文机制 |
| WebView与ETS | postMessage / runJavaScript | 通过WebView协议桥接 |
根据具体场景选择合适的方式,优先使用声明式数据驱动(如@State、@Link),复杂场景结合事件总线或全局状态管理。
want组件通讯(跨层级通讯能力)
在准备鸿蒙操作系统(HarmonyOS)相关的面试时,关于 Want 组件通讯的理解和应用是一个重要的考点。以下是针对 Want 组件通讯的一些简述和可能的面试题要点:
定义与用途:
Want是 HarmonyOS 中用于描述操作意图的对象,主要用于不同组件之间的通信,包括页面导航、服务调用和消息传递等。跨层级通信能力:
Want支持同应用内不同层级之间以及跨应用的组件间通信。通过指定目标组件的包名、Ability名称等信息,可以实现精准的消息传递和功能调用。构建与解析:
- 构建:使用
Intent对象,并通过OperationBuilder来设置目标组件的信息(如设备ID、包名、Ability名称),还可以使用setParam方法添加额外的数据。 - 解析:在目标组件中,可以通过
Intent获取传递过来的数据并进行处理。
- 构建:使用
应用场景:
- 页面跳转时携带参数。
- 启动后台服务并传递指令或数据。
- 实现组件间的松耦合通信。
在 ArkTS 中,如何处理组件间的通信?
- 父传子
- 使用
@Prop装饰器。父组件将数据作为属性传递给子组件,子组件接收只读属性,父数据更新子组件同步更新。
- 使用
- 子传父
- @Link:双向数据绑定,子组件可修改父组件状态。
- 跨层级通信
- 使用
@Provide和@Consume。祖先组件用@Provide提供数据,后代组件用@Consume消费。
- 使用
want的type和action参数是做什么的
首先Want是对象间信息传递的载体, 可以用于应用组件间的信息传递。
type参数是表示MIME type类型,打开文件的类型,主要用于文管打开文件。比如:'text/xml' 、 'image/*'等
action参数是表示要执行的通用操作(如:查看、分享、应用详情)您可以定义该字段,配合uri或parameters来表示对数据要执行的操作。
如何引入本地的web页面?
(1).创建Web组件实例:
在页面的.ets文件中,你需要创建一个Web组件实例,并指定src属性为网页资源的地址,以及一个WebviewController作为控制器。
(2).加载本地网页:
将本地网页文件放在应用的resources/rawfile目录下。然后,通过$rawfile函数引用本地网页资源。
(3).配置权限:
如果需要访问网络资源,需要在module.json5文件中声明网络访问权限
有用过H5混合开发?怎么实现
H5混合开发是指将HTML5、CSS和JavaScript与原生应用开发结合起来,通常用于构建跨平台的移动应用。
通过这种方式,开发者可以利用Web技术的灵活性,同时也能调用原生功能。
以下是实现H5混合开发的一般步骤:
- HTML5页面,包含必要的JavaScript和CSS。
- WebView控件加载H5页面。
- JavaScript与WebView进行通信,调用原生功能。
总结:
H5混合开发允许开发者利用Web技术的优势,同时访问原生功能,适合需要快速开发和跨平台支持的项目。选择合适的框架和工具是成功的关键。
h5跟arkts怎么交互通讯?
交互方式概览
在鸿蒙系统中,H5 页面可以通过多种方式与 ArkTS 代码进行交互:
| 交互方式 | 适用场景 | 特点 |
|---|---|---|
| Web组件通信 | 嵌入H5页面 | 通过Web组件提供的能力实现双向通信 |
| 消息通道 | 轻量级通信 | 使用postMessage和onMessage进行数据传递 |
| 路由跳转 | 页面导航 | 从H5跳转到ArkTS页面或反向跳转 |
| 数据共享 | 状态共享 | 通过AppStorage或LocalStorage共享数据 |
1. Web组件通信
ArkTS 调用 H5 方法
// ArkTS 代码
@Entry
@Component
struct WebComponentExample {
controller: webview.WebviewController = new webview.WebviewController();
build() {
Column() {
Button('调用H5方法')
.onClick(() => {
this.controller.runJavaScript('h5Function("来自ArkTS的数据")');
})
Web({ src: $rawfile('index.html'), controller: this.controller })
}
}
}
H5 调用 ArkTS 方法
// ArkTS 代码
@Entry
@Component
struct WebComponentExample {
controller: webview.WebviewController = new webview.WebviewController();
build() {
Column() {
Web({
src: $rawfile('index.html'),
controller: this.controller
})
.onMessageReceive((event: { message: string }) => {
console.log('收到H5消息:' + event.message);
})
}
}
}
<!-- H5 代码 -->
<script>
function sendToArkTS() {
// 发送消息给ArkTS
window.harmony.webview.postMessage('来自H5的数据');
}
</script>
2. 消息通道通信
配置消息通道
// ArkTS 代码
import webview from '@ohos.web.webview';
@Entry
@Component
struct MessageChannelExample {
controller: webview.WebviewController = new webview.WebviewController();
build() {
Column() {
Web({
src: $rawfile('index.html'),
controller: this.controller
})
.onMessageReceive((event) => {
console.log('收到H5消息:' + event.message);
// 回复消息
this.controller.postMessage('ArkTS回复');
})
}
}
}
H5 发送和接收消息
// H5 代码
// 发送消息
window.harmony.webview.postMessage('Hello ArkTS');
// 接收消息
window.harmony.webview.onmessage = function(event) {
console.log('收到ArkTS消息:', event.data);
};
3. 路由跳转交互
H5 跳转到 ArkTS 页面
// H5 代码
function navigateToArkTSPage() {
window.harmony.router.push({
uri: 'pages/ArkTSPage',
params: { key: 'value' }
});
}
ArkTS 接收参数
// ArkTS 页面
@Entry
@Component
struct ArkTSPage {
@State params: object = {};
onPageShow() {
this.params = router.getParams();
}
}
4. 数据共享方案
使用 AppStorage
// ArkTS 代码
AppStorage.SetOrCreate('sharedData', '初始值');
// H5 获取数据
const sharedData = window.harmony.appStorage.get('sharedData');
最佳实践建议
- 安全性:对H5传入的数据进行严格验证
- 性能优化:减少频繁的小数据量通信
- 错误处理:添加完善的错误处理机制
- 类型安全:ArkTS侧使用接口定义通信数据结构
注意事项
- H5页面需要放在工程的resources/rawfile目录下
- 通信数据量不宜过大,建议控制在1MB以内
- 跨平台兼容性需要考虑不同浏览器的差异
- 在config.json中需要声明web权限
{
"module": {
"abilities": [
{
"requestPermissions": [
{
"name": "ohos.permission.INTERNET"
}
]
}
]
}
}
webview组件如何使用,ets文件如何与h5通讯?
在 OpenHarmony 的 ArkUI(基于声明式开发范式)中,可以通过 Web 组件(即 WebView)加载 H5 页面,并通过 WebController 和 postMessage 机制实现 ETS 与 H5 的双向通信。以下是详细步骤和示例代码:
1. 使用 Web 组件加载 H5 页面
在 ETS 文件中定义 Web 组件,并绑定控制器以控制 WebView 的行为。
示例代码(ETS 文件)
// pages/WebViewPage.ets
import web_webview from '@ohos.web.webview';
@Entry
@Component
struct WebViewPage {
private webController: web_webview.WebviewController = new web_webview.WebviewController();
private h5Url: string = 'file:///entry/resources/rawfile/index.html'; // 本地H5路径或远程URL
build() {
Column() {
// Web组件加载H5
Web({
src: this.h5Url,
controller: this.webController
})
.width('100%')
.height('100%')
.onPageEnd(e => {
console.info('Page finished loading');
})
}
.width('100%')
.height('100%')
}
}
2. ETS 与 H5 双向通信
(1)ETS 调用 H5 的 JavaScript 函数
通过 WebController 的 runJavaScript 方法执行 H5 中的 JS 代码。
// 在ETS中调用H5的函数
this.webController.runJavaScript('window.h5Function("Hello from ETS!")');
(2)H5 向 ETS 发送消息
通过 Web 组件的 onMessage 事件监听 H5 通过 postMessage 发送的消息。
ETS 端代码:
Web({
src: this.h5Url,
controller: this.webController
})
.onMessage(e) {
console.info('Received message from H5:', e.message);
// 处理H5发送的数据
if (e.message === 'getUserInfo') {
this.webController.runJavaScript('window.updateUserInfo("John")');
}
}
H5 端代码(index.html):
<script>
// H5向ETS发送消息
function sendToETS() {
window.ohosWebView.postMessage('getUserInfo');
}
// 接收ETS的消息
window.h5Function = function(data) {
console.log('Received from ETS:', data);
};
</script>
3. 完整双向通信示例
ETS 文件(WebViewPage.ets)
@Entry
@Component
struct WebViewPage {
private webController: web_webview.WebviewController = new web_webview.WebviewController();
private h5Url: string = 'file:///entry/resources/rawfile/index.html';
build() {
Column() {
Button('Send to H5')
.onClick(() => {
// ETS主动调用H5函数
this.webController.runJavaScript('window.h5Function("Hello from ETS!")');
})
Web({
src: this.h5Url,
controller: this.webController
})
.width('100%')
.height('80%')
.onMessage(e) {
console.info('H5 message:', e.message);
}
}
.padding(10)
.width('100%')
.height('100%')
}
}
H5 文件(index.html)
<!DOCTYPE html>
<html>
<head>
<title>H5 Page</title>
</head>
<body>
<button onclick="sendToETS()">Send to ETS</button>
<script>
// H5向ETS发送消息
function sendToETS() {
window.ohosWebView.postMessage('Button clicked in H5');
}
// 供ETS调用的函数
window.h5Function = function(data) {
alert('ETS says: ' + data);
};
</script>
</body>
</html>
4. 关键注意事项
H5 路径配置
- 本地 H5 文件需放在
resources/rawfile目录下,路径格式为file:///entry/resources/rawfile/index.html。 - 远程 URL 直接填写
https://example.com。
- 本地 H5 文件需放在
权限配置
在module.json5中添加网络权限(如需加载远程 H5):"requestPermissions": [ { "name": "ohos.permission.INTERNET" } ]调试支持
启用 WebView 调试模式(仅在开发阶段使用):web_webview.WebviewController.setWebDebuggingAccess(true);安全限制
- OpenHarmony 的 WebView 基于系统浏览器内核,部分高级 API(如摄像头)可能需要额外权限。
- 跨域请求需在 H5 和服务器端配置 CORS。
通过上述步骤,即可实现 ETS 与 H5 的双向通信。如果需要更复杂的交互(如传递 JSON 数据),可以通过 JSON.stringify 序列化数据。
鸿蒙组件是如何通信的? @provide和@consume的使用?
父子组件之间通信可以利用装饰器来进行通信,例如@State、@Prop、@Link 、@Observed和@ObjectLink等。祖先和后代之间可以使用@Provide和@Consume来进行通信。
@Provide和@Consume是一种将数据传递给后代,和后代的数据进行双向同步的装饰器。使用步骤:
- 将父组件的状态属性使用@Provide修饰
- 子组件通过@Consume修饰
在父子组件关联的场景下,@Provide+@Consume和@State+@Prop/@Link谁的开销更大?
· @Provide+@Consume的开销主要体现在初始化时的跨层级数据传递上,一旦数据被成功传递并绑定,后续的UI更新通常较为高效。
· @State+@Prop/@Link的开销则与状态变量的变化频率和组件的渲染复杂度直接相关,频繁的状态变化或复杂的组件结构可能导致较高的渲染开销。
如何进行页面传值?
使用router中的params属性进行传值,
在跳转页面使用aboutToAppear(){const res =router.getparams()}进行接受完成页面传值
路由
说一说你对于路由的理解?
页面路由: 指的是在应用程序中实现不同页面之间的跳转以及数据传递; 通过 Router 模块就可以实现这个功能
页面栈: 用来存储程序运行时页面的一种 数据结构 ,遵循 先进后出 的原则, 页面栈最大容量是32个页面, 可以用 router.getLength 进行查看页面栈中页面的个数
常见的路由跳转方式:
router.pushUrl() : 目标页面不会替换当前页,而是压入页面栈。这样可以保留当前页的状态,并且可以通过返回键或者调用 router.back() 方法返回到当前页。
router.pushUrl({
url:'页面地址'
})
router.replaceUrl(): 目标页面会替换当前页,并销毁当前页。这样可以释放当前页的资源,并且无法返回到当前页
router.replaceUrl({
url:'页面地址'
})
- 返回
router.back()
常见的路由模式:
Standard(多实例模式) 默认的跳转模式, 目标页面会被添加到页面栈顶,无论栈中是否存在相同url的页面;
Single(单实例模式) 如果目标页面的url已经存在于页面栈中,则会将离栈顶最近的同url页面移动到栈顶,该页面成为新建页。如果目标页面的url在页面栈中不存在同url页面,则按照默认的多实例模式进行跳转。
// 多实例模式下,router.RouterMode.Standard参数可以省略。
// pushUrl 和 replaceUrl 均可以在第二个参数设置 【路由模式】
router.pushUrl(options,mode)
router.replaceUrl(options,mode)
navigation和router的区别(1)
在 HarmonyOS ArkTS 开发中,Navigation 和 Router (通常指 @ohos.router 模块) 是处理页面跳转和视图管理的两个不同层面的概念:
1. Navigation (导航容器组件)
- 类型: UI 组件 (
Navigation) - 作用: 主要作为一个容器组件,用于管理一组视图(通常是页面或页面的子视图)的层级关系和导航体验。它通常会提供视觉上的导航元素,如标题栏(
title)、返回按钮、工具栏按钮等。 - 机制:
Navigation内部维护一个导航栈(Navigation Stack)。当你通过Navigation的push方法加载新的视图(NavDestination)时,这个视图会被压入栈顶,并显示出来。点击返回按钮或调用pop方法时,栈顶视图会被弹出,显示前一个视图。 - 范围: 通常作用于单个 Ability 或一个特定的 UI 流程内部。它管理的是
Navigation容器内部的视图切换。 - 使用场景:
- 实现类似原生应用设置界面那种层层递进的导航。
- 在页面内部的不同区域或状态间进行切换,并提供统一的导航栏。
- 管理具有父子关系的视图。
- 示例: 你会将你的页面内容包裹在
Navigation组件内,并使用NavRouter(配合NavDestination) 来定义和触发Navigation容器内部的视图跳转。
// 示例:Navigation 的基本用法
Navigation(this.pathInfo) {
Column() {
Text('首页内容')
NavRouter() // NavDestination 的容器
}
.title('首页')
.titleMode(NavigationTitleMode.Mini)
}
.onAppear(() => {
this.pathInfo = new NavPathStack()
})
// 在某个按钮点击事件中 push 新视图
Button('进入详情')
.onClick(() => {
this.pathInfo.pushPath({ name: 'detailPage' }) // 假设 detailPage 是 NavDestination 定义的名称
})
// 定义 NavDestination
NavDestination() {
// 详情页内容
Text('这是详情页')
}
.title('详情')
2. Router (页面路由 @ohos.router)
- 类型: API 模块 (
@ohos.router) - 作用: 提供应用级别的页面跳转能力。它允许你在不同的页面 (Page) 之间进行导航,这些页面可以是同一个 Ability 的不同 Page,也可以是不同 Ability 的 Page。
- 机制: 通过调用
router.pushUrl(),router.replaceUrl(),router.back()等 API 方法,向系统请求加载、替换或返回页面。系统会根据 URL 和参数找到对应的 Ability 和 Page 进行加载和生命周期管理。 - 范围: 作用于整个应用程序,负责页面间的切换和数据传递。
- 使用场景:
- 从列表页跳转到详情页。
- 登录成功后跳转到主页。
- 在应用的不同功能模块之间切换。
- 跨 Ability 跳转。
- 示例:
import router from '@ohos.router';
// 跳转到应用内的另一个页面,并传递参数
Button('跳转到详情页')
.onClick(() => {
router.pushUrl({
url: 'pages/DetailPage', // 目标页面的路径,通常在 module.json5 中配置
params: { id: 123, name: '商品A' }
})
.catch(err => {
console.error(`Failed to push url: ${err.message}`)
})
})
// 返回上一页
Button('返回')
.onClick(() => {
router.back();
})
主要区别总结:
| 特性 | Navigation | Router (@ohos.router) |
|---|---|---|
| 类型 | UI 组件 | API 模块 (系统服务) |
| 范围 | 组件内部,管理视图层级 | 应用级别,管理页面跳转 |
| 机制 | 内部维护导航栈,控制视图显示/隐藏 | 通过 URL 请求系统调度页面加载/销毁 |
| 视觉 | 通常提供导航栏、返回按钮等 UI 元素 | 本身无 UI,纯粹的跳转逻辑 |
| 用途 | 管理容器内视图切换,提供层级导航体验 | 实现应用内不同页面间的跳转和数据传递 |
| 关联 | NavDestination, NavRouter, NavPathStack | router.pushUrl, router.back, module.json5 |
简单来说,Navigation 更侧重于单个复杂页面内部或相关视图集合的导航结构和视觉表现,而 Router 则负责整个应用不同独立页面之间的流转。
navigation和router的区别(2)
两者均可,看您的需求,router只是不演进,还是能继续使用的,我们这边给您列出 Navigation与router的对比,您可以根据实际开发情况进行选择。
易用性层面:
- Navigation天然具备标题、内容、回退按钮的功能联动,开发者可以直接使用此能力。
- Router若要实现此能力,需要自行定义;
- Navigation的页面是由组件构成,易于实现共享元素的转场
功能层面:
- Navigation天然支持一多,Router不支持;
- Navigation没有路由数量限制,Router限制32个;
- Navigation可以获取到路由栈NavPathStack,并对路由栈进行操作;
- Navigation可以嵌套在模态对话框中,也就是说可以模态框中定义路由,Router不支持;
- Navigation的组件全量由开发者自行控制,开发者可以自定义复杂的动效和属性的设置(背景、模糊等),Router的page对象不对外暴露,开发者无法对page进行处理;
性能层面
- Navigation传递参数性能更优,Navigation通过引用传递,Router通过深拷贝完成;
- Navigation可以配合动态加载,实现组件动态加载,Router页面使用@Entry进行修饰,当前模块加载时会生成全量页面。
如果开发一套系统,我应该如何选择navigation和router
在开发一套 HarmonyOS ArkTS 系统时,如何选择使用 Navigation 还是 Router (或者两者结合) 取决于您应用的具体导航需求和结构。
以下是一些选择的指导原则:
优先考虑
Router(@ohos.router) 进行页面间跳转:- 场景: 当您需要在应用的不同独立功能页面之间进行切换时,例如:
- 从首页跳转到用户中心。
- 从商品列表页跳转到商品详情页。
- 完成登录后跳转到主界面。
- 在不同的 Tab 对应的页面之间切换(虽然 Tab 切换本身可能由
Tabs组件处理,但 Tab 内的页面跳转通常用Router)。
- 原因:
Router是设计用来管理应用级页面生命周期和导航的。它处理的是独立的页面单元(通常对应一个.ets文件和module.json5中的配置),并且能够方便地传递参数。
- 场景: 当您需要在应用的不同独立功能页面之间进行切换时,例如:
在需要页面内层级导航或复杂视图管理时使用
Navigation:- 场景: 当您需要在单个页面内部实现类似“下钻”或“分步”的导航体验时,例如:
- 设置界面:点击一个设置项,进入下一级设置详情,再点击可能进入更深层级,
Navigation可以很好地管理这种层级关系并提供返回按钮。 - 文件管理器:浏览文件夹层级。
- 分步表单或向导:在一个页面容器内按步骤展示不同的视图。
- 主从视图(Master-Detail):左侧列表,右侧详情,在同一个
Navigation容器内管理。
- 设置界面:点击一个设置项,进入下一级设置详情,再点击可能进入更深层级,
- 原因:
Navigation作为一个 UI 容器,擅长管理其内部视图的堆栈 (NavPathStack),并自动提供导航栏、标题、返回等 UI 元素,简化了层级视图的管理和用户体验。
- 场景: 当您需要在单个页面内部实现类似“下钻”或“分步”的导航体验时,例如:
两者结合使用 (常见场景):
- 场景: 大多数复杂的应用会同时使用两者。
- 使用
Router进行大的功能模块或独立页面之间的跳转。 - 在某个具体的页面内部,如果需要管理子视图的层级导航,则在该页面中使用
Navigation组件。
- 使用
- 示例:
- 应用底部有几个
Tabs(首页、分类、购物车、我的)。使用Router来管理这几个主 Tab 页面的加载和切换(或者Tabs组件本身管理切换,但每个 Tab 对应一个独立的 Page Ability 或 UI Ability 组件,页面间的跳转仍依赖Router)。 - 在“我的”这个页面 (
Router跳转过来的),可能有一个“设置”入口,点击后在该页面内部使用Navigation来展示多层级的设置选项。
- 应用底部有几个
- 场景: 大多数复杂的应用会同时使用两者。
决策流程建议:
- 划分页面: 首先,根据应用的功能和信息架构,划分出主要的、相对独立的页面单元。
- 页面间跳转: 确定这些独立页面之间的跳转关系,这些跳转通常使用
Router来实现。 - 页面内导航: 审视每个独立页面,判断其内部是否需要管理多个视图的层级关系或提供类似向导的流程。如果需要,就在该页面内部嵌入
Navigation组件来管理这些子视图。
总结:
- 把
Router看作是应用的高速公路,连接各个城市(页面)。 - 把
Navigation看作是某个城市(页面)内部的详细地图和路标,指引你在该城市内部的不同**地点(视图)**之间穿梭。
根据您的系统设计,分析清楚哪些是页面级的跳转,哪些是页面内的视图切换,就能做出合适的选择了。
ArukUI
用过哪些组件,分别介绍一下?
- SideBarContainer提供侧边栏可以显示和隐藏的侧边栏容器,通过子组件定义侧边栏和内容区,第一个子组件表示侧边栏,第二个子组件表示内容区。(如豆瓣,小红书等app首页都用到了侧边栏容器)子组件类型:系统组件和自定义组件,不支持渲染控制类型(if/else、ForEach和LazyForEach)。子组件个数:必须且仅包含2个子组件。子组件个数异常时:3个或以上子组件,显示第一个和第二个。1个子组件,显示侧边栏,内容区为空白。
- WaterFlow瀑布流是应用开发中相当常见的开发场景。它通过容器自身的布局规则,将元素项目自上而下排列。借助其特点,瀑布流通常被用于展示图片资讯、购物商品、直播视频等多种形式的数据。(如小红书,美团,淘宝等app首页都用到了瀑布流)
有写过瀑布流布局?
瀑布流容器,由“行”和“列”分割的单元格所组成,通过容器自身的排列规则,将不同大小的“项目”自上而下,如瀑布般紧密布局。
waterFlow和Grid布局类似,同样支持columnsTemplate和rowsTemplate通过 fr的形式对行和列进行分割
瀑布流布局的特点:
● 每一列盒子的宽度一致, 盒子的高度不一致
● 自上而下, 形成参差错落效果
使用限制.
·LazyForEach必须在支持数据懒加载Q的容器组件内使用,如List、Grid、Swiper和WaterFlow组件,。在每次迭代中,LazyForEach必须创建且只允许创建一个子组件。
.生成的子组件必须是允许包含在LazyForEach父容器组件中的子组件,
·允许LazyForEach包含在if/else条件渲染语句中,也允许LazyForEach中出现if/else条件渲染语句。
·键值生成器必须针对每个数据生成唯一的值,以确保UI组件的正确渲染和更新。
·LazyForEach必须使用DataChangeListener对象来进行更新,以触发UI的刷新
1. 说说arkts怎么写一个页面并复用
需要介绍 ArkTS 页面构建和代码复用的流程,可以这样概括:
页面入口与结构: 使用
@Entry和@Component定义页面入口组件,这是 UI 的起点。页面内部通常使用Column,Row,List,Grid等布局容器来组织内容。**组件化开发 **:
- 核心思想: 将 UI 拆分成独立的、可复用的自定义组件。每个组件封装自己的视图(UI 结构和样式)和行为(逻辑、状态)。
- 实现: 创建
.ets文件,使用@Component装饰器定义组件结构 (build函数),并通过@Prop接收外部数据,@State或其他状态装饰器管理内部状态。 - 优势: 提高代码复用性、可维护性,降低耦合度。
样式复用 (Styling Reuse):
- @Styles: 定义一组可复用的样式属性集。比如定义一个通用的按钮样式、文本样式等。通过
.styleName()应用到组件上。 - @Extend: 继承并扩展已有的
@Styles,创建新的样式变体。例如,在基础按钮样式上扩展出禁用状态的样式。 - 优势: 保持 UI 风格统一,减少重复的样式代码。
- @Styles: 定义一组可复用的样式属性集。比如定义一个通用的按钮样式、文本样式等。通过
UI 片段复用 (@Builder):
- 目的: 复用局部 UI 结构,而不是完整的组件。适用于那些结构相似但内容或部分行为不同的 UI 片段,例如卡片、列表项等。
- 实现: 使用
@Builder装饰器定义一个函数,该函数返回一段 UI 描述。可以通过普通参数传递数据,通过@BuilderParam传递更复杂的 UI 内容块。 - 与自定义组件的区别:
@Builder更轻量,通常用于组件内部或特定场景的 UI 结构复用,而自定义组件是更完整的封装单元。
状态管理 (State Management):
- 使用
@State,@Link,@Prop,@Provide,@Consume等装饰器来管理组件内和组件间的状态传递和同步,确保数据驱动 UI 更新。
- 使用
总结流程: "在 ArkTS 中构建页面时,我们首先定义页面入口组件。然后,根据 UI 设计,将页面拆分成若干可复用的自定义组件。对于通用的样式,我们会使用 @Styles 和 @Extend 来封装和复用。对于一些重复的 UI 结构片段,我们会利用 @Builder 来提高效率。最后,通过 ArkTS 提供的状态管理机制来处理数据和 UI 的交互与同步。这种方式使得代码结构清晰、易于维护和扩展。"
这样介绍能够清晰地展现您对 ArkTS 核心复用机制的理解和应用流程。
2. ArkUI的核心设计思想是什么?
● 声明式开发
ArkUI采用声明式UI范式,通过状态驱动视图更新(如React/Vue)。
● 跨平台
一套代码能跑手机、平板、手表、电视等设备。
通过自适应布局,自动适配不同屏幕大小,开发者不用为每个设备单独写布局代码。
● 用户体验优先
优化渲染引擎让界面滑动更流畅,提供统一的设计规范组件库(比如按钮、弹窗样式统一),保证多端体验一致。
3. 什么是线性布局?
- 线性布局(LinearLayout)是开发中最常用的布局,通过线性容器
Row和Column构建。 - Column 容器:子元素按照垂直方向排列(从上到下)。
- Row 容器:子元素按照水平方向排列(从左到右)。
- 开发者可根据需求选择
Row或Column容器创建布局。
简单来说,它能让界面里的组件按照直线方向排列。这个方向有两种:
- 水平排:组件从左到右依次摆开;
- 垂直排:组件从上往下一个个排列。
使用线性布局时,你还能控制:
- 组件之间的间距
- 对齐方式(如水平左对齐、居中对齐、右对齐;垂直方向同理)
实际应用举例:
- 水平线性布局:适合菜单按钮排布(一排按钮);
- 垂直线性布局:适合列表排布(元素纵向堆叠)。
4. 线性布局对齐方式?
ArkUI 线性布局的对齐方式主要分两类:
主轴对齐(沿排列方向)
• start:默认,组件靠左(水平)或靠上(垂直)排列。 • end:组件靠右/下排列。 • center:组件居中排列。 • space-between:两端对齐,中间间距均分。 • space-around:每个组件左右/上下间距相等(两端留一半间距)。
交叉轴对齐(垂直方向)
• start:顶部对齐(水平布局时)。 • end:底部对齐。 • center:垂直居中。 • stretch:组件拉伸填满交叉轴空间(默认)。
例子:水平布局下,justifyContent: space-between 会让按钮左一个右一个,中间自动留白;alignItems: center 会让所有按钮垂直居中。
5. 什么是弹性布局?
弹性布局分类
弹性布局分为单行布局和多行布局。
布局特性
默认情况下,Flex 容器中的子元素都排在一条线(又称“轴线”)上。
• 当子元素尺寸总和大于 Flex 容器尺寸时,子元素尺寸会自动挤压。
• 支持主轴对齐(justifyContent)和交叉轴对齐(alignItems),例如可以让按钮在容器中居中或两端对齐。
示例
一个商品列表,标题和价格使用弹性布局,标题会自动占据剩余空间,价格固定宽度,整体屏幕大小。
6. Flex布局和Column/Row布局如何选择?
• 简单排列选Column/Row
如果只是单纯的一行排开(Row)或一列堆叠(Column),用Column/Row更简单。比如做个水平导航栏,直接用Row设置间距就行,代码量少。
• 复杂布局用Flex
需要弹性分配空间时(如让某个组件撑满剩余宽度),或者需要灵活控制对齐方式(如两端对齐、自动换行),Flex布局更合适。
比如商品列表里的价格和描述,价格固定宽度,描述用flexGrow:1自动扩展。
• 性能与习惯考量
• Column/Row本质是线性布局,渲染性能略优,但差异不大。
• 如果团队熟悉传统布局方式,Column/Row更易上手;如果习惯Web的Flexbox,直接用Flex布局更顺手。
• 例子
• 简单的垂直列表→Column
• 自适应宽度的按钮组→Flex(加flexWrap: wrap实现换行)
7. 什么是层叠布局(堆叠)?
• 层叠布局通过 Stack 容器组件实现位置的固定定位与层叠,容器中的子元素依次入栈,后一个子元素覆盖前一个子元素,子元素可以叠加,也可以设置位置。
• 通过 alignContent 来设置子组件的位置。
• 常用于需要视觉覆盖的场景,例如:
• 图片上叠加文字标签
• 按钮右上角显示未读消息红点
• 弹窗浮层覆盖背景
8. Stack 是纵向还是横向?
Stack(层叠布局)既不是单纯的纵向也不是横向布局,而是一种自由层叠的布局方式。
其主要特点如下:
布局特性
• 层叠排列:子元素按照添加顺序依次入栈,后添加的子元素会覆盖在先添加的子元素之上。
• 自由定位:子元素的位置可以通过 Positioned 组件或 Align 组件进行精确控制,支持绝对定位和相对定位。
• 重叠效果:适用于需要元素重叠显示的场景,如弹窗、悬浮按钮、覆盖层等。
与线性布局的区别
• 线性布局(Row/Column):
• Row:子元素在水平方向上依次排列。
• Column:子元素在垂直方向上依次排列。
• 层叠布局(Stack):
• 子元素不受限于单一的横向或纵向排列,可以在二维空间内任意定位和层叠。
使用场景
• 图片与文字叠加:在图片上叠加标题或描述文字。
• 按钮装饰:在按钮右上角添加徽章或未读消息提示。
• 模态弹窗:在页面上方覆盖一个半透明的背景层,显示弹窗内容。
示例代码
Stack(
children: [
Positioned(
top: 20,
left: 20,
child: Text('左上角文字'),
),
Positioned(
bottom: 20,
right: 20,
child: ElevatedButton(
onPressed: () {},
child: Text('右下角按钮'),
),
),
Center(
child: Image.asset('assets/image.png'),
),
],
)
效果说明: • 图片位于最底层,居中显示。 • 左上角显示一段文字。 • 右下角有一个按钮。
总结
• Stack 是一种二维层叠布局,不局限于横向或纵向排列。 • 适用于需要元素重叠和自由定位的复杂界面设计。 • 结合 Positioned 或 Align 组件,可以实现灵活的布局效果。
9. ArkUI中常用的容器组件?
Column / Row / Flex / Stack / List / Grid / Swiper / Tabs / Popup
10. 网格布局 Grid/GridItem?
在ArkUI里,网格布局由
Grid和GridItem配合使用,能将界面元素按二维网格排列Grid是用来创建网格布局的容器,它可以定义网格的整体结构和属性。常用属性:
columns:设置网格的列数。例如设置为 3,就会将网格布局划分为 3 列。
spacing:指定网格项之间的间距,既可以是一个统一的值,也能分别设置水平和垂直间距。
rows:可设置网格的行数,不过通常可以根据子组件数量和列数自动计算。
direction:决定网格的排列方向,有水平(FlexDirection.Row)和垂直(FlexDirection.Column)两种。
• GridItem 是 Grid 容器中的子组件,用于放置具体的内容,如文本、图片等。
rowStart、rowEnd、columnStart、columnEnd: 可以控制 GridItem 在网格中占据的行和列范围,实现跨越多个网格单元格的布局。
@Entry @Component struct GridExample {
build() {
Grid({ columns: 3, spacing: 10 }) {
GridItem() {
Text('Item 1')
}
GridItem() {
Text('Item 2')
}
// 更多 GridItem
}
}
}
11. 容器组件 Scroll?
Scroll 组件可以将其内部的子组件包裹起来,当子组件的尺寸超出 Scroll 组件的可视区域时,用户能够通过滚动操作来查看完整内容,就像手机上浏览长文章或者商品列表那样。
- 常用属性:
- scrollDirection: 用于指定滚动方向,有水平(ScrollDirection.Horizontal)和垂直(ScrollDirection.Vertical)两种,默认是垂直方向。
- scrollBar: 控制滚动条的显示,可设置为显示(ScrollBar.on)或隐藏(ScrollBar.off)。
- friction: 调节滚动时的摩擦力,影响滚动的顺滑程度。
- 应用场景:
- 长文章阅读:新闻、小说等应用中,文章内容较长,使用 Scroll 组件能让用户流畅地滚动阅读。
- 商品列表浏览:电商应用的商品展示列表,当商品数量较多时,可通过滚动查看更多商品。
- 图片轮播:在一些图片展示场景中,水平滚动的 Scroll 组件可以实现图片的轮播效果。
12. @Extend、@Styles、@Builder 区别?
| 名称 | 适合 | 参数 |
|---|---|---|
| @Styles | 抽取公共样式、事件 | 不可以传递参数 |
| @Extend | 扩展特定组件样式、事件 | 可以传递参数 |
| @Builder | 抽取结构、样式、事件 | 可以传递参数 |
13. 容器组件Tabs
· 通过页签进行内容视图切换的容器组件,每个页签对应一个内容视图。
组成属性
• TabBar:包含多个Tab标签,用于显示每个标签页的标题,用户点击这些标签来切换内容。
• TabContent:与每个Tab标签相对应,用于放置具体的内容,如文本、图片、列表等。
常用属性
• selectedIndex:指定当前选中的标签页索引,从0开始计数。通过设置这个属性,可以控制默认显示的标签页,也可以在代码中动态改变它来切换标签页。
• barPosition:设置TabBar的位置,有顶部(BarPosition.Top)和底部(BarPosition.Bottom)两种选择。
应用场景
• 多功能应用:如社交应用,可能有消息、联系人、动态等多个功能模块,使用Tabs组件可以方便用户在这些模块之间切换。
• 内容分类展示:在新闻应用中,可将不同类型的新闻(如时政、娱乐、体育等)以标签页的形式展示,用户可以快速切换查看不同类型的新闻。
@Entry
@Component
struct Index {
build() {
Tabs() { // 顶级容器
TabContent() {
// 内容区域:只能有一个子组件
Text('首页内容')
}
.tabBar('首页') // 导航栏
}
}
}
14. Badge组件?
以附加在单个组件上用于信息标记(气泡)的容器组件.
基本使用
Badge({
count:0, // 0 不显示 大于0显示
value:'0', // 设置字符串 可以是数字 会都显示
style:{}
}){
// 单个子元素
}
15.如何设置组件的垂直水平居中?
Flex布局
@Entry
@Component
struct FlexCenterExample {
build() {
Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {
Text('居中组件')
.fontSize(20)
.backgroundColor(Color.Grey)
}
.width('100%')
.height(300)
}
}
Stack布局
@Entry
@Component
struct StackCenterExample {
build() {
Stack({ alignContent: Alignment.Center }) {
Text('居中组件')
.fontSize(20)
.backgroundColor(Color.Grey)
}
.width('100%')
.height(300)
}
}
使用 Column 和 Row 布局组合
@Entry
@Component
struct ColumnRowCenterExample {
build() {
Column({
justifyContent: FlexAlign.Center,
alignItems: VerticalAlign.Center
}) {
Row({
justifyContent: FlexAlign.Center,
alignItems: ItemAlign.Center
}) {
Text('居中组件')
.fontSize(20)
.backgroundColor(Color.Grey)
}
}
.width('100%')
.height(300)
}
}
16. 页面布局上的性能和内存上的注意事项
- 使用row/column+layoutweight代替flex容器使用
- scroll嵌套list/grid容器时,要设置容器的宽高,数组数据渲染尽量使用LazyForeach渲染item
- 组件的显隐设置,要使用if语句来判断,避免使用visibility
- list/grid容器要根据具体场景来使用cachecount,避免卡顿
17. Row 组件如何将元素放到右下角?
.justifyContent(FlexAlign.End)
.alignItems(VerticalAlign.Bottom)
电商与社交及新闻类App布局与性能优化要点
电商App商品展示页
商品列表布局
• 避免过度嵌套:商品列表通常使用
List组件呈现。若在商品卡片内过度嵌套Column、Row等布局容器来展示商品信息(如标题、价格、图片等),会使渲染变慢。比如原本一个商品卡片用三层嵌套就能展示清楚,若写成五层嵌套,就会增加渲染负担。应尽量扁平化布局结构,把商品信息展示拆分成简单组件组合。• 合理使用布局容器:
List组件在处理大量商品数据时,本身具备懒加载特性,仅渲染当前可见区域的商品卡片。若不使用List而用Column逐个添加商品组件,会一次性加载所有商品信息,占用大量内存。商品图片展示
• 图片压缩:商品图片尺寸大,会导致加载缓慢且占用大量内存。比如一张高清商品图可能有几兆大小,应在上传前对图片进行压缩处理,同时根据不同设备分辨率提供不同尺寸图片。
• 图片懒加载:商品列表中可能有很多图片,采用懒加载机制,当商品图片滚动到可视区域时再加载,能减少初始加载时的内存占用和网络请求。
社交App动态信息流页
动态内容布局
• 减少不必要的组件创建:动态信息流中每条动态可能包含文本、图片、视频等内容。在使用
ForEach渲染动态列表时,若每次数据更新都整个动态组件,会消耗大量资源。可通过设置key属性复用组件,如根据动态的唯一ID设置key,ArkUI就能识别哪些组件可复用。• 优化数据绑定:动态的点赞数、评论数等数据会实时更新,若将这些数据全部绑定到界面,频繁更新会触发界面重新渲染。可将部分数据(如点赞数)缓存起来,设置一个更新阈值,达到一定阈值再更新界面显示,减少不必要的渲染。
视频播放组件
• 及时释放不再使用的组件:
当用户滑动动态列表,视频组件滑出可视区域时,应及时释放视频资源,避免内存泄漏。可监听组件的显示状态,当不可见时停止视频播放并释放相关资源。
新闻资讯App新闻列表页
新闻条目布局
• 避免过度嵌套:新闻条目通常包含标题、摘要、图片等信息。若用复杂的嵌套布局来展示这些内容,会影响渲染性能。可采用简单的
Column和Row组合布局,将标题、摘要、图片等信息合理排列。• 合理使用布局容器:新闻列表使用
List组件展示,若新闻有不同分类,可使用Group组件进行分组,避免使用过于复杂的布局容器。广告展示
• 避免过度响应式数据:广告部分可能会有一些动态展示内容,如倒计时、广告轮播等。对于一些静态的广告信息,如广告标题、描述等,使用普通变量存储,而不是响应式数据,减少内存开销。
异步
鸿蒙系统(HarmonyOS)中的异步任务主要通过Promise和async/await、TaskPool和Worker等方式实现。这些方法提供了不同的并发处理策略,适用于不同的场景。
说说你在鸿蒙里面怎么处理的异步函数
在鸿蒙系统中,处理异步函数通常使用async 和 await 语法,与 JavaScript 类似,在 async 函数中使用 await 来等待异步操作完成,从而简化异步代码的编写和错误处理,这样,可以将异步操作像同步代码一样处理,使代码更清晰易读。
什么是promise?
Promise是一种用于处理异步操作的对象,可以将异步操作转换为类似于同步操作的风格,
并提供了一种方式来处理操作成功或失败的结果以方便代码编写和维护
Promise 必然处于 3 种状态中的某一种,调用**resolve,reject **的本质就是更改他的状态
Promise的3 种状态:
待定(pending): 初始状态,既没有被兑现,也没有被拒绝
已兑现(fullfilled): 意味着操作成功完成
已拒绝(rejected): 意味着操作失败
回调函数:
then () 方法:用于指定当 Promise 状态变为 fulfilled 时要执行的回调函数。可以接收 Promise 的结果值作为参数。可以链式调用多个 then (),以处理多个异步操作的结果。
catch () 方法:用于指定当 Promise 状态变为 rejected 时要执行的回调函数。接收错误原因作为参数。
如果在异步操作过程中发生错误,Promise 会自动进入 rejected 状态,并触发 catch () 方法中的回调函数。这样可以方便地集中处理错误,避免使用**回调地狱(callback hell)**中复杂的错误处理逻辑。
Promise 的静态方法:
日常开发中除了使用 Promise 对象以外,还可以通过 Promise 提供的静态方法来管理多个异步:
Promise.all() // 多个Promise,全部成功,某一个失败
Promsie.race() // 多个 Promise,第一个成功 or 失败
Promise.resolve() // 成功状态的 Promise 对象
Promise.reject() // 失败状态的 Promise 对象
Promise.allSettled() // 获取所有的结果,无论成功失败(对象数组)
// 如果请求数据race是赛跑机制 - all是同步 一般使用allSettled全部成功有某个结果失败了其他也能出来
await async 的底层原理
例如:
async function func() {
// await 获取到的是 之后 Promise 对象的成功结果
const res1 = await Promise对象1
const res2 = await Promise对象2
const res3 = await Promise对象3
}
func()
async/await是一种用于处理异步操作的Promise语法糖,使得编写异步代码变得更加简单和易读。通过使用async关键字声明一个函数为异步函数,并使用await关键字等待Promise的解析(完成或拒绝),以同步的方式编写异步操作的代码。async 函数:
个函数被标记为async 时,它会自动返回一个 Promise。
在async 函数内部,可以使用 await 关键字来等待一个 Promise 的解决。如果 await 后面的表达式不是一个 Promise,它会被自动包装成一个 Promise。
await 等待成功(Promise 对象)
await:当遇到 await 关键字时,函数的执行会暂停,直到 await 后面的 Promise 被解决。如果 Promise 被成功解决, await 表达式的值就是 Promise 的结果;如果 Promise 被拒绝,await 会抛出一个错误。
await 只能在 async 函数内部使用
当async 函数内部执行到一个 await 表达式时,函数的执行会暂停,并将控制权返回给调用者。此时注册一个回调函数,当 await 后面的 Promise 被解决时,这个回调函数会被调用,以继续执行函数的剩余部分。
如果await 后面的 Promise 被拒绝,会抛出一个错误,这个错误会被 try/catch 块捕获,或者如果没有被捕获,会导致整个 Promise 链被拒绝。
鸿蒙怎么处理并发?
- Promise 和 async/await 提供异步并发能力,适用于单次 I/O 任务。
- TaskPool 和 Worker 提供多线程并发能力,适用于 CPU 密集型任务、I/O 密集型任务等复杂并发场景。
Promise 如何使用,用在什么场景?
1.promise.prototype.then()是promise实例的回调函数,接受两个回调函数作为参数,
第一个回调函数是promise对象的状态变为resolve时调用,(成功)
第二个回调函数是promise对象的状态变为 rejected时调用,(失败)
返回的是另一个promise对象,后面可以继续.then
2.promise.prototype.catch()
用于指定发生错误时的回调函数,返回的是一个promise对象,
3.promise.prototype.finally()
finally方法用于指定不管promise对象最后状态如何,都会执行的回调函数
静态API
promise.resolve() 返回新的状态为resolve的promise 对象
promise.reject() 返回一个新的状态为reject的promise对象
promise.all()等待所有异步成功结果在返回 回调
promise.race()哪个结果返回来的快就是哪个结果,不管是成功还是失败
promise.allSettled (【1,2,3】)数组中的异步依次返回成功结果 其中一个失败不会影响其他数据
使用场景(使用promise的多种方法)
(做耗时的操作时可以用promise包住,拿到结果)
1.解决回调地狱
可以链式调用,解决回调地狱(回调函数嵌套产生回调地狱问题)
async 和await 也可以解决回调地狱问题
2.多个并发请求
多个并发请求可以通过静态allsettled ,all,或者async/await 语法糖处理
Promise 异步、和TaskPool有什么区别吗
简言之,Promise 适合处理异步操作的结果,而 TaskPool 更适合管理大量并发任务。
http是在主线程内还是另起一个线程?那么异步和TaskPool有什么区别吗
HTTP请求默认是在主线程中执行的,但如果请求是耗时操作,通常建议将其放在后台线程中执行,以避免阻塞主线程。这是因为主线程通常负责UI更新和其他需要快速响应的操作,如果在主线程中执行耗时的网络请求,会导致应用程序变得无响应。
异步:关注的是如何处理耗时操作,使得主线程不必等待这些操作完成。
TaskPool:关注的是如何管理和调度任务,特别是并发任务的执行。
http请求怎么使用TaskPool实现?
1.引入依赖库: 确保你的项目中已经引入了必要的依赖库,如ohos.net.http.HttpRequest和ohos.taskpool.TaskPool。
2.创建TaskPool: 创建一个TaskPool实例,并设置线程池的大小。
3.定义HTTP请求任务: 定义一个任务类,实现Runnable接口,用于执行HTTP请求。
4.提交任务: 将HTTP请求任务提交到TaskPool中执行。
网络请求模块是怎么做到的(封装)?
网络请求模块是怎么做到的(封装)网络请求模块的封装是为了简化应用中的网络请求操作,提高代码的可维护性和复用性。一个典型的网络请求模块封装通常包括以下几个方面:
- 选择合适的网络库:选择一个稳定、高效的网络库作为基础,如Axios、Fetch API、Moya等。
- 统一配置:对网络请求的基本配置进行统一设定,比如基础URL、超时时间、默认请求头等。错误处理:封装时应该考虑各种可能的错误情况,并提供统一的错误处理逻辑。请求和响应拦截器:在请求发出前和响应返回后添加拦截器,可以用来修改请求参数或响应数据。请求取消机制:提供请求取消的能力,防止不必要的请求消耗资源。
3.API模块化:将不同的API请求封装成独立的模块或函数,每个模块负责一个特定的服务端接口。
是否用过多线程,鸿蒙有哪些多线程,为什么鸿蒙提供这些多线程,有什么使用场景
鸿蒙系统提供了多种多线程机制,主要包括TaskPool(任务池)和Worker。鸿蒙提供这些多线程机制的原因主要是为了提升系统的整体性能和响应能力,通过并发执行任务来优化资源利用,减少主线程的阻塞,从而提高应用的流畅度和用户体验。
TaskPool(任务池)
作用:TaskPool为应用程序提供一个多线程的运行环境,通过任务队列和调度算法来分配和执行任务,降低资源消耗,提高系统性能。
特点:
1.无需关心线程实例的生命周期。
2.支持任务的执行、取消,以及指定优先级的能力。
3.动态调整线程个数,根据硬件条件、任务负载等情况进行动态调度和负载均衡。
4.任务执行时长上限为3分钟(不包含异步调用的耗时)。
使用场景:适用于需要并发执行大量短时任务的情况,如周期性任务处理、延迟业务处理、串行业务处理等。
Worker
作用:Worker为应用程序提供一个独立的线程运行环境,可以在后台线程中执行耗时操作,避免阻塞主线程。
特点:
1.每个Worker子线程与宿主线程拥有独立的实例,包含基础设施、对象、代码段等。
2.Worker子线程和宿主线程之间的通信基于消息传递和序列化机制。
3.创建Worker的线程个数有限制(最多64个),且需要手动管理生命周期。
4.传输序列化数据大小限制在16MB。
使用场景:适用于执行 CPU密集型或I/O密集型任务,如复杂计算、文件读写、网络请求等。
综上所述,鸿蒙系统提供的TaskPool和Worker多线程机制为开发者提供了强大的并发处理能力,使得开发者能够更高效地开发高性能、高响应度的应用。
TaskPool线程的 数据怎么和主线程 同步? 线程间怎么同步数据
TaskPool 提供了sendData接口,允许子线程向主线程发送数据。
主线程通过注册onReceiveData回调来接收数据。
线程间的数据同步可以通过互斥锁、条件变量、信号量、消息队列和事件等机制来实现
说一说对于promise的理解?
Promise是一种用于处理异步操作的对象,可以将异步操作转换为类似于同步操作的风格,以方便代码编写和维护(解决回调地狱的问题)。如果异步操作用到了 Promise 来进行管理那么获取异步操作结果(成功 or 失败)的方式都是一样的。
Promise有三种状态,分别是pending—待定、fulfilled—已兑现、rejected—已拒绝。调用resolve,reject的本质就是更改他的状态,且状态的改变不可逆。已兑现的promise用.then来捕捉,then 方法会返回一个新Promise对象,then方法的返回值会影响这个新Promise对象的结果。已拒绝的promise用.catch捕捉。
有没有用过鸿蒙多线程 怎么用 什么场景会用到
用过。
使用方式:首先通过特定的API来创建线程,其次在任务代码中实现复杂的逻辑处理,如数据处理、网络通信、文件操作等。当多个线程需要访问共享资源或相互通信时,可以使用互斥锁、条件变量、信号量等来避免数据竞争、死锁等问题,并确保线程之间的正确交互。
常见使用场景:网络请求、数据处理、后台任务、复杂交互等
线程的两种方式有什么区别?
在鸿蒙(HarmonyOS)中,实现多线程的主要有两种方式:线程和协程,协程更轻量,适合高实时性需求;线程更独立,适合复杂并发操作。
http是在主线程内还是另起一个线程?
HTTP 请求通常在主线程中发起,但现代编程环境多采用异步方式处理,避免阻塞主线程,从而影响应用的响应速度。
为了避免这种情况,通常建议在子线程或通过异步任务(如 async、Promise 等)来执行网络请求,以确保主线程的流畅性。
多线程改变了主线程的值 那么主线程内的值会不会一起改变?
在多线程环境中,一个线程对共享变量的修改是否能被其他线程(包括主线程)看到,取决于以下几个方面:
1.内存可见性:确保线程之间的修改能够互相可见。
2.同步机制:确保线程之间的正确通信。
子线程和主线程如何通讯?
首先在华为HarmonyOS(鸿蒙系统)开发中,主线程(UI线程)通常用于处理UI相关的任务,而子线程则用于执行耗时的后台任务,如网络请求、文件读写等。
主线程子线程通讯包括有Emitter、Worker、EventHandler和EventRunner等方法。
1.Emitter的步骤通常包括:创建Emitter实例。
1. 在需要接收事件的线程中订阅事件。
2. 在触发事件的线程中发送事件。
2.Worker的步骤通常包括:创建Worker实例,并指定要执行的脚本或代码。
3. 在主线程中发送消息给Worker线程,启动任务。
4. Worker线程完成任务后,可以发送消息回主线程,主线程根据消息内容更新UI。
3.EventHandler和EventRunner
1. EventHandler用于在当前线程上投递InnerEvent事件或Runnable任务到异步线程上处理。
2. EventRunner是一种事件循环器,
3. 循环处理从该EventRunner创建的新线程的事件队列中获取InnerEvent事件或Runnable任务。
总结:HarmonyOS提供了多种线程间通信的方式以满足不同场景下的需求。开发者可以根据具体的应用场景和需求选择最合适的通信方式。无论是使用Emitter进行事件同步,还使用Worker执行耗时任务,或是通过事件处理机制进行线程间通信,都可以有效地提高应用的性能和响应性。
axios 的原理,以及用法?
Axios 是一个基于 Promise 的 HTTP 客户端,用于在浏览器和 Node.js 中发送 HTTP 请求。
其工作原理主要包括以下几个方面:创建请求配置对象:包含请求的方法(如 GET、POST 等)、URL、请求头、请求体等信息。利用 XMLHttpRequest 对象或浏览器的 fetch API 发送请求。处理请求的响应:包括解析响应数据、处理错误状态等。返回一个 Promise 对象,以便进行异步操作的处理和链式调用用法
http 请求工具如何封装?
http请求工具封装可以使用官方提供的@ohos.net.http模块,也可以使用前端流行的axios封装。
使用axios封装首先在Terminal窗口中,执行如下命令安装三方包
ohpm install @ohos/axis
开通网络权限
需要配置ohos.permission.INTERNET权限,在工程目录entry\src\main中找到module.json5文件,配置网络请求权限。
封装的步骤:
首先,我们创建一个axios实例,并配置一些全局的默认值,比如baseURL(基础URL)、timeout(超时时间)等。
然后,为axios实例添加请求拦截器,用于在请求发送前进行某些操作,比如添加token、设置请求头等。同样地,添加响应拦截器来处理响应数据。比如处理错误码、统一返回数据格式等。
最后,根据实际需求封装具体的请求方法,如get、post、put、delete等,这些方法接收URL、请求参数、请求头和请求体,并返回一个包含响应数据、状态码和可能异常的封装对象,并导出这些封装好的方法供其他模块使用。
通过以上步骤,我们可以有效地封装axios,实现HTTP请求的统一管理,增强代码的复用性和可维护性。同时,通过拦截器的使用,我们可以灵活地处理请求和响应,满足不同的业务需求。
使用过哪些第三方库?分别介绍一下
Axios ,是一个基于 promise 的网络请求库,可以运行 node.js 和浏览器中。
logger,日志打印工具
优化
怎么做性能优化?
1、使用row/column+layoutweight代替flex容器使用
- scroll嵌套list/grid容器时,要设置容器的宽高,长列表数组数据渲染尽量使用lazyforeach渲染item
- 组件的显隐设置,要使用if语句来判断,避免使用visibility
- list/grid容器要根据具体场最来使用cachecount,避免卡顿
5.性能优化,主要在于减少嵌套,减少动态渲染的频率
6.异步加载数据:在加载数据时,可以采用异步加载的方式,避免阻塞 UI 线程。这样可以保证 UI 的流畅性,提高用户体验。
7.进行性能测试:在开发过程中,要定期进行性能测试,包括压力测试、负载测试等。通过性能测试可以发现应用中的性能瓶颈,并及时进行优化。
8.使用性能监控工具:鸿蒙提供了一些性能监控工具,可以实时监测应用的性能指标,如 CPU 使用率、内存占用、网络流量等。通过监控这些指标,可以及时发现性能问题,并进行优化。
页面性能和内存优化?
性能和内存是两个指标,有时候追求性能,就需要做缓存处理,吃内存。
页面内存优化,主要在减少缓存的处理。
性能优化,主要在于减少嵌套,减少动态渲染的频率。
鸿蒙项目做性能优化的方式?
我们把应用性能分析的方法划分为了性能分析四板斧,下面将介绍如何使用性能分析四板斧,解决应用开发过程中的性能问题。
第一板斧:合理使用并行化、预加载和缓存,我们需要合理地使用并行化、预加载和缓存等方法,例如使用多线程并发、异步并发、Web预加载等能力,提升系统资源利用率,减少主线程负载,加快应用的启动速度和响应速度。
第二板斧:尽量减少布局的嵌套层数,在进行页面布局开发时,应该去除冗余的布局嵌套,使用相对布局、绝对定位、自定义布局、Grid、GridRow等扁平化布局,减少布局的嵌套层数,避免系统绘制更多的布局组件,达到优化性能、减少内存占用的目的。
第三板斧:合理管理状态变量,应该合理地使用状态变量,精准控制组件的更新范围,控制状态变量关联组件数量,控制对象级状态变量的成员变量关联组件数,减少系统的组件渲染负载,提升应用流畅度。
第四板斧:合理使用系统接口,避免冗余操作,应该合理使用系统的高频回调接口,删除不必要的Trace和日志打印,避免注册系统冗余回调,减少系统开销。
详情见鸿蒙面试宝典-加薪篇1-项目性能优化,重点查看官方文档:https://developer.huawei.com/consumer/cn/doc/best-practices-V5/bpta-performance-V5
鸿蒙中的后台任务有哪些?
为什么会有后台任务?简单的说是对系统资源cpu使用和电量的一个整体优化。如果软件在后台无限制的话,很容易造成系统资源浪费,耗电量增加的问题。
开发者可以根据如下的功能介绍,选择合适的后台任务,以满足应用退至后台后继续运行的需求。
- 短时任务:适用于实时性要求高、耗时不长的任务,例如应用更新时退至后台后继续更新,保存更新时的数据和状态。
- 长时任务:适用于长时间运行在后台、用户可感知的任务,例如后台播放音乐、导航、设备连接等,使用长时任务避免应用进程被挂起。
- 延迟任务:适用于实时性要求不高、可延迟执行的任务。应用退至后台后被放入执行队列,系统会根据内存、功耗等统一调度,例如有网络时不定期主动获取邮件。
- 代理提醒:代理提醒是指应用退后台或进程终止后,系统会代理应用做相应的提醒。适用于定时提醒类业务,当前支持的提醒类型包括倒计时、日历和闹钟三类。
链接地址:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V2/background-task-overview-0000001614944868-V2
如何优化长列表渲染?
在使用长列表时,如果直接采用循环渲染方式,会一次性加载所有的列表元素,从而导致页面启动时间过长,影响用户体验,可以通过以下方式来进行长列表性能优化:
· 懒加载:提供列表数据按需加载能力,解决一次性加载长列表数据耗时长、占用过多资源的问题,可以提升页面响应速度。
· 缓存列表项:提供屏幕可视区域外列表项长度的自定义调节能力,配合懒加载设置可缓存列表项参数,通过预加载数据提升列表滑动体验。
· 动态预加载:根据历史任务加载耗时情况,动态调整屏幕可视区域外数据预取数量,配合懒加载设置,可在列表不断滑动时,屏幕可视区外实时更新列表数据,通过预取和预渲染数据提升列表滑动体验。
· 组件复用:提供可复用组件对象的缓存资源池,通过重复使用已经创建过并缓存的组件对象,降低相同组件短时间内频繁创建和销毁的开销,提升组件渲染效率。
· 布局优化:使用扁平化布局方案,减少视图嵌套层级和组件数,避免过度绘制,提升页面渲染效率。
需要做复杂场景下长列表的性能优化,你怎么做?
1.分页:将长列表分成多个页面,用户可以逐步加载更多数据,降低一次性渲染的压力
2.懒加载:减少初始加载时间
3.减少事件监听:尽量减少在列表项上添加事件监听器
上线
鸿蒙怎么调试
在鸿蒙系统中,调试应用程序可以通过多种方式进行,以下是一些常用的调试方法和工具:
- 使用 DevEco Studio
DevEco Studio 是华为官方提供的开发环境,支持鸿蒙应用的开发和调试。使用 DevEco Studio 进行调试的步骤如下:
安装DevEco Studio:确保你已经安装了最新版本的 DevEco Studio。
创建或打开项目:在DevEco Studio 中创建新的鸿蒙项目或打开现有项目。
连接设备:将你的鸿蒙设备通过USB 连接到电脑,并确保设备已开启开发者模式。
选择调试配置:在DevEco Studio 中选择要调试的设备和应用模块。
设置断点:在代码中设置断点,以便在运行时暂停执行并检查变量状态。
启动调试:点击调试按钮,应用将在连接的设备上运行,并在设置的断点处暂停。 - 使用命令行工具
鸿蒙系统提供了一些命令行工具,可以用于调试和管理应用:
使用adb 命令:通过 Android Debug Bridge (ADB) 工具,可以执行多种调试操作,例如查看日志、安装应用等。 - 使用日志输出
在代码中使用日志输出,可以帮助你跟踪应用的运行状态。可以使用Log 类输出日志信息。 - 使用远程调试
如果你的应用运行在鸿蒙设备上,可以使用DevEco Studio 的远程调试功能。确保设备和开发环境在同一网络下,然后在 DevEco Studio 中配置远程调试。 - 性能分析
DevEco Studio 提供性能分析工具,可以帮助你识别应用中的性能瓶颈。通过分析 CPU、内存和网络使用情况,优化应用性能。 - UI 调试
使用DevEco Studio 的 UI 调试工具,可以实时查看和修改应用的 UI 组件,帮助你快速定位和解决界面问题。
总结
调试鸿蒙应用可以通过DevEco Studio、命令行工具、日志输出等多种方式进行。选择合适的调试方法,可以有效提高开发效率和应用质量。
项目流程是怎么样的?
(1). 环境准备:
- 下载并安装鸿蒙应用开发工具DevEco Studio。这是华为提供的官方IDE(集成开发环境),类似于Android Studio,提供了开发、调试、构建等功能。
- 安装必要的插件和组件,例如 通义灵码 等。
(2). 开发者账号注册:
- 注册并实名认证华为开发者账号。访问鸿蒙官网(www.harmonyos.com)注册并登录华为账号后,进行实名认证。
(3). 创建项目:
- 在DevEco Studio中,选择“Create Project”来创建一个新的项目。
- 选择项目模板,比如创建一个简单的Hello World项目。
(4). 设计界面:
- 使用DevEco Studio内置的设计工具来设计应用的UI(用户界面)。鸿蒙支持多种UI设计方式,包括XML布局文件和可视化的拖拽设计。
(5). 编码开发:
- 根据应用需求,使用 ArkTS、C++ 、仓颉语言进行编程。鸿蒙支持多种语言开发,其中 ArkTS 是最常用的。
- 鸿蒙特有的 Ability 模型也需要被理解和使用,Ability类似于Android中的Activity,但更加灵活,可以实现更细粒度的功能模块化。
(6). 集成开发资源:
- 将所需的图片、音频、视频等资源文件添加到项目中。
(7). 测试:
- 使用DevEco Studio自带的模拟器进行初步的功能测试。
- 对于更复杂的测试,可能需要真机测试,确保应用能够在不同的设备上正常运行。
(8). 调试与优化:
- 使用DevEco Studio的调试工具对应用进行调试,查找并修复bug。
- 对应用的性能进行优化,确保良好的用户体验。
(9). 打包发布:
- 使用DevEco Studio将应用打包成 HAP(HarmonyOS Ability Package)格式。
- 上传至华为应用市场 AppGallery Connect 平台进行审核。
- 审核通过后,应用即可上线供用户下载使用。
(10). 持续迭代维护: 根据用户反馈和市场变化,对应用进行持续更新和维护。
鸿蒙打包和上架流程
- 在开发工具编译 app 包,注意:签名 p12、p7b、csr、cer 提前弄好发布证书,受限权限需要发邮件申请通过。
- 在 agc 平台上传应用,注意:开发时的 bundleName 跟平台的包名要对上。
- 按要求填写应用信息,包括图标,说明,软著(花钱 - 需要等 6 - 10 天),备案信息。
- 审核通过,就可以把应用上架。
- 上架后可以管理应用的版本。
- 可以新建测试群组,通过开发者账号添加测试用户。
- 发不到不同的端,AGC 会自动按 entry 中的 deviceTypes 自动分发到对应的应用商店。
简述鸿蒙打包上线流程
一、准备阶段
确保开发环境:安装并配置好鸿蒙开发者工具(如DevEco Studio)和相应的SDK。
申请上架:在AppGallery Connect申请上架,华为审核通过后,用户即可在华为应用市场获取您的HarmonyOS应用/元服务。
二、AppGallery Connect项目创建
登录AppGallery Connect:使用您的华为开发者账号登录。
创建项目:在AppGallery Connect中找到“我的项目”,点击“创建项目”,输入项目名称后继续。
配置分析服务(可选):项目创建后,可以开通分析服务以便后续查看相关指标数据。
三、创建HarmonyOS应用/元服务
添加应用:在项目中点击“添加应用”。
填写应用信息:
选择平台:选择APP (HarmonyOS)。
应用包名:与项目配置文件中的bundleName值保持一致。
是否元服务:根据应用类型选择是或否。
四、编译打包HarmonyOS应用/元服务
生成密钥和证书请求文件:
在DevEco Studio中,选择“Build > Generate Key and CSR”。
填写密钥库信息和密钥信息,生成.p12密钥库文件和.csr证书请求文件。
申请发布证书:
登录AppGallery Connect,选择“用户与访问”->“证书管理”。
点击“新增证书”,填写证书信息并上传.csr文件,提交申请。
下载生成的证书文件(.cer)。
配置签名信息:
在DevEco Studio中,配置签名信息,包括密钥库文件、密钥库密码、密钥别名、密钥密码等。
编译打包:
选择“Build > Build Hap(s)/APP(s) > Build APP(s)”进行编译构建。
编译完成后,获取用于上架的软件包(HAP或APP)。
五、上架HarmonyOS应用/元服务
登录AppGallery Connect:选择“我的应用”。
填写应用信息:在应用列表中点击HarmonyOS应用页签,填写应用的基本信息(如语言、应用名称、应用介绍等)并上传应用图标。
填写版本信息:包括发布国家或地区、上传软件包、提交资质材料等。
提交审核:所有配置完成后,点击右上角“提交审核”。
注意事项
确保所有步骤中填写的信息准确无误,以避免审核不通过或后续问题。
在打包过程中,注意保持开发环境与生产环境的一致性,确保应用在不同环境下的稳定性。
关注华为开发者联盟的最新政策和指南,以确保应用符合最新的上架要求。
什么是鸿蒙应用签名?有哪些常用的应用签名?
鸿蒙应用签名是为了保证应用的安全性和可信任性,防止应用被篡改或恶意植入恶意代码,通过对应用进行数字签名,可验证应用的真实性和完整性,确保应用未被篡改。其主要涉及以下内容:
• 密钥:格式为.p12,包含非对称加密中使用的公钥和私钥,存储在密钥库文件中,公钥和私钥对用于数字签名和验证。
• 证书请求文件:格式为.csr,全称为 Certificate Signing Request,包含密钥对中的公钥和公共名称、组织名称、组织单位等信息,用于向 AppGallery Connect 申请数字证书。
• 数字证书:格式为.cer,由华为 AppGallery Connect 颁发。
• profile 文件:格式为.p7b,包含 HarmonyOS 应用的包名、数字证书信息、描述应用允许申请的证书权限列表,以及允许应用调试的设备列表(如果应用类型为 release 类型,则设备列表为空)等内容,每个应用包中必须包含一个 profile 文件。
通常,鸿蒙应用签名的实现流程如下:
- 生成密钥对:使用工具生成应用的密钥对,包括公钥和私钥。
- 对应用进行签名:使用私钥对应用进行签名,生成签名文件。
- 将签名文件添加到应用中:将签名文件添加到应用的 meta-inf 目录下。
- 验证应用的签名:在运行时,系统会验证应用的签名,确保应用的完整性和可信任性。
常见的应用签名主要有以下几种:
一、Android 应用签名 二.鸿蒙应用签名三、iOS 应用签名
ArkTS阶段
1、ArkTS 与 JavaScript 在语法上有哪些主要区别?
类型系统
● JavaScript:动态类型,适合小型、快速迭代项目,大项目易出运行时类型错误。
● ArkTS:静态类型,编译查错,用于大型鸿蒙应用保障数据交互。
类和继承
● JavaScript:基于原型链,简单复用可用,复杂关系难维护。
● ArkTS:支持抽象类和接口,构建严谨类结构,利于大型应用扩展。
装饰器
● JavaScript:实验特性,前端 AOP 编程受限。
● ArkTS:语言组成,用于鸿蒙组件标记与配置。
模块系统
● JavaScript:多规范,不同环境选择复杂。
● ArkTS:遵循 ES6 结合鸿蒙机制,管理高效。
应用开发语法
● JavaScript:操作 DOM 实现交互,适合传统 Web。
● ArkTS:声明式构建 UI,开发鸿蒙应用高效。
3、typeof 返回的数据类型?
typeof 是一个操作符,用于返回一个表示数据类型的字符串。它能返回以下几种结果:
- "number": 例如:
typeof 10和typeof 3.14都会返回"number"。 - "string": 例如:
typeof 'hello'返回"string"。 - "boolean": 例如:
typeof true返回"boolean"。 - "object": 用于对象、数组、
null。例如:typeof {}、typeof []和typeof null都返回"object"。不过需要注意,null是一个原始值,这属于typeof的历史遗留问题。 - "function": 例如:
typeof function() {}返回"function"。 - "undefined": 例如:
let a; typeof a返回"undefined"。 - "symbol": 用于 ES6 引入的
Symbol类型。例如:typeof Symbol()返回"symbol"。 - "bigint": 用于 ES2020 引入的
BigInt类型。例如:typeof 123n返回"bigint"。
typeof 与 instanceof 的区别
作用不同
typeof用于判断基本数据类型,对于对象类型,除函数外只能判断为"object",无法区分具体对象类型。instanceof用于判断对象是否是某个构造函数或类的实例,关注对象的原型链。
返回值不同
typeof返回表示数据类型的字符串。instanceof返回布尔值,是实例返回true,否则返回false。
5、== 和 === 的区别?
是什么?
- 都是我们的比较运算符,用来判断数据相等还是全等
区别
==表示是相等,比较值是否相等===表示是全等,不仅比较值,也比较类型是否相等
8、值类型和引用类型的区别?
- 是什么
- ArkTS中数据分为值类型(基本数据类型)和引用类型(复杂数据类型)
- **区别 **
存储位置不一样
值类型的会保存在栈内存中
引用类型的变量名(地址)会存在栈内存中,但是值会存储在堆内存中赋值方式不一样
值类型的直接赋值,赋的值本身
引用类型赋值,赋的是地址(影响=>修改值会相互影响=>解决:浅拷贝或深拷贝)-
12、说一说数组常用的API
数组都是 Array 的实例化对象,它提供了很多的 方法来帮助我们快速处理数据
- push() - 在数组末尾添加一个或多个元素,并返回新的长度。
- pop() - 删除数组的最后一个元素,并返回那个元素。
- shift() - 删除数组的第一个元素,并返回那个元素。
- unshift() - 在数组的开始添加一个或多个元素,并返回新的长度。
- slice() - 返回数组的一个浅拷贝。
- splice() - 通过删除现有元素和/或添加新元素来更改一个数组的内容。
- concat() - 连接两个或更多数组,并返回一个新数组。
- join() - 将数组中的所有元素转换为一个字符串。
- reverse() - 颠倒数组中元素的顺序。
- sort() - 对数组的元素进行排序。
- forEach() - 遍历数组中的每个元素并执行回调函数。
- map() - 创建一个新数组,其结果是该数组中的每个元素是调用一次提供的函数后的返回值。
- filter() - 创建一个新数组, 其包含通过所提供函数实现的测试的所有元素。
- reduce() - 对数组中的每个元素执行一个由您提供的reducer函数(升序执行),将其结果汇总为单个返回值。
- reduceRight() - 对数组中的每个元素执行一个由您提供的reducer函数(降序执行),将其结果汇总为单个返回值。
13、new操作符做了什么
- 创建一个新对象
- 构造函数/Class内部的this指向这个对象
- 执行构造函数代码
- 返回新对象
14、说说你对泛型的理解
泛型主要用于增强代码的复用性、类型安全性和可维护性。
- 常见应用场景
- 集合类型 :如列表、数组等容器类,通过泛型约束元素类型。
- 函数与返回值 :泛型函数可处理多种类型参数并返回对应类型结果。
- 接口与类 :定义通用接口或类时,通过泛型参数抽象数据类型。
- API响应结构 :例如
Result<T>接口中,T表示实际数据字段的类型,确保不同接口的数据类型明确。
// -----------------1. 泛型函数
function identity<T>(arg: T): T {
return arg;
}
// 调用时显式指定类型或通过类型推断
const output = identity<string>("hello");
// -----------------2. 泛型接口
interface Result<T> {
code: number;
data: T; // 数据字段类型由使用处指定
message: string;
}
// 使用示例
const userResult: Result<User> = { code: 200, data: user, message: "OK" };
// -----------------3. 泛型类
class Box<T> {
private value: T;
constructor(value: T) {
this.value = value;
}
getValue(): T {
return this.value;
}
}
// 实例化时指定类型
const numberBox = new Box<number>(42);
// -----------------4. 泛型约束
interface HasLength {
length: number;
}
function logLength<T extends HasLength>(arg: T): void {
console.log(arg.length);
}
// 调用时只能传入包含length属性的对象
logLength("text"); // OK,字符串有length属性
logLength([1, 2, 3]); // OK
15、 说一下interface和type的区别
interface 和 type 都用于定义类型,但它们在语法特性、扩展能力和应用场景上有显著区别:

优先使用**interface**的场景
**需要明确的对象结构: **如定义组件 Props、API 响应数据模型等。
面向对象设计:类实现接口(implements )或接口继承(extends )。
**声明合并需求: ** 扩展已有类型声明(如第三方库的类型补全)。
优先使用 **type**的场景
**复杂类型组合 ** :联合类型、交叉类型、元组、映射类型等。
**类型别名简化 ** :为复杂类型(如函数类型)提供可读性更高的别名
**字面量类型约束 ** :明确限定值的取值范围:
16、枚举(enum)是什么,它的优势,应用案例
枚举允许将一组 ** 相关的常量值 ** 封装在一个命名空间下,通过语义化的名称替代原始值(如数字或字符串)。在ArkTS中,枚举分为两种主要类型:
** 数字枚举(Numeric Enums) ** :默认从0开始递增的数值常量。
** 字符串枚举(String Enums) ** :每个成员显式指定字符串值。
应用场景:
定义应用或组件状态,如请求状态、UI状态等
限定函数或组件的可选参数,避免无效配置
标识业务类型或权限等级,增强逻辑清晰度
管理多语言或主题配置,便于动态切换
// 数字枚举(默认从0开始)
enum Direction {
Up, // 0
Down, // 1
Left, // 2
Right // 3
}
// 字符串枚举
enum Theme {
Light = "light",
Dark = "dark"
}
17、什么是联合类型和交叉类型?
联合类型 表示一个值可以是多种类型中的一种,而 交叉类型 表示一个新类型,它包含了多个类型的特性。
- 联合类型示例:
// typescript
let myVar: string | number;
myVar = "Hello"; // 合法
myVar = 123; // 合法
- 交叉类型示例:
interface A {
a(): void;
}
interface B {
b(): void;
}
type C = A & B; // 表示同时具备 A 和 B 的特性
18、什么是类型断言(Type Assertion)
类型断言允许程序员手动指定一个值的类型。这在需要明确告诉编译器某个值的类型时非常有用。
let someValue: any = "this is a string";
let strLength: number = (someValue as string).length;
19、 const和readonly的区别?
当在ArkTS中使用const和readonly时,它们的行为有一些显著的区别:
- const:
- const用于声明常量值。一旦被赋值后,其值将不能被重新赋值或修改。
- 常量必须在声明时就被赋值,并且该值不可改变。
- 常量通常用于存储不会发生变化的值,例如数学常数或固定的配置值。
const PI = 3.14;
PI = 3.14159; // Error: 无法重新分配常量
- readonly:
- readonly关键字用于标记类的属性,表明该属性只能在类的构造函数或声明时被赋值,并且不能再次被修改。
- readonly属性可以在声明时或构造函数中被赋值,但之后不能再被修改。
- readonly属性通常用于表示对象的某些属性是只读的,防止外部代码修改这些属性的值。
class Person {
readonly name: string;
constructor(name: string) {
this.name = name; // 可以在构造函数中赋值
}
}
let person = new Person("Alice");
person.name = "Bob"; // Error: 无法分配到"name",因为它是只读属性
总结来说, const 主要用于声明常量值,而 readonly 则用于标记类的属性使其只读。
20、interface可以给Function/Array/Class做声明吗?
interface 可以用来声明函数、数组和类(具有索引签名的类)。下面是一些示例代码:
1. Interface 声明函数
interface MyFunc {
(x: number, y: number): number;
}
let myAdd: MyFunc = function(x, y) {
return x + y;
};
在上述示例中, MyFunc 接口描述了一个函数类型,该函数接受两个参数并返回一个数字。
2. Interface 声明数组
interface StringArray {
[index: number]: string;
}
let myArray: StringArray;
myArray = ["Bob", "Alice"];
上面的示例中, StringArray 接口描述了一个具有数字索引签名的字符串数组。意味着我们可以通过数字索引来访问数组元素。
3. Interface 声明类(Indexable)
interface StringDictionary {
[key: string]: string;
}
let myDict: StringDictionary = {
"name": "John",
"age": "30"
};
在这个例子中,StringDictionary接口用于描述具有字符串索引签名的类或对象。这使得我们可以像操作字典一样使用对象的属性。
综上: interface 可以被用来声明函数、数组和具有索引签名的类,从而帮助我们定义和限定这些数据结构的形式和行为。
21、说一说静态类型和动态类型有什么区别?
- 静态类型 是在 编译期间 进行类型检查,可以在编辑器或 IDE 中发现大部分类型错误。
- 动态类型 是在 运行时 才确定变量的类型,通常与动态语言相关联。
静态类型(Static Typing)
- 定义 :静态类型是指在编译阶段进行类型检查的类型系统,通过类型注解或推断来确定变量、参数和返回值的类型。
- 特点 :静态类型能够在编码阶段就发现大部分类型错误,提供了更好的代码健壮性和可维护性。
- 优势 :可以在编辑器或 IDE 中实现代码提示、自动补全和类型检查,帮助开发者减少错误并提高代码质量。
动态类型(Dynamic Typing)
- 定义 :动态类型是指在运行时才确定变量的类型,通常与动态语言相关联,允许同一个变量在不同时间引用不同类型的值。
- 特点 :动态类型使得变量的类型灵活多变,在运行时可以根据上下文或条件动态地改变变量的类型。
- 优势 :动态类型可以带来更大的灵活性,适用于一些需要频繁变化类型的场景。
区别总结
- 时机差异 :静态类型在编译期间进行类型检查,而动态类型是在运行时才确定变量的类型。
- 代码稳定性 :静态类型有助于在编码阶段发现大部分类型错误,提高代码稳定性;动态类型对类型的要求较为灵活,但可能增加了代码的不确定性。
- 使用场景 :静态类型适合于大型项目和团队,能够提供更强的类型安全性;动态类型适用于快速原型开发和灵活多变的场景,能够更快地迭代和测试代码。
22、ArkTS中的模块化是如何工作的,举例说明?
- TypeScript 中使用 ES6 模块系统,可以使用 import 和 export 关键字来导入和导出模块。
- 可以通过 export default 导出默认模块,在导入时可以使用 import moduleName from 'modulePath' 。
// math.ts
export function sum(a: number, b: number): number {
return a + b;
}
export function subtract(a: number, b: number): number {
return a - b;
}
// app.ts
import { sum, subtract } from './math';
console.log(sum(3, 5)); // 输出 8
23、如何约束泛型参数的类型范围?
可以使用泛型约束( extends关键字 )来限制泛型参数的类型范围,确保泛型参数符合某种特定的条件。
interface Lengthwise {
length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length);
return arg;
}
loggingIdentity({length: 10, value: 3}); // 参数满足 Lengthwise 接口要求,可以正常调用
24、什么是泛型约束中的 keyof 关键字,举例说明其用法。
keyof 是 TypeScript 中用来获取对象类型所有键(属性名)的操作符。
可以使用 keyof 来定义泛型约束,限制泛型参数为某个对象的键。
function getProperty<T, K extends keyof T>(obj: T, key: K) {
return obj[key];
}
let x = { a: 1, b: 2, c: 3 };
getProperty(x, "a"); // 正确
getProperty(x, "d"); // 错误:Argument of type '"d"' is not assignable to parameter of type '"a" | "b" | "c"'
25、ArkTS 中如何联合枚举类型的 Key?
enum str {
A,
B,
C
}
type strUnion = keyof typeof str; // 'A' | 'B' | 'C'
28、如何实现class继承?
继承是一种从另一个类获取一个类的属性和行为的机制。它是面向对象编程的一个重要方面,并且具有从现有类创建新类的能力,继承成员的类称为基类,继承这些成员的类称为派生类。
继承可以通过使用extend关键字来实现。我们可以通过下面的例子来理解它。
class Shape {
Area:number
constructor(area:number) {
this.Area = area
}
}
class Circle extends Shape {
display():void {
console.log("圆的面积: "+this.Area)
}
}
var obj = new Circle(320);
obj.display()
30、如何在 ArkTS 中定义具有可选参数和默认参数的函数?举个例子。
function greet(name: string, message: string = 'Hello', times?: number): void {
for (let i = 0; i < (times || 1); i++) {
console.log(`${message}, ${name}!`);
}
}
greet('John'); // Output: "Hello, John!"
greet('Jane', 'Hi'); // Output: "Hi, Jane!"
greet('Tom', 'Hey', 3); // Output: "Hey, Tom!", "Hey, Tom!", "Hey, Tom!"
31、说一说对于递归的理解?
方法或函数调用自身的方式称为递归调用,调用称为递,返回称为归。 简单来说就是: ** 自己调用自己 ** 。
为什么使用递归 ?递归的优缺点 ?
- 优点:代码的表达力很强,写起来简洁。
- 缺点:空间复杂度高、有堆栈溢出风险、存在重复计算、过多的函数调用会耗时较多等问题。
递归常见问题及解决方案
- 警惕堆栈溢出:可以声明一个全局变量来控制递归的深度,从而避免堆栈溢出。
- 警惕重复计算:通过某种数据结构来保存已经求解过的值,从而避免重复计算。
如何实现递归 ?
// 输出 10-1
function fact(num) {
console.log('',num)
num--
if(num > 0){
fact(num)
}else {
return // 结束
}
}
fact(10)
34、项目中接口请求的数据,需要进行缓存吗?鸿蒙化处理过程
根据自己项目中的业务逻辑描述,MapKit需要将当前屏幕中展示的6个瓦片数据图片缓存到沙盒中,杀掉进程后,断开网络,打开app会展示缓存的数据瓦片。
37、三方应用调用系统应用,对于ability的交互和传值有什么限制?除了数据大小方面
重点介绍自己对ability的理解,描述显式want和隐式want的区别,带入到对应面试项目中场景来
- 启动应用内的UIAbility, 启动应用内的UIAbility并获取返回结果
- 启动其他应用的UIAbility, 启动其他应用的UIAbility并获取返回结果
- 启动UIAbility的指定页面:
- 显式Want启动:在want参数中需要设置该应用bundleName和abilityName,当需要拉起某个明确的UIAbility时,通常使用显式Want启动方式。
- 隐式Want启动:不明确指出要启动哪一个UIAbility,在调用startAbility()方法时,其入参want中指定了一系列的entities字段(表示目标UIAbility额外的类别信息,如浏览器、视频播放器)和actions字段(表示要执行的通用操作,如查看、分享、应用详情等)等参数信息,然后由系统去分析want,并帮助找到合适的UIAbility来启动。
38、在使用自定义组件时, 外部如何传递UI?
使用@BuilderParam 装饰器, 该装饰器可以用于声明任意UI描述的一个元素,类似前端vue中的slot占位符。
- 如果外部使用自定义组件时, 只传递一个UI, 可以使用尾随闭包的方式传入
- 组件内 有且仅有一个 使用 @BuilderParam 装饰的属性,即可使用尾随闭包
- 内容直接在 ** {} ** 传入即可
注意:此场景下自定义组件 不支持使用通用属性。
@Component
struct SonCom {
// 1.设置默认 的 Builder,避免外部不传入
@Builder
defaultBuilder() {
Text('默认的内容')
}
// 2.定义 BuilderParam 接受外部传入的 ui,并设置默认值
@BuilderParam ContentBuilder: () => void = this.defaultBuilder
build() {
Column() {
// 3. 使用 @BuilderParam 装饰的成员变量
this.ContentBuilder()
}
.width(300)
.height(200)
.border({ width: .5 })
}
}
// 使用自定义组件时,就可以使用如下方式传递 UI
// 不传递时会使用默认值
SonCom(){
// 传入的 UI
Button('我是传入的UI结构')
}
- 如果外部使用自定义组件时, 需要传递多个UI, 必须通过参数 的方式来传入
- 自定义组件-定义: 添加多个 @BuilderParam ,并定义默认值
- 自定义组件-使用: 通过参数的形式传入多个 Builder,比如:
SonCom({ titleBuilder: this.fTitleBuilder, contentBuilder: this.fContentBuilder })
参考代码:
@Component
struct SonCom {
// 由外部传入 UI
@BuilderParam titleBuilder: () => void = this.titleDefaultBuilder
@BuilderParam contentBuilder: () => void = this.contentDefaultBuilder
// 设置默认 的 Builder,避免外部不传入
@Builder
titleDefaultBuilder() {
Text('默认标题')
}
@Builder
contentDefaultBuilder() {
Text('默认内容')
}
build() {
Column() {
Row() {
this.titleBuilder()
}
.layoutWeight(1)
Divider()
Row() {
this.contentBuilder()
}
.layoutWeight(1)
}
.width(300)
.height(200)
.border({ width: .5 })
}
}
@Entry
@Component
struct Index {
@Builder
fTitleBuilder() {
Text('传入的标题')
.fontSize(20)
.fontWeight(600)
.fontColor(Color.White)
.backgroundColor(Color.Blue)
.padding(10)
}
@Builder
fContentBuilder() {
Text('传入的标题')
.fontSize(20)
.fontWeight(600)
.fontColor(Color.White)
.backgroundColor(Color.Blue)
.padding(10)
}
build() {
Column({ space: 15 }) {
SonCom({ titleBuilder: this.fTitleBuilder, contentBuilder: this.fContentBuilder })
}
}
}
40、怎么理解函数的防抖和节流
- 是什么
- 防抖: 单位时间内,频繁触发事件,只执行最后一次
- 节流: 单位时间内,频繁触发事件,只执行一次
- 实现防抖/节流
- JS库 => lodash.js
- 通过延时器自己手写
41、手写防抖和节流
- 防抖
function debounce(fn,t){
let timer
return function(){
if(timer) clearTimeout(timer)
timer = setTimeout(function(){
fn()
},t)
}
}
- 节流
function throttle(fn, t){
let timer = null
return function(){
if(!timer){
timer = setTimeout(function(){
fn()
timer = null
},t)
}
}
}
常见面试题汇集(带答案)
-
首选项通过什么方式存储?存在什么地方?
储存形式: 首选项(Preferences)通过键值对(Key-Value)的形式储存数据,用于为应用提供轻量级的数据处理能力,并支持数据的持久化储存
储存位置:默认储存位置为该应用的应用沙箱中
图案密码锁用的是什么?
- 图案密码锁的特点
输入方式:用户通过手指在图案密码锁组件的格子区域滑动,连接特定顺序的格子来创建密码。
验证机制:当用户再次按照相同的顺序连接格子时,系统会根据输入的顺序与预设的密码进行比对,以验证用户的身份。
界面表现:图案密码锁组件通常会以图形化的方式展示格子,并在用户选择格子时显示选中的状态(如颜色变化)。
- 在DevEco中的实现
组件支持:从HarmonyOS的某个版本(如API Version 9开始)起,DevEco Studio支持图案密码锁组件的开发。
属性与事件:图案密码锁组件提供了一系列属性和事件,用于自定义组件的外观和行为。例如,可以设置格子的大小、颜色、边框宽度等属性,以及监听密码输入完成等事件。
开发文档与示例:DevEco Studio提供了详细的开发文档和示例代码,帮助开发者快速上手并实现图案密码锁功能。开发者可以参考这些资源来了解组件的详细用法和最佳实践。
forEach和 LazyForeach 的区别?
1.数据源类型:○ForEach:直接接受一个数组作为数据源。○LazyForEach:接受一个实现了 IDataSource 接口的对象作为数据源。
2.渲染策略:○ForEach:一次性渲染所有数据项,适用于数据量较少的情况。○LazyForEach:按需渲染数据项,只渲染可视区域内的数据项,适用于数据量较大的情况,提升性能。
3.内存使用:○ForEach:会一次性加载所有的数据项,内存使用较高。○LazyForEach:根据可视区域按需加载数据项,并回收滑出可视区域的数据项,内存使用较低。
4.组件复用:○ForEach:没有内置的组件复用机制。○LazyForEach:类似于原生开发中的 cell 复用机制,滑出可视区域的组件会被回收,新出现的组件优先使用复用池中的组件。
5.性能优化:○ForEach:适用于数据量较少且性能要求不高的场景。○LazyForEach:适用于数据量较大且性能要求较高的场景,显著提升页面性能和用户体验。
6.数据监听:○ForEach:没有专门的数据监听机制,依赖于数组变化触发的 UI 更新。○LazyForEach:需要通过 DataChangeListener 来监听数据变化,手动通知数据变动以刷新 UI。
forEach有几个参数?
1.数据源,数组类型,可以设置为空数组,此时不会创建子组件。
2.组件生成函数。
为数组中的每个元素创建对应的组件。
item参数:arr数组中的数据项。
index参数(可选):arr数组中的数据项索引。
说明:- 组件的类型必须是ForEach的父容器所允许的。例如,ListItem组件要求ForEach的父容器组件必须为List组件。
3.键值生成函数。- 为数据源arr的每个数组项生成唯一且持久的键值。函数返回值为开发者自定义的键值生成规则。
- item参数:arr数组中的数据项。- index参数(可选):arr数组中的数据项索引。
说明:- 如果函数缺省,框架默认的键值生成函数为(item: T, index: number) => { return index + '__' + JSON.stringify(item); }- 键值生成函数不应改变任何组件状态。
说说对Ability的理解,怎么跳转
Ability 是应用所具备能力的抽象,也是应用程序的重要组成部分。Ability 是系统调度应用的最小单元,是能够完成一个独立功能的组件。一个应用可以包含一个或多个 Ability。
Ability跳转分为两种:第一种是同模块内的Ability跳转。实现步骤分为以下三步: 第一步,导入common 和 Want。第二步,获取上下文对象。第三步,在合适的地方声明want参数,把want参数中的abilityName换成目标abilityName,然后调用上下文对象的startAbility方法,把want参数传入即可实现跳转。第二种是跨模块Ability跳转,在want参数中,除了要修改abilityName之外,还要修改模块名moduleName,其他步骤同上。
了解过多线程吗?和 Promise 区别是什么?
- 多线程
多线程是指程序在运行时可以同时执行多个线程的能力。每个线程都是一个轻量级的进程,它们共享相同的内存空间,并且可以并发地执行不同的任务。在多核处理器的现代计算机上,多线程可以充分利用硬件资源,提高应用程序的性能。多线程编程通常用于处理耗时的任务,如网络请求、文件读写或复杂的计算,这样可以让用户界面保持响应状态。
2.Promise
Promise 是一种编程模式,特别是在JavaScript中被广泛采用,用来解决异步操作中的回调地狱问题。Promise 提供了一种更加优雅的方式来处理异步操作的结果,它代表了一个最终可能会产生一个值的操作的最终完成(或失败)及其产生的值。Promise 可以被链接起来,使得异步操作可以像同步操作一样按顺序编写。
- 区别
概念层次:多线程是操作系统提供的并行执行能力;Promise是一种编程模式,用于简化异步编程。
用途:多线程用于实现任务的并发执行,提高程序效率;Promise用于组织和简化异步逻辑,避免回调嵌套。
语言支持:多线程通常依赖于底层操作系统的支持;Promise则是语言层面或库层面提供的功能。
总的来说,多线程和Promise都是为了处理异步操作,但是它们解决的问题层面不同。多线程关注的是如何让程序的不同部分能够并行执行,而Promise关注的是如何优雅地处理这些异步操作的结果。
做过鸿蒙项目有哪些难点,怎么处理的?
一、数据管理和存储
难点:
鸿蒙应用需要处理不同类型的数据存储需求,如本地文件存储、数据库存储、分布式数据存储等,选择合适的存储方式并进行有效的数据管理较为复杂。
数据的同步和共享也是一个难点,特别是在分布式环境下,如何确保数据的一致性和实时性是一个挑战。
处理方法:
根据应用的具体需求选择合适的数据存储方式。对于简单的数据可以使用本地文件存储或Preferences 进行存储;对于复杂的数据结构可以使用关系型数据库或 NoSQL 数据库。
对于分布式数据存储,可以使用鸿蒙的分布式数据服务(DistributedData)来实现数据的同步和共享。在设计数据模型时,要考虑数据的一致性和冲突解决策略。
二、性能优化
难点:
确保鸿蒙应用在不同设备上的性能表现良好是一个挑战,包括响应速度、内存占用、电量消耗等方面。
鸿蒙应用可能需要与其他设备进行交互和通信,如何优化通信性能也是一个难点。
处理方法:
进行性能测试和分析,找出性能瓶颈所在。可以使用DevEco Studio 提供的性能分析工具,如 Profiler 和 Memory Profiler 等。
优化代码结构和算法,减少不必要的计算和内存占用。例如,避免频繁的对象创建和销毁、使用高效的数据结构等。
对于通信性能优化,可以采用异步通信方式、数据压缩等技术,减少网络延迟和数据传输量。
如何实现沉浸式模式?
// 开启全屏
const win = windowStage.getMainWindowSync()
win.setWindowLayoutFullScreen(true)
// 获取安全区域
// 顶部安全区域的高度
const top = win.getWindowAvoidArea(window.AvoidAreaType.TYPE_SYSTEM).topRect
AppStorage.setOrCreate<number>('topHeight', px2vp(top.height))
// 底部安全区域的高度
const bottom = win.getWindowAvoidArea(window.AvoidAreaType.TYPE_NAVIGATION_INDICATOR).bottomRect
AppStorage.setOrCreate<number>('bottomHeight', px2vp(bottom.height))
图片上传,如果上传过程中中断了,这个要怎么解决办法?
图片上传过程中断的问题可能由多种因素引起,解决方法取决于导致中断的具体原因。下面是一些可能导致图片上传中断的原因及相应的解决方案:
- 网络不稳定:
如果网络信号差或连接不稳定,尝试切换到更稳定的网络环境,如从移动数据切换到Wi-Fi,或重启路由器。
- 图片格式不兼容:
验证图片的格式是否符合上传平台的要求。通常支持的格式有JPEG、PNG等。如果不符合,可以使用图像编辑软件转换格式。
- 图片损坏:
如果图片文件损坏,尝试用图像编辑工具打开图片,看是否能正常显示。如果不能,说明图片文件有问题,需要修复或替换图片。
- 文件大小超出限制:
检查上传平台对图片大小的限制,如果图片太大,可以使用图像压缩工具减小图片大小。
- 硬件问题:
如果使用数据线连接设备上传图片,检查数据线是否有问题,尝试更换数据线。
检查电脑USB插口是否正常工作,尝试更换其他USB插口。
- 驱动问题:
如果是通过USB连接设备上传图片,确保电脑上的驱动程序是最新的。可以尝试更新相关驱动程序。
- 设备设置问题:
对于手机等移动设备,检查设备设置,确保设备处于正确的传输模式,并且设置了正确的格式选项。
8.USB连接问题:
如果是通过USB连接设备上传图片,尝试重新插拔USB连接,确保连接稳固。
- 软件或应用问题:
如果是在某个特定的应用程序中上传图片出现问题,尝试清除应用缓存或重新安装应用。
检查应用是否有最新的版本,更新到最新版本可能会解决问题。
- 权限问题:
确保应用程序有足够的权限访问存储设备和网络。
根据上述建议逐一排查,应该能够找到导致图片上传中断的原因并采取相应的措施来解决问题。如果问题依旧存在,可能需要联系设备制造商或应用提供商寻求进一步的帮助
应用中如何动态申请权限?
在config.json文件中声明所需要的权限。
使用ohos.app.Context.verifySelfPermission接口查询应用是否已被授予该权限。如果已被授予权限,可以结束权限申请流程。
如果未被授予权限,继续执行下一步。
使用canRequestPermission查询是否可动态申请。如果不可动态申请,说明已被用户或系统永久禁止授权,可以结束权限申请流程。
如果可动态申请,使用requestPermissionFromUser动态申请权限。
通过重写ohos.aafwk.ability.Ability的回调函数onRequestPermissionsFromUserResult接收授予结果,但这个方法一定要写Ability里面,才能接收授予结果, 如果写在AbilitySlice里面,是收不到结果的。
@Builder 和@Component 的区别?
自定义构建函数(@Builder)更轻量,其作为UI元素抽象的方法,实现和调用相较于自定义组件比较简洁。
在自定义组件中,可以定义成员函数/变量、自定义组件生命周期等。
而自定义构建函数(@Builder)不支持定义状态变量和自定义生命周期。
在自定义组件中,可直接通过状态变量的改变,来驱动UI的刷新。
而自定义构建函数(@Builder)默认的按值参数传递方式不支持动态改变组件,当传递的参数为状态变量时,状态变量的改变不会引起@Builder方法内的UI刷新,要实现UI动态刷新需要按引用传递参数。
自定义构建函数(@Builder)中使用了自定义组件,那么该方法每次被调用时,对应的自定义组件均会重新创建。
针对简历中的项目问功能的实现步骤?
说说对Ability的理解
在鸿蒙开发中,Ability 可以从以下几个方面来理解:
一、架构核心组件
Ability 是鸿蒙操作系统应用程序模型中的核心组件。它构建了应用的基本功能单元,如同搭建大厦的基石。通过不同类型的 Ability 组合,可以构建出丰富多样、功能强大的应用程序。
二、功能承载主体
交互与展示:对于 Feature Ability(FA)来说,它提供了用户界面,是用户与应用进行交互的主要场所。开发者可以在 FA 中设计精美的界面布局,实现各种交互操作,如点击、滑动、输入等,为用户带来直观的使用体验。
后台服务:Particle Ability(PA)通常在后台运行,为应用提供各种服务。比如数据同步、消息推送、后台计算等任务可以由 PA 来承担,确保应用在后台也能高效运行,不影响用户前台操作的同时,提升应用的整体性能。
三、生命周期管理
Each Ability 都有自己独立的生命周期。这意味着开发者可以精确地控制 Ability 的启动、暂停、恢复和销毁等状态。了解和正确管理 Ability 的生命周期对于开发稳定、高效的应用至关重要。例如,在 Ability 进入暂停状态时,可以释放一些不必要的资源,以节省系统资源;当 Ability 重新恢复时,可以快速重新加载必要的数据,保证用户体验的连续性。
四、通信与协作
内部通信:在一个应用内部,不同的 Ability 之间可以通过 Intent 机制进行通信。开发者可以通过发送 Intent 来启动其他 Ability,并传递数据。这种方式使得应用内部的各个功能模块能够紧密协作,实现复杂的业务逻辑。
跨应用通信:鸿蒙系统还支持不同应用之间的 Ability 通信。通过特定的权限设置和通信协议,不同应用的 Ability 可以相互调用和共享数据,为用户提供更加便捷和一体化的服务体验。
五、安全与隔离
鸿蒙系统为每个Ability 提供了安全隔离的运行环境。这意味着不同的 Ability 之间的数据和运行状态是相互独立的,一个 Ability 的故障或安全问题不会影响到其他 Ability 或整个系统的稳定性。同时,开发者可以通过设置权限等方式来进一步加强 Ability 的安全性,保护用户数据和隐私。
api9 和 api11的区别?
- 语法差异:
ArkTS(鸿蒙的TypeScript方言)在API 11中相较于API 9有了一些语法上的变化和限制。
函数表达式不再被支持,必须使用箭头函数。
展开运算符(spread operator)仅支持数组。
不再支持赋值解构(destructuring assignment)。
var关键字不再被支持,必须使用let或const来声明变量。
类型限制更为严格,声明变量时后面必须定义类型。
- 功能更新:
API 11相较于API 9,可能包含了一些新的API方法和功能,这些新特性可能提供了更强大的功能和更好的性能。然而,由于文档和具体细节的限制,这里无法给出具体的更新内容。
- 兼容性:
API 9通常与HarmonyOS 3.1和HarmonyOS 4.0兼容,而API 11可能与更新的HarmonyOS版本或Harmony OS Next(纯血鸿蒙)有更好的兼容性。不过,由于Harmony OS Next去除了AOSP(Android Open Source Project)兼容性,并且SDK版本之间没有直接的对应关系,因此开发者需要特别关注这一点。
- 开发工具支持:
DevEco Studio是鸿蒙系统的官方集成开发环境(IDE)。尽管API 11的文档可能已经放开,但IDE并没有直接放开对API 11的支持(可能需要签约合作后才能使用)。目前,DevEco Studio最新可用的版本可能仍然是4.0 Release版本,对应API 10。
- 真机开发环境:
如果在年初没有参与鸿蒙Next版系统的预约测试,那么对于API 11或更新的版本,可能无法获得可运行的真机环境。这意味着开发者可能需要在模拟器或远程设备上进行开发。
- 文档和示例代码:
鸿蒙系统为API 9和API 11提供了不同的文档和示例代码。开发者应该根据所使用的API版本参考相应的文档和示例代码。
用户首选项的封装?
1、 创建首选项工具类(PreferencesUtil)
2、获取首选项实例
3、销毁首选项实例
4、从首选项实例中获取键对应的值
5、从首选项实例中写入数据
6、new一下工具类,并导出
怎么持续化数据,有哪些方法?
AppStorage:用于应用级别的状态管理,适用于需要在整个应用中共享的数据。
PersistentStorage:用于持久化存储数据,即使应用关闭后数据也不会丢失
Preferences 首选项 : 用户首选项为应用提供Key-Value键值型的数据存储能力,支持应用持久化轻量级数据,并对其进行增删除改查等。该存储对象中的数据会被缓存在内存中,因此它可以获得更快的存取速度。
Preferences与PersistentStorage的区别
PersistentStorage在UI之后,Preferences在UI之前
用过第三方http请求库吗 怎么实现加密?
1.选择合适的HTTP请求库: 鸿蒙系统中有多个HTTP请求库可供选择,如HttpRequest、Fetch等。根据项目需求选择一个合适的库。
2.配置SSL证书: 使用HTTPS协议进行加密通信时,需要配置SSL证书。可以通过以下步骤进行:
获取SSL证书,并将其导入到鸿蒙系统中。在HTTP请求库中配置SSL证书,确保所有HTTP请求都通HTTPS协议进行。
3.实现加密功能:使用HTTP请求库的加密功能,对请求数据进行加密处理。在请求头中添加相关的加密信息,如SSL证书的指纹、加密算法等。
4.处理响应数据:在接收到HTTP响应后,使用HTTP请求库的解密功能,对响应数据进行解密处理。确保响应数据的完整性和安全性。
5.认证机制:可以使用证书认证或AccessToken认证等机制,确保HTTP请求的合法性和安全性。配置相关的认证参数,确保HTTP请求和响应的安全性。
用户频繁切换搜索条件会出现数据混乱问题,怎么解决?
1.数据同步机制:
确保在用户切换搜索条件时,数据同步机制能够及时更新和同步数据。可以使用异步加载和更新机制,避免在切换条件时出现数据不一致的情况。
在数据同步过程中,可以使用标志位来标识是否加载完数据,并在用户界面上展示加载中的效果,提升用户体验。
2.状态管理:
使用状态管理工具来管理搜索条件和结果显示状态。例如,可以使用全局状态管理工具来存储和更新搜索条件,确保在切换条件时状态能够正确同步。
在状态管理中,确保在每次搜索条件变化时更新相关状态,并在视图层进行相应的数据绑定和更新。
3.UI优化:
在用户界面上,优化搜索条件切换的体验。例如,可以使用下拉菜单或输入框来动态更新搜索条件,避免直接修改页面数据导致的混乱。
在切换搜索条件时,可以通过动画或过渡效果来平滑过渡,提升用户体验。
在鸿蒙怎么调用C++的接口?
在鸿蒙系统中,您可以通过以下两种方式在ArkTS中调用C++接口:
1.使用JSBridge实现跨语言调用:
适用场景:适用于应用架构中已经存在C++环境的情况。
实现步骤: 在ArkTS侧声明自定义标识webTag,并通过NAPI传至应用C++侧 。
在C++侧使用ArkWeb_ControllerAPI和ArkWeb_ComponentAPI实现JSBridge功能 。
通过registerJavaScriptProxy将应用侧函数注册至前端页面 。
通过ArkWeb_ComponentAPI注册组件生命周期回调 。
使用runJavaScript调用前端页面函数 。
2.使用JSVM-API实现跨语言交互:
适用场景:适用于需要在ArkTS和JS之间实现模块的注册和加载的情况。
实现步骤: 在ArkTS/JS侧实现C++方法的调用,import对应的so库后即可调用C++方法 。
在Native侧实现模块的注册,提供注册lib库的名称,并在注册回调方法中定义接口的映射关系 。
在index.d.ts文件中提供JS侧的接口方法 。
在oh-package.json5文件中将index.d.ts与cpp文件关联起来 。
在CMakeLists.txt文件中配置CMake打包参数 。
实现Native侧的具体C++接口 。
这两种方法都可以帮助您在鸿蒙系统中实现从ArkTS调用C++接口的需求。根据您的具体应用场景选择合适的方法进行实现。
API9和PI11有什么改变?
鸿蒙实现多在API 9和API 11版本中,输入法框架和List组件都有一些重要的改变:
输入法框架:
1.权限要求:
在API version 9-10版本中,切换输入法的方法switchInputMethod仅支持系统应用调用且需要权限ohos.permission.CONNECT_IME_ABILITY 。
在API version 11版本中,切换输入法的方法switchInputMethod仅支持当前输入法应用调用 。
2.方法改变:
switchInputMethod方法在API 11版本中增加了对当前输入法应用的支持,不再支持系统应用调用。
switchCurrentInputMethodSubtype方法在API 11版本中仍然支持系统应用调用,但需要权限ohos.permission.CONNECT_IME_ABILITY 。
3.错误码:
在API 11版本中,错误码12800008(输入法管理服务错误)被移除。
List组件: 1.支持版本:
List组件从API Version 7开始支持 。
2.属性和方法:
在API 9版本中,List组件的editMode属性设置为true时,可以实现可编辑模式,但该功能从API 9开始废弃 。
在API 11版本中,List组件的editMode属性设置不再必要,拖拽功能也不再需要设置editMode属性。
这些改变影响了输入法框架和List组件的功能和使用方式,开发者需要根据新的版本要求进行相应的调整。
讲一下你如何处理高并发
通过回调函数:使用回调函数来处理异步操作的结果,避免阻塞主线程
通过Promise来管理异步操作,简化异步编程的复杂度
鸿蒙录音用的是哪个api,录音的文件是什么格式
AudioCapturer:主要用于音频采集,可以捕捉原始音频数据
AVRecorder:提供了更高级别的接口,包括音频的录制、编码和保存等功能
录音的文件格式是.m4a
DevEcoStudio你项目中编辑器是哪个版本(鸿蒙4.0开发)
DevEco Studio 是华为提供的官方集成开发环境(IDE),用于开发 HarmonyOS 应用程序。
对于鸿蒙 4.0 的开发,根据已知信息,适用于 OpenHarmony 的 DevEco Studio 版本为 4.0.0。因此,如果您在进行鸿蒙 4.0 的开发,您应该使用 DevEco Studio 4.0.0 或更高版本来进行项目开发。
你有在项目中遇到this的指向问题吗?如果不用this可以吗?
可以不用 this,但需要替代方案:
1,箭头函数:自动绑定外部 this。
2,闭包:保存状态。
3,函数参数:传递对象。
ArkTS–Ability中的生命周期有哪些
ArkTS(Ark TypeScript)中的Ability生命周期主要包括以下几个关键阶段,这些阶段定义了Ability从创建到销毁,以及在前台和后台之间切换的整个生命周期过程。
- ** :
当Ability实例首次被创建时触发。在这个阶段,通常会执行初始化操作,如变量定义、资源加载等,为后续的UI界面展示做准备。
- ** :
Ability实例创建完成后,在进入前台之前,系统会创建一个WindowStage。WindowStage创建完成后,会进入此回调。可以在此回调中设置UI界面加载、设置WindowStage的事件订阅等。
- ** :
当Ability的UI界面可见时触发,如Ability切换至前台。在此回调中,可以执行一些与前台显示相关的操作,如申请系统资源、重新申请在后台释放的资源等。
- ** :
当Ability的UI界面完全不可见时触发,如Ability切换至后台。在此回调中,可以执行一些与后台运行相关的操作,如释放UI界面不可见时无用的资源、执行较为耗时的操作(如状态保存)等。
- ** :
在Ability实例销毁之前,会先进入此回调。可以在此回调中释放与UI相关的资源,如注销WindowStage的事件订阅等。
- ** :
当Ability实例被销毁时触发。在这个阶段,通常会执行一些清理操作,如释放资源等。
现想要获取组件内(可能包含子组件)的所有文本控件和图片控件的数量 应该怎么做? 面试官给了个伪方法,并提示用用递归
1、遍历组件及其子组件:首先,需要遍历给定的组件及其所有子组件。这可以通过递归函数来实现,递归地访问每个子组件,直到遍历完所有的子组件。
2、使用选择器识别控件:在遍历过程中,使用适当的选择器来识别文本控件(如输入框input、文本域textarea等)和图片控件(如img标签)。
3、计数控件数量:对于每个识别的文本控件或图片控件,增加相应的计数器。这样,在遍历完所有组件及其子组件后,就可以得到文本控件和图片控件的总数。
通过这种方法,可以准确地获取到组件内所有文本控件和图片控件的数量,无论这些组件是否嵌套在其他组件之内。这种方法不仅适用于前端框架如Vue.js,也适用于其他基于DOM操作的前端技术栈。
如果动态去获取开屏广告 每次打开现判断远端有没有更新 难免会时间比较长 开屏会有一段白屏 怎么解决?
应考虑采用缓存和预加载技术,同时优化广告加载策略,以减少开屏广告的显示时间。即在用户首次访问时预先下载广告内容,并在后续访问时直接从本地加载,这样可以显著减少加载时间,提升用户体验。但这种策略需要广告平台和应用的开发者进行相应的优化设置。
一个list是一列 想有好几个list怎么办 四列list的效果在一起怎么实现(就像淘宝首页那样)
外层使用线性布局,布局内部使用List等列表加载组件加载不同的数据
用任意语言写个栈 写个方法返回栈内最大值
// 这种方法使用一个额外的栈来存储当前栈中的最大值。每当向主栈中添加元素时,我们同时检查辅助栈的栈顶元素和要添加的元素,根据比较结果更新辅助栈。
class MaxStack {
private stack: number[] = [];
private maxStack: number[] = [];
push(x: number): void {
this.stack.push(x);
if (this.maxStack.length === 0 || x >= this.maxStack[this.maxStack.length - 1]) {
this.maxStack.push(x);
}
}
pop(): number {
if (this.stack.length === 0) {
throw new Error('pop from empty stack');
}
const popped = this.stack.pop();
if (popped === this.maxStack[this.maxStack.length - 1]) {
this.maxStack.pop();
}
return popped;
}
top(): number {
if (this.stack.length === 0) {
throw new Error('top of empty stack');
}
return this.stack[this.stack.length - 1];
}
getMax(): number {
if (this.maxStack.length === 0) {
throw new Error('stack is empty');
}
return this.maxStack[this.maxStack.length - 1];
}
}
// 使用示例
const maxStack = new MaxStack();
maxStack.push(5);
maxStack.push(1);
maxStack.push(5);
console.log(maxStack.getMax()); // 输出 5
maxStack.pop();
console.log(maxStack.top()); // 输出 5
console.log(maxStack.getMax()); // 输出 5
淘宝有个tab 每次双击某个tabbar想重新¡更新一下tab对应的页面的数据,那么这个方法应该写在哪里,需要传递哪些内容?
在ArkTS中可以使用Emitter,主要提供线程间发送和处理事件的能力,包括对持续订阅事件或单次订阅事件的处理、取消订阅事件、发送事件到事件队列等。他主要有两个方法emitter.emit(event, eventData); 发送事件,emitter.on 用来接收事件和传递过来的参数。该方法在实际中可以写在双击tabbar的双击事件上,传递需要更新tab页面的所需必要参数。
比如说双十一有促销活动,开屏广告需要是双十一相关的 那么等双十一过了之后 怎么恢复到原来的开屏页面呢 ?(猜测:写个定时器方法获取广告时也获取一下显示多久的值)
可以在设计广告系统时,为每个广告设置一个开始时间和结束时间。对于双十一等特定节日的广告,设置其结束时间为双十一当天结束的时刻,当广告的有效期过了之后,系统自动切换到默认的开屏页面
元服务和APP的区别是什么,具体有哪些区别?
Meta Service)和应用程序(APP)之间的区别主要体现在以下几个方面:
- 定义
l 元服务:指的是一种提供基础设施、数据或功能的服务,通常用于支持其他应用程序的开发和运行。它并不直接面向最终用户,而是为开发者或其他服务提供支持。
l 应用程序(APP):是最终用户直接使用的软件,通常具有特定功能和界面,解决特定的需求或问题。
- 目标用户
l 元服务:主要面向开发者、系统管理员或其他服务提供者,提供API、数据存储、身份验证等功能。
l 应用程序:面向最终用户,提供友好的用户界面和交互体验,解决用户的实际问题。
- 功能聚焦
l 元服务:通常专注于提供通用的功能组件,例如数据处理、消息队列、身份认证等。
l 应用程序:聚焦于具体的业务逻辑和用户需求,如社交媒体、游戏、办公软件等。
- 依赖关系
l 元服务:可以被多个应用程序调用,作为其基础设施的一部分。
l 应用程序:通常依赖于多个元服务来实现完整的功能。
- 运行环境
l 元服务:通常运行在云端或服务器上,提供持续的服务。
l 应用程序:可以在多种平台上运行,如移动设备、桌面计算机或网页浏览器。
- 维护与更新
l 元服务:需要定期更新以保证安全性和性能,通常由专业团队维护。
l 应用程序:也需要更新,通常更频繁,包含新功能或用户反馈的改进。
总结
元服务和应用程序在定位、用户、功能和依赖关系上有显著区别。元服务更侧重于提供基础支持,而应用程序则直接面向用户,解决具体问题。
一个滚动列表,如何实现tab固定在顶部,如何计算滚动的位置和怎么滚动?
Tabs导航栏位置属性:
.barPosition设置Tabs的页签位置。
参数:
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| value | BarPosition | 是 | 设置Tabs的页签位置。 默认值:BarPosition.Start |
参数类型:
| 名称 | 描述 |
|---|---|
| Start | vertical属性方法设置为true时,页签位于容器左侧;vertical属性方法设置为false时,页签位于容器顶部。 |
| End | vertical属性方法设置为true时,页签位于容器右侧;vertical属性方法设置为false时,页签位于容器底部。 |
.vertical设置是否为纵向Tab
参数**😗*
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| value | boolean | 是 | 是否为纵向Tab。 默认值:false,横向Tabs,为true时纵向Tabs。 |
当vertical为true时,.barPosition(BarPosition.Start)Tab为纵向,且在页面左侧,.barPosition(BarPosition.End)Tab为纵向,且在页面右侧。
计算列表滚动的位置和距离:
创建scroller控制器
scroller = new Scroller()
将scroller绑定到需要计算滚动距离的列表,目前支持List、Scroll、ScrollBar、Grid、WaterFlow。
通过事件.onDidScroll(()=>{})事件滚动时触发,同时在事件里使用scroller的方法this.scroller.currentOffset().xOffset获取横向滚动的距离this.scroller.currentOffset().yOffset获取纵向滚动距离
用过什么组件,flex? 使用时候有什么注意地方
使用Flex布局时,需要注意以下几个方面以确保布局的有效性和响应性:
- 设置Flex容器
启用Flex布局:将父级元素的display属性设置为flex或inline-flex,以启用Flex布局模型。
- 子元素属性的变化
float、clear和vertical-align失效:在Flex布局中,子元素的float、clear和vertical-align属性将不再生效。因此,需要通过Flex布局自身的属性来控制子元素的排列和对齐。
- 主轴与交叉轴
理解主轴与交叉轴:Flex布局中有两个轴:主轴(main axis)和交叉轴(cross axis)。主轴是子元素排列的方向(由flex-direction属性控制),交叉轴则垂直于主轴。
- Flex属性设置
flex-grow、flex-shrink和flex-basis:这些属性用于控制子元素在Flex容器中的伸缩行为。flex-grow属性定义项目的放大比例,flex-shrink属性定义了项目的缩小比例,而flex-basis属性定义了项目在分配多余空间之前,占据的主轴空间(main size)。简写属性flex可以同时设置这三个值。
- 对齐方式
justify-content:用于设置主轴上的对齐方式,如flex-start、flex-end、center、space-between、space-around等。
align-items:用于设置交叉轴上的对齐方式,当子元素为单行时有效。
align-content:用于设置多行子元素在交叉轴上的对齐方式,仅当子元素换行时有效。
- 换行与顺序
flex-wrap:当子元素在一行内无法容纳时,可以通过flex-wrap属性设置为wrap来允许换行。
order:用于控制子元素的排列顺序,数值越小排列越靠前。
- 响应式设计
使用百分比或视口单位:尽量避免将宽度和高度设置为固定值,而是使用百分比或视口单位(如vw、vh)来确保布局的响应性。
- 兼容性
测试不同浏览器:Flex布局在不同浏览器上的支持情况可能存在差异,因此建议在使用时进行充分的测试,以确保布局的兼容性。
- 避免过度嵌套
减少CSS嵌套:虽然Flex布局可以减少一些CSS嵌套,但过度嵌套仍然会影响性能。因此,在使用Flex布局时,应注意避免不必要的嵌套。
- 命名规范
避免命名冲突:在给类或ID命名时,应避免与Flex布局相关的关键字冲突,以确保样式的正确应用。
综上所述,使用Flex布局时需要注意容器设置、子元素属性变化、主轴与交叉轴的理解、Flex属性的设置、对齐方式的选择、换行与顺序的控制、响应式设计、兼容性测试、避免过度嵌套以及命名规范等方面。这些注意事项有助于更好地利用Flex布局来创建高效、灵活和响应式的网页布局。
问使用过的鸿蒙项目,手机端和手表端都做过,如何交互
1.具体实现步骤
建立连接:首先,在手机端和手表端之间建立稳定的连接,可以通过蓝牙、Wi-Fi等方式实现。
数据同步:通过调用鸿蒙系统提供的API接口,实现手机端和手表端之间的数据同步。例如,可以将手机的通知信息同步到手表上,以便用户在不方便查看手机时也能及时获取通知。
功能控制:在手表端实现一些控制功能,如音乐播放控制、电话接听/挂断等,并通过API接口将这些控制指令发送到手机端执行。
状态反馈:手机端在接收到手表端的控制指令后,执行相应的操作,并将操作结果通过API接口反馈给手表端,以便用户了解操作是否成功。
2.示例场景
通知同步:当手机接收到新的通知时,通过鸿蒙系统的分布式特性将通知内容同步到手表上,用户可以直接在手表上查看通知详情。
运动数据同步:用户在使用手表进行运动时,手表会记录相关的运动数据(如步数、心率等),并将这些数据同步到手机上,以便用户在手机端查看更详细的运动报告。
远程控制:用户可以通过手表远程控制手机的某些功能,如播放/暂停音乐、接听/挂断电话等,提升使用便捷性。
总之,在鸿蒙项目中实现手机端和手表端的交互需要充分利用鸿蒙系统的分布式特性、API接口和通信协议等资源,通过合理的设计和实现步骤来确保设备间的协同操作和数据交换的准确性和实时性。
用过哪些API
** 1. 组件API **
ArkUI提供了多种内置组件API,如Text、Button、List、ListItem、ListItemGroup、Image等,用于构建应用界面的各种元素。
** 2. 布局API **
ArkUI支持多种布局方式,如Column、Row、Flex、Grid等,这些布局API提供了灵活的布局选项,如对齐方式、间距、方向等,以满足不同的设计需求。
** 3. 数据绑定与状态管理 **
ArkUI通过数据双向绑定机制简化了页面变化逻辑的处理。开发者可以使用@State装饰器来声明状态变量,并通过UI组件的属性来绑定这些变量。当状态变量发生变化时,UI组件会自动更新以反映最新的状态。
** 4. 滚动与交互API **
ArkUI提供了滚动相关的API,如Scroller、List等,ArkUI还支持组件内转场动画API,通过transition属性可以配置组件插入和删除时的过渡动效,提升用户体验。
** 5. 跨平台与扩展能力**
ArkUI支持跨平台开发,通过一套API即可在多个HarmonyOS设备上提供一致的用户界面体验。同时,ArkUI还提供了扩展服务Extension机制,支持开发者在不同场景下定制和扩展应用功能。
** 6. 第三方库与扩展 **
ArkUI还支持第三方库和扩展的接入,如axios鸿蒙扩展库等,允许开发者利用现有的JavaScript库和工具来加速开发过程并扩展应用功能。
综上所述,ArkUI提供的API涵盖了组件、布局、数据绑定、滚动与交互、性能优化、以及第三方库与扩展等多个方面,为开发者提供了强大而灵活的界面开发能力。
是否有用过axios, 跟华为提供的http有什么区别
是的,我使用过axios。Axios是一个基于Promise的HTTP客户端,用于浏览器和node.js环境,能够发送异步HTTP请求到REST端点并处理响应。它提供了简洁的API来处理XMLHttpRequests和Fetch API背后的复杂性,是现代Web开发中非常流行的HTTP请求库。
与华为提供的 HTTP的区别
在 HarmonyOS(鸿蒙系统)开发环境中,华为提供了自己的HTTP请求处理能力,通常是通过系统级别的API如@ohos/net.http模块来实现。将axios与华为提供的HTTP进行比较时,可以从以下几个方面阐述它们的区别:
功能性和易用性:
axios:功能丰富,支持Promise API,自动转换JSON数据,提供请求和响应的拦截器,支持请求取消等。这些特性使得axios在开发过程中更加灵活和方便。
华为 HTTP(如@ohos/net.http):作为系统级别的API,它提供了对HTTP协议的底层支持,保证了性能和兼容性。虽然功能基本满足HTTP通信需求,但在易用性和高级功能上可能不如axios那样丰富和直观。
跨平台支持:
axios:天生支持浏览器和node.js环境,对于需要同时开发Web应用和Node.js后端的开发者来说非常友好。
华为 HTTP:专为HarmonyOS设计,更适合在鸿蒙系统上进行原生应用开发。
社区和生态:
axios:作为一个开源项目,axios拥有庞大的社区支持和丰富的生态资源,开发者可以轻松找到相关的教程、插件和解决方案。
华为 HTTP:作为华为鸿蒙系统的一部分,其社区和生态相对更加专注于鸿蒙系统的开发者和用户。
集成和适配:
在 HarmonyOS中,虽然可以直接使用@ohos/net.http进行网络请求,但axios也通过第三方库(如@ohos/axios)进行了适配,使得开发者可以在鸿蒙项目中继续使用axios的语法和特性。
@ohos/axios:作为axios的鸿蒙适配版本,它保留了axios的原有功能和用法,同时可能还增加了一些针对鸿蒙系统的优化和特性。
性能考虑:
在大多数情况下,无论是使用 axios还是华为提供的HTTP API,性能差异可能并不明显,因为它们最终都会通过底层的网络协议栈进行通信。然而,在某些特定场景下(如高并发请求、大文件传输等),性能差异可能会变得显著。
综上所述,axios和华为提供的HTTP各有优势,选择哪个取决于具体项目的需求、开发者的习惯和项目的技术栈。在HarmonyOS项目中,如果需要更丰富的网络请求管理功能和更便捷的API,可以考虑使用@ohos/axios这样的封装库。
@state为什么要用这个装饰器,为什么会有这么多装饰器,而不是像vue那样集大其成
首先,装饰器(Decorators)在JavaScript中是一种特殊类型的函数,它可以应用于类、方法、属性或参数,并且能够修改它们的行为或添加新的特性。在特定的框架或库中,如OpenHarmony或Vue.js(通过插件或社区支持的方式),装饰器被用于增强状态管理、组件通信、属性监听等功能。
对于 state使用装饰器的原因,主要有以下几点:
明确性和封装性:装饰器能够明确地将状态( state)标记为具有特定行为或属性的变量,增强了代码的可读性和可维护性。同时,装饰器提供了一种封装机制,使得状态的管理更加集中和统一。
响应式系统:在如 Vue.js这样的现代前端框架中,装饰器(尽管Vue.js本身不直接使用装饰器语法,但可以通过插件或类库实现类似功能)可以用于实现响应式系统。对于state的装饰,可以确保当状态发生变化时,能够自动触发视图的更新。在OpenHarmony中,@State装饰器就起到了这样的作用,它使状态变量与组件的渲染绑定起来,当状态改变时,UI会发生对应的渲染改变。
模块化和可扩展性:通过装饰器,可以将复杂的状态管理逻辑拆分成更小的、可重用的模块。这不仅有助于代码的组织和管理,还提高了代码的可扩展性和可维护性。
为什么会有这么多装饰器?
关于为什么会有如此多的装饰器,这主要源于以下几点原因:
不同框架和库的需求:不同的前端框架和库有着不同的设计理念和功能需求,因此它们需要不同类型的装饰器来满足这些需求。例如, Vue.js中的装饰器可能更侧重于组件的声明和属性的监听,而OpenHarmony中的装饰器则更侧重于状态管理和UI渲染的绑定。
功能的细分和专业化:随着前端技术的不断发展,越来越多的功能被细分和专业化。装饰器作为一种灵活的语法糖,能够很好地支持这种细分和专业化的需求。例如, OpenHarmony中的@State、@Prop、@Link等装饰器就分别对应于状态管理、父子组件数据同步等不同的功能。
社区和生态的推动:前端社区和生态的繁荣也为装饰器的多样化提供了动力。开发者们根据自己的需求和喜好,不断创造出新的装饰器来解决实际问题,从而推动了装饰器数量和种类的增加。
为什么不像 Vue那样集大成?
每个前端框架和库都有其独特的设计理念和哲学, Vue.js选择了一种相对集中和集成的方式来处理状态管理和组件通信等问题,而OpenHarmony等其他框架则可能更倾向于通过装饰器等更灵活、更细分的方式来实现这些功能。这种差异主要源于它们对前端开发的不同理解和追求。
总的来说,装饰器的多样性和灵活性为前端开发提供了更多的选择和可能性,而不同的框架和库则根据自己的需求和设计理念来选择适合的实现方式。
用鸿蒙原生做个按钮,用户频繁地点这个按钮,怎么控制这个按钮
为了控制这个按钮的点击频率,可以采取以下几种方法:
禁用按钮:在按钮被点击后,立即禁用按钮,直到操作完成后再启用。
Throttle):使用节流技术,限制按钮在一定时间内只能被点击一次。
Debounce):在用户停止点击一段时间后再执行操作,避免频繁触发。
例如:准备一个按钮状态当被点击时,可以用setTimeout模拟异步操作
描述自动化构建流程Hvigor
Hvigor 是一个用于自动化构建和持续集成的工具,
步骤:
1.代码提交:开发者将代码提交到版本控制系统
2.触发构建:Hvigor 监控版本控制系统的变更,一旦检测到新的提交,自动触发构建流程
3.代码编译:Hvigor 会执行编译命令,将源代码编译成可执行文件或其他目标格式
4.单元测试:在构建完成后,Hvigor 会自动运行单元测试,以确保代码的正确性。测试结果会被记录并报告。
5.集成测试:如果单元测试通过,Hvigor 会继续执行集成测试,验证不同模块之间的交互是否正常
6.构建报告:构建完成后,Hvigor 会生成构建报告,包含构建状态、测试结果、代码覆盖率等信息,并通过邮件或其他方式通知相关人员。
7.部署:直接将构建产物上传到服务器
命令行调试工具如何使用
鸿蒙命令行调试工具的使用主要包括Command Line Tools、HiDumper和hdc命令行工具。
Command Line Tools 是一组集合了代码检查、包管理、命令行解析和编译构建等功能的工具,用于HarmonyOS应用开发。在使用前,需要将其添加到系统的环境变量中。在Windows上,需要将解压后的command-line-tools文件夹的bin目录添加到环境变量中。在macOS/Linux上,下载后的命令行工具解压后,通过编辑.bash_profile或.zshrc文件,将命令行工具的路径添加到PATH中,然后保存并使配置的环境变量生效。
HiDumper 是一个为开发、测试人员提供的系统信息获取工具,帮助分析、定位问题。它可以帮助获取UI界面组件树信息,以及内存和CPU使用情况等系统数据,用于评估应用性能。使用HiDumper可以通过开启ArkUI的debug模式,获取当前页面对应应用的window ID,然后使用hdc shell hidumper命令导出系统元能力信息或窗口的系统元能力信息。
hdc命令行工具 是HarmonyOS为开发人员提供的用于调试的命令行工具,允许在Windows、Linux、macOS系统上与真实设备进行交互。使用前需要准备环境变量,包括将hdc工具添加到HarmonyOS SDK的toolchains目录到环境变量中,并在Windows上设置HDC_SERVER_PORT环境变量。
详细说说你对arkts的认识
ArkTS(Ark TypeScript)是鸿蒙操作系统中用于开发的编程语言,类似于 TypeScript。它专注于提高开发效率和代码质量。ArkTS 提供了与 JavaScript 兼容的类型系统,但优化了跨平台性能,并增强了对鸿蒙系统特性的支持。
其主要特点包括:
1、类型安全:增强了代码的可靠性和可维护性。
2、性能优化:特别针对鸿蒙操作系统优化,提升了执行效率。
3、组件化支持:便于开发和管理复杂的 UI 组件。
4、跨平台兼容:支持在不同设备上无缝运行,如手机、平板、电视等。
ArkTS 的设计目的是为了简化开发过程,并充分利用鸿蒙系统的功能,提供更好的用户体验和更高效的开发流程。
你对安卓了解多少?和鸿蒙的区别是什么?
区别
1、设备兼容性:鸿蒙OS是一款面向各种设备的分布式操作系统,支持于机、平板电脑、智能手表、智能家居、汽车等多种设备类型,并能在这些设备之间实现无缝切换和共享数据。而Android系统则主要用于移动设备,如手机和平板电脑.系统架构:鸿蒙OS采用分布式技术架构,通过分布式技术实现多设备间的协作和数据共享,更加灵活、安全、高效。而Android则采用单一设备架构,其多设备协作能力较弱,数据共享相对不便。
2、应用生态:Android系统已经建立了非常完善的应用生态系统,拥有数白万的应用程序,涵盖了各种应用场景。相比之下,鸿蒙OS的应用程序数量较少,生态系统相对不成熟。不过,鸿蒙OS的应用程序数量正在不断增长,未来可能会吸引更多的开发者和应用。
3、安全性:鸿蒙OS采用了多层安全防护措施,包括安全隔离、安全通信、安全识别等,相比Android更加安全。此外,鸿蒙OS还采用了一种名为“微内核”的操作系统内核架构,该架构的安全性和稳定性都非常高。
4、性能和效率:鸿蒙OS在性能和效率方面进行了优化,采用了分布式架构,可以根据设备的资源情况进行智能调度和管理,旨在提供更流畅的用户体验。而Android系统在某些低端设备上可能存在卡顿和性能瓶颈的问题。
说说你在鸿蒙里面怎么处理的异步函数
1.使用then() 方法是用于处理异步操作成功后的结果,并且可以链式调用以实现多个异步操作的顺序执行.then() 处理初始 Promise 的结果,然后返回一个新的值,这个新值被传递给下一个 .then(),以此类推
错误处理:如果在 .then() 中的函数抛出错误,这个错误会被传递给下一个 .then() 的 onRejected 参数或者 .catch() 方法进行处理
2.async/await 是一种非常方便的语法糖,用于处理异步操作,它基于 Promise 实现,但提供了更像同步代码的写法。可以用async 关键字用于声明一个异步函数,await 关键字用于暂停函数的执行,等待一个 Promise 被解决。如果 Promise 被成功解决,await 表达式的值就是 Promise 的结果;如果 Promise 被拒绝,await 会抛出一个错误,这个错误可以被 try/catch 块捕获。语法糖使得异步代码更易于阅读和理解,减少了回调地狱的复杂性,提高了代码的可维护性。它们允许开发者以更接近同步代码的方式编写异步逻辑
给对象设置类型的方式?
1.显式声明类型:在静态类型编译语言中,可以在定义变量时明确指定对象的类型。
2.使用类型推断:在一些现代编程语言中,支持类型推断功能,可以让编译器根据上下文自动推断对象的类型。
3.使用泛型:泛型允许在定义类、接口和方法时使用类型参数,使得代码可以适用于不同的类型。
4.接口实现和继承:通过让对象实现特定的接口或继承特定的类来确定对象的类型。
5.强制类型转换:在某些情况下,可以使用强制类型转换将一个对象转换为特定的类型。但是需要注意的是,如果转换不合法的话,强制类型转化可能会导致运行时错误。
interface和type的区别?
1.定义和用途:
Interface: (1)通常用于定义对象的行为规范,它只包含方法签名和属性声明,而没有具体的实现。
(2) 目的是为了实现多态性,让不同的类可以遵循同一个接口,以统一的方式进行调用。
(3)可以被多个类实现,每个实现类都需要提供接口中定义的所有方法和属性的具体实现。
Type: (1)可以用来为已有的类型起一个别名,或者创建自定义的复合类型。
(2) 类型别名主要是为了提高代码的可读性和可维护性,避免重复书写复杂的类型表达式。
2.语法和机构:
Interface: (1)以interface关键字开头,后面跟着接口名称,然后在大括号内定义方法和属性。
(2)接口中的方法只有签名,没有具体的实现代码。
(3)可以继承其他接口,实现接口的扩展。
Type: (1)使用type关键字,后面跟着类型别名或自定义类型的名称,然后进行类型定义。
(2)类型别名可以是任何有效的类型表达式,包括基本类型、对象类型、函数类型等。
(3)不能像接口一样继承其他类型,但可以通过交叉类型(&)和联合类型(|)来组合多个类型。
3.实现和约束:
Interface:(1)类通过implements关键字来实现接口,必须提供接口中所有方法和属性的具体实现。
(2)接口可以作为函数参数、返回值类型等,用于约束对象的类型。
(3)在一些编程语言中,接口还可以用于定义可迭代对象、可比较对象等特定的行为规范。
Type: (1)类型别名只是给一个已有的类型起一个新的名字,不会对值的结构或行为产生任何影响。
(2)自定义类型可以通过类型断言、类型保护等方式进行类型检查和转换。
(3)类型别名和自定义类型主要用于在代码中提供更清晰的类型信息,而不是像接口那样用于定义行为规范。
枚举的使用场景? 字面量联合类型?
枚举的使用场景
枚举在程序设计中有广泛的应用场景,常见的使用情况包括:
表示有限的离散状态:例如表示方向(上、下、左、右)、星期几、性别等。
2. 替代常量的魔法数值:用有意义的名称代替不清晰的数值,提高代码可读性。 3. 提高代码可维护性:通过枚举名称描述含义,减少代码中的硬编码,当需要修改常量时只需要修改枚举定义即可,无需在代码中逐个替换。 4. API 设计:在 API 设计中,枚举可以作为参数或返回值,约束输入和输出的取值范围。 字面量联合类型 "字面量联合类型"(Literal Union Types)通常指的是一种类型系统特性,它允许你将一组具体的字面量值(如字符串、数字等)联合(union)成一个类型。这种类型表示的值可以是这些字面量中的任何一个 type Status = 'apple' | 'banana' | 'orange' let fruit : Status = 'apple' fruit = 'pear' 1.增强代码可读性:通过明确的类型定义,开发者可以很容易地理解变量的可能取值范围。 2.提高代码健壮性:在编译时就能捕获到错误的赋值,从而避免了运行时错误。 3.优化代码补全和类型推断:许多现代编辑器和IDE都支持根据类型定义提供代码补全和类型推断功能,字面量联合类型可以进一步提升这些功能的效果。
说说你对泛型的理解?
1.是什么
泛型程序设计是程序设计语言的一种风格和范式,定义函数接口或者类的时候,不预先定义好具体的类型,而在使用的时候在指定类型的一种特性。
2.使用方法
泛型通过<>的形式进行表述,可以声明:
- 函数
function returnItem <T> ( para :T ) :T {
return para
}
- 接口
interface ReturnItemFn <T> {
( para : T ) : T
}
-类
class Stack<T> {
private arr : T [ ] = [ ]
public push( item : T ) {
this.arr.push( item )
}
public pop ( ) {
this.arr.pop ( )
}
}
数据持久化的方案?
1.应用数据持久化,是指应用将内存中的数据通过文件或数据库的形式保存到设备上。内存中的数据形态通常是任意的数据结构或数据对象,存储介质上的数据形态可能是文本、数据库、二进制文件等。
2.HarmonyOS标准系统支持典型的存储数据形态,包括用户首选项、键值型数据库、关系型数据库。
用户首选项(Preferences):通常用于保存应用的配置信息。数据通过文本的形式保存在设备中,应用使用过程中会将文本中的数据全量加载到内存中,所以访问速度快、效率高,但不适合需要存储大量数据的场景。
键值型数据库(KV-Store):一种非关系型数据库,其数据以“键值”对的形式进行组织、索引和存储,其中“键”作为唯一标识符。适合很少数据关系和业务关系的业务数据存储,同时因其在分布式场景中降低了解决数据库版本兼容问题的复杂度,和数据同步过程中冲突解决的复杂度而被广泛使用。相比于关系型数据库,更容易做到跨设备跨版本兼容。
关系型数据库(RelationalStore):一种关系型数据库,以行和列的形式存储数据,广泛用于应用中的关系型数据的处理,包括一系列的增、删、改、查等接口,开发者也可以运行自己定义的SQL语句来满足复杂业务场景的需要。
父组件如何给子组件传递UI?
ArkTS(Ark Template Script)是华为提供的一个用于ArkUI的声明式开发语言,主要用于HarmonyOS应用开发。在ArkTS中,父组件可以通过属性(props)传递数据给子组件,从而实现UI的动态传递和更新。
具体操作步骤如下:
定义子组件的属性:首先在子组件中定义需要接收的属性,比如可以通过props关键字定义接收的参数。
在父组件中引用子组件:使用子组件标签,并通过属性名来传递数据。
传递UI数据:如果需要传递复杂的UI结构,可以通过传递一个模板字符串或者一个函数来动态构建UI。
Typescript
// 子组件
@Entry
@Component
struct ChildComponent {
// 定义接收的属性
props: { message: string }
build() {
Column() {
Text(this.props.message)
}
}
}
// 父组件
@Entry
@Component
struct ParentComponent {
build() {
Column() {
// 引用子组件,并传递一个简单的字符串数据
ChildComponent({ message: 'Hello, World!' })
// 传递复杂的UI结构
ChildComponent({
message: () => Text('Dynamic UI')
.fontSize(20)
.color(Color.Blue)
})
}
}
}
在上述代码中,ChildComponent定义了一个props属性来接收父组件传递的数据,而ParentComponent则通过不同的方式将数据传递给ChildComponent。这种方式可以实现父组件向子组件传递UI数据的场景。
说一说哪些地方用到了权限?
一、应用访问权限功能:相机权限、麦克风权限、位置权限、储存权限
二、系统安全与隐私保护:通知权限、通讯录权限、日历权限
总之,在鸿蒙系统中,权限的管理是为了平衡应用的功能需求和用户的隐私安全。用户可以根据自己的实际需求,合理的授予或拒绝应用的权限请求。
项目中的一些亮点?
通用类的封装,提高代码复用性,例如:封装了统一日志类、沉浸式模式类、统一请求处理类,统一上传文件类,录音控制类,语音播放控制类,AI评测接口访问类等
通用组件(搜素组件、打卡组件、Loading组件,难易程度组件)、业务组件抽取(题目分类组件、题目列表组件、单个题目显示内容组件),方便业务的复用
鸿蒙原生应用与网页方法交互显示高亮代码
录制音频文件写入到应用程序缓存,播放在线音频
鸿蒙原生api操作二进制流写文件
vue2和vue3的区别?
Vue 3 和 Vue 2 有以下一些主要区别:
一、性能方面
编译优化
Vue 3 引入了更高效的编译策略。在模板编译阶段,通过静态提升(Static Hoisting)将不会变化的节点提升到函数外部,避免在每次渲染时重复创建,从而减少了不必要的性能开销。
还使用了补丁标记(Patch Flags),使得在虚拟 DOM 对比和更新过程中更加精准,只更新有变化的部分,提高了渲染性能。
响应式系统
Vue 3 使用了全新的响应式系统 Proxy 替代了 Vue 2 中的 Object.defineProperty。Proxy 可以直接代理对象和数组的各种操作,并且能够深度监听对象属性的变化,而无需像 Vue 2 那样对每个属性进行遍历和劫持。这使得响应式性能得到了显著提升,尤其是在处理大型数据结构时。
二、开发体验方面
组合式 API(Composition API)
Vue 3 引入了组合式 API,它允许开发者将相关的逻辑代码封装在函数中,更好地组织和复用代码。相比 Vue 2 的选项式 API,组合式 API 使得代码更加清晰、易于维护和测试。
可以更方便地进行逻辑复用,通过自定义函数将可复用的逻辑提取出来,在不同的组件中调用。
更好的类型支持
Vue 3 对 TypeScript 的支持更加友好。由于组合式 API 的函数式风格,使得类型推断更加准确和容易。同时,Vue 3 的类型定义也更加完善,为使用 TypeScript 进行开发的开发者提供了更好的开发体验。
三、语法和功能方面
Teleport 组件
Vue 3 新增了 Teleport 组件,它允许将一个组件的模板内容渲染到 DOM 中的其他位置,而不是直接在组件挂载的位置。这对于创建模态框、通知等需要脱离组件层级结构进行渲染的场景非常有用。
多个根节点的模板支持
在 Vue 3 中,组件的模板可以有多个根节点,这使得模板的结构更加灵活,可以更好地适应复杂的布局需求。
自定义渲染器
Vue 3 提供了自定义渲染器的功能,允许开发者使用 Vue 的核心逻辑来创建针对不同平台的渲染器,如用于原生移动应用开发的渲染器。
如果确保员工离职, 所有账号都无效?
可以使用ArkTS编写前端逻辑来调用后端API,从而撤销员工的电子邮件账号权限。
示例场景:撤销电子邮件账号权限
假设你有一个后端API,可以用来更新用户的账号状态。我们将使用ArkTS来编写前端逻辑,调用这个后端API来撤销员工的电子邮件账号权限。
1.导入HTTP模块:
import { http } from '@ohos.net.http';
2. 定义API URL:
const API_URL = 'https://your-backend-api-url/api/users/disable';
3. 定义员工信息:
const employeeEmail = 'employee@example.com';
4.调用后端API:
async function disableEmailAccount() {
try {
const requestBody = JSON.stringify({ email: employeeEmail });
const requestInfo = {
url: API_URL,
method: http.RequestMethod.POST,
headers: {
'Content-Type': 'application/json'
},
data: requestBody
};
const response = await http.sendRequest(requestInfo);
const responseText = await response.responseText;
const responseBody = JSON.parse(responseText);
if (response.statusCode === 200 && responseBody.status === 'success') {
console.log('Email account disabled successfully.');
} else {
console.error('Failed to disable email account:', responseBody.message);
}
} catch (error) {
console.error('Error disabling email account:', error);
}
}
5.调用函数
disableEmailAccount();
通过以上代码,你可以使用ArkTS编写前端逻辑来调用后端API,从而撤销员工的电子邮件账号权限
如果前端伪造token, 如何做跳转拦截?
在鸿蒙开发中,可以通过以下方式来处理前端伪造 token 时的跳转拦截:
1.在进行页面跳转前,先向后端发送一个预验证请求:
使用网络请求工具(如鸿蒙的 HttpClient)发送请求到后端的验证接口,检查当前用户的登录状态和权限。
如果后端返回验证通过的信息,再进行页面跳转;如果返回未授权或其他错误信息,阻止跳转并显示相应提示。
2.利用页面路由守卫:
在鸿蒙应用中,可以在页面导航逻辑中添加类似路由守卫的机制。
在进入特定页面之前,检查本地存储中的 token 是否有效。如果 token 无效或不存在,阻止用户访问该页面,并将用户重定向到登录页面或其他提示页面。
3.对网络请求进行拦截:
在发送网络请求时,可以添加拦截器来检查请求中的 token 是否有效。
如果 token 无效,取消请求并进行相应的处理,如显示错误提示或重定向到登录页面。
需要注意的是,以上只是一个基本的思路,实际开发中还需要根据具体的业务需求和安全要求进行进一步的优化和完善。同时,确保后端的 token 生成和验证机制足够安全,以防止 token 被伪造或篡改。
以下是一些在鸿蒙开发中防范前端伪造 token 的方法:
一、后端加强 token 安全性
1.使用强加密算法生成 token:
后端在生成 token 时,采用安全的加密算法,确保 token 难以被破解和伪造。例如,可以使用 JSON Web Token(JWT),它结合了数字签名和加密技术,可以有效防止篡改。
配置合适的密钥长度和加密强度,增加破解难度。
2.设置合理的 token 有效期:
避免设置过长的 token 有效期,以减少被攻击的时间窗口。同时,也不要设置过短的有效期,以免给用户带来频繁登录的困扰。
可以考虑在用户进行敏感操作时,延长 token 的有效期,以确保操作的安全性。
3.严格的权限控制:
后端根据 token 解析出用户信息后,进行精细的权限控制。只允许用户访问其具有相应权限的资源和接口。
二、前端安全措施:
1.安全存储 token:
在鸿蒙应用中,避免将 token 明文存储在本地存储或其他易被访问的地方。可以使用安全的存储方式,如鸿蒙的加密存储机制。
或者将 token 存储在内存中,并在应用关闭时及时清除,以减少被窃取的风险。
2.防止 XSS 攻击:
对用户输入进行严格的过滤和验证,防止跨站脚本攻击(XSS)。XSS 攻击可能导致恶意脚本窃取用户的 token。
使用安全的模板引擎和输入验证库,确保用户输入的安全性。
3.网络请求安全:
在发送网络请求时,确保使用安全的通信协议,如 HTTPS。这样可以防止中间人攻击,保护 token 在传输过程中的安全。
说一说管理系统的权限?
鸿蒙管理系统的权限主要包括以下方面:
一、权限定义与声明:
1.系统预定义权限:鸿蒙系统提供了一系列预定义的权限,这些权限涵盖了对各种系统资源和功能的访问控制。例如,ohos.permission.CAMERA用于访问相机设备、ohos.permission.LOCATION用于获取设备的位置信息、ohos.permission.STORAGE用于访问存储设备等。应用开发者需要根据应用的功能需求,在应用的配置文件(config.json)中声明所需要的系统预定义权限。
自定义权限:如果系统预定义的权限无法满足应用的特殊访问控制需求,开发者可以在config.json中以"defpermissions"属性来定义新的权限。通过设置 “availableScope” 和 “grantMode” 两个属性,可以分别确定权限的开放范围和授权方式,使权限定义更加灵活 。
二、权限申请与授权:
1.动态申请:对于敏感权限,应用需要在运行时主动调用系统的动态申请权限接口。系统会弹出一个对话框,询问用户是否同意授权。用户可以选择允许或拒绝。即使用户之前已经授予了某个权限,应用在每次调用受该权限管控的接口前,都应该先检查自己是否仍然拥有该权限,因为用户可以在设置中取消应用的权限 。
2.权限申请的约束:同一应用申请的权限个数不能超过 1024 个。为避免与系统权限名冲突,应用自定义权限名不能以ohos开头,且权限名长度不能超过 256 个字符。自定义权限的授予方式不能为user_grant,开放范围不能为restricted。
三、权限保护对象:
1.数据保护:权限保护的对象包括个人数据(如照片、通讯录、日历、位置等)、设备数据(如设备标识、相机、麦克风等)以及应用数据。应用在访问这些数据时,需要具备相应的权限。
2.能力保护:能力方面包括设备能力(如打电话、发短信、联网等)和应用能力(如弹出悬浮框、创建快捷方式等)等。如果应用想要使用这些能力,也必须获得相应的权限许可。
四、权限管理的运作机制:
1.应用沙盒隔离:系统利用内核保护机制识别和隔离应用资源,将不同的应用隔离开来,保护应用自身和系统免受恶意应用的攻击。默认情况下,应用只能访问有限的系统资源,应用间不能彼此交互。
2.多种访问控制机制:权限管理是通过多种不同的形式实现的,如自主访问控制(DAC)、强制访问控制(MAC)以及应用权限机制等,共同保证系统的安全性和应用的正常运行。
说一说最近做的一个鸿蒙项目? 比较特色的功能?
我最近做的一个项目是面试通,该项目是基于 HarmonyOS NEXT 开发的鸿蒙应用,主要包含鸿蒙和前端的企业面试题以及真实的面试经验,还有一些面试辅助类的工具如常用开发单词和面试录音。
比较特色的功能有:
1..通用类的封装,提高代码复用性,例如:封装了统一日志类、沉浸式模式类、统一请求处理类,统一上传文件类,录音控制类,语音播放控制类,AI评测接口访问类等。
2.通用组件(搜素组件、打卡组件、Loading组件,难易程度组件)、业务组件抽取(题目分类组件、题目列表组件、单个题目显示内容组件),方便业务的复用。
3.鸿蒙原生应用与网页方法交互显示高亮代码。
4.录制音频文件写入到应用程序缓存,播放在线首频。
5.鸿蒙原生api操作二进制流写文件。
这里面的技术难点有:
1.首页6大组件的拆分,相互传值模式设计->最终采用 @Prop 结合 @Watch以及传函数的方式来合理的完成业务并简化了代码的耦合。
2.研究了使用AVPlayer状态机来控制整个单词播放的流程。
3.研究了使用AudioCapturer来控制整个语音的录制。
4.对比研究了多家公司的A!口语评测接口,都没有鸿蒙SDK,最后采用http协议来上传语音文件完成语音评测业务。
5.录制语音采样率、音频采样深度、声道数、音频编码格式等参数要与A|接口要求的录音文件参数相匹配,使用AVRecorder技术方案不能设置采样深度这个参数而放弃,最终改用AudioCapturer完成技术攻关。
我在这个项目开发中遇到的坑有:
1.首选项使用异步方法的时候会出现运行紊乱的问题,当异步方法文件还没写完,如果去读就会读取失败,建议使用同步方法。
2.文件操作的时候,如果使用异步方法执行会出现紊乱的问题,建议改为使用同步方法。
3.async方法忘记使用await调用出现执行结果紊乱。
4.AVPlayer播放在线mp3文件时,http协议的url播放不了,但是https协议的url可以正常播放。
5.AudioCapturer实例化的时候,在win模拟器上实例化报错,真机可以。
6.上传文件时直接读取系统相册文件没有权限,上传失败,需要把系统相册图片通过fs拷贝到应用程序缓存中使用internel://cache方式来读取就能成功。
7.RichText富文本兼容性问题。
8.跨线程通讯问题 ->头像上传百分比进度不更新->使用emitter解决。
9.Web组件高度兼容性问题:Web组件加载完html后,再调用writeCode方法写入内容,显示不出来内容,在Web组件上增加固定高度可以解决。
有没有做过键盘避让?
做过,我们可以通过 setKeyboardAvoidMode 属性来设置键盘避让模式。鸿蒙提供了两种方式来设置键盘避让模式。
// 在ability中设置键盘避让模式
// 在onWindowStageCreate生命周期中,在loadContent完成之后,通过UIContext对象的setKeyboardAvoidMode方法来设置键盘避让模式:
// 这种避让模式在全局生效,如果我们只希望某个页面使用键盘避让模式,可以通过在页面中设置的方式来实现。
// 在页面中设置键盘避让模式
// 在页面中通过设置组件的expandSafeArea属性来设置键盘避让模式,此时expandSafeArea的参数为 [SafeAreaType.KEYBOARD, SafeAreaType.SYSTEM]
如果处理屏幕安全区域的处理?
安全区域是指页面的显示区域,默认不与系统设置的非安全区域比如状态栏、导航栏区域重叠等。可以使用setWindowLayoutFullScreen 方法来设置沉浸式状态栏。首先,我们获取当前页面的 Window 对象,然后调用 setWindowLayoutFlags 方法将窗口布局设置为全屏,并调用 addFlags 方法更新窗口布局,从而实现沉浸式效果。
获取全局信息的时机?
在鸿蒙(HarmonyOS)应用开发中,获取全局信息的时机通常取决于具体的应用场景和需求。全局信息可能包括但不限于设备状态、系统设置、位置信息等。以下是一些常见的需要获取全局信息的情况:
启动时初始化:
应用启动时,可能需要检查设备的一些基本信息,如屏幕尺寸、方向、系统版本等,以便根据这些信息来调整应用的行为或UI布局。
用户登录或注册时:
在用户登录或注册的过程中,可能需要获取一些设备信息用于安全验证或记录用户的设备环境。
功能调用前:
在调用某些功能之前,比如拍照或录音,需要检查是否拥有相应的权限以及设备是否支持相关硬件。
后台服务运行时:
如果应用有一个后台服务,它可能需要周期性地获取某些全局信息,例如位置更新或者网络状态变化。
响应系统事件时:
当系统事件发生时,比如屏幕旋转、电池电量变化、网络连接状态改变等,应用可能需要监听这些事件并作出相应处理。
说一说冒泡排序?
** 1. 算法步骤 **
冒泡排序(Bubble Sort)是一种简单的排序算法。它重复地遍历要排序的数列,一次比较两个元素,如果它们的顺序错误就把它们交换过来。遍历数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小(或越大)的元素会经由交换慢慢“浮”到数列的顶端。
冒泡排序算法的运作如下:
(1). 比较相邻的元素。如果第一个比第二个大(升序排序中),就交换它们两个;
(2). 对每一对相邻元素做同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数;
(3). 针对所有的元素重复以上的步骤,除了最后一个;
(4). 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
冒泡排序的平均时间复杂度和最坏时间复杂度都是O(n^2),其中n是数组的长度。因此,对于大数据集,冒泡排序可能不是一个高效的排序算法。然而,由于其实现简单,冒泡排序在数据量不大的情况下仍然是一个很好的教学示例。
** 2.冒泡排序示例代码 **
function bubbleSort(arr) {
let len = arr.length;
for (let i = 0; i < len - 1; i++) {
for (let j = 0; j < len - 1 - i; j++) {
if (arr[j] > arr[j+1]) { // 相邻元素两两对比
let temp = arr[j+1]; // 元素交换
arr[j+1] = arr[j];
arr[j] = temp;
}
}
}
return arr;
}
** 3.冒泡排序动图演示 **

说一说http和https的区别? GET和POST的请求的区别? 说一说3次握手和4次挥手?
** (1)http和https的区别: **
http是超文本传输协议,信息是明文传输,https则是具有安全性的加密传输协议http和https用的端口不一样,前者是80,后者是443
** (2)GET和POST的请求的区别? **
GET请求相对不安全,POST请求相对安全GET请求可以缓存,POST请求不能缓存GET请求有长度限制(每个浏览器的限制长度不同),POST请求没有长度限制GET只能传输字符串,POST可以传输多种类型数据GET请求入参在URL上,POST请求入参在Request body上
** (3)说一说3次握手和4次挥手? **
三次握手
第一次握手:建立连接时,客户端发送syn包到服务器,等待服务器确认
第二次握手:服务器收到syn包,必须确认客户的SYN,同时自己也发送一个SYN包(syn=y)到客户端
第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK,此包发送完毕,客户端和服务器进入(TCP连接成功)状态,完成三次握手
通俗:主机1告诉主机2,我可以向你请求数据吗。主机2告诉主机1,可以请求数据。主机1告诉主机2,那我来请求数据了,请求完成,实现三次握手
四次挥手
第一次分手:主机1(可以使客户端,也可以是服务器端)向主机2发送一个FIN报文段;此时,主机1进入FIN_WAIT_1状态;这表示主机1没有数据要发送给主机2了
第二次分手:主机2收到了主机1发送的FIN报文段,向主机1回一个ACK报文段,主机1进入FIN_WAIT_2状态;主机2告诉主机1,我“同意”你的关闭请求
第三次分手:主机2向主机1发送FIN报文段,请求关闭连接,同时主机2进入LAST_ACK状态
第四次分手:主机1收到主机2发送的FIN报文段,向主机2发送ACK报文段,然后主机1进入TIME_WAIT状态;主机2收到主机1的ACK报文段以后,就关闭连接;此时,主机1等待2MSL后依然没有收到回复,则证明Server端已正常关闭,那好,主机1也可以关闭连接了
通俗:主机1告诉主机2,我没有数据要发送了,希望断开连接。主机2接到请求后说,同意断开。主机2告诉主机1可以关闭连接了。主机1接到可以关闭的指令后,关闭连接,四次挥手完成
如何获取数组的类型?
可以通过 typeof 运算符来获取类型,返回的是一个 字符串
// 后面跟上需要获取类型的 数据或变量 即可
typeof 表达式
console.log(typeof [1, 2, 3]) // object
给对象做类型可以用哪些方式? interface和type的区别?
给对象做类型可以用那些方式?
①基本类型直接写②定义对象的类并实例化对象③接口定义对象的形状④用Type定义对象类型别名⑤泛型
interface和type的区别?
相同点
①都可以描述对象和函数②都可以拓展extend
不同点
Type可以声明基本类型联合类型interface不行;type可以使用Typeof获取的实例的类型进行赋值
interface可以声明合并type不行
说说你对泛型的理解?
泛型在保证类型安全(不丢失类型信息)的同时,可以让函数等与多种不同的类型一起工作,灵活可复用
通俗一点就是:类型是可变的!
说一说函数的使用?
函数:是可以被重复使用的代码块
基本使用:先定义后调用
函数参数的作用:函数能够接收和处理外部的数据,提高函数的灵活性和通用性。
实参:真实数据(函数调用时候的传入的真实数据)
形参:形式上的参数(函数定义时候写的参数)
返回值
返回值作用:将函数的执行结果传递给其他部分使用(函数外部)。
默认情况下,函数外部无法直接使用函数内部执行结果;如果想要在函数外部使用内部执行结果,需要将这个结果设置为函数的返回值。
关键字:return
箭头函数是 比普通函数 更简洁 的一种函数写法
你在项目中使用首选项主要用来做什么? 首选项有没有长度限制?
1.项目中用的是鸿蒙【首选项】api来做的数据持久化方案,例如:在【搜索题目功能】中,我们就用到了【首选项】来把用户输入过的搜索关键字保存起来,专门提供了一个类来对数据进行,新增,查询,删除处理
首选项只能保存字符串,所以我们借助了JSON的stringify和parse两个方法来进行相互转换。在存的时候调用JSON.stringify将数组转换成json字符串来存储,读取的时候调用了JSON.parse将json字符串转换成了数组,方便操作
2.首选项有长度限制,首选项大概能存储8KB数量的数据, 建议存储的数据不超过一万条,否则会在内存方面产生较大的开销。
你项目中遇到了哪些难点,如何解决的?
开发鸿蒙项目难点分为两块讲:业务难点和技术难点
业务难点
如何对单词语音文件进行完整度、流利度、标准度的评分检测
经过多家公司的技术验证后,最后采取了云知声公司的AI口语评测接口结合AudioCapturer进行录音后,将录音文件上传到AI评测接口完成结果的解析获得
技术难点
录制语音采样率、音频采样深度、声道数、音频编码格式等参数要与AI接口要求的录音文件参数相匹配,使用AVRecorder技术方案不能设置采样深度这个参数而放弃,最终改用AudioCapturer完成技术攻关
说一说大文件上传的方案?
(1) 权限申请
在ArkTS中,首先需要确保应用拥有必要的权限,如ohos.permission.INTERNET用于网络访问,以及ohos.permission.READ_MEDIA或ohos.permission.WRITE_MEDIA等用于文件读写。这些权限需要在应用的配置文件中声明,并在运行时请求用户授权。
(2) 文件选择
使用ArkTS提供的文件选择器(如picker.PhotoViewPicker)让用户选择需要上传的文件。注意,由于大文件可能占用较多存储空间,建议在选择时给予用户明确的提示。
(3) 文件预处理
对于特别大的文件,可以考虑在上传前进行分块处理。将大文件分割成多个较小的部分,可以更有效地利用网络资源,并减少因网络中断导致的上传失败风险。
(4)网络传输
ArkTS提供了@ohos.request接口用于处理网络请求,包括文件上传。通过该接口,可以创建上传任务,并配置URL、请求头、请求体等参数。
(5)分片上传
对于分块处理后的文件,可以使用分片上传技术。即将文件分成的每个小块分别上传到服务器,并在服务器端进行合并。这样可以提高上传效率,并允许在上传过程中进行断点续传。
访问拦截处理, 如果处理安全问题?
(1). 自定义事件拦截
ArkTS为组件提供了自定义事件拦截能力,开发者可以根据事件的具体信息(如事件在控件上发生的位置、输入源等)来决定如何处理这些事件。这允许开发者在事件传递的初期就进行安全检查,如验证事件来源是否合法、检查事件数据是否安全等。
(2). 数据传输安全
加密传输数据:对于敏感数据,可以使用加密算法进行加密处理,确保数据在传输过程中即使被截获也无法被轻易解密。
验证数据完整性:在数据接收端,可以通过校验和、哈希值等方式验证数据的完整性,确保数据在传输过程中未被篡改。
(3). 权限控制
对于不同的操作和资源,应该实施严格的权限控制。确保只有具有相应权限的用户或线程才能访问特定的资源或执行特定的操作。
(4). 监控和日志记录
为了及时发现和处理安全问题,应该在ArkTS程序中实施监控和日志记录机制。通过监控程序的运行状态和记录关键操作的日志信息,可以及时发现异常行为和安全事件,并采取相应的应对措施。
如何避免明文传输, 怎么加密, 加密算法?
(1)使用HTTPS协议:HTTPS通过SSL/TLS协议建立安全的加密通信通道,确保传输过程中的数据被加密,从而保护数据不以明文形式传输。
(2)前端加密处理:在前端对敏感数据进行加密处理后再发送请求,可以使用AES、RSA等加密算法对数据进行加密。
(3)请求签名验证:对请求参数进行签名处理,并与请求一起发送到服务器,服务器端根据签名算法和密钥对请求参数进行验证,确保请求的完整性和可靠性。
(4)Token验证:用户登录时,后端生成一个Token并返回给前端,前端在发送请求时将Token添加到请求头或请求参数中,后端验证Token的有效性。
(5)请求头加密处理:将请求头中的一些关键信息进行加密处理后再发送到服务器,服务器端接收到请求后对请求头进行解密以获取信息。
(6)哈希算法:虽然哈希算法是单向的,不用于加密解密,但可以用于验证数据的完整性和一致性,如保存密码时使用哈希算法加盐进行存储。
(7)非对称加密:使用非对称加密算法,如RSA,其中公钥加密的数据需要私钥解密,私钥加密的数据需要公钥解密,这种方式安全性更高,即使公钥被泄露,攻击者也无法解密密码。
(8)电子邮件加密:对于敏感的电子邮件通信,可以使用加密技术来保护邮件内容,确保只有授权的收件人能够阅读。
(9)存储加密:对存储在数据库或其他存储介质中的数据进行加密,以防止未经授权的访问或数据泄露。
(10)区块链加密:在区块链技术中,使用加密算法对交易信息进行加密处理,确保交易的不可篡改性和安全性。
应用APL的等级有哪些?
(1)normal:这是默认的应用APL等级,普通应用都属于这个级别。在这个级别下,应用可以申请一些基本的权限,但无法申请更高级别的系统权限。
(2)system_basic:这个等级的应用可以提供系统基础服务,能够申请的权限范围比normal级别更广,包括一些涉及系统基础功能的权限。
(3)system_core:这是最高级别的APL等级,应用在这个级别可以提供操作系统核心能力,能够申请的权限范围最广,包括操作系统核心资源的权限。但需要注意的是,不是所有应用都能配置为system_core级别。
实现一个下拉刷新效果如何操作?
实现步骤
1.引入必要的组件:
在你的页面中引入Refresh组件,这是HarmonyOS提供的用于实现下拉刷新的标准组件。
2.设置状态变量:
使用@State装饰器声明一些状态变量,例如translateY来跟踪列表的偏移量,text来显示提示文本,以及isRefreshing来表示是否正在加载数据。
3.绑定事件处理程序:
给Refresh组件绑定一个onRefresh事件处理程序,当用户执行下拉动作时,这个处理程序会被触发。
4.实现事件处理逻辑:
在事件处理程序中,你可以改变isRefreshing的状态,并调用异步函数去获取新的数据。
当数据加载完成后,更新UI,并将isRefreshing状态重置为false。