小程序笔记总结(二)

143 阅读15分钟

8.2 WXML模版

developers.weixin.qq.com/miniprogram…

WXML(WeiXin Markup Language)是框架设计的一套标签语言,结合基础组件事件系统,可以构建出页面的结构

developers.weixin.qq.com/miniprogram…

8.2.1 数据绑定

WXML 中的动态数据均来自对应 Page 的 data。

// app.json
{
	pages: [
		"pages/test/test",
		....
	]
}
  • 简单绑定

  • 组件属性(需要在双引号之内)

  • 控制属性(需要在双引号之内)

  • 关键字(需要在双引号之内)

true:boolean 类型的 true,代表真值。

false: boolean 类型的 false,代表假值。

8.2.2 列表渲染

8.2.3 条件渲染

8.2.4 模版

WXML提供模板(template),可以在模板中定义代码片段,然后在不同的地方调用。

8.2.5 引用

WXML 提供两种文件引用方式importinclude

以上代码参照 pages/test/test.wxml 以及 pages/test/test.js

<!--pages/test/test.wxml-->
<!-- <text>pages/test/test.wxml</text>
<view style="height: 300px;font-size:40px;font-weight: bold">1</view>
<view style="height: 300px;font-size:40px;font-weight: bold">2</view>
<view style="height: 300px;font-size:40px;font-weight: bold">3</view>
<view style="height: 300px;font-size:40px;font-weight: bold">4</view>
<view style="height: 300px;font-size:40px;font-weight: bold">5</view>
<view style="height: 300px;font-size:40px;font-weight: bold">6</view>
<view style="height: 300px;font-size:40px;font-weight: bold">7</view>
<view style="height: 300px;font-size:40px;font-weight: bold">8</view>
<view style="height: 300px;font-size:40px;font-weight: bold">9</view> -->
<include src="footer.wxml"/>
<!-- 
  如果属性值是变量,boolean类型,number类型,对象、数组、null、undefined,
  在vue中使用绑定属性解决问题
  在小程序中使用{{}} 解决问题
 -->
<view>{{ message }}</view> 
<view class="item-100"></view>
<view class="item-{{ id }}"></view>
<view wx:if="{{condition}}"></view>
<checkbox value="篮球" checked="{{false}}"/>篮球
<view>{{ condition ? '真真' : '假假'}}</view>
<view wx:for="{{['a', 'b', 'c']}}" wx:key="*this">{{ item }}</view>
<view class="{{ { a: 1, b: 2 } }}"></view>
<!--
  在组件上使用 wx:for 控制属性绑定一个数组,即可使用数组中各项的数据重复渲染该组件。
  默认数组的当前项的下标变量名默认为 index,数组当前项的变量名默认为 item
  * 使用 wx:for-item 可以指定数组当前元素的变量名,
  * 使用 wx:for-index 可以指定数组当前下标的变量名: 
  需要使用 wx:key 来指定列表中项目的唯一的标识符。
  wx:key 的值以两种形式提供
  * 字符串,代表在 for 循环的 array 中 item 的某个 property,该 property 的值需要是列表中唯一的字符串或数字,且不能动态改变。
  * 保留关键字 *this 代表在 for 循环中的 item 本身,这种表示需要 item 本身是一个唯一的字符串或者数字。
  * 不可以使用索引值作为key值
  -->
<view wx:for="{{ list }}" wx:key="*this">{{ item }}</view>
<view wx:for="{{ arr }}" wx:key="id">{{ item.unique }}</view>
<view wx:for="{{ arr }}" wx:key = "id" wx:for-item="itm">{{ itm.unique }}</view>
<!-- 
  条件渲染
  vue  v-if v-show
    v-if 是“真实的”按条件渲染,因为它确保了在切换时,条件区块内的事件监听器和子组件都会被销毁与重建。
    v-if 也是惰性的:如果在初次渲染时条件值为 false,则不会做任何事。条件区块只有当条件首次变为 true 时才被渲染。
    相比之下,v-show 简单许多,元素无论初始条件如何,始终会被渲染,只有 CSS display 属性会被切换。
    总的来说,v-if 有更高的切换开销,而 v-show 有更高的初始渲染开销。因此,如果需要频繁切换,则使用 v-show 较好;如果在运行时绑定条件很少改变,则 v-if 会更合适。
  minpro wx:if hidden
    因为 wx:if 之中的模板也可能包含数据绑定,所以当 wx:if 的条件值切换时,框架有一个局部渲染的过程,因为它会确保条件块在切换时销毁或重新渲染。
    同时 wx:if 也是惰性的,如果在初始渲染条件为 false,框架什么也不做,在条件第一次变成真的时候才开始局部渲染。
    相比之下,hidden 就简单的多,组件始终会被渲染,只是简单的控制显示与隐藏。
    一般来说,wx:if 有更高的切换消耗而 hidden 有更高的初始渲染消耗。因此,如果需要频繁切换的情景下,用 hidden 更好,如果在运行时条件不大可能改变则 wx:if 较好。
  
  vue v-if v-else-if v-else
  minpro wx:if  wx:elif wx:else
 -->

