引用组件

一个由 JSON 配置、WXML 模板、WXSS 样式、JavaScript 脚本组成的自定义组件编写完成后,它就可以被其他组件引用了。

基本引用方式

在引用组件时,需要在 JSON 配置的 usingComponents 中指定引用的标签名和它对应的组件路径。下面用一个具体的例子来说明组件的引用方法。

首先,我们先编写一个自定义组件,位于 components/foo。首先编写 components/foo.json

{
  "component": true,
  "usingComponents": {}
}

然后编写 components/foo.wxml

<view class="foo">
  foo
</view>

编写 components/foo.wxss

.foo {
  color: blue;
}

最后编写 components/foo.js(以 Chaining 形式为例):

export default Component()
  .register()

这样,它就可以在其他自定义组件(包括页面)内使用。如果想在 pages/index/index 中引用它,那就在 pages/index/index.json 中这样写:

{
  "usingComponents": {
    "foo": "/components/foo"
  }
}

其中,"foo" 表示引用节点名,只能包含字母、数字、连字符和下划线,不能以数字开头。"/components/foo" 表示引用组件的路径,可以是相对路径或绝对路径。

然后,可以像使用内置组件那样,在 WXML 里面使用 <foo> 标签:

<foo />

slot 节点

在自定义组件中,可以使用一个特殊的标签 <slot />,用于接收外部传入的节点。

例如,在 components/foo.wxml 中:

<view class="foo">
  <slot />
</view>

pages/index/index.wxml 中,可以为 <foo> 添加子节点:

<foo>
  <view> bar </view>
</foo>

这样,在 <view class="foo"> 中的 <slot /> 就会被替换成 <view> bar </view>。通过这个特性就可以将两个组件 WXML 结构相互穿插到一起。

slot 节点还有几种不同的模式,请参考 slot 模式 。

组件脚本

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

组件脚本可以用 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()

组件框架

组件化

微信小程序的界面代码组织以 组件 作为基本单位。

一个页面可以由很多组件组合而成。这些组件分为两类:

  • 基础组件 是不可再分解的基础功能单元,如 <view> <image>
  • 可以用若干组件拼成一个大的组件,这样的组件称为 自定义组件 。

换句话说,页面由基础组件和自定义组件组合而成,而其中的自定义组件又由基础组件和其他自定义组件组合而成。特别地:

  • 页面实际上就是一个很大的自定义组件,也称为 页面根组件,所以页面具备所有自定义组件具有的特性;
  • 自定义组件可以递归地引用自身(但无限递归会导致栈溢出,请控制好递归条件)。

自定义组件相关特性自微信小程序基础库版本 1.6.3 开始支持。

组件代码构成

每个自定义组件的代码由四部分构成:

  • JSON 配置;
  • WXML 模板
  • WXSS 样式
  • JavaScript(或 TypeScript)脚本

这四个文件必须放在同一文件路径下、仅有文件扩展名不同。

其中,JSON 配置中需要至少包含 usingComponentscomponent 其中一项。

对于页面根组件,通常可以这样写:

{
  "usingComponents": {}
}

对于非页面根组件的自定义组件,通常可以这样写:

{
  "component": true,
  "usingComponents": {}
}

usingComponents 表示这个组件依赖的其他组件,可参考 组件引用 文档。从另一个角度说,一个组件的 JSON 配置、WXML 模板、WXSS 样式、JavaScript 脚本写完后,它就可以被其他组件引用了。

component 字段并不是必需的。它只会影响一些组件框架细节表现;即使未写明,只要有 usingComponents 字段,它依然可以作为组件使用。不过,仍然建议为所有非页面根的组件添加 component 字段,作为标识。

在开发者工具中预览效果

组件框架选择

目前,有两个组件框架可选。

exparser 是传统的组件框架,对旧代码具有最佳的兼容性。但由于历史原因,它缺少部分新特性、性能也不是最优。

glass-easel 是新一代的组件框架,由 更多特性更优性能 。如果想要深入了解它本身,可以参考 glass-easel 开源项目 。

在使用传统的 WebView 渲染引擎时,每个页面都可以选择使用其中一个组件框架来渲染。默认情况下,使用 exparser 作为组件框架,如果想改用 glass-easel,需要在页面 JSON 配置中声明:

