---
title: 功能页
summary: "介绍功能页的概念、开发配置、跳转规则及跨小程序页面共享复用机制。"
tags: [功能页, 页面共享复用, functionalPages配置, 跨小程序页面调用]
questions:
  - 什么是功能页，它有哪些特点？
  - 如何在宿主小程序中引入功能页？
  - 功能页的 functional.config.ts 如何配置？
  - 如何跳转到功能页页面？
  - 功能页的多语言优先级规则是什么？
  - 如何配置功能页的体验版本依赖？
  - 功能页的加载策略有哪些，如何选择？
  - 如何实现宿主小程序与功能页之间的跨页面事件通信？
  - 如何使用 presetFunctionalData 预设功能页初始化数据？
  - 功能页开发有哪些环境版本要求？
---

# 功能页

表示一类特定的功能界面，如：登录页、注册页、忘记密码页、支付页等。此类页面的特点的功能单一、流程完整，可独立访问，具有明确的业务入口与出口。

功能页与普通的页面相同，通过框架函数 Page() 注册。功能页经发布后可在其他小程序内打开。实现应用页面共享、复用的能力。

应用案例：
- A 小程序 - 存在注册页、登录页、忘记密码页等需求。
- B 小程序 - 存在注册页、登录页、忘记密码页等需求。

可将 A 小程序的注册页、登录页、忘记密码页等以功能页模式开发。自身引用且可提供给 B 小程序引入使用，实现共享页面的能力，以及后续其他小程序也有此业务需求，也可引入使用。

**环境要求**

- 基础库版本: `>= 2.12.0`
- 容器版本：`>= 3.5.0`
- App 公版： `>= 5.0.0`
- IDE 版本：`>= 0.7.1`

## 术语

### 宿主小程序

表示主体小程序应用，可导入其他功能页小程序进行使用。源码路径为 `miniprogramRoot`。

### 功能页小程序

表示可被导入到宿主小程序内的小程序。源码路径为 `functionalRoot`。

### 功能页示例

#### 设备详情

设备详情是设备基本信息的承载页面，包括设备名称、设备图标、设备状态、设备控制等。每个面板业务都应添加设备详情配置。

```typescript
{
    functionalPages: {
    settings: {
      appid: "tycryc71qaug8at6yt",
      entryCode: "entryyvaqnocapvsl1"
    },
  },
}
```
<Image style={{ boxShadow: '0 0 5px rgba(0,0,0,0.3)', width: '187.5px' }} src='https://images.tuyacn.com/content-platform/hestia/17291343700a81343182c.png'/>

#### 酷玩吧


酷玩吧是情景音乐律动等功能集合页面，照明设备基础、高级能力板块的集合，属于增值服务类型。

```typescript
{
    functionalPages: {
    rayPlayCoolFunctional: {
      appid: "tyg0szxsm3vog8nf6n"
    },
  },
}
```
<Image style={{ boxShadow: '0 0 5px rgba(0,0,0,0.3)', width: '187.5px' }} src='https://images.tuyacn.com/content-platform/hestia/17291343831185bc2d180.png'/>


#### 定时倒计时

定时倒计时是基础设备能力，用于设备定时开关，定时倒计时等功能。


```typescript
{
    functionalPages: {
    rayPlayCoolFunctional: {
      appid: "tyjks565yccrej3xvo"
    },
  },
}
```
<Image style={{ boxShadow: '0 0 5px rgba(0,0,0,0.3)', width: '187.5px' }} src='https://images.tuyacn.com/content-platform/hestia/17291344136899f9a1b60.png'/>

#### 生物节律

生物节律功能可以模拟一天当中自然光亮度和色温的变化，让我们感受回归自然的灯光。

```typescript
{
    functionalPages: {
    rayPlayCoolFunctional: {
      appid: "ty53odnmk2cxnzcxm6"
    },
  },
}
```
<Image style={{ boxShadow: '0 0 5px rgba(0,0,0,0.3)', width: '187.5px' }} src='https://images.tuyacn.com/content-platform/hestia/1729134400538b8b85486.png'/>

## 功能页开发

功能页源码有独立的目录，其源码内部不可引用（`miniprogramRoot` `widgetRoot`）目录内的文件，包括 js、tyml、tyss、图片等。


## 快速上手

### 搭建环境及小程序开发流程

功能页开发，**需要使用智能小程序创建项目，请勿使用非智能小程序**。项目创建流程与智能各小程序一致，具体请参考[智能小程序快速开始](/cn/miniapp/develop/ray/guide/start/smart)。