<view>
  <text> 1: 第一条消息 </text>
  <text> Time: 2023/12/26 10:17 </text>
</view>
<!-- 模板 -->
<template name="msgItem">
  <view>
    <text> {{ index }}: {{ msg }} </text>
    <text> Time: {{ time }} </text>
  </view>
</template>
<!--
   使用模板时,遇到对象 不要写 {}
   如果数据源是对象类型的变量,使用...展开对象
 -->
<template is="msgItem" data="{{ index: 0,  msg: 'this is a template', time: '2016-09-15' }}"></template>
<template wx:for="{{ timeArr }}" wx:key="index" is="msgItem" data="{{ ...item }}"></template>

<!-- 
  import引入模板
  作用域问题C import B,B import A,在C中可以使用B定义的template,在B中可以使用A定义的template,但是C不能使用A定义的template
 -->
<import src="item.wxml"/>
<template wx:for="{{ timeArr }}" wx:key="index" is="importItem" data="{{ ...item }}"></template>

<!-- include 使用模板 -->
<include src="footer.wxml"/>
// pages/test/test.js
Page({

  /**
   * 页面的初始数据
   */
  data: {
    message: "hello world",
    id: 999,
    condition: false,
    list: ['aa', 'bb', 'cc', 'dd'],
    arr: [
      {id: 5, unique: 'unique_5'},
      {id: 4, unique: 'unique_4'},
      {id: 3, unique: 'unique_3'},
      {id: 2, unique: 'unique_2'},
      {id: 1, unique: 'unique_1'},
      {id: 0, unique: 'unique_0'},
    ],
    timeArr: [
      { index: 1,  msg: '第1条消息', time: '2023/12/16 10:24' },
      { index: 2,  msg: '第2条消息', time: '2023/12/16 10:25' },
      { index: 3,  msg: '第3条消息', time: '2023/12/16 10:26' }
    ]
  },

  /**
   * 生命周期函数--监听页面加载
   */
  onLoad(options) {

  },

  /**
   * 生命周期函数--监听页面初次渲染完成
   */
  onReady() {

  },

  /**
   * 生命周期函数--监听页面显示
   */
  onShow() {

  },

  /**
   * 生命周期函数--监听页面隐藏
   */
  onHide() {

  },

  /**
   * 生命周期函数--监听页面卸载
   */
  onUnload() {

  },

  /**
   * 页面相关事件处理函数--监听用户下拉动作
   */
  onPullDownRefresh() {

  },

  /**
   * 页面上拉触底事件的处理函数
   */
  onReachBottom() {

  },

  /**
   * 用户点击右上角分享
   */
  onShareAppMessage() {

  }
})
<!-- pages/test/footer.wxml -->
<view>这里是公共的底部组件</view>
<!-- pages/test/item.wxml -->
<template name="importItem">
  <view>
    <text> {{ index }}: {{ msg }} </text>
    <text> Time: {{ time }} </text>
  </view>
</template>

8.3 WXSS 样式

WXSS 具有 CSS 大部分的特性,小程序在 WXSS 也做了一些扩充和修改

  1. 新增了尺寸单位。在写 CSS 样式时,开发者需要考虑到手机设备的屏幕会有不同的宽度和设备像素比,采用一些技巧来换算一些像素单位。WXSS 在底层支持新的尺寸单位 rpx ,开发者可以免去换算的烦恼,只要交给小程序底层来换算即可,由于换算采用的浮点数运算,所以运算结果会和预期结果有一点点偏差。
  2. 提供了全局的样式和局部样式。和前边 app.json, page.json 的概念相同,你可以写一个 app.wxss 作为全局样式,会作用于当前小程序的所有页面,局部页面样式 page.wxss 仅对当前页面生效。
  3. 此外 WXSS 仅支持部分 CSS 选择器

