WeChat Mini Program Development: Architecture, Components, and API Integration
WeChat Mini Programs operate within a structured environment requiring specific directory conventions. Upon initialization, the framework establishes the following hierarchy:
project-root/
├── pages/ # Route containers
│ └── index/
│ ├── index.js # Page logic controller
│ ├── index.json # Page-level configuration
│ ├── index.wxml # View template
│ └── index.wxss # Scoped styles
├── components/ # Reusable UI modules
├── utils/ # Service utilities
├── app.js # Application lifecycle
├── app.json # Global manifest
├── app.wxss # Global stylesheet
└── project.config.json # Development environment
Configuration Architecture
Global behavior is governed through app.json, which registers routes and defines window presentation defaults:
{
"pages": [
"pages/landing/landing",
"pages/catalog/catalog",
"pages/cart/cart",
"pages/account/account"
],
"window": {
"navigationBarBackgroundColor": "#ffffff",
"navigationBarTitleText": "Commerce App",
"navigationBarTextStyle": "black",
"enablePullDownRefresh": false
},
"tabBar": {
"color": "#999999",
"selectedColor": "#ff3300",
"backgroundColor": "#fafafa",
"borderStyle": "white",
"list": [
{
"pagePath": "pages/landing/landing",
"text": "Home",
"iconPath": "/static/tab-home.png",
"selectedIconPath": "/static/tab-home-active.png"
},
{
"pagePath": "pages/catalog/catalog",
"text": "Browse",
"iconPath": "/static/tab-browse.png",
"selectedIconPath": "/static/tab-browse-active.png"
}
]
}
}
Individual pages override global settings via their local page.json files, enabling granular control over navigation appearance and gesture behaviors.
WXML Templating System
The view layer employs WXML, a declarative syntax supporting data binding and directiev-based rendering:
// landing.js
Page({
data: {
greeting: "Hello WeChat",
categories: ['electronics', 'apparel', 'home'],
isAuthenticated: false
}
})
<!-- landing.wxml -->
<view class="container">
<text class="header">{{greeting}}</text>
<view class="category-list">
<block wx:for="{{categories}}" wx:key="*this" wx:for-item="category">
<view class="tag">{{category}}</view>
</block>
</view>
<view wx:if="{{isAuthenticated}}" class="member-badge">
Premium Member
</view>
<view wx:else class="guest-notice">
Sign in for benefits
</view>
</view>
Template reusability is achieved through definition blocks:
<template name="productCard">
<view class="card">
<image src="{{thumbnail}}" mode="aspectFill"/>
<view class="meta">
<text class="name">{{productName}}</text>
<text class="price">${{unitPrice}}</text>
</view>
</view>
</template>
<template is="productCard" data="{{...selectedItem}}"/>
Template scope management utilizes import for encapsulated references or include for code injection without template logic duplicasion.
Application Lifecycle
The global application instance manages state across sessions:
// app.js
App({
globalState: {
apiBaseUrl: 'https://service.example.com',
userSession: null,
cartCache: []
},
onLaunch(launchOptions) {
// Initialize analytics and validation
this.validateSession()
},
onShow() {
// Resume background tasks
},
onHide() {
// Persist temporary state
},
validateSession() {
// Authentication logic
}
})
Access the application context from any module:
const appInstance = getApp()
console.log(appInstance.globalState.apiBaseUrl)
Page Lifecycle Management
Pages implement specific hooks for state transitions:
Page({
data: {
contentList: [],
paginationCursor: 1,
hasMoreContent: true
},
onLoad(routeParameters) {
// Initial data population
this.fetchInitialData()
},
onReady() {
// First render completion
},
onShow() {
// Re-activation from background
},
onHide() {
// Deactivation
},
onUnload() {
// Cleanup resources
},
onPullDownRefresh() {
// Reset pagination and refresh
this.setData({ paginationCursor: 1 })
this.fetchInitialData().finally(() => {
wx.stopPullDownRefresh()
})
},
onReachBottom() {
// Infinite scroll implementation
if (this.data.hasMoreContent) {
this.loadAdditionalItems()
}
},
onShareAppMessage() {
return {
title: 'Check this collection',
path: '/pages/landing/landing'
}
}
})
Network Abstraction Layer
Centralize API communication for maintainability:
// utils/apiService.js
const BASE_ENDPOINT = 'https://api.service.io'
class ApiService {
request(path, payload = {}, method = 'GET') {
return new Promise((resolve, reject) => {
wx.request({
url: `${BASE_ENDPOINT}${path}`,
method,
data: payload,
header: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${this.getToken()}`
},
success: (response) => resolve(response.data),
fail: (error) => reject(error)
})
})
}
fetchFeed(page) {
return this.request('/feed', { page })
}
fetchItemDetail(id) {
return this.request(`/items/${id}`)
}
getToken() {
return wx.getStorageSync('accessToken') || ''
}
}
export default new ApiService()
Integration within page logic:
import apiService from '../../utils/apiService.js'
Page({
async fetchInitialData() {
try {
const response = await apiService.fetchFeed(1)
this.setData({
contentList: response.items
})
} catch (err) {
wx.showToast({ title: 'Load failed', icon: 'error' })
}
}
})
Component Development
Custom components encapsulate complex UI patterns:
// components/mediaGrid/mediaGrid.js
Component({
properties: {
mediaItems: {
type: Array,
value: [],
observer(newVal) {
// React to data changes
this.processMedia(newVal)
}
},
layoutMode: {
type: String,
value: 'grid'
}
},
data: {
processedItems: []
},
methods: {
processMedia(rawData) {
const formatted = rawData.map(item => ({
...item,
displayUrl: item.cdnUrl + '?thumbnail=300'
}))
this.setData({ processedItems: formatted })
},
handleTap(event) {
const index = event.currentTarget.dataset.index
this.triggerEvent('mediaselect', {
index,
item: this.data.processedItems[index]
})
}
}
})
Registration requires declaration in the consuming page's configuration:
{
"usingComponents": {
"media-grid": "/components/mediaGrid/mediaGrid"
}
}
Communication Patterns
Parent components transmit data via attribute binding:
<media-grid
mediaItems="{{galleryPhotos}}"
layoutMode="masonry"
bind:mediaselect="onPhotoSelected" />
Child components emit events upward:
// Parent handler
onPhotoSelected(event) {
const { item } = event.detail
wx.navigateTo({
url: `/pages/viewer/viewer?url=${encodeURIComponent(item.url)}`
})
}
Navigation Strategies
Declarative routing maintains history stack:
<navigator
url="/pages/detail/detail?sku={{productCode}}"
open-type="navigate"
hover-class="navigator-hover">
<view class="product-row">
<text>View Details</text>
</view>
</navigator>
Imperative navigation enables dynamic routing:
navigateToDetail(sku) {
wx.navigateTo({
url: `/pages/detail/detail?sku=${sku}`,
events: {
// Listen for data from target page
acceptDataFromDetail(data) {
console.log('Received:', data)
}
},
success: (res) => {
// Pass data to target page
res.eventChannel.emit('sendDataToDetail', { preview: true })
}
})
}
switchToMainTab() {
wx.switchTab({ url: '/pages/account/account' })
}
replaceCurrentPage() {
wx.redirectTo({ url: '/pages/login/login' })
}
navigateBackTo(delta = 1) {
wx.navigateBack({ delta })
}
Interaction Enhancements
Implemant smooth scrolling behaviors:
returnToTop() {
wx.pageScrollTo({
scrollTop: 0,
duration: 400,
easingFunction: 'easeInOut'
})
}
Enable pull-to-refresh via page configuration:
{
"enablePullDownRefresh": true,
"backgroundTextStyle": "dark",
"backgroundColor": "#f5f5f5"
}
Handle pagination in scroll events:
async loadAdditionalItems() {
const nextPage = this.data.paginationCursor + 1
try {
const response = await apiService.fetchFeed(nextPage)
if (response.items.length === 0) {
this.setData({ hasMoreContent: false })
wx.showToast({ title: 'End of list', icon: 'none' })
return
}
this.setData({
contentList: [...this.data.contentList, ...response.items],
paginationCursor: nextPage
})
} catch (err) {
wx.showToast({ title: 'Error loading', icon: 'error' })
}
}
User Authorization and Media
Request user profile data:
<button
open-type="getUserInfo"
bindgetuserinfo="processUserInfo">
Authenticate
</button>
processUserInfo(event) {
const profile = event.detail.userInfo
if (profile) {
this.setData({
nickname: profile.nickName,
avatar: profile.avatarUrl
})
wx.setStorageSync('userProfile', profile)
}
}
Geolocation requires manifest permissions:
{
"permission": {
"scope.userLocation": {
"desc": "Location services enable delivery tracking"
}
}
}
Image selection and preview capabilities:
selectProfilePicture() {
wx.chooseMedia({
count: 1,
mediaType: ['image'],
sourceType: ['album', 'camera'],
success: (res) => {
const tempPath = res.tempFiles[0].tempFilePath
this.uploadAvatar(tempPath)
}
})
}
previewImageGallery(event) {
const currentUrl = event.currentTarget.dataset.src
const allUrls = this.data.gallery.map(img => img.highResUrl)
wx.previewImage({
current: currentUrl,
urls: allUrls,
showmenu: true
})
}
Utility Access
Open system settings for revoked permissions:
openAuthorizationSettings() {
wx.openSetting({
success: (res) => {
console.log('Updated permissions:', res.authSetting)
if (res.authSetting['scope.userLocation']) {
this.initializeLocationServices()
}
}
})
}