学习鸿蒙,我学到的东西与浅显理解(1-3、ArkTs)

74 阅读8分钟

ArkTS

相同点

与 TypeScript 相比, 大部分语法是相同的,这里就不多赘述了


不同点

包括语法方面,和功能方面的区别

1. 语法区别

在 TypeScript 的语法基础上

  • 强化了静态类型检查
    • 静态分析阶段可以获取更稳定的信息
    • 提升代码的健壮性和性能
  • 去掉了一些过于灵活的特性,例如:
    1. delete 操作符
    1. any
    1. unknown
    1. @ts-ignore
1.1 强化了静态类型检查
  • 例如:变量必须写明确的类型
  • 目的:静态分析阶段可以获取更稳定的信息 image.png
1.2 去掉了一些过于灵活的特性
  • 例如:any 不能使用了
  • 目的:减少运行时的判断开销 image.png

2. 功能区别

在 TypeScript 的功能基础上

  • 增加了
    1. 声明式 UI
    1. 状态管理支持
    1. 并发能力
2.1 声明式 UI
  • UI 描述语法,支持用 Ts 的方式,描述 UI (HTML + CSS)
  • 提供了:
    1. UI 描述机制
      1. Text
      1. ForEach
      1. ...
    1. 各种装饰器
      1. Entry 预览
      1. Preview 预览
      1. Component 自定义组件
      1. Builder
      1. ...

具体的语法细节,内容较多,所以抽出去了,请查看 UI 描述语法

2.2 状态管理
  • UI 相关联的数据,支持:
    1. 在组件内、组件间、页面间、应用内、跨设备传递
    1. 状态改变,视图响应更新
  • ps: 我看的课程里还没有细讲,后期补上
2.3 并发能力
  • ps: 我看的课程里还没有讲到,后期补上

UI 描述语法

  • 用 ts 的方式去描述 UI

内置组件

以下仅为示意,更多组件及参数 请点击

Text
Text('这是一个文本组件')
Button
Button('点击我')
Image
Image('resources/images/picture.png')
TextInput
TextInput({ placeholder: '请输入' })
Slider
Slider({ min: 0, max: 100, value: 50 })
Progress
Progress({ value: 0.5 })
Toggle
Toggle({ checked: true })
Divider
Divider()
Blank
// 空白填充组件
// 在容器主轴方向上,自动填充容器空余部分
// 仅当父组件为Row/Column/Flex时生效
Blank()
  • 以上都是非容器组件,不支持往内部嵌套组件
  • 以下 Column、Row、Stack、Grid、List 都是容器组件
  • 支持在尾随闭包{}内嵌套组件
Row
Row() {
  Text('学习')
  Text('成长')
}
Column
Column() {
  Text('学习')
  Text('成长')
}
Grid
Grid() {
  GridItem() { Text('单元格1') }
  GridItem() { Text('单元格2') }
  GridItem() { Text('单元格3') }
  GridItem() { Text('单元格4') }
}
.rowsTemplate('1fr 1fr')
.columnsTemplate('1fr 1fr')
List
List() {
  ListItem() {
    Text('第一项')
  }
  ListItem() {
    Text('第二项')
  }
}
Scroll
Scroll() {
  Column() {
    Text('项目1')
    Text('项目2')
    Text('项目3')
    Text('项目4')
  }
}
Stack
// 堆叠容器,子组件按照顺序依次入栈,后一个子组件覆盖前一个子组件
Stack({ alignContent: Alignment.Bottom }) {
  Text('First child, show in bottom')
  Text('Second child, show in top')
}

组件的方法

通用的
// 常用的样式方法
Text(text)
.fontSize(17)        // 设置字体大小
.fontWeight('bold')  // 设置字体粗细
.color('#ff0000')    // 设置字体颜色
.backgroundColor('#ffff00') // 设置背景颜色
.padding(10)         // 设置内边距
.margin(5)           // 设置外边距
.textAlign('center') // 设置文本对齐方式
.lineHeight(20)      // 设置行高
.fontStyle('italic') // 设置字体样式为斜体
.textDecoration('underline') // 设置文本装饰为下划线
.borderRadius(5)     // 设置边框圆角
.borderWidth(2)      // 设置边框宽度
.borderColor('#0000ff') // 设置边框颜色
.width('100%')       // 设置宽度
.height(50)          // 设置高度
.maxWidth(200)       // 设置最大宽度
.maxHeight(100)      // 设置最大高度
.minWidth(100)       // 设置最小宽度
.minHeight(50)       // 设置最小高度
.shadowColor('#888888') // 设置阴影颜色
.shadowOffset({ x: 0, y: 2 }) // 设置阴影偏移
.shadowOpacity(0.8)  // 设置阴影透明度
.shadowRadius(5);    // 设置阴影半径

