title: 模板

Taro3 通过把 DOM 树的数据进行 setData,从而驱动模板(<template>)拼接来渲染出视图。

因此开发者可以看到编译后的代码中,页面模板文件的内容很简单,只是引用了公共模板 base.xml,所有组件的模板都在此文件中进行声明。

我们可以创建一个模板类,控制 base 模板的编译结果。

递归与非递归模板

我们把模板相关的处理逻辑封装成了基类。分别是给支持模板递归的小程序继承的 RecursiveTemplate 类,和给不支持模板递归的小程序继承的 UnRecursiveTemplate 类。

可递归模板

支持模板递归的小程序中,同一个模板能够不断调用自身,包括支付宝、头条、百度小程序。

view_0 引用 container_0container_0 能再引用 view_0

模板 - 图1

非递归模板

不支持模板递归的小程序中,引用过的模板不能再调用自身,包括微信、QQ、京东小程序。

view_0 引用 container_0container_0 不能再引用 view_0,只能引用新的 view 模板 view_1

模板 - 图2

模板基类

this.Adapter

object

平台的模板语法关键词。

例子:

  1. // 声明了微信小程序模板语法关键词的 Adapter
  2. class Template extends UnRecursiveTemplate {
  3. Adapter = {
  4. if: 'wx:if',
  5. else: 'wx:else',
  6. elseif: 'wx:elif',
  7. for: 'wx:for',
  8. forItem: 'wx:for-item',
  9. forIndex: 'wx:for-index',
  10. key: 'wx:key',
  11. xs: 'wxs',
  12. type: 'weapp'
  13. }
  14. }

this.isSupportRecursive

boolean

只读,是否支持模板递归。

this.supportXS

boolean

默认值:false

是否支持渲染层脚本,如微信小程序的 wxs,支付宝小程序的 sjs。

this.exportExpr

string

默认值:’module.exports =’

渲染层脚本的导出命令。

例子:

  1. // 支付宝小程序 sjs 脚本的导出命令为 ES 模式
  2. class Template extends RecursiveTemplate {
  3. exportExpr = 'export default'
  4. }

this.internalComponents

object

Taro 内置组件列表,包括了相对通用的组件及其部分通用属性。

this.focusComponents

Set<string>

可以设置 focus 聚焦的组件。

默认值:

  1. focusComponents = new Set([
  2. 'input',
  3. 'textarea',
  4. 'editor'
  5. ])

this.voidElements

Set<string>

不需要渲染子节点的元素。配置后这些组件不会渲染子节点,能够减少模板体积。

默认值:

  1. voidElements = new Set([
  2. 'progress',
  3. 'icon',
  4. 'rich-text',
  5. 'input',
  6. 'textarea',
  7. 'slider',
  8. 'switch',
  9. 'audio',
  10. 'live-pusher',
  11. 'video',
  12. 'ad',
  13. 'official-account',
  14. 'open-data',
  15. 'navigation-bar'
  16. ])

this.nestElements

Map<string, number>

对于一个小程序来说,只有部分组件有可能递归调用自身。如 <Map> 组件不会再调用 <Map>,而 <View> 则可以不断递归调用 <View>

如果此小程序不支持递归,我们又把 <Map> 模板循环渲染了 N 次,那么小程序体积就会变大,而这些循环出来的模板又是不必要的。因此使用了 nestElements 去标记那些可能递归调用的组件。

但考虑到例如 <Form> 这些组件即使可能递归调用,但也不会递归调用太多次。因此在 nestElements 中可以对它的循环渲染次数进行控制,假设 <Form> 不会递归调用超过 N 次,进一步减少模板体积。

默认值:

  1. nestElements = new Map([
  2. ['view', -1],
  3. ['cover-view', -1],
  4. ['block', -1],
  5. ['text', -1],
  6. ['slot', 8],
  7. ['slot-view', 8],
  8. ['label', 6],
  9. ['form', 4],
  10. ['scroll-view', 4]
  11. ])

key 值为可以递归调用自身的组件。

value 值代表递归生成此组件的次数,-1 代表循环 baseLevel 层。

replacePropName (name, value, componentName)

代替组件的属性名。

参数 类型 说明
name string 属性名
value string 属性值
componetName string 组件名

例子:

  1. replacePropName (name, value, componentName) {
  2. // 如果属性值为 'eh',代表这是一个事件,把属性名改为全小写。
  3. if (value === 'eh') return name.toLowerCase()
  4. return name
  5. }

buildXsTemplate ()

支持渲染层脚本的小程序,Taro 会生成一个 utils 脚本在根目录。此时需要声明此函数以设置 base 模板中对 utils 脚本的引用语法。

例子:

  1. // 微信小程序 base 模板引用 utils.wxs 脚本
  2. buildXsTemplate () {
  3. return '<wxs module="xs" src="./utils.wxs" />'
  4. }

modifyLoopBody (child, nodeName)

