Skip to content

页面开发

深入学习小程序页面开发的核心技术,包括页面结构、数据绑定、事件处理和生命周期管理。

📄 页面基础结构

页面文件组成

每个小程序页面由四个文件组成:

pages/index/
├── index.js    # 页面逻辑
├── index.json  # 页面配置
├── index.wxml  # 页面结构
└── index.wxss  # 页面样式

页面逻辑 (index.js)

javascript
Page({
  // 页面初始数据
  data: {
    title: '欢迎使用小程序',
    userInfo: null,
    hasUserInfo: false,
    canIUse: wx.canIUse('button.open-type.getUserInfo'),
    list: [],
    loading: false,
    page: 1,
    hasMore: true
  },

  // 生命周期函数--监听页面加载
  onLoad: function (options) {
    console.log('页面加载', options)
    this.initPage()
  },

  // 生命周期函数--监听页面初次渲染完成
  onReady: function () {
    console.log('页面初次渲染完成')
  },

  // 生命周期函数--监听页面显示
  onShow: function () {
    console.log('页面显示')
  },

  // 生命周期函数--监听页面隐藏
  onHide: function () {
    console.log('页面隐藏')
  },

  // 生命周期函数--监听页面卸载
  onUnload: function () {
    console.log('页面卸载')
  },

  // 页面相关事件处理函数--监听用户下拉动作
  onPullDownRefresh: function () {
    console.log('下拉刷新')
    this.refreshData()
  },

  // 页面上拉触底事件的处理函数
  onReachBottom: function () {
    console.log('上拉触底')
    this.loadMoreData()
  },

  // 用户点击右上角分享
  onShareAppMessage: function () {
    return {
      title: '分享标题',
      path: '/pages/index/index',
      imageUrl: '/images/share.jpg'
    }
  },

  // 自定义方法
  initPage() {
    this.setData({
      loading: true
    })
    this.loadData()
  },

  loadData() {
    // 模拟API请求
    setTimeout(() => {
      this.setData({
        list: [
          { id: 1, title: '项目1', desc: '描述1' },
          { id: 2, title: '项目2', desc: '描述2' },
          { id: 3, title: '项目3', desc: '描述3' }
        ],
        loading: false
      })
      wx.stopPullDownRefresh()
    }, 1000)
  },

  refreshData() {
    this.setData({
      page: 1,
      list: [],
      hasMore: true
    })
    this.loadData()
  },

  loadMoreData() {
    if (!this.data.hasMore || this.data.loading) return
    
    this.setData({
      loading: true,
      page: this.data.page + 1
    })
    
    // 模拟加载更多数据
    setTimeout(() => {
      const newList = [
        { id: Date.now(), title: '新项目', desc: '新描述' }
      ]
      
      this.setData({
        list: [...this.data.list, ...newList],
        loading: false,
        hasMore: newList.length > 0
      })
    }, 1000)
  },

  // 事件处理函数
  onItemTap(e) {
    const { item } = e.currentTarget.dataset
    console.log('点击项目', item)
    
    wx.navigateTo({
      url: `/pages/detail/detail?id=${item.id}`
    })
  },

  onButtonTap() {
    wx.showToast({
      title: '按钮被点击',
      icon: 'success'
    })
  }
})

页面结构 (index.wxml)

xml
<!--pages/index/index.wxml-->
<view class="container">
  <!-- 头部区域 -->
  <view class="header">
    <text class="title">{{title}}</text>
    <button class="btn" bindtap="onButtonTap">点击按钮</button>
  </view>

  <!-- 列表区域 -->
  <view class="list-container">
    <view 
      class="list-item" 
      wx:for="{{list}}" 
      wx:key="id"
      data-item="{{item}}"
      bindtap="onItemTap"
    >
      <view class="item-title">{{item.title}}</view>
      <view class="item-desc">{{item.desc}}</view>
    </view>
    
    <!-- 空状态 -->
    <view class="empty" wx:if="{{!loading && list.length === 0}}">
      <text>暂无数据</text>
    </view>
    
    <!-- 加载状态 -->
    <view class="loading" wx:if="{{loading}}">
      <text>加载中...</text>
    </view>
  </view>
