目录

我的第一个微信小程序开发实战总结

前言

作为一个有Go、PHP、JavaScript基础的后端开发者,这是我第一次完整开发微信小程序。通过"不德不推计分器"这个德州扑克筹码管理工具的开发,我从零开始学习了小程序的完整技术栈,并成功部署到生产环境。

这篇文章总结了我在这个过程中学到的核心技术知识,希望对其他初学者有所帮助。

项目背景

项目名称: 不德不推计分器
项目类型: 微信小程序
核心功能: 德州扑克筹码计数和房间管理系统
技术架构: 小程序前端 + Go后端 + MySQL数据库

小程序基础架构

1. 项目结构理解

小程序的项目结构非常清晰,这和我熟悉的Web项目有相似之处:

project/
├── app.js              # 小程序入口逻辑(类似main.js)
├── app.json            # 全局配置文件(类似config)
├── app.wxss            # 全局样式文件(类似global.css)
├── pages/              # 页面目录(类似views)
│   └── index/
│       ├── index.js    # 页面逻辑(类似controller)
│       ├── index.json  # 页面配置
│       ├── index.wxml  # 页面结构(类似HTML)
│       └── index.wxss  # 页面样式(类似CSS)
└── components/         # 自定义组件目录

关键理解

  • 每个页面都是一个完整的MVC结构

  • 配置文件分为全局配置(app.json)和页面配置(页面.json)

  • 小程序使用自己的文件格式:.wxml(结构)、.wxss(样式)

2. 配置系统设计

小程序的配置系统让我印象深刻,采用了双配置文件的设计:

// project.config.json (公共配置)
{
  "appid": "wxYOUR_APP_ID_HERE",
  "compileType": "miniprogram",
  "setting": {
    "es6": true,
    "postcss": true,
    "minified": true
  }
}

// project.private.config.json (私有配置)
{
  "libVersion": "3.9.0",
  "projectname": "xiaodejifenqi",
  "setting": {
    "skylineRenderEnable": true,
    "compileHotReLoad": true
  }
}

设计优势

  • 团队协作时公共配置保证环境一致

  • 私有配置允许个人定制开发环境

  • 私有配置会覆盖公共配置的同名字段

页面开发核心概念

1. 页面生命周期管理

这是我学到的最重要的概念之一。小程序的页面生命周期和我熟悉的Web应用有很大不同:

Page({
  data: {
    // 页面数据
  },
  
  onLoad(options) {
    // 页面加载时触发,只会调用一次
    // 适合:接收页面参数、初始化数据
    const { roomId } = options
    if (roomId) {
      this.loadRoomData(roomId)
    }
  },
  
  onShow() {
    // 页面显示时触发,每次显示都会调用
    // 适合:刷新数据、重新获取状态
    this.refreshUserInfo()
  },
  
  onHide() {
    // 页面隐藏时触发
    // 适合:清理定时器、暂停音视频
    if (this.data.timer) {
      clearInterval(this.data.timer)
    }
  }
})

关键要点

  • onLoad只执行一次,onShow每次显示都执行

  • 页面参数通过onLoad的options对象获取

  • 生命周期的执行顺序很重要,要合理安排初始化逻辑

2. 数据绑定与状态管理

小程序的数据绑定机制和React有相似性,但也有自己的特点:

// 错误的做法(直接修改data)
this.data.players.push(newPlayer)  // ❌ 不会触发视图更新

// 正确的做法(使用setData)
const newPlayers = [...this.data.players, newPlayer]
this.setData({
  players: newPlayers  // ✅ 会触发视图更新
})

核心规则

  • 所有数据更新必须通过setData()方法

  • 避免直接修改data中的对象和数组

  • 使用扩展运算符保持数据的不可变性

  • setData()是异步的,但大多数情况下不需要考虑回调

3. WXML模板语法

WXML的语法和我熟悉的模板引擎(如PHP的Twig)类似:

<!-- 数据绑定 -->
<text>{{userInfo.nickname}}</text>

<!-- 条件渲染 -->
<view wx:if="{{showError}}">
  <text>{{errorMessage}}</text>
</view>