// 常用的事件方法
// 注意:传入非箭头函数时,需要注意 this 指向问题
Button(text)
.onClick(() => void)

TextInput()
.onChange(() => void)
.stateStyle
// 给组件增加在不同状态时的样式
// 可以使用组件内的普通变量和状态变量

Button('点我')
.stateStyles({
   // 正常态
  normal: {
    .backgroundColor('ff2787d9')
  },
   // 按压态
  pressed: {
    .backgroundColor('ff707070')
  },
   // 获焦态
  focused: {
    .backgroundColor('#ffffeef0')
  },
   // 不可用态
  disabled: {
    .backgroundColor('ff2787d9')
  },
   // 选中态
  selected: {
    .backgroundColor('ff2787d9')
  }
})

组件的装饰器

用于标记组件,给组件增加附属功能

@Entry
// 标记此页面,可以作为应用的入口页面
@Entry
struct Index {
  builder() {
    Text('你好')
  }
}
@Component

仅能装饰 struct 关键字声明的数据结构 被装饰后具备组件化的能力,需要实现 build 方法描述 UI

// 标记这是一个组件
@Component
struct Hello {
  builder() {
    Text('你好')
  }
}
@Page
// 标记这是一个页面
@Page
struct MyPage {
  builder() {
    Column() {
      Text('这是一个页面');
    }
  }
}

@Builder
  • 除了组件外,还有一种更轻量化的 UI 复用机制
  • 它所装饰的函数遵循 build() 函数的语法规则,可以将重复使用的UI元素,提取成一个方法
  • 传递时参数需要注意下,请参考
// 全局自定义构建函数
@Builder function overBuilder(params: { label: string }) {
  Row() {
    Text(`UseStateVarByValue: ${params.label} `)
  }
}

@Entry
@Component
struct Parent {
  @State label: string = 'Hello';

  // 私有自定义构建函数
  @Builder customBuilder() {
    Text(`UseStateVarByValue: ${ this.label } `)
  }

  build() {
    Column() {
      this.customBuilder()
      
      // 必须写成对象字面量,才能引用传递
      // 也就是这个状态变量的更新,才会引起它内部 UI 刷新
      overBuilder({ label: this.label }) 
    }
  }
}
@Style
  • 将多条样式封装成一个方法,给组件调用
  • 支持在全局定义,或组件内部定义
  • 组件内 @Styles 的优先级高于全局 @Styles
  • 仅支持通用属性,通用事件
  • 不支持传递参数
// 定义在全局的@Styles封装的样式
@Styles function globalFancy  () {
  .width(150)
  .height(100)
  .backgroundColor(Color.Pink)
}

@Entry
@Component
struct FancyUse {
  @State heightValue: number = 100
  // 定义在组件内的@Styles封装的样式
  @Styles fancy() {
    .width(200)
    .height(this.heightValue)
    .backgroundColor(Color.Yellow)
    .onClick(() => {
      this.heightValue = 200
    })
  }

  build() {
    Column({ space: 10 }) {
      // 使用全局的@Styles封装的样式
      Text('FancyA').globalFancy()
      // 使用组件内的@Styles封装的样式
      Text('FancyB').fancy()
    }
  }
}
@Extend
  • 用于扩展原生组件的样式
  • 仅支持在全局定义,不支持在组件内部定义
  • 支持封装指定组件的私有属性、私有事件和自身定义的全局方法
  • 支持传递参数,调用遵循TS方法传值调用
  • 参数可以为状态变量,当状态变量改变时,UI可以正常的被刷新渲染
@Extend(Text) function fancy (fontSize: number) {
  .fontColor(Color.Red)
  .fontSize(fontSize)
}

@Entry
@Component
struct FancyUse {
  build() {
    Row({ space: 10 }) {
      Text('Fancy').fancy(16)
      Text('Fancy').fancy(24)
    }
  }
}
@Require
  • 可用于校验,自定义组件内的各种变量,是否需要调用该自定义组件时传参