developers.weixin.qq.com/miniprogram…

  • rpx(responsive pixel): 可以根据屏幕宽度进行自适应。规定屏幕宽为750rpx。如在 iPhone6 上,屏幕宽度为375px,共有750个物理像素,则750rpx = 375px = 750物理像素,1rpx = 0.5px = 1物理像素。
设备rpx换算px (屏幕宽度/750)px换算rpx (750/屏幕宽度)
iPhone51rpx = 0.42px1px = 2.34rpx
iPhone61rpx = 0.5px1px = 2rpx
iPhone6 Plus1rpx = 0.552px1px = 1.81rpx

建议: 开发微信小程序时设计师可以用 iPhone6 作为视觉稿的标准。

假如设计是给我们的设计稿,打开以后,发现 设计稿的宽度是 750px,那么我们在wxss中写宽度和高度时,可以直接写量取的数据,单位 rpx

如果设计师给的设计稿为375px,假设量取的宽度为100px,那么建议将wxss的单位写为 200rpx

注意: 在较小的屏幕上不可避免的会有一些毛刺,请在开发时尽量避免这种情况。

8.4 js逻辑交互

8.4.1 什么是事件

  • 事件是视图层到逻辑层的通讯方式。
  • 事件可以将用户的行为反馈到逻辑层进行处理。
  • 事件可以绑定在组件上,当达到触发事件,就会执行逻辑层中对应的事件处理函数。
  • 事件对象可以携带额外信息,如 id, dataset, touches。

切记,自定义的事件是需要写到 js中的 选项中的

8.4.2 如何给事件传递参数

传递参数时,id具有特殊性,其余数据通过 data-params 属性传值

8.4.3 冒泡

biantap并不会阻止事件冒泡

catchtap 会阻止冒泡

<!--pages/test/test.wxml-->
<!-- <text>pages/test/test.wxml</text>
<view style="height: 300px;font-size:40px;font-weight: bold">1</view>
<view style="height: 300px;font-size:40px;font-weight: bold">2</view>
<view style="height: 300px;font-size:40px;font-weight: bold">3</view>
<view style="height: 300px;font-size:40px;font-weight: bold">4</view>
<view style="height: 300px;font-size:40px;font-weight: bold">5</view>
<view style="height: 300px;font-size:40px;font-weight: bold">6</view>
<view style="height: 300px;font-size:40px;font-weight: bold">7</view>
<view style="height: 300px;font-size:40px;font-weight: bold">8</view>
<view style="height: 300px;font-size:40px;font-weight: bold">9</view> -->
<include src="footer.wxml"/>
<!-- 
  如果属性值是变量,boolean类型,number类型,对象、数组、null、undefined,
  在vue中使用绑定属性解决问题
  在小程序中使用{{}} 解决问题
 -->
<view>{{ message }}</view> 
<view class="item-100"></view>
<view class="item-{{ id }}"></view>
<view wx:if="{{condition}}"></view>
<checkbox value="篮球" checked="{{false}}"/>篮球
<view>{{ condition ? '真真' : '假假'}}</view>
<view wx:for="{{['a', 'b', 'c']}}" wx:key="*this">{{ item }}</view>
<view class="{{ { a: 1, b: 2 } }}"></view>
<!--
  在组件上使用 wx:for 控制属性绑定一个数组,即可使用数组中各项的数据重复渲染该组件。
  默认数组的当前项的下标变量名默认为 index,数组当前项的变量名默认为 item
  * 使用 wx:for-item 可以指定数组当前元素的变量名,
  * 使用 wx:for-index 可以指定数组当前下标的变量名: 
  需要使用 wx:key 来指定列表中项目的唯一的标识符。
  wx:key 的值以两种形式提供
  * 字符串,代表在 for 循环的 array 中 item 的某个 property,该 property 的值需要是列表中唯一的字符串或数字,且不能动态改变。
  * 保留关键字 *this 代表在 for 循环中的 item 本身,这种表示需要 item 本身是一个唯一的字符串或者数字。
  * 不可以使用索引值作为key值
  -->