修改组件模板的子节点循环体。

参数 类型 说明
child string 组件模板的子节点循环体
nodeName string 组件名

没有在 this.voidElements 中声明过的组件,会遍历子节点进行渲染。

这些组件的模板通用格式为:

  1. <template name="tmpl_0_view">
  2. <view>
  3. <!-- 子节点循环 begin -->
  4. <block wx:for="{{i.cn}}" wx:key="uid">
  5. <!-- 子节点循环体 begin -->
  6. <template is="{{...}}" data="{{...}}" />
  7. <!-- 子节点循环体 end -->
  8. </block>
  9. <!-- 子节点循环 end -->
  10. </view>
  11. </template>

例子:

  1. // 支付宝小程序的 <swiper> 组件中,循环体套一层 <swiper-item> 和 <view> 组件
  2. modifyLoopBody (child, nodeName) {
  3. if (nodeName === 'swiper') {
  4. return `<swiper-item>
  5. <view a:for="{{item.cn}}" a:key="id">
  6. ${child}
  7. </view>
  8. </swiper-item>`
  9. }
  10. return child
  11. }

modifyLoopContainer (children, nodeName)

修改组件模板的子节点循环。

参数 类型 说明
children string 组件模板的子节点循环
nodeName string 组件名

例子:

  1. // 支付宝小程序的 <picker> 组件中,子节点循环套一层 <view> 组件
  2. modifyLoopContainer (children, nodeName) {
  3. if (nodeName === 'picker') {
  4. return `
  5. <view>${children}</view>
  6. `
  7. }
  8. return children
  9. }

modifyTemplateResult (res, nodeName, level, children)

修改组件模板的最终结果。

参数 类型 说明
res string 组件模板的结果
nodeName string 组件名
level string 循环层级
children string 组件模板的子节点循环

例子:

  1. // 支付宝小程序当遇到 <swiper-item> 组件时不渲染其模板
  2. modifyTemplateResult = (res: string, nodeName: string) => {
  3. if (nodeName === 'swiper-item') return ''
  4. return res
  5. }

getAttrValue (value, key, nodeName)

设置组件的属性绑定语法。

参数 类型 说明
value string 属性值
key string 属性名
nodeName string 组件名

例子:

  1. getAttrValue (value, key, nodeName) {
  2. const swanSpecialAttrs = {
  3. 'scroll-view': ['scrollTop', 'scrollLeft', 'scrollIntoView']
  4. }
  5. // 百度小程序中 scroll-view 组件部分属性的属性绑定语法是: {= value =}
  6. if (isArray(swanSpecialAttrs[nodeName]) && swanSpecialAttrs[nodeName].includes(key)) {
  7. return `= ${value} =`
  8. }
  9. // 其余属性还是使用 {{ value }} 绑定语法
  10. return `{ ${value} }`
  11. }

例子

头条小程序模板

  • 头条小程序支持模板递归,所以继承 RecursiveTemplate 基类。

  • 因为不需要调整模板内容,所以只用设置 supportXSAdapter 属性即可。

  1. import { RecursiveTemplate } from '@tarojs/shared/dist/template'
  2. export class Template extends RecursiveTemplate {
  3. supportXS = false
  4. Adapter = {
  5. if: 'tt:if',
  6. else: 'tt:else',
  7. elseif: 'tt:elif',
  8. for: 'tt:for',
  9. forItem: 'tt:for-item',
  10. forIndex: 'tt:for-index',
  11. key: 'tt:key',
  12. type: 'tt'
  13. }
  14. }

微信小程序模板

  • 微信小程序不支持模板递归,所以继承 UnRecursiveTemplate 基类。
  • 设置 supportXSAdapter 属性。
  • 因为微信小程序支持渲染层脚本 wxs,所以通过 buildXsTemplate 设置 base 模板中对 utils 脚本的引用语法。
  • 利用 replacePropName 修改了组件绑定的属性名。
  1. import { UnRecursiveTemplate } from '@tarojs/shared/dist/template'
  2. export class Template extends UnRecursiveTemplate {
  3. supportXS = true
  4. Adapter = {
  5. if: 'wx:if',
  6. else: 'wx:else',
  7. elseif: 'wx:elif',
  8. for: 'wx:for',
  9. forItem: 'wx:for-item',
  10. forIndex: 'wx:for-index',
  11. key: 'wx:key',
  12. xs: 'wxs',
  13. type: 'weapp'
  14. }
  15. buildXsTemplate () {
  16. return '<wxs module="xs" src="./utils.wxs" />'
  17. }
  18. replacePropName (name, value, componentName) {
  19. if (value === 'eh') {
  20. const nameLowerCase = name.toLowerCase()
  21. if (nameLowerCase === 'bindlongtap' && componentName !== 'canvas') return 'bindlongpress'
  22. return nameLowerCase
  23. }
  24. return name
  25. }
  26. }