浅谈ArkTS/ArkUI组件开发
本篇文章将从一个移动开发头脑的维度出发,浅谈ArkTS组件开发的底子问题,比如状态管理、装饰器、属性传递、自定义构建函数、插槽、条件渲染,模块引用和路由跳转等。
创建项目
这里利用截图简朴过一下,不再赘述了
页面和组件
- @Entry
- @Component
- struct Index {
- @State message: string = 'Hello World';
- build() {
- RelativeContainer() {
- Text(this.message)
- .id('HelloWorld')
- .fontSize(50)
- .fontWeight(FontWeight.Bold)
- .alignRules({
- center: { anchor: '__container__', align: VerticalAlign.Center },
- middle: { anchor: '__container__', align: HorizontalAlign.Center }
- })
- }
- .height('100%')
- .width('100%')
- }
- }
复制代码 看页面入口Index组件,装饰器
- @Entry装饰器修饰的组件,表示这是个入口组件。且表示是一个独立page页面,通过router举行跳转。
- @Component利用该装饰器修饰的组件,表示自定义组件,可举行页面渲染。
- build雷同ReactNative中的render方法。生成节点树实例。
另外,鸿蒙ArkUI是一套构建分布式应用界面的声明式UI开发框架,利用声明式的写法。也就是只能利用表达式,与jsx语法对比来看
- // 感受下ReactNative中的组件,响应式编程范式(特点:都有一个return,属性样式以插入标签方式实现)
- // return 之后是如何写的~
- render() {
- const {errDisMsg, title='账**', backMsg='返回'} = this.state;
- //公共错误
- ...省略...
- return (
- <View style={{ flex: 1 }}>
- <View style={styles.container}>
- <View>
- <ICBCWAPHeader {...header} />
- <View style={{ backgroundColor: '#F5F7F9' }} />
- {bodytext}
- </View>
- </View>
- </View>
- );
- }
- }
复制代码- // 与上面使用ReactNative组件响应式开发对比,该ArkUI声明式开发如下
- // 方式与响应式相近,无return返回,有属性样式的链式调用
- build() {
- Stack({ alignContent: Alignment.Top }) {
- Image($r('app.media.ic_app_logo'))
- .width(80)
- .height(80)
- .margin({ top: 160 })
- .borderRadius(30)
- .sharedTransition('imgAppLogo', {
- duration: 300,
- curve: Curve.Linear
- })
- Column() {
- Text("剧影新视界")
- .fontSize(SizeConstant.TEXT_XXL)
- .fontColor('#CC333333')
- .fontWeight(FontWeight.Medium)
- }
- .width("100%")
- .height(100)
- .margin(260)
- .padding({ top: SizeConstant.SPACE_XL, bottom: SizeConstant.SPACE_XL })
- .alignItems(HorizontalAlign.Center)
- .justifyContent(FlexAlign.SpaceEvenly)
- ... 省略...
- }.width("100%").height("100%").backgroundColor('#F5F5F5')
- }
复制代码 可以发现声明式的开发中的一个小特点,组件中不必要renturn。
自定义组件
上面组件的开发示例代码中,我们用到 @Entry装饰器。它装饰的组件,则是通过路由可跳转进入的页面组件。其实对于自定义组件,不必要@Entry装饰器的修饰利用(@Entry修饰的组件,属于入口组件,可通过router举行跳转),且不是页面组件,不可路由进入。利用开发中的一段代码介绍下
- /**
- * @auther y**
- * @description 登陆页面
- * @date 今天
- */
- @Entry // 通过该装饰器装饰,可路由页面组件
- @Component
- struct LoginPage {
- @StorageLink(THEME_TYPE) themeType: ThemeType = ThemeType.DEFAULT
- @State qrcodeValueBean?: Partial<QrcodeValueBean> = {}
- @State authStep: LoginAuthStep | undefined = undefined
- aboutToAppear() {
- this.authStep = LoginAuthStep.GENERATE_QRCODE_ING
- }
- build() {
- Stack({ alignContent: Alignment.Top }) {
- Image($r('app.media.ic_app_logo'))
- ... 省略 ...
- Stack() {
- this.QrcodeBuilder()
- }.layoutWeight(1)
- ...省略...
- }
- }.width("100%").height("100%").backgroundColor(AppTheme.primary)
- }
- @Builder QrcodeBuilder() {
- ViewStateLayout({ onLoadData: async (viewState) => {
- this.qrcodeValueBean = (await loginViewModel.qrcodeAuth(viewState,
- (authStep: LoginAuthStep) => {
- this.authStep = authStep
- })).data
- } }) {
- // 这里引入自定义组件
- CpnQRCode({ qrcodeValueBean: this.qrcodeValueBean as QrcodeValueBean })
- }
- }
- ... 省略转场动画...
- // 省略放在下方~
- // 自定义组件
复制代码 最根本的自定义组件,也就是下面如许写的了
- // 自定义组件,不可路由进入的组件,且非页面组件
- @Component /**自定义组件:第一步 */
- struct/**自定义组件:第二步 */ CpnQRCode {
- @ObjectLink qrcodeValueBean: QrcodeValueBean
- build()/**自定义组件:第三步 */ {
- QRCode(this.qrcodeValueBean.qraurl).width(160).height(160)
- }
- }
复制代码 链式调用
链式调用,就是我们常用到的可以不停通过.方法调用执行方法的路子。如安卓火热的RxJava(Rxjava其基于变乱流的链式调用、逻辑简洁)。另外,有Flutter开发经验的会发现,与Flutter声明式编码如出一辙。那相对于混合开发技能栈的ReactNative和React、Vue相应式UI编程范式就大不一样啦。但是只要有以上任何一种技能栈的丰富开发经验,ArkUI样式写法就很容易理解和上手。
而且上面许多样式并不常用,只要有UI样式编码经验,心底成竹在胸的知道,该如何定义和使其显现,开发则不成问题。样式开发中雷同上图提示的功能,同样可以通过@Extend和@Styles这俩装饰器来集中实现。@Styles或@Extend目前不支持export导出,后续这两个装饰器不会继续演进
- @Extend装饰器 定义扩展组件样式。
- @Styles装饰器 定义组件重用样式,支持通用属性和通用变乱。
@Styles装饰器
@Styles装饰器,用来定义一些公共底子的样式能力。如颜色背景,透明度、内边距、外边距等。可以定义在组件内或全局,在全局定义时需在方法名前面添加function关键字,组件内定义时则不必要添加function关键字。定义在组件内部,该方法被认为是该组件的私有、特殊范例的成员函数,有助于提升组件内聚;定义在外部,可构建作为底子样式库提供支持。
@Extend
@Styles不能携带参数,恰好@Extend增补了这一缺点。(和@Styles不同,@Extend装饰的方法支持参数,开发者可以在调用时传递参数,调用遵循TS方法传值调用。)
和@Styles不同,@Extend仅支持在全局定义,不支持在组件内部定义
- @Entry
- @Component
- struct Index {
- @State message: string = 'Hello World';
- build() {
- RelativeContainer() {
- Text(this.message)
- .TextStyle()
- .TextStyleExtend()
- }
- .height('100%')
- .width('100%')
- }
- }
- @Extend(Text) function TextStyleExtend (fontSize: number = 20, id: string = 'hello-world') {
- .fontSize(fontSize)
- .fontWeight(FontWeight.Bolder)
- .id(id)
- .alignRules({
- center: { anchor: '__container__', align: VerticalAlign.Center },
- middle: { anchor: '__container__', align: HorizontalAlign.Center }
- })
- }
- @Styles
- function TextStyle () {
- .backgroundColor(Color.Blue)
- .border({width: {top:2}, color: Color.Gray})
- .margin({left: '15vp'})
- }
复制代码 属性和状态
在声明式UI编程范式中,UI是应用步伐状态的函数,应用步伐状态的修改会更新相应的UI界面。ArkUI采用了MVVM模式,其中ViewModel将数据与视图绑定在一起,数据更新的时候直接更新视图。如下图所示:
属性prop,通过外部传入,由父到子的单向数据流传递;利用者(子组件)无法修改。
状态state,组件私有状态,参于组件内部交互处理逻辑。
@State装饰器
上述截图及代码中有用到@State装饰器,修饰一个标量,在该变量被改变时,会触发UI重绘。
利用@State装饰器,表示定义状态与当前组件的一种绑定;即必要用到状态的变化引发组件的重绘渲染。否则,不要利用绑定。
@Prop装饰器
被@Prop装饰器修饰的成员标量。引用从外部传入,在内部利用。相比ReactNative自定义组件,可认为是自定义组件对外开放的接口。以方便自定义组件吸收参数并机动的设计出可预期的组件样式和功能。
源码中,同时支持状态和属性的改变
- @Entry
- @Component
- struct Index {
- @State message: string = 'Hello World';
- build() {
- RelativeContainer() {
- // 在父级组件中轻松调用,并传入子组件@Prop修饰的属性'text_color'赋值
- // `若`text_color传入值使用@State装饰的message,即ZidingyiComponent({text_color: this.message})
- // 且在当前组件(父级)生命周期方法中对该状态message做修改变化,
- // 那么在父组件状态值的变化同时会触发子组件(ZidingyiComponent)属性的变化。达到数据流的单向传递效果。
- ZidingyiComponent({text_color: 'red'})
- }
- .height('100%')
- .width('100%')
- }
- }
- /**自定义子组件,子组件中定义了@State和@Prop*/
- @Component
- struct ZidingyiComponent {
- @Prop text_color: string = '#FFFFFF'
- @State text_size: number = 18
- aboutToAppear(): void {
- this.text_size = 66 // 自定义组件生命周期中改变状态,触发渲染
- }
- build() {
- Text('自定义组件,prop由外部传入,默认是白色')
- .TextStyleExtend(this.text_size, this.text_color, 'id')
- .TextStyle()
- }
- }
- @Extend(Text) function TextStyleExtend (fontSize: number = 20, fontColor:string = 'black', id: string = 'hello-world') {
- .fontSize(fontSize)
- .fontColor(fontColor)
- .fontWeight(FontWeight.Bolder)
- .id(id)
- .alignRules({
- center: { anchor: '__container__', align: VerticalAlign.Center },
- middle: { anchor: '__container__', align: HorizontalAlign.Center }
- })
- }
- @Styles
- function TextStyle () {
- .backgroundColor(Color.Blue)
- .border({width: {top:2}, color: Color.Gray})
- .margin({left: '15vp'})
- }
复制代码 属性监听
@Watch属性监听装饰器,可以监听@Prop和@State装饰对象的变化。所监听的装饰对象发生变化,触发@Watch的回调方法。且,一个对象只能被监听一次。
- @State @Watch('onMessageChange') message: string = 'Hello World';
- onMessageChange() {
- hilog.info(0x88, 'onMessageChange', '@Watch装饰器监听变量message发生变化后,触发该方法执行')
- }
- // 页面每次显示时触发一次
- onPageShow(): void {
- this.message = '修改message,为了触发@Watch'
- }
复制代码 构建函数组件和插槽实现
从官网定义看@Builder称作UI元素复用机制,@Builder所装饰的函数遵循build()函数语法规则,开发者可以将重复利用的UI元素抽象成一个方法,在build方法里调用自定义构建函数,也可作为一种自定义组件利用与看待。
@Builder 和 @BuilderParam
值得注意的是,@BuilderParam装饰的方法只能被自定义构建函数(@Builder装饰的方法)初始化。@BuilderParam可实现插槽
ArkUI引入了@BuilderParam装饰器,该装饰器用于声明任意UI描述的一个元素,雷同slot占位符。
简朴的举例,(定义需求)在父级组件中定义了一个@Builder函数构件(一个完整的函数组件,且具备通用性);然后在子组件中定义并初始化了@BuilderParam。父组件在利用子组件的同时,将父级组件中的通用函数组件传递到子组件中利用,以实现雷同的slot占位符效果。
提示:@BuilderParam出现的同时,必然会有@Builder的出现。反之不同。
- @Entry
- @Component
- struct Index {
- @Builder BuilderComponent() {
- Button('Builder自定义构建函数')
- .type(ButtonType.Capsule)
- .fontColor('#333333')
- .fontSize('16fp')
- }
-
- build() {
- RelativeContainer() {
- // 父组件中使用自定义子组件,并传入子组件(父组件的函数组件)
- // 传递组件哦
- ZidingyiComponent({BuilderParamsComponent: this.BuilderComponent})
- }
- .height('100%')
- .width('100%')
- }
- }
- @Component
- struct ZidingyiComponent {
- @Builder BuilderComponent() {
- // 空的,初始化@BuilderParam使用
- }
- @BuilderParam BuilderParamsComponent:()=>void = this.BuilderComponent
- build() {
- RelativeContainer() {
- Text('自定义子组件')
- }
- .height('100%')
- .width('100%')
- }
- }
复制代码 装饰器
单向同步
数据流单向同步的方式,在以上已描述带过。即在子孙组件中利用@Prop装饰器修饰定义的变量对象,可以引流父组件数据向子组件单向传递。
双相同步
数据流的双向同步,即父级组件中嵌入子组件。通过双向数据流装饰器@Link/@StorageLInk/@ObjectLink装饰的变量,任何一方的改变都会触发另外一方的数据变化,从而变化的数据驱动UI重绘。