{
  "usingComponents": {},
  "componentFramework": "glass-easel",
  "glassEaselWebview": true
}

关于上述配置的详细说明,请参考 迁移到 glass-easel 文档。

如果使用 Skyline 渲染引擎 ,就只能选用 glass-easel 作为组件框架。

其中的 componentFramework 字段可以放在 app.json 中使它全局生效。

页面中的自定义组件跟随其所在页面选用的组件框架。

尽管这两个组件框架有极高的相似性,它们之间仍有一些细微差异。可参考 迁移到 glass-easel 来进行组件框架升级。

页面间关系

微信小程序的界面可以由多个页面组成。

页面之间可以相互跳转,如打开一个新页面,关闭一个已有页面等。这些页面切换的时机称为 页面路由 。

页面路径参数

最基本地,一个页面可以用 router.navigateTo 打开一个新页面。打开新页面时,可以携带路径参数。参数采用 URL 编码形式。

// page/index/index.js
Page({
  viewTap: function() {
    var id = 'PRODUCT#1'
    this.router.navigateTo({
      // 传入路径参数时,使用 encodeURIComponent 是个好习惯
      url: '../product/product?id=' + encodeURIComponent(id),
    })
  },
})

在新打开的页面中,通过 this.options 可以获取到页面的路径参数。

// page/product/product.js
Page({
  onLoad: function() {
    // this.options 中的路径参数需要 decodeURIComponent
    var id = decodeURIComponent(this.options.id) // id === 'PRODUCT#1'
  }
})

目前更推荐使用 Component 构造器 来构造页面。这样使用时,可以将路径参数定义在 properties 中。

// page/product/product.js
Component({
  properties: {
    id: String,
  },
  lifetimes: {
    attached() {
      // this.data.id === 'PRODUCT#1'
    },
  },
})

路径参数的本质是页面的启动状态。在实践中,如果页面内容是由某些数据决定的,那这些数据应当是一个路径参数。例如:

  • 对于一个商品的详情页,页面所展示的商品由商品 ID 来决定,那么商品 ID 就应当是一个页面路径参数;
  • 对于一个学生的个人信息页,页面所展示的是哪个学生由学号来决定,那么学号就应当是一个页面路径参数。

使用路径参数可以在新页面启动时,向新页面传递数据。但如果两个页面之间需要持续进行双向的数据通信,或者有些数据不适合写在路径参数中,那就需要用到 EventChannel 页面间通信。

页面间通信

基础库 2.7.3 开始支持,低版本需做兼容处理。

如果一个页面由另一个页面通过 navigateTo 打开,这两个页面间将建立一条数据通道:

  • 被打开的页面可以通过 this.getOpenerEventChannel() 方法来获得一个 EventChannel 对象;
  • navigateTosuccess 回调中也包含一个 EventChannel 对象。

这两个 EventChannel 对象间可以使用 emiton 方法相互发送、监听事件。

在开发者工具中预览效果

界面渲染架构

微信小程序的界面由多个页面组成。在渲染每个页面时,微信小程序基础库内部的两个模块起着关键作用。

  • 组件框架 提供了 PagesetData 等重要的 JavaScript 接口,并结合 WXML 来构建界面结构。
  • 渲染引擎 将界面结构最终绘制成用户界面。

组件框架

组件框架为微信小程序提供了页面相关的 JavaScript 接口。与它相关的主要特性包括(但不限于):

  • 页面注册
  • 自定义组件
  • 事件系统
  • WXML

微信小程序基础库提供了两个可选的组件框架。选择不同的组件框架时,Page 等接口的具体实现、WXML 的运行方式都会有所不同。(每个页面可以独立选择使用哪个组件框架。)

  • exparser 是传统的组件框架(默认启用)。
  • glass-easel 是新一代组件框架,是一个开源项目,提供了更多特性、具有更好的性能表现。

详情请参考组件框架章节。

渲染引擎

渲染引擎为微信小程序提供了最终的界面绘制支持。与它相关的主要特性包括(但不限于):

  • 基础组件
  • WXSS 中的具体样式规则

微信小程序基础库提供了两个可选的渲染引擎。选择不同的渲染引擎时,可用的基础组件和 WXSS 样式规则会有一定区别。(每个页面可以独立选择使用哪个渲染引擎。)

  • Webview 是传统的渲染引擎(默认启用)。
  • Skyline 是更高效的渲染引擎,提供了很多增强特性、具有更好的性能表现。