</view>

页面样式 (index.wxss)

css
/* pages/index/index.wxss */
.container {
  padding: 20rpx;
  background-color: #f5f5f5;
  min-height: 100vh;
}

.header {
  background: white;
  padding: 30rpx;
  border-radius: 12rpx;
  margin-bottom: 20rpx;
  box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.1);
}

.title {
  font-size: 36rpx;
  font-weight: bold;
  color: #333;
  display: block;
  margin-bottom: 20rpx;
}

.btn {
  background-color: #07c160;
  color: white;
  border: none;
  border-radius: 8rpx;
  padding: 20rpx 40rpx;
  font-size: 28rpx;
}

.list-container {
  background: white;
  border-radius: 12rpx;
  overflow: hidden;
}

.list-item {
  padding: 30rpx;
  border-bottom: 1rpx solid #f0f0f0;
  transition: background-color 0.3s;
}

.list-item:last-child {
  border-bottom: none;
}

.list-item:active {
  background-color: #f5f5f5;
}

.item-title {
  font-size: 32rpx;
  color: #333;
  margin-bottom: 10rpx;
  font-weight: 500;
}

.item-desc {
  font-size: 28rpx;
  color: #666;
  line-height: 1.5;
}

.empty, .loading {
  padding: 60rpx;
  text-align: center;
  color: #999;
  font-size: 28rpx;
}

页面配置 (index.json)

json
{
  "navigationBarTitleText": "首页",
  "navigationBarBackgroundColor": "#ffffff",
  "navigationBarTextStyle": "black",
  "backgroundColor": "#f5f5f5",
  "backgroundTextStyle": "light",
  "enablePullDownRefresh": true,
  "onReachBottomDistance": 50,
  "usingComponents": {
    "custom-loading": "/components/loading/index"
  }
}

🔄 数据绑定

基础数据绑定

xml
<!-- 文本绑定 -->
<text>{{message}}</text>

<!-- 属性绑定 -->
<view id="item-{{id}}">内容</view>

<!-- 控制属性 -->
<view wx:if="{{condition}}">条件渲染</view>
<checkbox checked="{{false}}"></checkbox>

<!-- 关键字需要在花括号内 -->
<checkbox checked="{{true}}"></checkbox>

运算表达式

xml
<!-- 三元运算 -->
<view hidden="{{flag ? true : false}}">Hidden</view>

<!-- 算数运算 -->
<view>{{a + b}} + {{c}} + d</view>

<!-- 逻辑判断 -->
<view wx:if="{{length > 5}}">长度大于5</view>

<!-- 字符串运算 -->
<view>{{"hello" + name}}</view>

<!-- 数据路径运算 -->
<view>{{object.key}} {{array[0]}}</view>

条件渲染

xml
<!-- wx:if -->
<view wx:if="{{condition}}">True</view>
<view wx:elif="{{condition2}}">Elif</view>
<view wx:else>Else</view>

<!-- hidden -->
<view hidden="{{condition}}">Hidden</view>

<!-- block wx:if -->
<block wx:if="{{true}}">
  <view>view1</view>
  <view>view2</view>
</block>

列表渲染

xml
<!-- 基础列表渲染 -->
<view wx:for="{{array}}" wx:key="*this">
  {{index}}: {{item}}
</view>

<!-- 自定义索引和当前项变量名 -->
<view wx:for="{{array}}" wx:for-index="idx" wx:for-item="itemName" wx:key="*this">
  {{idx}}: {{itemName}}
</view>

<!-- 对象列表渲染 -->
<view wx:for="{{objectArray}}" wx:key="id">
  {{item.id}}: {{item.name}}
</view>