@Component
struct Child {
  @Require regular_value: string = 'Hello';
  @Require @State state_value: string = "Hello";
  @Require @Provide provide_value: string = "Hello";
  @Require @BuilderParam buildTest: () => void;
  @Require @Prop message: string;

  build() {
    Column() {
      Text(this.message).fontSize(30)
      this.buildTest();
    }
  }
}

@Entry
@Component
struct Parent {
  @State message: string = 'Hello World';

  @Builder buildTest() {
    Row() {
      Text('Hello, world').fontSize(30)
    }
  }

  build() {
    Row() {
     // 因为 Child 组件这些变量设置了@Require,所以这里必须传才行
      Child({
          regular_value: this.message, 
          state_value: this.message, 
          provide_value: this.message, 
          message: this.message,
          buildTest: this.buildTest}
       )
    }
  }
}

组件内变量的装饰器

用于给变量增加附属功能

@State
// 标记这是一个状态,状态改变时会触发重渲染
@Component
struct Counter {
  @State count: number = 0;

  increment() {
    this.count += 1;
  }

  builder() {
    Column() {
      Text(`计数:${this.count}`);
      Button('增加').onClick(() => this.increment());
    }
  }
}

@Prop
  • 不能在 @Entry 装饰的自定义组件中使用
  • 当数据源更改时,@Prop装饰的变量都会更新,并且会覆盖本地所有更改
  • 允许在本地修改,但修改后的变化不会同步回父组件
  • 它装饰变量时会进行深拷贝,在拷贝的过程中除了基本类型、Map、Set、Date、Array外,都会丢失类型
// 标记这是一个属性,从父组件传递的值
@Component
struct Greeting {
  @Props name: string;

  builder() {
    Text(`你好, ${this.name}`);
  }
}

@Link

子组件中被@Link装饰的变量与其父组件中对应的数据源建立双向数据绑定 不能在@Entry装饰的自定义组件中使用


自定义组件

build 函数
  • 所有声明在build()函数的语句,我们统称为UI描述
  • 自定义组件必须实现 build 方法
  • 以下是它支持的语法
// if else if
Row() {
  if (Math.random() < 0.5) {
    Text('小于')
  } else {
    Text('大于')
  }
}

// forEach
ForEach(items, (item) => {
  Text(item.text)
}, item => item.id)

// 点击事件
Row() {
  Text('学习')
}
.onClick(() => {
  console.log('哈哈哈')
})

// @Entry 装饰的,根节点唯一且必要,禁止 ForEach 作为根节点,必须为容器组件
@Entry
@Component
struct MyComponent {
  build() {
    Row() {
      ChildComponent() 
    }
  }
}

// Component 装饰的,根节点唯一且必要,禁止 ForEach 作为根节点,可为非容器组件
@Component
struct ChildComponent {
  build() {
    Image('test.jpg')
  }
}
  • 以下是它不支持的语法
build() {
  // 反例:不允许声明本地变量
  let a: number = 1;
}

build() {
  // 反例:不允许console.log
  console.log('print debug log');
}


build() {
  // 反例:不允许本地作用域
  {
    ...
  }
}


@Component
struct ParentComponent {
  doSomeCalculations() {
  }

  calcTextValue(): string {
    return 'Hello World';
  }

  @Builder doSomeRender() {
    Text(`Hello World`)
  }

  build() {
    Column() {
      // 反例:不能调用没有用@Builder装饰的方法
      this.doSomeCalculations();
      // 正例:可以调用
      this.doSomeRender();
      // 正例:参数可以为调用TS方法的返回值
      Text(this.calcTextValue())
    }
  }
}

build() {
  Column() {
    // 反例:不允许使用switch语法
    switch (expression) {
      case 1:
        Text('...')
        break;
      case 2:
        Image('...')
        break;
      default:
        Text('...')
        break;
    }
    // 正例:使用if
    if(expression == 1) {
      Text('...')
    } else if(expression == 2) {
      Image('...')
    } else {
      Text('...')
    }
  }
}

build() {
  Column() {
    // 反例:不允许使用表达式
    (this.aVar > 10) ? Text('...') : Image('...')
  }
}