### 工程配置

project.tuya.json

若要进行功能页开发，需要在 project.tuya.json 文件中声明 `miniprogramRoot` 和 `functionalRoot` 分别对应小程序代码目录和功能页代码目录，对于基于原生小程序语法开发的业务，需要指定开发目录即可，对于 ray 框架开发的业务，需要指定 ray 编译产物目录。

- 配置内容

```javascript
{
  projectname: 'functional-demo',
  i18n: true,
  miniprogramRoot: 'dist/tuya/miniprogram', // 小程序编译后源码
  functionalRoot: 'dist/tuya/functional', // 功能页编译后源码目录
  projectId: 'your_project_id',
  baseversion: '2.12.0',
  dependencies: {
    // ...
  },
}
```

- 对应的目录结构

```bash
dist/ #编译产物
|--functional/ #编译后的功能页源码
     └──├── functional.json  # 功能页配置文件
        ├── functional.tyss # 功能页全局样式
        ├── theme.json # 功能页主题配置，如有
        ├── assets/
        │   └── logo.png  # 功能页内的资源
|--miniprogram/ # 小程序编译后的源码
functional/  # 功能页 ray 源码。目录名固定为 functional
  └──├── functional.config.ts  # 功能页配置文件
  	 ├── functional.tyss # 功能页全局样式
  	 └── theme.json # 功能页主题配置，如有
src/ # 小程序功能页目录
project.tuya.json # 项目配置文件
```

### 功能页配置 `functional.config.ts`

用于描述当前功能页小程序的信息。

#### 配置字段 

| 字段            | 类型    | 必填 | 说明                                                                                                                                                                                                                                     |
| --------------- | ------- | ---- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| pages           | Array   | 是   | 功能页内的页面列表，与小程序的 `app.json` 一致。 声明当前功能页包含的页面地址，可以有多个，至少存在一个。                                                                                                                                |
| publicPages     | Object  | 是   | 只有发布的页面才可被宿主小程序访问，访问路由为： `functional://{name}/{pageName} `一经发布的页面，页面名不可更改，否则会造成宿主小程序访问到错误路由的问题，应在发布前确定好发布的页面名。并发布到功能页小程序文档中，提供给引入方查阅。 |
| themeLocation   | string  | 否   | [参考 app.json](/cn/miniapp/develop/miniapp/framework/app/app-json#themelocation)                                                                                                                                                        |
| usingComponents | Object  | 否   | [参考 app.json](/cn/miniapp/develop/miniapp/framework/app/app-json#usingcomponents)                                                                                                                                                      |
| dependencies    | Object  | 否   | [参考 project.tuya.json](/cn/miniapp/develop/miniapp/framework/app/config)                                                                                                                                                               |
| darkmode        | boolean | 否   | 是否支持暗黑模式, 默认 `true`                                                                                                                                                                                                            |
| themeLocation   | string  | 否   | 主题配置文件相对路径                                                                                                                                                                                                                     |

#### 示例

```typescript
export default {
  routes: [
    {
      name: 'detail', // 发布 detail 页面 对应 app.json 文件中 publicPages 下的 key
      isPublic: true, // 是否对外发布
      route: '/detail',
      path: 'pages/detail/index',
    },
    {
      name: 'third',
      isPublic: false,
      route: '/third',
      path: '/pages/third/index',
    },
  ],
};

```
上述示例中， detail 页面是对外发布的， 业务中可以通过该路由跳转 detail 页面。

如：
```javascript
// 正确的调用
ty.navigateTo({
  url: `functional://settings/detail?${deviceId}`,
  success: (res) => {
    console.log('跳转功能页 success', `functional://settings/detail?${query}`, res);
  },
  fail: (err) => {
    console.error('跳转功能页 fail', `functional://settings/detail?${query}`, err);
  }
});

// 由于 publicPages 只注册了 detail，因此以下路由无法跳转
ty.navigateTo({
  url: `functional://settings/third?${deviceId}`,
  success: (res) => {
    console.log('跳转功能页 success', `functional://settings/third?${deviceId}`, res);
  },
  fail: (err) => {
    console.error('跳转功能页 fail', `functional://settings/third?${deviceId}`, err);
  }
})
```

### 功能页开发调试

#### global.config.ts 文件中声明依赖的功能页

宿主小程序中 `global.config.ts` 中通过 `functionalPages` 字段导入功能页小程序。使用 key-value 的形式，key 为功能页的插件名，value 为功能页的配置信息。

```typescript
{
  functionalPages: {
    settings: {
      appid: "tycryc71qaug8at6yt",
      entryCode: "entryyvaqnocapvsl1",
    },
  },
}
```
appid 为小程序 ID。
    与当前小程序的 projectId (project.tuya.json) 中一致时，则表示引用自身的功能页。

#### 跳转到功能页页面
使用路由 API 跳转， 仅支持 ty.navigateTo、 ty.redirectTo、 ty.reLaunch 三个方法。

格式为 `functional://{功能页插件名}/{对外暴露的页面名}` 支持 query 参数。