<!-- 列表渲染 -->
<view wx:for="{{players}}" wx:key="id">
  <text>{{item.playerName}}: {{item.netChips}}</text>
</view>

<!-- 事件绑定 -->
<button bindtap="onCreateRoom" data-room-id="{{roomId}}">
  创建房间
</button>

重要细节

  • wx:key是必须的,类似React的key

  • 事件参数通过data-*属性传递,在事件处理函数中通过e.currentTarget.dataset获取

  • 双花括号内可以写简单的JavaScript表达式

4. 响应式布局单位 - rpx详解

这是我在学习小程序样式时学到的最重要概念之一:

/* rpx单位的实际应用 */
.container {
  width: 750rpx;        /* 占满整个屏幕宽度 */
  height: 200rpx;       /* 高度自适应不同设备 */
  padding: 20rpx;       /* 内边距自适应 */
  font-size: 28rpx;     /* 字体大小自适应 */
}

.button {
  width: 300rpx;        /* 按钮宽度占屏幕40% */
  height: 88rpx;        /* 标准按钮高度 */
}

rpx核心概念

  • 全称: Responsive Pixel(自适应像素)

  • 设计思想: 将所有设备屏幕宽度等分为750份

  • 换算公式: 1rpx = 设备屏幕宽度 / 750

不同设备的换算示例

  • iPhone6 (375px宽): 1rpx = 0.5px

  • iPhone6 Plus (414px宽): 1rpx = 0.552px

  • 微信开发者工具: 1rpx = 0.5px

实用技巧

  • 设计稿通常按750px宽度设计,可直接将px改为rpx

  • 1:1转换规则:设计稿中的750px = 小程序中的750rpx

  • 避免过小的rpx值(如1rpx、2rpx),可能导致渲染精度问题

这个单位解决了我在移动端开发中最头疼的屏幕适配问题!

5. 公共样式管理与代码优化

在开发过程中,我发现了一个严重的问题:大量的样式代码重复。通过深入分析项目的8个WXSS文件,发现了惊人的重复情况:

样式重复统计

  • align-items: center - 32次重复

  • justify-content: center - 20次重复

  • 主题渐变色 - 4个页面重复使用

  • border-radius - 18处重复

  • box-shadow - 17次类似效果

这种重复不仅增加了代码量,也让维护变得困难。书上提到的公共样式抽取方法完美解决了这个问题。

公共样式文件设计

我创建了 /styles/common.wxss 公共样式文件,采用模块化设计:

/* 导入方式 */
@import "/styles/common.wxss";

/* 布局工具类 */
.flex-center {
  display: flex;
  align-items: center;
  justify-content: center;
}

.flex-column-center {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
}

/* 主题色系统 */
.bg-primary-gradient {
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}

.bg-glass {
  background: rgba(255, 255, 255, 0.15);
  backdrop-filter: blur(10rpx);
  border: 1rpx solid rgba(255, 255, 255, 0.2);
}

/* 圆角系统 */
.radius-xs { border-radius: 8rpx; }
.radius-sm { border-radius: 12rpx; }
.radius-md { border-radius: 16rpx; }
.radius-lg { border-radius: 20rpx; }
.radius-xl { border-radius: 24rpx; }

/* 阴影系统 */
.shadow-sm { box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.08); }
.shadow-md { box-shadow: 0 6rpx 20rpx rgba(0, 0, 0, 0.12); }
.shadow-card { box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.08); }

优化效果对比

优化前的重复代码

.user-info {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  border-radius: 20rpx;
  box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.08);
  padding: 40rpx 30rpx;
  margin-bottom: 60rpx;
}

.card-container {
  display: flex;
  flex-direction: column;
  align-items: center;
  border-radius: 20rpx;
  box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.08);
  /* 几乎相同的样式又写了一遍 */
}

优化后使用工具类

<!-- WXML中使用工具类 -->
<view class="user-info flex-column-center radius-lg shadow-card p-lg mb-xl">
  <!-- 内容 -->
</view>

<view class="card-container flex-column-center radius-lg shadow-card">
  <!-- 内容 -->  
</view>
/* WXSS中只需要写个性化样式 */
.user-info {
  /* 只写这个组件独有的样式 */
  background: white;
}