@Component
struct CompA {
  @State col1: Color = Color.Yellow;
  @State col2: Color = Color.Green;
  @State count: number = 1;
  build() {
    Column() {
      // 应避免直接在Text组件内改变count的值
      Text(`${this.count++}`)
        .width(50)
        .height(50)
        .fontColor(this.col1)
        .onClick(() => {
          this.col2 = Color.Red;
        })
      Button("change col1").onClick(() =>{
        this.col1 = Color.Pink;
      })
    }
    .backgroundColor(this.col2)
  }
}

// 不能在自定义组件的build()或@Builder方法里直接改变状态变量
// 反例
@State arr : Array<...> = [ ... ];
ForEach(this.arr.sort().filter(...), 
  item => { 
  ...
})
// 正确的执行方式为:filter返回一个新数组,后面的sort方法才不会改变原数组this.arr
ForEach(this.arr.filter(...).sort(), 
  item => { 
  ...
})

使用方式
@Component
struct Child {
  private countDownFrom: number = 0;
  private color: Color = Color.Blue;

  build() {
  }
}

@Entry
@Component
struct Parent {
  private someColor: Color = Color.Pink;

  build() {
    Column() {
      // 创建 Child 实例
      // 并将成员变量 countDownFrom 初始化为 10,
      // color 初始化为 this.someColor
      Child({ countDownFrom: 10, color: this.someColor })
    }
  }
}
通用样式
// 相当于给 Child 套了一个不可见的容器组件
// 这些样式是设置在容器组件上的,而非设置给 Child 的 Button 组件
@Component
struct Child {
  build() {
    Button(`Hello World`)
  }
}

@Entry
@Component
struct Parent {
  build() {
    Row() {
      MyComponent2()
        .width(200)
        .height(300)
        .backgroundColor(Color.Red)
    }
  }
}
生命周期

内容较多,可参考 地址

// Index.ets
import { router } from '@kit.ArkUI';

@Entry
@Component
struct MyComponent {
  @State showChild: boolean = true;
  @State btnColor:string = "#FF007DFF"

  // 只有被@Entry装饰的组件才可以调用页面的生命周期
  onPageShow() {
    console.info('Index onPageShow');
  }
  // 只有被@Entry装饰的组件才可以调用页面的生命周期
  onPageHide() {
    console.info('Index onPageHide');
  }

  // 只有被@Entry装饰的组件才可以调用页面的生命周期
  onBackPress() {
    console.info('Index onBackPress');
    this.btnColor ="#FFEE0606"
    return true // 返回true表示页面自己处理返回逻辑,不进行页面路由;返回false表示使用默认的路由返回逻辑,不设置返回值按照false处理
  }

  // 组件生命周期
  aboutToAppear() {
    console.info('MyComponent aboutToAppear');
  }

  // 组件生命周期
  onDidBuild() {
    console.info('MyComponent onDidBuild');
  }

  // 组件生命周期
  aboutToDisappear() {
    console.info('MyComponent aboutToDisappear');
  }

  build() {
    Column() {
      // this.showChild为true,创建Child子组件,执行Child aboutToAppear
      if (this.showChild) {
        Child()
      }
      // this.showChild为false,删除Child子组件,执行Child aboutToDisappear
      Button('delete Child')
      .margin(20)
      .backgroundColor(this.btnColor)
      .onClick(() => {
        this.showChild = false;
      })
      // push到page页面,执行onPageHide
      Button('push to next page')
        .onClick(() => {
          router.pushUrl({ url: 'pages/page' });
        })
    }

  }
}

@Component
struct Child {
  @State title: string = 'Hello World';
  // 组件生命周期
  aboutToDisappear() {
    console.info('[lifeCycle] Child aboutToDisappear')
  }

  // 组件生命周期
  onDidBuild() {
    console.info('[lifeCycle] Child onDidBuild');
  }

  // 组件生命周期
  aboutToAppear() {
    console.info('[lifeCycle] Child aboutToAppear')
  }

  build() {
    Text(this.title).fontSize(50).margin(20).onClick(() => {
      this.title = 'Hello ArkUI';
    })
  }
}

工具函数

// $r形式引入应用资源,可应用于多语言场景
Text($r('app.string.title_value'))

$r('app.media.ic_default')

$rawfile('media.ic_default')

内置常量

Color.Red
FontWeight.Bold
以此类推,略...

系列文章


参考资料


写在最后

  • 不是教程,只是学习记录
  • 包含了一些自己的理解,一边学一边写的,难免有不对的地方
  • 写出来希望能与大家探讨,看到有错误的地方,望大家指正~