如设备详情功能页名称为：settings （如上介绍，固定名称，业务方不能更改）；对外暴露的地址：detail （固定名称，由设备详情功能页开发者命名）

```html
<navigator url="functional://settings/detail?deviceId=xxxxx" open-type="navigate">
  跳转到功能页
</navigator>
```

```javascript
ty.navigateTo({
  url: 'functional://settings/detail?deviceId=xxxxx',
});
```

#### 功能页内部跳转

功能页内部需要使用相对路径的形式进行跳转。
  
```javascript
ty.navigateTo({
  url: '../detail/index',
});
```

### 功能页发布

开发完成后，发布功能页所属小程序即可。IDE 内部版本已移除上传功能，内部开发者需要在面板管理平台进行打包上传。

## 进阶开发

### 整体架构

#### 同业务架构

<Image style={{ boxShadow: '0 0 5px rgba(0,0,0,0.3)' }} src='/images/functional/same_project.png'/>

如上图所示， 同业务中的功能页加载结构较为简单，逻辑存在一个小程序包中。
- 业务小程序：用于承载功能页入口，及多语言。
  - 业务逻辑：常规业务逻辑，无特殊性限制，需要在小程序中声明对功能页插件的依赖及插件名称。
  - 多语言：承载了小程序及功能页中使用到的多语言，与功能页使用同一个多语言包。
- 业务功能页：
  - 在业务开发过程中拆分出可跨业务使用的公共逻辑，除必须参数及多语言外，对业务小程序无其他依赖。
  - 多语言：使用所属小程序的多语言包。
  - 对外路由：需要对外开放的页面，应在 publicPages 中进行声明

#### 非同业务架构

<Image style={{ boxShadow: '0 0 5px rgba(0,0,0,0.3)' }} src='/images/functional/different_project.png'/>

如上图所示，小程序业务可能会使用到除自身拆分出的功能页外，还会依赖其他功能页。

- 业务小程序：承载业务功能，业务多语言。
  - 业务逻辑：常规业务逻辑，无特殊性限制，需要在小程序中声明对功能页的依赖及配置功能页插件名，不同的功能页需要配置不同的插件名称。
  - 多语言： 承载了小程序及功能页中使用到的多语言，与功能页使用同一个多语言包。
- 业务功能页：在业务开发过程中拆分出可跨业务使用的公共逻辑，除必须参数及多语言外，对业务小程序无其他依赖。非必须。
  - 多语言：使用所属小程序的多语言包。
  - 对外路由：需要对外开放的页面，应在 publicPages 中进行声明
- 其他功能页：承载了一些公共业务逻辑，一般为共性业务逻辑，对宿主业务一般无强制性依赖。
  - 多语言：多语言存在于功能页所属小程序的多语言包中。
- 多语言优先级：宿主小程序多语言 > 功能页多语言。 
  - 宿主小程序若包含功能页功能，则该功能页多语言与宿主小程序共用多语言包。
  - 在使用非宿主小程序所包含的功能页时，功能页多语言包与宿主小程序多语言包独立。
  - 功能页多语言包中字段 key 值与宿主小程序多语言包中字段 key 值相同时，取宿主小程序多语言包中的字段值。
  - 多个功能页间多语言包独立，不会相互影响。
- 添加前缀：在开发功能页时，为避免功能页内多语言无意识地被宿主多语言覆盖。
  建议多语言字段 key 值添加特定前缀，建议以项目名称缩写开头：如:灯光渐变功能页(LampMutationFunctional)以 `lmf_` 作为多语言 key 值的前缀。
### 体验版本依赖

体验版本依赖可以帮助功能页开发者在未发布时即可验证相关功能，提升开发体验。

`global.config.ts` 中 `functionalPages` 中版本控制相关有以下字段可以根据需求选择性配置：
- `versionType`: 表示所依赖的功能页版本类型，可选值为 `release`、`preview`，默认为 `release`。
- `version`: 表示所依赖的功能页的版本号，配置后只会加载指定版本的功能页。默认不配置，加载线上最新版本。

