在 ArkTS 开发中,组件可以通过定义 builderParams(构建函数参数)来允许父组件自定义视图区域,类似于 Vue 的 slot 插槽或 React 的 Render Props。
具体来说,构建函数内的 this 指向调用它的子组件,官方文档对此有明确说明。以下例子进一步阐释了这一点:
@Component
struct TestBuilder_bind_this_and_update_parent_state_Child {
@State count: number = 0;
@Builder builder() {
Text(`builder: ${this.count}`)
.fontSize(50)
}
@BuilderParam builderParams: () => void = this.builder
build() {
Column() {
Text(`child: ${this.count}`).fontSize(40)
Button('child: add').onClick(() => {
this.count++;
})
Column() {
this.builderParams()
}
.border({
width: 1
})
}
.border({
width: 1
})
}
}
@Entry
@Component
struct TestBuilder_bind_this_and_update_parent_state {
@State count: number = 0;
@Builder builder() {
Text(`parent builder: ${this.count}`).fontSize(50)
Button('parent builder: add').onClick(() => {
this.count++;
})
}
build() {
Column() {
Column() {
Column() {
Text(`parent: ${this.count}`).fontSize(40)
Button('parent: add').onClick(() => {
this.count++;
})
}
TestBuilder_bind_this_and_update_parent_state_Child({
builderParams: this.builder,
}).border({
width: 1
})
}.border({
width: 1
})
}
.size({
width: '100%',
height: '100%'
})
.justifyContent(FlexAlign.Center)
}
}
在这个示例中,点击父组件的 add 按钮时,父组件的状态会变化,但不会影响子组件。反之,点击子组件或构建函数中的 add 会相互影响,这与我们的预期一致。
但如果构建函数定义在父组件中,通常情况下,函数内部逻辑会调用组件自身的状态。这种情况下,可以通过 bind 方法将父组件的 this 绑定到构建函数上,如下所示:
builderParams: this.builder.bind(this),
这样,点击父组件的 add 后,插槽和父组件的状态都会发生变化并且得到渲染。
如果连续点击子组件的 add 5次,只有子组件状态会发生变化。
接下来,考虑子组件传递状态到构建函数的场景。这里的问题是,使用 bind 后,子组件的状态变化不会触发构建函数的更新。如下是父组件和子组件的代码修改:
@Builder builder($$: {count: number}) {
Text(`parent builder count: ${$$.count}`).fontSize(50)
Text(`parent builder this.count: ${this.count}`).fontSize(50)
Button('parent builder: add').onClick(() => {
this.count++;
})
}
@Component
struct TestBuilder_bind_this_and_update_parent_state_Child {
@State count: number = 0;
@Builder builder($$: { count: number }) {
Text(`builder: ${$$.count}`)
.fontSize(50)
}
@BuilderParam builderParams: ($$: { count: number }) => void = this.builder
build() {
Column() {
Text(`child: ${this.count}`).fontSize(40)
Button('child: add').onClick(() => {
this.count++;
})
Column() {
this.builderParams({ count: this.count })
}
.border({
width: 1
})
}
.border({
width: 1
})
}
}
当修改父组件状态时,因为使用了 bind,父组件和构建函数中的 this 都会变化。
但修改子组件状态时,注入的状态不会更新。
若想使子组件能够控制插槽注入的更新,可以去除 bind。但这样做会导致父组件无法控制构建函数中使用的状态。解决方案是使用 @Link,它无需 bind。子组件通过 link 变量(对象类型)为插槽提供数据接口,父组件可以定义并在构建函数中通过 this 获取这些数据,同时子组件注入的状态变化也能同步更新。
@Component
struct TestBuilder_bind_this_and_update_parent_state_Child {
@State count: number = 0;
@Link builderLinkData: object
@Builder builder($$: { count: number }) {
Text(`builder: ${$$.count}`)
.fontSize(50)
}
@BuilderParam builderParams: ($$: { count: number }) => void = this.builder
build() {
Column() {
Text(`child: ${this.count}`).fontSize(40)
Button('child: add').onClick(() => {
this.count++;
})
Column() {
this.builderParams({ count: this.count })
}
.border({
width: 1
})
}
.border({
width: 1
})
}
}
@Entry
@Component
struct TestBuilder_bind_this_and_update_parent_state {
@State count: number = 0;
@State builderLinkData: { count: number } = { count: 0 }
@Builder builder($$: { count: number }) {
Text(`parent builder count: ${$$.count}`).fontSize(19)
Text(`parent builder this.count: ${this.count}`).fontSize(19)
Text(`parent builder builderLinkData.count: ${this.builderLinkData.count}`).fontSize(19)
Button('parent builder: add').onClick(() => {
this.count++;
})
Button('parent builder link: add').onClick(() => {
this.builderLinkData.count++;
})
}
build() {
Column() {
Column() {
Column() {
Text(`parent: ${this.count}`).fontSize(40)
Button('parent: add').onClick(() => {
this.count++;
})
}
TestBuilder_bind_this_and_update_parent_state_Child({
builderParams: this.builder,
builderLinkData: $builderLinkData
}).border({
width: 1
})
}.border({
width: 1
})
}
.size({
width: '100%',
height: '100%'
})
.justifyContent(FlexAlign.Center)
}
}
更新父组件时,只有父组件状态变化。
更新子组件状态时,注入的参数和 this 指向的 count 都会变化。
父组件可以正常修改 link data,并观察到其更新和变化。
但使用 link 作为组件参数有一个缺点:必须传递。如果插槽数量多,组件的易用性会降低,且子组件内部使用 object 类型的 link 可能会引发错误。
总结:
-
父组件想在传给子组件的插槽中使用和修改状态,最简单的办法是在传入构建函数前使用 bind this。
-
要在构建函数中使用子组件的状态,并且让子组件控制状态更新,不能使用 bind。若要同时考虑第一点,则需要在子组件暴露 link 参数。这样父组件的构建函数可以使用定义在自身的准备传递给子组件的 link 状态,且不影响子组件的状态更新。由于子组件需要暴露具体类型的 link,这会降低组件的通用性,并且 link 参数必须传入,这也会降低组件易用性,尤其是当插槽数量增多时。
最后
有很多小伙伴不知道学习哪些鸿蒙开发技术?不知道需要重点掌握哪些鸿蒙应用开发知识点?而且学习时频繁踩坑,最终浪费大量时间。所以有一份实用的鸿蒙(HarmonyOS NEXT)资料用来跟着学习是非常有必要的。
点击领取→纯血版全套鸿蒙HarmonyOS学习资料希望这一份鸿蒙学习资料能够给大家带来帮助,有需要的小伙伴自行领取~
鸿蒙(HarmonyOS NEXT)最新学习路线
有了路线图,怎么能没有学习资料呢,小编也准备了一份联合鸿蒙官方发布笔记整理收纳的一套系统性的鸿蒙(OpenHarmony )学习手册(共计1236页)与鸿蒙(OpenHarmony )开发入门教学视频,内容包含:ArkTS、ArkUI、Web开发、应用模型、资源分类…等知识点。
获取以上完整版高清学习路线,请点击→纯血版全套鸿蒙HarmonyOS学习资料
HarmonyOS Next 最新全套视频教程
《鸿蒙 (OpenHarmony)开发基础到实战手册》
OpenHarmony北向、南向开发环境搭建
大厂面试必问面试题
鸿蒙南向开发技术
鸿蒙APP开发必备
总结
总的来说,华为鸿蒙不再兼容安卓,对中年程序员来说是一个挑战,也是一个机会。只有积极应对变化,不断学习和提升自己,他们才能在这个变革的时代中立于不败之地。