<!-- 嵌套列表渲染 -->
<view wx:for="{{[1, 2, 3, 4, 5, 6, 7, 8, 9]}}" wx:for-item="i" wx:key="*this">
  <view wx:for="{{[1, 2, 3, 4, 5, 6, 7, 8, 9]}}" wx:for-item="j" wx:key="*this">
    <view wx:if="{{i <= j}}">{{i}} * {{j}} = {{i * j}}</view>
  </view>
</view>

🎯 事件处理

事件绑定

xml
<!-- 点击事件 -->
<view bindtap="tapHandler">点击我</view>

<!-- 阻止事件冒泡 -->
<view catchtap="tapHandler">阻止冒泡</view>

<!-- 互斥事件绑定 -->
<view mut-bind:tap="tapHandler">互斥绑定</view>

<!-- 事件捕获 -->
<view capture-bind:tap="tapHandler">事件捕获</view>
<view capture-catch:tap="tapHandler">捕获且阻止冒泡</view>

事件对象

javascript
Page({
  tapHandler(e) {
    console.log('事件对象', e)
    
    // 事件类型
    console.log('事件类型', e.type)
    
    // 时间戳
    console.log('时间戳', e.timeStamp)
    
    // 触发事件的组件
    console.log('target', e.target)
    
    // 绑定事件的组件
    console.log('currentTarget', e.currentTarget)
    
    // 额外信息
    console.log('detail', e.detail)
    
    // 触摸事件信息
    console.log('touches', e.touches)
    console.log('changedTouches', e.changedTouches)
  }
})

数据传递

xml
<!-- 通过 data-* 传递数据 -->
<view 
  bindtap="itemTap" 
  data-id="{{item.id}}" 
  data-item="{{item}}"
>
  {{item.name}}
</view>
javascript
Page({
  itemTap(e) {
    const { id, item } = e.currentTarget.dataset
    console.log('ID:', id)
    console.log('Item:', item)
  }
})

🔄 页面生命周期

生命周期流程

页面加载 → onLoad

页面显示 → onShow

页面渲染完成 → onReady

页面隐藏 → onHide

页面卸载 → onUnload

生命周期详解

javascript
Page({
  // 页面加载时触发,只会调用一次
  onLoad(options) {
    console.log('页面加载', options)
    // 获取页面参数
    const { id, type } = options
    
    // 初始化页面数据
    this.initPageData()
    
    // 设置页面标题
    wx.setNavigationBarTitle({
      title: '动态标题'
    })
  },

  // 页面显示时触发,每次显示都会调用
  onShow() {
    console.log('页面显示')
    // 刷新页面数据
    this.refreshData()
    
    // 统计页面访问
    this.trackPageView()
  },

  // 页面初次渲染完成时触发,只会调用一次
  onReady() {
    console.log('页面渲染完成')
    // 获取节点信息
    this.getElementInfo()
    
    // 创建动画
    this.createAnimation()
  },

  // 页面隐藏时触发
  onHide() {
    console.log('页面隐藏')
    // 暂停音频/视频
    this.pauseMedia()
    
    // 清除定时器
    this.clearTimers()
  },

  // 页面卸载时触发
  onUnload() {
    console.log('页面卸载')
    // 清理资源
    this.cleanup()
    
    // 取消网络请求
    this.cancelRequests()
  }
})

📱 页面交互

页面跳转

javascript
Page({
  // 保留当前页面,跳转到应用内的某个页面
  navigateTo() {
    wx.navigateTo({
      url: '/pages/detail/detail?id=123',
      success: (res) => {
        console.log('跳转成功', res)
      },
      fail: (err) => {
        console.error('跳转失败', err)
      }
    })
  },

  // 关闭当前页面,跳转到应用内的某个页面
  redirectTo() {
    wx.redirectTo({
      url: '/pages/login/login'
    })
  },

  // 跳转到 tabBar 页面
  switchTab() {
    wx.switchTab({
      url: '/pages/index/index'
    })
  },

  // 关闭当前页面,返回上一页面或多级页面
  navigateBack() {
    wx.navigateBack({
      delta: 1 // 返回的页面数,如果 delta 大于现有页面数,则返回到首页
    })
  },

  // 关闭所有页面,打开到应用内的某个页面
  reLaunch() {
    wx.reLaunch({
      url: '/pages/index/index'
    })
  }
})

页面通信

javascript
// 页面A - 传递数据
Page({
  goToPageB() {
    wx.navigateTo({
      url: '/pages/pageB/pageB',
      events: {
        // 监听由子页面传递的数据
        acceptDataFromPageB: (data) => {
          console.log('收到页面B的数据', data)
          this.setData({
            dataFromB: data
          })
        }
      },
      success: (res) => {
        // 向子页面传递数据
        res.eventChannel.emit('acceptDataFromPageA', {
          message: 'Hello from Page A'
        })
      }
    })
  }
})

// 页面B - 接收和发送数据
Page({
  onLoad() {
    // 监听页面A传递的数据
    const eventChannel = this.getOpenerEventChannel()
    eventChannel.on('acceptDataFromPageA', (data) => {
      console.log('收到页面A的数据', data)
      this.setData({
        dataFromA: data
      })
    })
  },

  sendDataToPageA() {
    // 向页面A发送数据
    const eventChannel = this.getOpenerEventChannel()
    eventChannel.emit('acceptDataFromPageB', {
      result: 'Data from Page B'
    })
  }
})

🎨 页面样式

尺寸单位

css
/* rpx - 响应式像素 */
.container {
  width: 750rpx;  /* 屏幕宽度 */
  height: 200rpx;
}

/* px - 物理像素 */
.border {
  border: 1px solid #ccc;
}

/* % - 百分比 */
.full-width {
  width: 100%;
}

/* vh/vw - 视口单位 */
.full-height {
  height: 100vh;
}

样式导入

css
/* 导入外部样式 */
@import "common.wxss";

/* 全局样式变量 */
:root {
  --primary-color: #07c160;
  --text-color: #333;
  --bg-color: #f5f5f5;
}

/* 使用变量 */
.button {
  background-color: var(--primary-color);
  color: white;
}

选择器

css
/* 类选择器 */
.class-name {
  color: red;
}

/* ID选择器 */
#id-name {
  color: blue;
}

/* 标签选择器 */
view {
  display: block;
}

/* 属性选择器 */
[data-active="true"] {
  background-color: yellow;
}

/* 伪类选择器 */
.button:active {
  opacity: 0.8;
}

/* 后代选择器 */
.container .item {
  margin: 10rpx;
}

/* 子选择器 */
.list > .item {
  padding: 20rpx;
}

🔧 页面优化

性能优化

javascript
Page({
  data: {
    list: [],
    // 使用对象而不是数组存储大量数据
    itemMap: {}
  },

  // 使用节流防抖
  onScrollThrottled: throttle(function(e) {
    console.log('滚动事件', e)
  }, 100),

  // 懒加载数据
  loadDataLazily() {
    if (this.data.loading) return
    
    this.setData({ loading: true })
    
    // 分页加载
    this.loadPage(this.data.currentPage + 1)
  },

  // 使用 setData 优化
  updateList() {
    // 避免频繁调用 setData
    const updates = {}
    updates[`list[${index}].status`] = 'updated'
    updates[`list[${index}].time`] = Date.now()
    
    this.setData(updates)
  }
})

内存管理

javascript
Page({
  onLoad() {
    // 创建定时器
    this.timer = setInterval(() => {
      this.updateTime()
    }, 1000)
    
    // 创建动画
    this.animation = wx.createAnimation()
  },

  onUnload() {
    // 清理定时器
    if (this.timer) {
      clearInterval(this.timer)
      this.timer = null
    }
    
    // 清理动画
    if (this.animation) {
      this.animation = null
    }
    
    // 清理事件监听
    wx.offNetworkStatusChange()
  }
})

📚 相关文档


掌握页面开发的核心技术,构建出交互丰富、性能优异的小程序页面!🚀

连接多端,赋能创新