```typescript
{
  functionalPages: {
    settings: {
      appid: "tycryc71qaug8at6yt",
      entryCode: "entryyvaqnocapvsl1",
      versionType: "preview",
      version: "1.0.0"
    },
  },
}
```
<Callout type="info" emoji="ℹ️">
  注意：
  1. `versionType`: 仅所加载的小程序为体验版本时生效。小程序发布正式版本后，跳转功能页该字段不再生效。将使用正式版本进行跳转。<br/>
  2. `version`: 被指定为固定版本号后， 若该版本的功能页被下架，会无法加载到该功能页相关功能，存在一定的风险，建议谨慎使用。<br/>
  3. App 版本 >= 5.18
</Callout>

### 加载策略

在实际业务开发中，一个小程序可能会依赖多个功能页，如果依赖过多，下载耗时就会越长，所以需要指定加载策略，保证功能页加载的效果。

`global.config.ts` 中 `functionalPages` 中 `strategy` 字段表示使用哪种策略加载功能页，可选值为 `lazyload`、`preload`，默认为 `preload`。

**lazyload**: 懒加载模式，当跳转功能页时进行下载。
**preload**: 预加载模式，当打开宿主小程序时下载，默认值。

示例：
```typescript
{
  functionalPages: {
    settings: {
      appid: "tycryc71qaug8at6yt",
      entryCode: "entryyvaqnocapvsl1",
      strategy: "lazyload"
    },
  },
}
```
<Callout type="info" emoji="ℹ️">
  App 版本 >= 5.18
</Callout>

### 跨页面事件通信

支持 eventChannel 通信模式，具体参考 [getOpenerEventChannel](/cn/miniapp/develop/miniapp/framework/api/page#pageprototypegetopenereventchannel)

需要功能页开发时提前预留特定的事件名，供宿主小程序调用。

### 预设功能页初始化数据

当需要定制功能页页面数据时，或跳转页面需要传递较多参数，url可能会超出最大长度时，可通过 `ty.presetFunctionalData` API 进行。

用于设置功能页初始化的页面数据，一经设置永久生效，除非主动清空

```javascript
// 自定义数据
ty.presetFunctionalData({
	url: 'functional://mySettings/home',
    data: { name: 'pre' }
})
// 进行跳转
ty.navigateTo({ url: 'functional://mySettings/home'})

// 清空数据
ty.presetFunctionalData({
  url: 'functional://mySettings/home',
  data: null
})
```
预设数据后，功能页页面实例通过 Page 实例的 this.getPresetData() 获取数据

```tsx | sandbox
import { usePageInstance, View } from '@ray-js/ray';
export default function Index() {
  const page = usePageInstance();
  const presetData = page.getPresetData();
  return <View>...</View>;
}
```

## 注意事项

1. 不受 app.tyss 样式影响，功能页内聚自身样式。
2. light / dark 开发模式，跟随宿主配置。
3. getApp, getCurrentPages 接口的返回只可访问自身空间数据。
4. 功能页多语言会合并宿主小程序多语言，多语言优先级宿主多语言 > 功能页多语言。
5. 真机调试 App 版本需要 >= 5.18。IDE 版本需要 >= 0.7.1。
6. 业务小程序配置所依赖的功能页配置时，可能会出现指定功能页版本号的情况：
```typescript
{
  functionalPages: {
    settings: {
      // 加载指定版本的功能页
      appid: "tycryc71qaug8at6yt",
    },
    settings: {
      // 加载 1.0.0 版本的功能页
      appid: "tycryc71qaug8at6yt",
      version: "1.0.0"
    },
    settings: {
      // 通过 entryCode 查找 appid，加载线上最新可用版本的功能页。entryCode 优先级高于 appid
      entryCode: "entryyvaqnocapvsl1",
      // 此时 appid 和 version 字段会被忽略
      appid: "tycryc71qaug8at6yt",
      version: "1.0.0"
    },
  },
}
```
该场景下可能会遇到如下几种情况或异常：
- 仅指定 appid： 此时会加载线上最新可用版本的功能页，若不存在任何可用版本，则表现为无法跳转到该功能页，跳转接口会抛出异常。
- 指定 appid 和 version： 此时会加载指定版本的功能页，若不存在该版本，则表现为无法跳转到该功能页，跳转接口会抛出异常。
- 指定 entryCode 的情况： 此时 appid 和 version 字段会被忽略，会通过 entryCode 查找 appid，若查到，则会加载线上最新可用版本的功能页；若未查到则表现为无法跳转到该功能页，跳转接口会抛出异常。