赞
踩
提示:有使用过 vue 或 react 的小伙伴更容易理解
知识点强调
: ArkTS所有内容都不支持深层数据更新 UI渲染
答: vp 是 virtual pixel 的缩写,根据设备像素密度转化为屏幕物理像素,px 直接表示设备的像素,因为我们设备的分辨率密度不同,最好是使用 vp
适配: 可以使用伸缩布局layoutWeight,flex布局,网格系统,栅格系统布局,
在开发过程中会出现大量代码在进行重复样式设置,@Styles 和 Extend 可以帮我们进行样式复用
1. @styles 方式
@Styles function textStyle () {
.width(100)
.height(50)
.backgroundColor(Color.Pink)
.borderRadius(25)
.onClick(() => {
promptAction.showToast({
message: "测试"
})
})
}
2. Extend 方式
// 全局 原生组件 参数
// ↓ ↓ ↓
@Extend(Text) function textInputAll (callback?: () => void) {
.width(100)
.height(50)
.backgroundColor(Color.Pink)
.borderRadius(25)
.textAlign(TextAlign.Center)
.fontColor(Color.White)
.onClick(() => {
callback && callback()
})
}
// 可以新建一个文件夹,里面放本地图片(ets下)
Image('/assets/a.png')
// resource/media (a 是文件名,扩展名省略)
Image($r('/app.media.a'))
// resource/rawfile
Image($rawfile('a.png'))
// resource/rawfile
Image("https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fsafe-img.xhscdn.com%2Fbw1%2F2bf1b169-d217-44c3-a5b3-dd00813bc20d%3FimageView2%2F2%2Fw%2F1080%2Fformat%2Fjpg&refer=http%3A%2F%2Fsafe-img.xhscdn.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1704614176&t=e15a2fd5193aeeb24fc95b5dbe395907")
"requestPermissions": [{
"name":"ohos.permission.INTERNET"
}],
如果你不想在直接抽象组件, ArkUI 还提供了一种更轻量的UI元素复用机制
@Builder
,可以将重复使用的 UI 元素抽象成一个方法,在 build 方法里调用。称之为自定义构建函数
// 定义 Builder
@Builder
function getCellContent(leftTitle: string, rightValue: string) {
Row() {
Row() {
Text(leftTitle)
Text(rightValue)
}
.width('100%')
.justifyContent(FlexAlign.SpaceBetween)
.padding({
left: 15,
right: 15
})
.borderRadius(8)
.height(40)
.backgroundColor(Color.White)
}.padding({
left: 10,
right: 10
})
}
class CardClass {
time: string = ""
location: string = ""
type: string = ""
}
@State formData: CardClass = {
time: "2023-12-12",
location: '回龙观',
type: '漏油'
}
// 在组件里使用
Column({ space: 10 }) {
getCellContent("异常时间", this.formData.time)
getCellContent("异常位置", this.formData.location)
getCellContent("异常类型", this.formData.type)
Button("修改数据").onClick(() => {
this.formData.location = "望京"
})
}
.width('100%')
全局自定义函数的问题
全局的自定义构建函数可以被整个应用获取(下一代可用-当前4.0暂不支持),不允许使用this和bind方法。
不可被其他文件引用
当我点击按钮时数据即使是响应式的,当数据发生改变,该函数不会自动渲染
解决方案:改为按引用传递
// 完整代码
@Entry
@Component
struct BuilderCase {
@State formData: CardClass = {
time: "2023-12-12",
location: '回龙观',
type: '漏油'
}
@Builder
getCellContent($$: CellParams) {
Row() {
Row() {
Text($$.leftTitle)
Text($$.rightValue)
}
.width('100%')
.justifyContent(FlexAlign.SpaceBetween)
.padding({
left: 15,
right: 15
})
.borderRadius(8)
.height(40)
.backgroundColor(Color.White)
}.padding({
left: 10,
right: 10
})
}
build() {
Row() {
Column() {
Column({ space: 10 }) {
this.getCellContent({ leftTitle: '异常时间', rightValue: this.formData.time })
this.getCellContent({ leftTitle: '异常位置', rightValue: this.formData.location })
this.getCellContent({ leftTitle: '异常类型', rightValue: this.formData.type })
}
.width('100%')
Button("修改数据").onClick(() => {
this.formData.location = "望京"
})
}
.width('100%')
}
.height('100%')
.backgroundColor('#ccc')
}
}
class CardClass {
time: string = ""
location: string = ""
type: string = ""
}
class CellParams {
leftTitle: string = ""
rightValue: string = ""
}
Vue里面有个叫做slot插槽的东西,就是可以传入自定义的结构,整体复用父组件的外观
ArkTS提供了一个叫做BuilderParam的修饰符,你可以在组件中定义这样一个函数属性,在使用组件时直接传入
// 使用BuilderParam 声明组件
@Component
struct HMCard {
@BuilderParam
content: () => void
build() {
Column () {
Text("卡片组件")
Divider()
Text("传入内容")
if(this.content) {
this.content() // 子组件要在想要显示插槽的地方来调用传入的方法
}
}
}
}
@Entry
@Component
struct BuilderParamCase {
// 声明渲染的函数组件
@Builder
getContent () {
Row() {
Text("插槽内容")
.fontColor(Color.Red)
}
}
build() {
Row() {
Column() {
HMCard({ content: this.getContent }) // 调用组件并传入要渲染的函数
}
.width('100%')
}
.height('100%')
}
}
父传子
// 父组件的子组件上,传递一个对象(HmCommentItem)
HmCommentItem({ item:item})
// 子组件上进行接收
item: Partial<ReplyItem> = {} // 默认是public
- item 默认是用 public 修饰的,第一次渲染数据是准确的,当父组件的值发生改变时,子组件是不会改变的
- 如果想让子组件跟着发生改变,看下面的组件共享
子传父
(目的是修改父组件的值)// 父组件的子组件上,传递一个方法(HmCommentItem)
HmCommentItem({ item:item,changeLike:(item)=>{
this.changeLike(item)
}})
// 子组件上进行接收
changeLike: (params: ReplyItem) => void = () => {} // 接受一个无返回值的方法,默认是空函数
状态共享 (父子单向)
// 子组件上进行接收
@Prop item: Partial<ReplyItem>
- Prop只能修饰string number boolean类型的数据- (Next全部都支持了各种类型),Prop 只会在当前子组件生效,不会传到父组件
- 在子组件里是不能直接修改父组件的值
状态共享 (父子双向)
- 在上面我们用了 Prop 可以在父组件里修改数据,子组件也会同步数据,
- 但是prop 的作用在当前的组件,
- @Link , 可以实现父子同步
// 子组件上进行接收
@Link item: Partial<ReplyItem>
ps :
Link修饰的数据必须得是最外层的 State数据,也就是说不能是数组对象的某一项
状态共享 (后代组件)
- 如果我们的组件层级特别多,ArkTS支持跨组件传递状态数据来实现双向同步@Provide和 @Consume
- 这特别像Vue中的依赖注入
假设我们有三层组件,Index-Child-Grand,
Index的数据不想经过Child而直接给到Grand可以使用该修饰器
@Entry
@Component
struct ProvideCase02 {
@Provide count: number = 0
build() {
Row() {
Column({ space: 15 }) {
Text(this.count.toString())
.fontSize(50)
Button("顶级组件+1")
.onClick(() => {
this.count++
})
Divider()
Child()
}
.width('100%')
}
.height('100%')
}
}
@Component
struct Child {
build() {
Column() {
Text("子组件")
.fontSize(40)
Divider()
Grand()
}
}
}
@Component
struct Grand {
@Consume count: number
build() {
Column() {
Text("孙组件")
.fontSize(30)
Text(this.count.toString())
}
}
}
注意:这是双向的修改数据
状态共享 (状态监听器)
- 如果开发者需要关注某个状态变量的值是否改变,可以使用 @Watch 为状态变量设置回调函数。
- Watch(“回调函数名”)中的回调必须在组件中声明,该函数接收一个参数,参数为修改的属性名
- Watch修饰符要写在 State Prop Link Provide的修饰符下面,否则会有问题
● 在第一次初始化的时候,@Watch装饰的方法不会被调用
@Provide('aa')
@Watch('updateCount')
count: number = 0
updateCount(keyName: string) {
promptAction.showToast({
message: this.count.toString()
})
console.log(keyName,this.count.toString())
}
状态共享 (@Observed 、@ObjectLink)
之前讲解Link的时候,我们遇到了一个问题,就是循环生成的item没办法用item传递给子组件的Link,也就是封装的组件没办法做双向更新同步,那么ArtTS支持 Observed和@ObjectLink来实现这个需求
使用步骤:
● 类 class 数据需要定义通过构造函数,使用 @Observed 修饰这个类
● 初始化数据:需要通过初始化构造函数的方式添加
● 通过 @ObjectLink 关联对象,可以直接修改被关联对象来更新UI
@Entry
@Component
struct ObjectLinkCase {
@State message: string = 'Hello World'
// 定义数据时,使用new创建对象,之前是字面量创建
// 原因:
@State
list: FoodObjectClass[] = [new FoodObjectClass({
order_id: 1,
food_name: '鱼香肉丝',
food_price: 18.8,
food_count: 1
}) ,new FoodObjectClass({
order_id: 2,
food_name: '粗溜丸子',
food_price: 26,
food_count: 2
}) , new FoodObjectClass({
order_id: 3,
food_name: '杂粮煎饼',
food_price: 12,
food_count: 1
}) ]
build() {
Row() {
Column({ space: 20 }) {
ForEach(this.list, (item: FoodObjectClass) => {
FoodItem({ item: item })
})
BottomCart({ myList: $list })
}
.width('100%')
}
.height('100%')
}
}
@Extend(Text)
function TextStyle () {
.layoutWeight(1).textAlign(TextAlign.Center).fontSize(20)
}
@Extend(Text)
function AddCutStyle () {
.width(40)
.height(40)
.borderRadius(20)
.backgroundColor(Color.Grey)
.textAlign(TextAlign.Center)
.fontSize(20)
}
@Component
struct FoodItem {
// 步骤二 : Observed必须和ObjectLink才有UI更新的效果
@ObjectLink
item: FoodObjectClass
build() {
Row() {
Text(this.item.food_name).TextStyle()
Text(this.item.food_price.toFixed(2)).TextStyle()
Row() {
Text("-").AddCutStyle()
.onClick(() => {
this.item.food_count--
})
.visibility(this.item.food_count > 0 ? Visibility.Visible : Visibility.Hidden)
Text(this.item.food_count.toString()).TextStyle()
.visibility(this.item.food_count > 0 ? Visibility.Visible : Visibility.Hidden)
Text("+").AddCutStyle()
.onClick(() => {
this.item.food_count++
})
}.layoutWeight(1)
}
.width('100%')
.height(40)
}
}
// 底部组件
@Component
struct BottomCart {
@Link
myList: FoodObjectClass[]
build() {
Button("更改菜品的数量")
.onClick(() => {
this.myList = this.myList.map(item => {
item.food_count++
return item
})
})
}
}
// 初始化数据 : 定义了一个接口
interface IFoodInfo {
order_id: number
food_name: string
food_price: number
food_count: number
}
// 步骤一 : 食品类
// implements : 使用接口
@Observed
class FoodObjectClass implements IFoodInfo {
order_id: number = 0
food_name: string = ""
food_price: number = 0
food_count: number = 0
constructor(obj: IFoodInfo) {
this.order_id = obj.order_id
this.food_name = obj.food_name
this.food_price = obj.food_price
this.food_count = obj.food_count
}
}
注意:
interface声明类型不需要给初始值,class声明类型必须给初始值(next 版本要求)
使用了Observed这个装饰器来修饰class,那么只要我们改动class的属性,它就会驱动UI的更新(只是第一层,多层怎么办,往下看)
只有Observed修饰的class才可以被 ObjectLink使用,并且Entry修饰的组件不允许使用ObjectLink
ObjectLink只能修饰被Observed修饰的class类型
Observed修饰的class的数据如果是复杂数据类型,需要采用赋值的方式才可以具备响应式特性-因为它监听的是该属性的set和get
总结:
( State组件内状态)
- Prop 子组件修饰符 -4.0 boolean/number/string- 单向数据流
- Link 子组件修饰符-双向数据流,所有类型都支持- 必须通过$前缀-(循环数据就没有办法传入)
- Provide和Consume 双向数据流-所有结构均支持
- Watch 可以监听State Link Prop ObjectLink的数据变化
- Observed和ObjectLink
我们都知道 ArkTS 所有内容都不支持深层数据更新 UI渲染 , next 版本取消了 解构赋值我们怎么办呢?
@Entry
@Component
struct MulitiStateCase {
@State
user: IUserProfileModel = new IUserProfileModel({
username: '老高',
age: 34,
sex: "男",
address: new IAddressModel({
province: '河北',
city: '衡水',
area: '深州'
})
})
build() {
Row() {
Column() {
// UI更新只能监听到一层
Row() {
Text(this.user.username).fontSize(40)
Text(this.user.age.toString()).fontSize(40)
Text(this.user.address.province).fontSize(40)
Text(this.user.address.city).fontSize(40)
Text(this.user.address.area).fontSize(40)
}
.width('100%')
.height(50)
Button("更新名字和年龄")
.onClick(() => {
// this.user.username = "老高坏坏的"
// this.user.age = 25
this.user.address.city = "廊坊"
this.user.address = new IAddressModel(this.user.address)
})
}
.width('100%')
}
.height('100%')
}
}
// flag1
interface IAddress {
province: string
city: string
area: string
}
interface IUserProfile {
username: string
age: number
sex: '男' | '女'
address: IAddress
}
export class IAddressModel implements IAddress {
province: string = ''
city: string = ''
area: string = ''
constructor(model: IAddress) {
this.province = model.province
this.city = model.city
this.area = model.area
}
}
export class IUserProfileModel implements IUserProfile {
username: string = ''
age: number = 0
sex: '男' | '女' = '男'
address: IAddress = new IAddressModel({} as IAddress)
constructor(model: IUserProfile) {
this.username = model.username
this.age = model.age
this.sex = model.sex
this.address = model.address
}
}
ArtTS提供了好几种状态用来帮助我们管理我们的全局数据
LocalStorage:
创建 LocalStorage 实例:const storage = new LocalStorage({ key: value })
import router from '@ohos.router'
// 步骤一: 定义数据
export class UserInfoClass {
name: string = ""
age: number = 0
}
let user: Record<string, UserInfoClass> = { "user": {
name: '老高',
age: 34
}};
// 步骤二:存数据
let storage: LocalStorage = new LocalStorage(user);
// ps: entry 这里需要先接受
@Entry(storage)
@Component
struct LocalStorageCase {
@State message: string = 'Hello World'
// 步骤三: 取数据 ( @LocalStorageProp 数据流是单向的,说明不能修改)
@LocalStorageProp("user")
myUser: UserInfoClass = {}
build() {
Row() {
Column({ space: 15 }) {
// 步骤四 : 当做变量就可以直接使用了
Text("姓名:" + this.myUser.name)
Text("年龄:" + this.myUser.age)
Button("跳转到另一个页面")
.onClick(() => {
router.pushUrl({
url: 'pages/LocalStorageCase2'
})
})
}
.width('100%')
}
.height('100%')
}
}
AppStorage :
用法:
import router from '@ohos.router'
import promptAction from '@ohos.promptAction'
@Entry
@Component
struct AppStorageCase02 {
@StorageProp("user_token")
token: string = ''
@StorageLink("user_token")
linkToken: string = ''
onPageShow() {
promptAction.showToast({
message: AppStorage.Get<string>("user_token") || '无token'
})
}
build() {
Row() {
Column() {
Text(this.token)
Button("登录")
.onClick(() => {
AppStorage.SetOrCreate<string>("user_token", "123456")
router.pushUrl({
url: 'pages/AppStorageCaseTrans'
})
})
Button("修改token")
.onClick(() => {
// this.linkToken = '678910'
const link = AppStorage.Link("user_token") as SubscribedAbstractProperty<string>
link.set("abcde")
})
}
.width('100%')
}
.height('100%')
}
}
PersistentStorage:
前面讲的所有状态均为内存状态,也就是应用退出便消失,所以如果我们想持久化的保留一些数据,应该使用 PersistentStorage
注意:
UI和业务逻辑不直接访问 PersistentStorage 中的属性,所有属性访问都是对 AppStorage 的访问,AppStorage 中的更改会自动同步到 PersistentStorage。也就是,我们和之前访问AppStorage是一样的,只不过需要提前使用PersistentStorage来声明
PersistentStorage.PersistProp("user_token", '') // 初始化磁盘
只要初始化了数据,我们以后使用AppStorage就可以读取和设置,它会自动同步到我们的磁盘上
目前不支持复杂对象的持久化,如果你需要存储,你需要把它序列化成功字符串
● 测试:需要在真机或模拟器调试
大家可以在上一个例子之前添加 PersistentStorage.PersistProp(‘属性名’, 值)
然后直接使用AppStorage进行Set就可以了,设置完成之后,使用模拟器先把任务销毁,然后再查看数据是否显示
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。