# 组件脚本

组件脚本是页面的逻辑核心。它可以通过改变页面数据来最终决定界面上展示的内容。

组件脚本可以用 JavaScript 编写。不过,对于复杂的组件,更推荐使用 TypeScript 编写:api-typings 库中包含重要的类型定义,可以用于规范代码编写。

无论创建多少个实例,组件脚本文件本身只会被执行一次。

在组件脚本中,必须使用 Page 构造器或 Component 构造器来定义组件(而且只能定义一次)。通常有以下几种定义方式,可任选其一:

  • Page 构造器适合用来构造非常简单的页面;
  • Component 构造器的 Definition 形式适合用来构造简单的自定义组件(包括页面);
  • Component 构造器的 Chaining 形式适合用来构造逻辑复杂的自定义组件(包括页面)、适合使用 TypeScript 编写。

在使用构造器时,推荐将它 export default 出来(尽管这不是必需的)。

# Page 构造器

对于简单的页面,可以使用 Page 构造器进行构造。它的结构更简洁,但不能使用某些复杂特性。

export default Page({
  data: {
    foo: 'bar',
  },
  onLoad() {
    // 页面创建时执行
  },
  onButtonTap() {
    // 用户事件触发时执行
  },
})

Page 构造器只能使用数据、简单生命周期和事件响应函数。如果需要使用其他组件框架特性,就必须使用 Component 构造器。

# Component 构造器的 Definition 形式

Component 构造器有两种使用形式:Definition 形式和 Chaining 形式。它们是可以混用的,实现时可以根据习惯任选其一。

export default Component({
  data: {
    foo: 'bar',
  },
  lifetimes: {
    attached() {
      // 页面创建时执行
    },
  },
  methods: {
    onButtonTap() {
      // 用户事件触发时执行
    },
  },
})

如果想要将一个已经用 Page 构造的页面改用 Component 构造,只需要将简单生命周期和事件响应函数移入 methods 中。

# Component 构造器的 Chaining 形式

Chaining 形式仅 glass-easel 原生支持。对于 exparser,需要使用 chaining-api-polyfill

对于新代码,更推荐使用 Chaining 形式编写。因为 Chaining 形式更适合组织复杂的组件逻辑,对 TypeScript 的支持也更好。

export default Component()
  .data(() => ({
    foo: 'bar',
  }))
  .init(function ({ data, setData, lifetime, method }) {
    // 组件实例初始化时执行一次

    // 上方定义的 `data` 可以直接作为局部变量使用(不需要写成 `this.data`)
    // `setData` 亦然(不需要写成 `this.setData`)

    lifetime('attached', () => {
      // 页面创建时执行
      data.foo === 'bar' // true
    })

    const buttonTap = method(() => {
      // 用户事件触发时执行
      setData({ foo: 'new bar' })
    })

    return {
      buttonTap,
    }
  })
  .register()

# Chaining 形式的实例局部变量

Chaining 形式的 .init(...) 是一个实例初始化函数,它对于每个实例执行一次。

在这个函数里,可以自由定义局部变量。这些变量在实例之间是相互独立的。这也就是 Chaining 形式最大的优势。

// 在 JS 文件中的全局量是所有实例共享的
const sharedVar = 'shared'

export default Component()
  .init(function () {
    // 在 init 中的变量是每个实例专属的
    const instanceVar = 'instance'
  })
  .register()

# Chaining 形式的逻辑拆分与形式混用

Chaining 形式中,链式方法可以多次调用。可以借此将复杂的组件逻辑自然拆分为几个部分。

export default Component()
  // 组件逻辑单元 A
  .data(() => ({
    appleCount: 0,
    applePrice: 1.2,
  }))
  .init(function ({ data, setData, method }) {
    const buyApple = method(() => {
      setData({ appleCount: data.appleCount + 1 })
    })
    return { buyApple }
  })

  // 组件逻辑单元 B
  .data(() => ({
    bananaCount: 0,
    bananaPrice: 3.4,
  }))
  .init(function ({ data, setData, method }) {
    const buyBanana = method(() => {
      setData({ bananaCount: data.bananaCount + 1 })
    })
    return { buyBanana }
  })
  .register()

也可以将 Definition 形式混入到 Chaining 形式中。

export default Component()
  // 组件逻辑单元 A(使用 Definition 形式)
  .definition({
    data: {
      appleCount: 0,
      applePrice: 1.2,
    },
    methods: {
      buyApple() {
        this.setData({
          data: this.data.appleCount + 1,
        })
      },
    },
  })

  // 组件逻辑单元 B
  .data(() => ({
    bananaCount: 0,
    bananaPrice: 3.4,
  }))
  .init(function ({ data, setData, method }) {
    const buyBanana = method(() => {
      setData({ bananaCount: data.bananaCount + 1 })
    })
    return { buyBanana }
  })
  .register()