<view wx:for="{{ list }}" wx:key="*this">{{ item }}</view>
<view wx:for="{{ arr }}" wx:key="id">{{ item.unique }}</view>
<view wx:for="{{ arr }}" wx:key = "id" wx:for-item="itm">{{ itm.unique }}</view>
<!-- 
  条件渲染
  vue  v-if v-show
    v-if 是“真实的”按条件渲染,因为它确保了在切换时,条件区块内的事件监听器和子组件都会被销毁与重建。
    v-if 也是惰性的:如果在初次渲染时条件值为 false,则不会做任何事。条件区块只有当条件首次变为 true 时才被渲染。
    相比之下,v-show 简单许多,元素无论初始条件如何,始终会被渲染,只有 CSS display 属性会被切换。
    总的来说,v-if 有更高的切换开销,而 v-show 有更高的初始渲染开销。因此,如果需要频繁切换,则使用 v-show 较好;如果在运行时绑定条件很少改变,则 v-if 会更合适。
  minpro wx:if hidden
    因为 wx:if 之中的模板也可能包含数据绑定,所以当 wx:if 的条件值切换时,框架有一个局部渲染的过程,因为它会确保条件块在切换时销毁或重新渲染。
    同时 wx:if 也是惰性的,如果在初始渲染条件为 false,框架什么也不做,在条件第一次变成真的时候才开始局部渲染。
    相比之下,hidden 就简单的多,组件始终会被渲染,只是简单的控制显示与隐藏。
    一般来说,wx:if 有更高的切换消耗而 hidden 有更高的初始渲染消耗。因此,如果需要频繁切换的情景下,用 hidden 更好,如果在运行时条件不大可能改变则 wx:if 较好。
  
  vue v-if v-else-if v-else
  minpro wx:if  wx:elif wx:else
 -->

<view>
  <text> 1: 第一条消息 </text>
  <text> Time: 2023/12/26 10:17 </text>
</view>
<!-- 模板 -->
<template name="msgItem">
  <view>
    <text> {{ index }}: {{ msg }} </text>
    <text> Time: {{ time }} </text>
  </view>
</template>
<!--
   使用模板时,遇到对象 不要写 {}
   如果数据源是对象类型的变量,使用...展开对象
 -->
<template is="msgItem" data="{{ index: 0,  msg: 'this is a template', time: '2016-09-15' }}"></template>
<template wx:for="{{ timeArr }}" wx:key="index" is="msgItem" data="{{ ...item }}"></template>

<!-- 
  import引入模板
  作用域问题C import B,B import A,在C中可以使用B定义的template,在B中可以使用A定义的template,但是C不能使用A定义的template
 -->
<import src="item.wxml"/>
<template wx:for="{{ timeArr }}" wx:key="index" is="importItem" data="{{ ...item }}"></template>

<!-- include 使用模板 -->
<include src="footer.wxml"/>

<!-- 
  js逻辑运行
  on ===>  bind
  click ===> tap (300ms延迟故事,点击穿透的故事:tap事件代替click事件、touch事件代替click事件、引入第三方 fastclick 事件)
  meta viewport

  minpro绑定事件 不要给事件添加(),会自带默认参数为event
  如果遇到需要传递参数,使用dataset的方式 
    proid ===》 data-proid="1234"
  自定义的事件,在js文件中可以作为 页面Page函数的参数对象的一个属性使用
  事件冒泡 catch事件代替bind事件

-->
<view>{{ msg }}</view>
<button bind:tap="changeMsg">改变msg</button>
<button bind:tap="changeParamMsg" data-str="haha">改变msg,传递参数</button>

<view style="width: 200px;height: 200px;background-color: #ff6666;" bind:tap="clickContainer">
  <view style="width: 100px;height: 100px;background-color: #00ff00;" bind:tap="clickBox"></view>
  <view style="width: 100px;height: 100px;background-color: #0000ff;" catch:tap="clickBlue"></view>
</view>
<!-- wxs语法简单使用 -->
<!-- <script></script> -->
<wxs module="m1">
  var msg = "hello world";
  var num = 10000;

  // module.exports.message = msg;
  // module.exports.num = num;
  module.exports = {
    message: msg,
    num: num
  };
</wxs>
<view> {{m1.message}} </view>
<view> {{m1.num}} </view>
// pages/test/test.js
Page({

  /**
   * 页面的初始数据
   */
  data: {
    message: "hello world",
    id: 999,
    condition: false,
    list: ['aa', 'bb', 'cc', 'dd'],
    arr: [
      {id: 5, unique: 'unique_5'},
      {id: 4, unique: 'unique_4'},
      {id: 3, unique: 'unique_3'},
      {id: 2, unique: 'unique_2'},
      {id: 1, unique: 'unique_1'},
      {id: 0, unique: 'unique_0'},
    ],
    timeArr: [
      { index: 1,  msg: '第1条消息', time: '2023/12/16 10:24' },
      { index: 2,  msg: '第2条消息', time: '2023/12/16 10:25' },
      { index: 3,  msg: '第3条消息', time: '2023/12/16 10:26' }
    ],
    msg: 'hello world'
  },
  changeMsg () {
    // vue this.msg = "hello minpro"
    // react this.setState({ msg: 'hello minpro' })
    this.setData({ msg: 'hello minpro' })
  },
  changeParamMsg (event) {
    // 只要我们将事件处理程序直接绑定到目标元素,那么目标元素事件执行时,target 和 currentTarget 均指向的是该目标元素。
    // 如果事件处理程序并未绑定在目标元素,而是在其祖先元素上时,那么target则指向的是该目标元素,而currentTarget指向的是当前绑定事件的祖先元素。
    console.log(event)
    this.setData({
      msg: event.target.dataset.str
    })
  },
  clickContainer () {
    console.log('container')
  },
  clickBox () {
    console.log('box')
  },
  clickBlue () {
    console.log('blue')
  },
  /**
   * 生命周期函数--监听页面加载
   */
  onLoad(options) {

  },

  /**
   * 生命周期函数--监听页面初次渲染完成
   */
  onReady() {

  },

  /**
   * 生命周期函数--监听页面显示
   */
  onShow() {

  },

  /**
   * 生命周期函数--监听页面隐藏
   */
  onHide() {

  },

  /**
   * 生命周期函数--监听页面卸载
   */
  onUnload() {

  },

  /**
   * 页面相关事件处理函数--监听用户下拉动作
   */
  onPullDownRefresh() {

  },

  /**
   * 页面上拉触底事件的处理函数
   */
  onReachBottom() {

  },

  /**
   * 用户点击右上角分享
   */
  onShareAppMessage() {

  }
})

9.小程序的宿主环境

9.1 渲染层和逻辑层

WXML 模板和 WXSS 样式工作在渲染层

JS 脚本工作在逻辑层

9.1.1 注册小程序

每个小程序都需要在 app.js 中调用 App 方法注册小程序实例,绑定生命周期回调函数、错误监听和页面不存在监听函数等

developers.weixin.qq.com/miniprogram…

注册小程序。接受一个 Object 参数,其指定小程序的生命周期回调等。

App() 必须在 app.js 中调用,必须调用且只能调用一次。不然会出现无法预期的后果。

属性类型默认值必填说明最低版本
onLaunchfunction生命周期回调——监听小程序初始化。
onShowfunction生命周期回调——监听小程序启动或切前台。
onHidefunction生命周期回调——监听小程序切后台。
onErrorfunction错误监听函数。
onPageNotFoundfunction页面不存在监听函数。1.9.90
onUnhandledRejectionfunction未处理的 Promise 拒绝事件监听函数。2.10.0
onThemeChangefunction监听系统主题变化2.11.0
其他any开发者可以添加任意的函数或数据变量到 Object 参数中,用 this 可以访问
// app.js
// app.js
App({
  onLaunch() {
    console.log('onLaunch')
    // 展示本地存储能力
    const logs = wx.getStorageSync('logs') || []
    logs.unshift(Date.now())
    wx.setStorageSync('logs', logs)

    // 登录
    wx.login({
      success: res => {
        // 发送 res.code 到后台换取 openId, sessionKey, unionId
      }
    })
  },
  // 其他
  globalData: {
    userInfo: null
  },
  onShow () {
    console.log('onShow')
  },
  onHide () {
    console.log('onHide')
  },
  onError () {
    console.log('onError')
  }
})

9.1.2 注册页面