公共样式的模块化结构

我将公共样式分为以下模块:

  1. 布局模块 - Flex布局的各种组合

  2. 主题模块 - 颜色、渐变、背景

  3. 几何模块 - 圆角、阴影、边框

  4. 间距模块 - Padding、Margin的标准化

  5. 字体模块 - 字号、字重的层级系统

  6. 组件模块 - 按钮、卡片、头像等通用样式

  7. 工具模块 - 各种实用的CSS工具类

实施最佳实践

命名规范

  • 语义化命名:.radius-lg.shadow-card.text-md

  • 尺寸级别:xs/sm/md/lg/xl 五级标准

  • 功能分类:布局(flex-)、主题(bg-)、几何(radius-)等前缀

使用策略

  • 新页面优先使用公共样式类

  • 旧页面渐进式重构

  • 基于公共样式进行个性化扩展

维护规范

  • 不直接修改公共样式文件

  • 新增样式先考虑是否具备通用性

  • 定期审查和优化公共样式系统

优化成果

通过实施公共样式管理,预期能够:

  • 减少60%+的重复CSS代码 (约300+行代码)

  • 提高开发效率 - 新页面开发时直接使用工具类

  • 提升维护性 - 统一修改主题色、间距只需改一处

  • 保证一致性 - 所有页面视觉元素标准化

  • 增强可扩展性 - 便于添加新的设计规范

这个优化让我深刻理解了代码复用在前端开发中的重要性,也体验到了良好架构设计带来的开发效率提升。

API集成与网络请求

1. 网络请求最佳实践

作为后端开发者,我对网络请求的错误处理特别重视。小程序的wx.request需要处理多层次的错误:

// 标准的API调用模式
wx.request({
  url: 'https://xdjsq.codekissyoung.com/api/room/create',
  method: 'POST',
  data: {
    room_name: roomName,
    chips_per_hand: chipsPerHand
  },
  success: (res) => {
    // 1. HTTP状态码检查
    if (res.statusCode === 200 && res.data) {
      // 2. 业务状态检查
      if (res.data.success) {
        // 成功处理
        this.handleSuccess(res.data)
      } else {
        // 业务错误处理
        wx.showToast({
          title: res.data.message || '操作失败',
          icon: 'none'
        })
      }
    } else {
      // HTTP错误处理
      this.handleHttpError(res.statusCode)
    }
  },
  fail: (err) => {
    // 3. 网络异常处理
    console.error('网络请求失败:', err)
    wx.showToast({
      title: '网络连接失败',
      icon: 'none'
    })
  }
})

三层错误处理机制

  1. 网络层:连接失败、超时等网络异常

  2. HTTP层:状态码非200的HTTP错误

  3. 业务层:接口返回的业务逻辑错误

2. 用户体验优化

小程序的用户体验优化主要体现在加载状态和错误提示:

// 操作前显示加载
wx.showLoading({
  title: '创建中...'
})

// API调用
wx.request({
  // ... API配置
  success: (res) => {
    wx.hideLoading()  // 隐藏加载
    if (res.data.success) {
      wx.showToast({
        title: '创建成功',
        icon: 'success',
        duration: 1500,
        complete: () => {
          // Toast完成后跳转页面
          wx.navigateTo({
            url: `/pages/room/room?roomId=${res.data.room_id}`
          })
        }
      })
    }
  },
  fail: (err) => {
    wx.hideLoading()  // 确保隐藏加载
    // 错误处理...
  }
})

组件化开发

1. 自定义组件结构

小程序的组件化开发让我想起了Vue的单文件组件:

// components/create-room-modal/create-room-modal.js
Component({
  properties: {
    // 组件属性,类似Vue的props
    show: {
      type: Boolean,
      value: false
    }
  },
  
  data: {
    // 组件内部数据
    roomName: '',
    chipsPerHand: 1000
  },
  
  methods: {
    // 组件方法
    onConfirm() {
      // 向父组件发送事件,类似Vue的$emit
      this.triggerEvent('confirm', {
        roomName: this.data.roomName,
        chipsPerHand: this.data.chipsPerHand
      })
    },
    
    onClose() {
      this.triggerEvent('close')
    }
  }
})