上面截图来自实际项目工程,表达的不够明确。下面手写一段代码再次解释下利用装饰器如何实现数据的双向同步。
- @Entry
- @Component
- struct Index {
- @State @Watch('showChangeCallback') isShow: boolean = false;
- showChangeCallback() {
- // 监听`isShow`的变化,并触发该回调方法。
- // 回调方法中,修改全局缓存的`isShow`的值。之后被装饰器@StorageLink、@StorageProp绑定`isShow`的变量对象。
- // 变量对象的值都将响应变化
- AppStorage.setOrCreate<Boolean>('isShow', !this.isShow);
- }
- aboutToAppear(): void {
- // 初始化isShow的全局缓存
- AppStorage.setOrCreate<Boolean>('isShow', this.isShow);
- }
- build() {
- RelativeContainer() {
- ZidingyiComponent()
- Button('改变isShow在AppStorage的缓存').onClick(()=>{this.isShow = true/**命中变化*/})
- }
- .height('100%')
- .width('100%')
- }
- }
- @Component
- struct ZidingyiComponent {
- @StorageLink('isShow') _isShow: boolean = false; // 响应AppStorage中缓存的`isShow`,双向传递
- build() {
- RelativeContainer() {
- if (this._isShow) Text('自定义子组件文本'); else Button('自定义子组件按钮');
- Zidingyi2Component({_isShow_: this._isShow})
- }
- .height('45vp')
- .width('100%')
- }
- }
- @Component
- struct Zidingyi2Component {
- // @Link 装饰器修饰的对象 _isShow_ 可实现双向传递
- // _isShow_的修改(变化)会同时修改到『ZidingyiComponent』中的`_isShow`值,同时修改到AppStorage中缓存的`isShow`。
- // 然后『ZidingyiComponent』中「@StorageLink('isShow') _isShow: boolean」的`_isShow`值同时响应变化。
- @Link _isShow_: boolean;
- build() {
- RelativeContainer() {
- if (this._isShow_) Text('自定义子组件文本2');
- else Button('自定义子组件按钮2').onClick(()=>this._isShow_ = true/**命中修改*/);
- }
- .height('45vp')
- .width('100%')
- }
- }
复制代码 模块引用
如果我要对我的工程开发举行模块化架构解耦处理,如下截图
其中common目次下,创建的module模块,利用的静态共享包;features目次下,创建的module模块,利用的是动态共享包;
此时,在Index.ets主模块入口文件中利用common模块中的资源。那么必要做以下配置操纵利用。
- 引用配置,在主模块的oh-package.json5中配置
- {
- "name": "visionofscript",
- "version": "1.0.0",
- "description": "Please describe the basic information.",
- "main": "",
- "author": "",
- "license": "",
- "dependencies": {
- "@ohos/DrawerLayoutLib": "file:../common/DrawerLayoutView",
- "@ohos/CommonAndViewLib": "file:../common/CommonAndViewLib"
- }
- }
复制代码
- 引用利用,在主模块(其他模块)中的文件中import后,即可正常利用
- import { DrawerLayout } from '@ohos/DrawerLayoutLib';
复制代码 路由跳转
跳转利用体系os自带api,必要引入import router from '@ohos.router';
模块内跳转
entry主模块内部路由的跳转,由接待页跳转到应用首页
分解上面路由跳转参数,比方~ 注意:目的页面文件名后,不加后缀名 .ets
router.replaceUrl({url: 「页面所在」})
- 页面所在 pages/home/HomeIndex
router.replaceUrl({url: 'pages/home/HomeIndex'})
更值得注意的是:跳转的目的页面,雷同安卓的Activity(每创建出一个Activity,都必要在AndroidManifest.xml中配置),必要在 main_pages.json中举行注册。否则,找不到跳转目的页面。
跨模块跳转
如上面模块引用下截图展示的架构介绍~
跨module的路由跳转的方式: entry主模块页面,点击跳转按钮,跳转至hsp模块指定页面。
router.pushUrl({ url: '@bundle:「包名」/「模块名」/「页面所在」'})
分解上面路由跳转参数,比方~ 注意:目的页面文件名后,不加后缀名 .ets
- 包名 cn.com.cec.tiomovie
- 模块名 mine
- 页面所在 ets/pages/Index
router.pushUrl({url: '@bundle:cn.com.cec.tiomovie/mine/ets/pages/Index'})
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |