
其实我第一次碰小程序时,也踩过一模一样的坑——上来就抄github上的热门源码,结果抄了个壳子,根本不懂里面的逻辑,改个按钮颜色都要调两小时。后来摸透了底层逻辑再回头看,才发现:不是源码难,是你没搞懂“框架到底怎么工作”,就像没学过开车就敢上高速,不翻车才怪。
先搞懂小程序框架的底层逻辑,别上来就抄源码
要搭好小程序框架,得先把“小程序到底是啥”整明白——它不是网页,也不是原生APP,是微信客户端自带的“容器”里跑的应用,核心是“双线程分离”的架构。我用朋友卖生鲜的场景给你翻译翻译:
你可以把小程序想成两家配合干活的小店:
一家是“门面店”(渲染层):负责把菜价表、下单按钮这些内容摆出来,用的是WXML(相当于网页的HTML)和WXSS(相当于CSS)——就像你朋友门店里贴的价目表,顾客能直接看见;
另一家是“后台仓库”(逻辑层):负责管库存、算价格、接订单,用的是JavaScript——比如顾客点了“2斤苹果”,后台要查库存够不够,再算总价;
这两家店不能直接传消息,得通过微信客户端这个“快递员”传话——比如后台仓库把“苹果库存剩5斤”的消息发给快递员,快递员再转给门面店,门面店就把价目表上的“库存充足”改成“仅剩5斤”。
这就是小程序最核心的逻辑:渲染层负责“显示”,逻辑层负责“计算”,两者通过微信客户端通信。我之前犯过一个低级错误:在门面店(WXML)里写了个JS函数想查库存,结果怎么点都没反应——后来查微信官方文档(https://developers.weixin.qq.com/miniprogram/dev/framework/quickstart/framework.htmlnofollow)才知道:渲染层和逻辑层是分开的,JS函数得写在逻辑层(.js文件)里,想让门面店调用,得用“自定义事件”或者“全局方法”传消息。
搞懂这个逻辑,你再看源码就不会懵了——比如开源框架里的app.js
为什么要写App()
函数?因为它是整个小程序的“入口”,负责初始化全局数据(比如用户登录状态);pages/index/index.js
里的Page()
函数是啥?那是首页的逻辑层,负责管首页的所有数据(比如今日推荐的蔬菜列表)和交互(比如“加入购物车”按钮的点击事件)。
记住:源码是“结果”,逻辑是“原因”——先搞懂“为什么要这么写”,再抄“怎么写”,才不会抄错。
从0到1搭框架的具体步骤,附可复用源码模板
我把朋友那个生鲜小程序的框架拆成了“3步搭建法”,每一步都附可直接用的源码,你跟着做,半天就能搭出个能跑的基础框架。
第一步:先搭“骨架”——初始化项目结构
小程序的项目结构其实特别规矩,就像你朋友卖生鲜的货架:得把蔬菜、水果、肉类分开摆,顾客才好找。我帮你整理了个最实用的项目结构模板,你直接复制用:
文件夹/文件 | 作用说明 |
---|---|
pages | 放所有页面(比如首页、订单页、个人中心),每个页面有4个文件:.js(逻辑)、.wxml(结构)、.wxss(样式)、.json(配置) |
components | 放可复用的组件(比如顶部导航栏、底部tab栏、商品卡片),组件能减少重复代码——比如所有页面都要用的导航栏,做一个组件就行 |
utils | 放工具函数(比如封装的网络请求、时间格式化函数),相当于“万能工具盒” |
app.js/app.json/app.wxss | 全局配置:app.js初始化全局数据,app.json注册所有页面、设置窗口样式,app.wxss写全局样式(比如所有页面的字体颜色) |
project.config.json | 项目配置(比如微信开发者工具的编译设置),不用改,工具会自动生成 |
举个例子:你要做“首页”,就在pages
文件夹里建index
文件夹,里面建4个文件:
index.wxml
(结构):写首页的布局,比如
放商品列表; index.wxss
(样式):给商品列表加样式,比如.goods-list { display: flex; flex-wrap: wrap; }
; index.js
(逻辑):写Page({ data: { goodsList: [] }, onLoad() { this.getGoodsList() } })
,负责加载商品数据; index.json
(配置):写{ "navigationBarTitleText": "今日生鲜" }
,设置首页的导航栏标题。 踩坑提示:新建页面后,一定要在app.json
的pages
数组里加路径!比如"pages/index/index"
——微信小程序是“配置驱动”的,你建了页面不告诉它,它根本找不到。我朋友第一次就忘加这个,结果点首页直接跳404,以为代码错了,其实是配置漏了。
第二步:做“工具包”——封装重复逻辑,别写冗余代码
做小程序最烦的就是“重复写同样的代码”——比如每个页面都要调接口查数据,总不能每次都写wx.request
吧?我帮朋友封装了个通用网络请求函数,放在utils/request.js
里,你直接复制用:
// utils/request.js
const BASE_URL = 'https://你的服务器域名/api'; // 改成你自己的接口地址
// 封装wx.request,返回Promise,方便用async/await
const request = (url, method = 'GET', data = {}) => {
return new Promise((resolve, reject) => {
wx.showLoading({ title: '加载中...', mask: true }); // 显示加载提示,mask防止用户点其他地方
wx.request({
url: BASE_URL + url,
method,
data,
header: {
'Content-Type': 'application/json',
'token': wx.getStorageSync('token') || '' // 带用户登录态,没有就传空
},
success: (res) => {
wx.hideLoading();
// 假设接口返回的格式是{ code: 200, msg: '成功', data: {} }
if (res.data.code === 200) {
resolve(res.data.data); // 只把有用的data返回
} else {
wx.showToast({ title: res.data.msg, icon: 'none' }); // 提示错误信息
reject(res.data);
}
},
fail: (err) => {
wx.hideLoading();
wx.showToast({ title: '网络错误,请重试', icon: 'none' });
reject(err);
}
});
});
};
module.exports = request; // 导出函数,方便其他页面引入
用这个函数调接口,比直接写wx.request
省一半代码——比如首页要查“今日推荐蔬菜”,只需要在pages/index/index.js
里写:
// 引入封装的request函数
const request = require('../../utils/request.js');
Page({
data: {
goodsList: [] // 存储商品列表
},
onLoad() {
this.getGoodsList(); // 页面加载时调用查商品函数
},
// 查商品列表的函数
async getGoodsList() {
try {
const data = await request('/goods/list', 'GET', { type: 'vegetable' });
this.setData({ goodsList: data }); // 把接口返回的数据存到data里,页面会自动更新
} catch (err) {
console.log('查商品失败:', err);
}
}
});
为什么要封装? 我之前帮朋友做小程序时,没封装请求,结果每个页面都要写wx.showLoading
、wx.hideLoading
,改个加载提示的文字,得改10个页面——封装之后,只需要改request.js
里的wx.showLoading
,所有页面都同步变了,省了超多时间。
第三步:做“积木”——封装可复用组件,减少重复劳动
朋友的生鲜小程序里,“商品卡片”是每个页面都要用的(首页、分类页、搜索结果页),如果每个页面都写一遍wxml
和wxss
,得浪费半天时间。我帮他做了个商品卡片组件,放在components/GoodsCard
里,你直接用:
先建components/GoodsCard
文件夹,里面建4个文件:
GoodsCard.wxml
(组件结构): GoodsCard.wxss
{{ goods.name }}
¥{{ goods.price }}
/{{ goods.unit }}
(组件样式):
css
.goods-card {
width: 48%;
margin: 1%;
background: #fff;
border-radius: 8rpx;
padding: 10rpx;
box-shadow: 0 2rpx 4rpx rgba(0,0,0,0.1);
}
.goods-img {
width: 100%;
height: auto;
border-radius: 8rpx;
}
.goods-name {
font-size: 24rpx;
margin: 10rpx 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.goods-price {
font-size: 26rpx;
color: #ff5722;
}
.unit {
font-size: 20rpx;
color: #999;
margin-left: 5rpx;
}
GoodsCard.js
(组件逻辑):
javascript
Component({
properties: {
goods: { // 父组件传给子组件的“商品数据”
type: Object,
value: {} // 默认值是空对象
}
},
methods: {
// 点击商品卡片的事件,传给父组件
onCardTap() {
this.triggerEvent('cardTap', { goodsId: this.data.goods.id }); // 触发“cardTap”事件,带商品ID
}
}
});
GoodsCard.json
(组件配置):
json
{
"component": true, // 声明这是个组件
"usingComponents": {} // 组件里用其他组件的话,在这里注册
}
pages/index/index.wxml
然后在首页(
)里用这个组件:
xml
<!-
<!-
wx:for=”{{ goodsList }}”
wx:key=”id”
goods=”{{ item }}”
bind:cardTap=”onGoodsTap”
>
再在首页逻辑层(
pages/index/index.js)里处理点击事件:
javascript
Page({
// …其他代码
onGoodsTap(e) {
const goodsId = e.detail.goodsId; // 拿到组件传过来的商品ID
wx.navigateTo({ url: /pages/goodsDetail/goodsDetail?id=${goodsId} }); // 跳转到商品详情页
}
});
这个组件有多好用? 朋友的小程序里,首页、分类页、搜索页都用了这个卡片,只改了点样式(比如分类页的卡片加了个“热销”标签),省了至少300行代码——你要是做电商、餐饮、生鲜类小程序,直接拿这个组件用,绝对省事。
踩过的坑全告诉你,别再走我走过的弯路
我帮朋友搭框架时,踩了5个超蠢的坑,每一个都让我对着电脑骂过街——现在告诉你,别再犯:
data里的
count从1改成2,不能写
this.data.count = 2,得写
this.setData({ count: 2 })——因为
setData会通知渲染层“数据变了,赶紧更页面”,直接改数据,页面不会更,我之前改了10次
count,页面还是显示1,差点把电脑砸了。
properties),但子组件不能直接改父组件的数据——比如子组件想让父组件改
count,得用
this.triggerEvent触发事件,让父组件自己改。我之前做底部tab栏时,直接在子组件里改了父组件的
currentTab,结果页面跳来跳去,差点把用户搞晕。
request.js,要写
../../utils/request.js,别写
/utils/request.js——小程序不识别绝对路径,我之前写绝对路径,结果报错“文件找不到”,查了半小时才发现是路径的问题。
let或
const代替——
var会变量提升,容易搞混作用域,我之前用
var声明了个
index变量,结果循环里的函数全拿到最后一个
index,差点把商品列表搞成重复的。
其实小程序框架开发真的没那么难——先搞懂“为什么”,再学“怎么写”,最后用“组件和封装”省时间。你要是按我上面说的步骤搭框架,再结合自己的业务改一改,不出一周就能做出个能用的小程序。
对了,我把朋友那个生鲜小程序的完整基础框架源码整理了一份,里面有导航栏、tab栏、商品卡片、网络请求这些基础组件,需要的话可以私我要。要是搭框架时遇到问题,比如“组件传值没反应”“接口调不通”,直接在评论区留问题,我帮你排查—— 踩过坑的人,最懂怎么绕坑。
首先肯定得装微信开发者工具啊,这是微信官方出的“小程序开发必备神器”——你写的代码能不能跑通?点个按钮会不会没反应?都得用它调试;想在手机上看真实效果?扫一下工具里的二维码就行;最后要上线审核?也得通过它上传代码。我第一次做小程序时,没装这个工具,直接用记事本写代码,结果连“页面找不到”的错都查不出来,后来装了开发者工具,控制台一点开,红色报错直接指着“pages数组没注册页面”,当场就改好了,省了我半天时间。
然后写代码推荐用VS Code,比微信开发者工具自带的编辑器好用太多了——微信的编辑器总有点卡,自动补全功能也不太灵,比如打个“wx.”,它半天弹不出可选的API;换成VS Code就不一样了,装个Prettier插件,代码格式一键就能调整齐,不用自己手动对齐缩进;再装个ESLint,像漏写分号、变量名拼错(比如把“goodsList”写成“goodList”)这种低级错误,它都会在代码旁边标红提示。我之前帮朋友改生鲜小程序的代码,就是ESLint帮我找出了“request”函数里漏传的“header”参数,不然接口一直调不通,还得查半天原因。
还有啊,别乱搜那些“XX小程序开发教程”的博客,很多都是过时或者抄来的内容,想搞懂框架逻辑或者API用法,直接看微信官方文档(https://developers.weixin.qq.com/miniprogram/dev/framework/nofollow)最靠谱——比如我之前搞不懂“双线程分离”到底是啥,查官方文档里的“框架原理”部分,它用“渲染层负责显示、逻辑层负责计算,两者通过微信客户端通信”的例子一讲,我立刻就明白了;还有一次想知道“wx.navigateTo”和“wx.redirectTo”的区别,官方文档里直接写了“navigateTo是保留当前页面,点返回能退回去;redirectTo是关闭当前页面,返回直接上一级”,比看十篇博客都清楚。我之前踩过坑,搜了篇博客说“小程序能直接操作DOM”,结果按那方法写了代码,控制台全是红报错,后来查官方文档才知道,小程序根本不能直接操作DOM,得用setData更新数据,当场就把代码改对了。
直接抄GitHub上的热门小程序源码为什么容易翻车?
因为热门源码是别人的“成品”,你没搞懂它的底层逻辑(比如双线程架构、数据通信方式),就像没学过开车直接开别人的车——改个按钮颜色都不知道该动哪行代码,出了“点击没反应”“数据不更新”的问题,根本找不到原因。文章里朋友抄源码做首页就是例子:抄了壳子,不懂逻辑,结果按钮没反应还报错。
小程序“双线程分离”架构对开发有什么具体影响?
最直接的影响是“不能直接操作页面元素”——比如你想改商品价格的文字颜色,不能像网页那样用JS直接选DOM元素改,得通过setData
更新逻辑层的数据,再让微信客户端把新数据传给渲染层。 事件交互(比如点击按钮查库存)也得通过微信客户端“传话”,不能直接调用逻辑层函数。
为什么要封装小程序的组件?直接写页面不行吗?
直接写页面会重复造轮子——比如生鲜小程序的“商品卡片”要在首页、分类页、搜索页都用,如果每个页面都写一遍wxml
和wxss
,不仅费时间,以后要改卡片样式(比如加“热销”标签),还得改三个页面。封装成组件后,只需要改组件代码,所有用了这个组件的页面都会同步更新,省至少30%的时间。
小程序接口请求为什么要封装成工具函数?直接用wx.request不好吗?
直接用wx.request
会写很多重复代码——每个接口都要写“加载提示”“错误提示”“带登录态”,改个加载提示的文字都要改所有接口。封装成工具函数(比如文章里的request
函数),可以统一处理这些逻辑:要加“加载中”提示,只需要改工具函数里的wx.showLoading
;要换接口域名,只需要改工具函数里的BASE_URL
,省心太多。
搭建小程序框架需要准备哪些工具?
必备工具是微信开发者工具(微信官方出的,能调试、预览、上传小程序);写代码推荐用VS Code(比微信开发者工具的编辑器更好用,还有Prettier、ESLint之类的插件帮你格式化代码);另外要常看微信官方文档(https://developers.weixin.qq.com/miniprogram/dev/framework/nofollow),里面有最权威的框架逻辑和API说明,比乱搜博客靠谱。