渲染引擎对组件框架有一定的依赖。目前,如果选用了 Skyline 渲染引擎,就必须选用 glass-easel 组件框架。详情请参考 Skyline 渲染引擎章节。

程序

有一个特殊的 JS 文件 app.js

App()

整个微信小程序只有一个 App 实例,是全部页面共享的,更多的事件回调参考文档 注册程序 App 。

通过这个章节,你了解了微信小程序涉及到的文件类型以及对应的角色,在 下个章节 中,将介绍微信小程序协同工作与发布的相关内容。

wx.showTabBar(Object object)

基础库 1.9.0 开始支持,低版本需做兼容处理。

以 Promise 风格 调用:支持

需要页面权限:当前是插件页面时,宿主微信小程序不能调用该接口,反之亦然

微信小程序插件:不支持

微信 Windows 版:支持

微信 Mac 版:支持

微信 鸿蒙 OS 版:支持

功能描述

显示 tabBar

参数

Object object

属性 类型 默认值 必填 说明
animation boolean false 是否需要动画效果
success function 接口调用成功的回调函数
fail function 接口调用失败的回调函数
complete function 接口调用结束的回调函数(调用成功、失败都会执行)

wx.showTabBarRedDot(Object object)

基础库 1.9.0 开始支持,低版本需做兼容处理。

以 Promise 风格 调用:支持

需要页面权限:当前是插件页面时,宿主微信小程序不能调用该接口,反之亦然

微信小程序插件:不支持

微信 Windows 版:支持

微信 Mac 版:支持

微信 鸿蒙 OS 版:支持

功能描述

显示 tabBar 某一项的右上角的红点

参数

Object object

属性 类型 默认值 必填 说明
index number tabBar 的哪一项,从左边算起
success function 接口调用成功的回调函数
fail function 接口调用失败的回调函数
complete function 接口调用结束的回调函数(调用成功、失败都会执行)

wx.setBackgroundColor(Object object)

基础库 2.1.0 开始支持,低版本需做兼容处理。

以 Promise 风格 调用:支持

需要页面权限:当前是插件页面时,宿主微信小程序不能调用该接口,反之亦然

微信小程序插件:支持,需要微信小程序基础库版本不低于 2.4.0

在微信小程序插件中使用时,只能在当前插件的页面中调用

微信 鸿蒙 OS 版:支持

功能描述

动态设置窗口的背景色

参数

Object object

属性 类型 默认值 必填 说明
backgroundColor string 窗口的背景色,必须为十六进制颜色值
backgroundColorTop string 顶部窗口的背景色,必须为十六进制颜色值,仅 iOS 支持
backgroundColorBottom string 底部窗口的背景色,必须为十六进制颜色值,仅 iOS 支持
success function 接口调用成功的回调函数
fail function 接口调用失败的回调函数
complete function 接口调用结束的回调函数(调用成功、失败都会执行)

示例代码

wx.setBackgroundColor({
  backgroundColor: '#ffffff', // 窗口的背景色为白色
})

wx.setBackgroundColor({
  backgroundColorTop: '#ffffff', // 顶部窗口的背景色为白色
  backgroundColorBottom: '#ffffff', // 底部窗口的背景色为白色
})

wx.setBackgroundTextStyle(Object object)

基础库 2.1.0 开始支持,低版本需做兼容处理。

以 Promise 风格 调用:支持

需要页面权限:当前是插件页面时,宿主微信小程序不能调用该接口,反之亦然

微信小程序插件:支持,需要微信小程序基础库版本不低于 2.4.0

微信小程序插件中使用时,只能在当前插件的页面中调用

微信 鸿蒙 OS 版:支持

功能描述

动态设置下拉背景字体、loading 图的样式

参数

Object object

属性 类型 默认值 必填 说明
textStyle string 下拉背景字体、loading 图的样式。
合法值 说明
dark dark 样式
light light 样式
success function 接口调用成功的回调函数
fail function 接口调用失败的回调函数
complete function 接口调用结束的回调函数(调用成功、失败都会执行)

示例代码

wx.setBackgroundTextStyle({
  textStyle: 'dark' // 下拉背景字体、loading 图的样式为dark
})