对于小程序中的每个页面,都需要在页面对应的 js 文件中进行注册,指定页面的初始数据、生命周期回调、事件处理函数等。

属性类型默认值必填说明
dataObject页面的初始数据
optionsObject页面的组件选项,同 Component 构造器 中的 options ,需要基础库版本 2.10.1
behaviorsString Array类似于 mixins 和traits的组件间代码复用机制,参见 behaviors,需要基础库版本 2.9.2
onLoadfunction生命周期回调—监听页面加载
onShowfunction生命周期回调—监听页面显示
onReadyfunction生命周期回调—监听页面初次渲染完成
onHidefunction生命周期回调—监听页面隐藏
onUnloadfunction生命周期回调—监听页面卸载
onPullDownRefreshfunction监听用户下拉动作
onReachBottomfunction页面上拉触底事件的处理函数
onShareAppMessagefunction用户点击右上角转发
onShareTimelinefunction用户点击右上角转发到朋友圈
onAddToFavoritesfunction用户点击右上角收藏
onPageScrollfunction页面滚动触发事件的处理函数
onResizefunction页面尺寸改变时触发,详见 响应显示区域变化
onTabItemTapfunction当前是 tab 页时,点击 tab 时触发
onSaveExitStatefunction页面销毁前保留状态回调
其他any开发者可以添加任意的函数或数据到 Object 参数中,在页面的函数中用 this 可以访问。这部分属性会在页面实例创建时进行一次深拷贝

9.1.3页面路由

  • 编程式导航路由

    • wx.switchTab({}) 跳转到 tabBar 页面,并关闭其他所有非 tabBar 页面

    • wx.reLaunch({}) 关闭所有页面,打开到应用内的某个页面

    • wx.redirectTo({}) 关闭当前页面,跳转到应用内的某个页面。但是不允许跳转到 tabbar 页面

    • wx.navigateTo({}) 保留当前页面,跳转到应用内的某个页面。但是不能跳到 tabbar 页面。使用 wx.navigateBack 可以返回到原页面。小程序中页面栈最多十层。

    • wx.navigateBack({}) 关闭当前页面,返回上一页面或多级页面。可通过 getCurrentPages 获取当前的页面栈,决定需要返回几层

  • 声明式导航路由

<navigator url="" open-type=""></navigator>

下面为open-type的属性值

合法值说明最低版本
navigate对应 wx.navigateTowx.navigateToMiniProgram 的功能
redirect对应 wx.redirectTo 的功能
switchTab对应 wx.switchTab 的功能
reLaunch对应 wx.reLaunch 的功能1.1.0
navigateBack对应 wx.navigateBack 的功能1.1.0
exit退出小程序,target="miniProgram"时生效2.1.0

9.1.4 模块化

可以将一些公共的代码抽离成为一个单独的 js 文件,作为一个模块。

  • 方式1:使用commonjs规范,模块只有通过 module.exports 或者 exports 才能对外暴露接口。
// pages/test/common.js
function sayHello(name) {
  console.log(`Hello ${name} !`)
}
function sayGoodbye(name) {
  console.log(`Goodbye ${name} !`)
}

module.exports.sayHello = sayHello
exports.sayGoodbye = sayGoodbye
// pages/test/test.js
var common = require('common.js')
Page({
  ...,
	onLoad () {
		common.sayHello('千锋教育')
		common.sayGoodbye('吴大勋')
	},
  ...
})
  • 方式2:使用es6模块化规范
// pages/test/es6md.js
export function sayHello(name) {
  console.log(`Hello ${name} !`)
}
export function sayGoodbye(name) {
  console.log(`Goodbye ${name} !`)
}
// pages/test/test.js
import { sayHello, sayGoodbye } from './es6md'
Page({
	...,
	onLoad () {
		sayHello('千锋教育 - 太原')
    sayGoodbye('吴大勋 - HTML5')
	},
    ...
})

9.2 组件

developers.weixin.qq.com/miniprogram…

搭建小程序页面时,参照组件篇章

通过给 app.json的pages选项添加pages/com/com测试小程序的常用组件

9.3 API

developers.weixin.qq.com/miniprogram…

通过小程序调用微信的功能时,参照API章节

通过给 app.json的pages选项添加pages/api/api测试小程序的常用组件

10小程序的自定义组件

developers.weixin.qq.com/miniprogram…

pages/com/components/child