组件通信机制

  • 父→子:通过properties传递数据

  • 子→父:通过triggerEvent发送事件

  • 这和Vue的props/emit机制非常相似

2. 组件使用方式

// 页面配置中引入组件
{
  "usingComponents": {
    "create-room-modal": "/components/create-room-modal/create-room-modal"
  }
}
<!-- 页面中使用组件 -->
<create-room-modal 
  show="{{showCreateRoomModal}}" 
  bind:close="onCloseCreateRoomModal"
  bind:confirm="onConfirmCreateRoom">
</create-room-modal>

权限控制与安全

1. 前端权限控制

在我的项目中,实现了基于用户身份的权限控制:

// 权限检查逻辑
canOperatePlayer(playerId) {
  // 创建者可以操作所有人
  if (this.isRoomCreator()) {
    return true
  }
  // 普通玩家只能操作自己
  return playerId === this.data.currentUser.openid
}

isRoomCreator() {
  return this.data.currentUser.openid === this.data.roomInfo.creatorId
}

// 权限控制的UI表现
onTakeChips(e) {
  const { playerId } = e.currentTarget.dataset
  
  if (!this.canOperatePlayer(playerId)) {
    wx.showToast({
      title: '只能操作自己的筹码',
      icon: 'none',
      duration: 2000
    })
    return
  }
  
  // 执行操作...
}

设计原则

  • 前端权限检查提升用户体验

  • 后端必须有对应的权限验证

  • 用户友好的权限提示信息

2. 微信登录集成

微信小程序的登录机制是我学习的重点:

// 微信登录流程
autoLogin() {
  wx.login({
    success: (res) => {
      if (res.code) {
        this.loginWithCode(res.code)
      }
    },
    fail: (err) => {
      console.error('wx.login 失败:', err)
      this.generateTempUserInfo()  // 降级处理
    }
  })
}

loginWithCode(code) {
  wx.request({
    url: 'https://xdjsq.codekissyoung.com/api/user/login',
    method: 'POST',
    data: { code },
    success: (res) => {
      if (res.data.success) {
        const userInfo = {
          openid: res.data.user.openid,
          nickname: res.data.user.nickname,
          avatar_url: res.data.user.avatar_url
        }
        
        // 保存到本地存储
        wx.setStorageSync('userInfo', userInfo)
        this.setData({ userInfo })
      }
    }
  })
}

登录机制理解

  1. wx.login()获取临时code

  2. 将code发送给后端服务器

  3. 后端调用微信接口获取openid

  4. 返回用户信息并保存到本地

数据持久化策略

1. 本地存储使用

小程序的本地存储API非常简单:

// 保存数据
wx.setStorageSync('userInfo', userInfo)

// 获取数据
const savedUserInfo = wx.getStorageSync('userInfo')
if (savedUserInfo && savedUserInfo.openid) {
  this.setData({ userInfo: savedUserInfo })
}

// 清除数据
wx.removeStorageSync('userInfo')

2. 数据同步策略

我采用了"本地优先,服务器同步"的策略:

// 用户信息更新流程
onEditProfile() {
  wx.showModal({
    title: '修改昵称',
    editable: true,
    success: (res) => {
      if (res.confirm && res.content) {
        const newNickname = res.content.trim()
        
        // 1. 立即更新本地显示
        const userInfo = { ...this.data.userInfo }
        userInfo.nickname = newNickname
        this.setData({ userInfo })
        
        // 2. 保存到本地存储
        wx.setStorageSync('userInfo', userInfo)
        
        // 3. 异步同步到服务器
        this.syncUserInfoToServer(userInfo)
      }
    }
  })
}

后端API设计经验

虽然重点是小程序开发,但我也想分享一些后端API设计的经验,因为这对前端开发也很重要。

1. RESTful API设计

我的API遵循RESTful规范:

GET  /api/room/{roomId}              - 获取房间信息
POST /api/room/create                - 创建房间  
POST /api/room/join/{roomId}         - 加入房间
POST /api/room/{roomId}/chip/operate - 筹码操作
GET  /api/room/{roomId}/chip/history - 筹码历史

2. 统一响应格式

所有API都使用统一的响应格式:

{
  "success": true,
  "data": {
    "room_id": "ABC123",
    "room_name": "周末局"
  },
  "message": "创建成功"
}

这样前端可以使用统一的处理逻辑。

部署与上线经验

1. 域名配置

小程序要求所有网络请求必须使用HTTPS,并且域名需要在微信公众平台配置:

  1. 购买SSL证书并配置到服务器

  2. 在微信公众平台设置request合法域名

  3. 确保所有API调用使用HTTPS协议

2. 真机测试

开发者工具和真机环境有差异,需要重点测试:

  • 网络请求的稳定性

  • 页面跳转和参数传递

  • 用户体验和界面适配

开发中踩过的坑

1. 数据更新不生效

问题:直接修改this.data中的数据,页面不更新

// 错误
this.data.players.push(newPlayer)

// 正确  
const newPlayers = [...this.data.players, newPlayer]
this.setData({ players: newPlayers })

2. 页面参数传递

问题:页面跳转时参数传递错误

// 错误:参数会被转为字符串
wx.navigateTo({
  url: `/pages/room/room?roomData=${roomData}`
})

// 正确:复杂数据通过全局变量或重新请求
getApp().globalData.roomData = roomData
wx.navigateTo({
  url: `/pages/room/room?roomId=${roomId}`
})

3. 异步操作处理

问题:在生命周期函数中进行异步操作时机不对

// 有问题的做法
onShow() {
  // 每次显示都会重新请求,可能造成重复请求
  this.loadData()
}

// 更好的做法
onShow() {
  if (this.data.needRefresh) {
    this.loadData()
    this.setData({ needRefresh: false })
  }
}

性能优化经验

1. setData优化

// 避免频繁的setData调用
// 错误
for (let i = 0; i < 100; i++) {
  this.setData({
    [`list[${i}]`]: newValue
  })
}

// 正确:批量更新
const updates = {}
for (let i = 0; i < 100; i++) {
  updates[`list[${i}]`] = newValue
}
this.setData(updates)

2. 图片优化

我在项目中使用了DiceBear API生成用户头像,避免了用户上传图片的复杂性:

generateAvatar(id) {
  return `https://api.dicebear.com/7.x/bottts/svg?seed=${encodeURIComponent(id)}`
}

学习心得和建议

1. 从后端转前端的思维转变

作为后端开发者学习小程序,需要适应:

  • 状态管理:后端是无状态的,前端需要管理页面状态

  • 异步编程:回调、Promise的大量使用

  • 用户体验:需要考虑加载状态、错误提示、交互反馈

2. 学习建议

  1. 先理解架构:项目结构、配置文件、生命周期

  2. 掌握核心API:setData、网络请求、页面跳转

  3. 重视组件化:提高代码复用性和维护性

  4. 注重用户体验:加载状态、错误处理、交互反馈

  5. 前后端协同:API设计要考虑前端的使用便利性

3. 调试技巧

  • 使用console.log大量输出调试信息

  • 善用微信开发者工具的网络面板

  • 真机调试时开启远程调试功能

  • 利用微信开发者工具的存储面板查看本地数据

总结

通过这个项目,我从一个小程序零基础的后端开发者,成长为能够独立开发完整小程序应用的全栈开发者。小程序开发虽然有其特殊性,但核心的编程思想和我熟悉的技术栈是相通的。

核心收获

  1. 技术栈掌握:小程序前端开发 + Go后端 + MySQL数据库

  2. 架构设计:前后端分离、RESTful API、组件化开发

  3. 工程实践:错误处理、权限控制、性能优化

  4. 产品思维:用户体验、交互设计、功能完整性

这个项目现在已经部署到生产环境,具备了完整的商业应用功能。从技术学习的角度来看,它覆盖了小程序开发的核心技能,是一个很好的学习和实践项目。

希望这些经验总结能对其他初学者有所帮助。小程序开发虽然有学习曲线,但掌握了核心概念和最佳实践后,可以快速构建出功能丰富的应用。


技术栈:微信小程序 + Go + MySQL + Apache + SSL
开发时间:2025年1月