01-wechat微信小程序基础

image-20260201221415938

参考:

1. 环境准备

1.1 运行环境

运行环境 逻辑层 渲染层
iOS JavaScriptCore WKWebView
安卓 V8 chromium定制内核
小程序开发者工具 NWJS Chrome WebView

image-20260202110625791

1.2 官方小程序功能体验

可以查看到微信小程序所有的功能。

img

1.3 开发流程

  1. 注册小程序:https://mp.weixin.qq.com/cgi-bin/wx
  2. 登陆小程序后台:https://mp.weixin.qq.com/
  3. 拿到 AppID(小程序ID):【开发与服务】-【开发管理】,eg: wx51c55a4653bba442
  4. 安装开发者工具(稳定版):https://developers.weixin.qq.com/miniprogram/dev/devtools/download.html
  5. 扫码开发者工具登陆,新建小程序项目,选择目录(自动取最后一级目录作为项目名称-提前创建),填写 AppID,不使用云服务,模版默认选择官方推荐第一个即可。
    • 默认 JS Skyline 模板是微信小程序的「新一代性能版模板」,在兼容原有开发体验的前提下,实现了渲染性能的跨越式提升,是目前微信小程序开发的主流推荐选择
    • 本文示例以 JS 基础模版 为准。

image-20260202112210691

  1. 点击创建
  2. 多人开发设置:【管理】-【成员管理】添加项目成员(多人开发)、体验成员(测试人员)
  3. 预览、上传,小程序后台:【管理】-【版本管理】查看开发版本上传情况,按需 选为体验版本提交审核(上线)

image-20260202113121682

image-20260202113517796

1.4 快捷键映射

打开微信开发者工具 → 菜单栏「设置」→「通用设置」→「快捷键」;或按 Ctrl + , 直接打开设置面板。

手动配置:以下是 IDEA 高频快捷键与微信开发者工具的命令映射,直接修改即可适配开发习惯,避免冲突。

IDEA 常用快捷键 功能描述 微信开发者工具命令 修改方法
Ctrl + Alt + L(Win)/ Command + Option + L(Mac) 格式化代码 格式化文档 搜索「格式化文档」→ 右键「更改键绑定」→ 按下对应组合键 → 回车确认
Ctrl + D(Win/Mac) 复制行 复制行 搜索「复制行」→ 更改键绑定为 Ctrl + D / Command + D
Alt + Enter(Win)/ Option + Enter(Mac) 触发意图 / 快速修复 触发建议 搜索「触发建议」→ 更改键绑定为 Alt + Enter / Option + Enter
Ctrl + /(Win)/ Command + /(Mac) 单行注释 添加 / 移除行注释 搜索「添加 / 移除行注释」→ 确认或修改为对应组合键
Ctrl + Shift + /(Win)/ Command + Option + /(Mac) 块注释 添加 / 移除块注释 搜索「添加 / 移除块注释」→ 更改键绑定
  1. 搜索定位:在快捷键面板顶部搜索框输入命令关键词(如「格式化」「删除行」)快速找到目标。
  2. 修改方式:选中命令 → 右键「更改键绑定」→ 按下组合键(如 Ctrl + Alt + L)→ 回车保存。
  3. 冲突处理:若提示「已被占用」,可先修改冲突快捷键,或选择「添加额外键绑定」保留多组映射。

2. 全局配置 app.json

文件 必需 作用
app.js 小程序逻辑
app.json 小程序公共配置
app.wxss 小程序公共样式表

2.1 pages

官方文档:https://developers.weixin.qq.com/miniprogram/dev/reference/configuration/app.html#pages

页面组成:①.js-逻辑 ②.wxml-结构 ③.json-配置 ④.wxss-样式

新建页面-方式1:pages/ 目录下,新建一个目录如 demo/ 目录,在 demo/ 目录下右键【新建Page】输入页面名字,不需要输入后缀,默认生成页面组成4个文件。

新建页面-方式2:在 app.json 的 pages 数组中添加 "pages/页面名称/页面名称" 即可自动创建页面并生成文件。固定第 1 个是首页。

1
2
3
4
5
6
7
8
9
{
"pages": [ //配置不同的页面,自动生成文件
"pages/demo/demo", //固定第1个是首页
"pages/home/home",
"pages/order/order",
"pages/center/center"
],
...
}

image-20260202124900084

2.2 window

官方文档:https://developers.weixin.qq.com/miniprogram/dev/reference/configuration/app.html#window

用于设置小程序的状态栏、导航条、标题、窗口背景色。

1
2
3
4
5
6
7
8
9
10
{
...
"window": {
"navigationBarTextStyle": "white", //导航栏标题、状态栏颜色,仅支持 black / white
"navigationBarTitleText": "小布旅游助手", //导航栏标题文字内容
"navigationBarBackgroundColor": "#14c145", //导航栏背景颜色
"enablePullDownRefresh": true //是否开启全局的下拉刷新(可剪贴到页面配置中,仅单页支持下拉刷新)
},
...
}

image-20260202124942244

2.3 tabbar

官方文档:https://developers.weixin.qq.com/miniprogram/dev/reference/configuration/app.html#tabBar

如果小程序是一个多 tab 应用(客户端窗口的底部或顶部有 tab 栏可以切换页面),可以通过 tabBar 配置项指定 tab 栏的表现,以及 tab 切换时显示的对应页面。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
...
"tabBar": {
"selectedColor": "#f60", //tabbar 上的文字选中时的颜色,仅支持十六进制颜色
"backgroundColor": "#fff", //tabbar 的背景色,仅支持十六进制颜色
"borderStyle": "white", //tabbar 上边框的颜色, 仅支持 black / white
"position": "bottom", //tabbar 位置:top | bottom
"list": [ //一般配置 2-4 个,如图 4 个的效果
{
"pagePath": "pages/demo/demo", //页面路径,必须在 pages 中先定义
"text": "测试", //tab 上按钮文字
"iconPath": "images/camera.png", //图片路径,icon 大小限制为 40kb,建议尺寸为 81px * 81px,不支持网络图片
"selectedIconPath": "images/camera_light.png" //选中时的图片路径,icon 大小限制为 40kb,建议尺寸为 81px * 81px,不支持网络图片
},
...
]
},
...
}

image-20260202125411320

3. 页面配置

官方文档:https://developers.weixin.qq.com/miniprogram/dev/reference/configuration/page.html

页面中配置项在当前页面会覆盖 app.json 中相同的配置项(样式相关的配置项属于 app.json 中的 window 属性,但这里不需要额外指定 window 字段)。

如 pages/home/home

1
2
3
4
5
{
"usingComponents": {},
"navigationBarTitleText": "首页", //具体导航页标题
"enablePullDownRefresh": true //去掉 app.json 中 window 中的 下拉刷新,下方到具体的页面支持
}

4. 基础语法

4.1 标签

<view>

  • 定位:通用块级布局容器,类比 Web 中的 <div>,是小程序布局的「基础骨架」。
  • 底层:渲染层基于原生视图容器实现,无文本专属优化,主要负责承载其他标签(<view>/<text>/<image> 等)、实现页面结构布局。
  • 核心作用:划分页面区域、实现 Flex/Grid 布局、承载非文本内容,是布局的核心载体。

<text>

  • 定位:专属行内文本标签,类比 Web 中的 <span>(但比 <span> 有更多文本专属能力)。
  • 底层:渲染层基于原生文本控件实现,做了文本渲染、选中文本、复制文本等专属优化,仅用于包裹纯文本内容;
  • 核心作用:渲染页面中的文字、实现文本的专属交互(选中、复制),是文本展示的「唯一标准标签」。

<view> 是通用块级容器,主打布局承载;<text> 是专属行内文本标签,主打文本渲染与交互。

demo.wxml

1
2
3
4
5
<!--pages/demo/demo.wxml-->
<!-- 文本标签 -->
<text>{{ 10+20 }}</text>
<!-- 布局容器 -->
<view>{{ 10>20 ? 'aaa' : 'bbb' }}</view>

4.2 数据绑定

官方文档:https://developers.weixin.qq.com/miniprogram/dev/reference/wxml/data.html

demo.wxml

1
2
3
4
<view>{{myname}}</view>
<view id="my-{{ids[0]}}">动态id-{{ids[0]}}</view>
<view id="my-{{ids[1]}}">动态id-{{ids[1]}}</view>
<view id="my-{{ids[2]}}">动态id-{{ids[2]}}</view>

demo.js

1
2
3
4
5
6
7
8
9
10
11
// 注册页面的方法
Page({
/**
* 页面的初始数据
*/
data: {
myname: "Jerry", //定义变量,页面上可以 {{myname}} 使用
ids: ["aaa", "bbb", "ccc"] //定义数组变量
},
...
})

image-20260202170450838

4.3 列表渲染 wx:for, wx:key

官方文档:https://developers.weixin.qq.com/miniprogram/dev/reference/wxml/list.html

wx:for 在组件上使用 wx:for 控制属性绑定一个数组,即可使用数组中各项的数据重复渲染该组件。默认数组的当前项的下标变量名默认为 index,数组当前项的变量名默认为 item

wx:key 如果列表中项目的位置会动态改变或者有新的项目添加到列表中,并且希望列表中的项目保持自己的特征和状态(如 input 中的输入内容,switch 的选中状态),需要使用 wx:key 来指定列表中项目的唯一的标识符。

demo.wxml

1
2
3
4
5
6
7
8
<!-- wx:for 循环, wx:key 必须添加 -->
<view wx:for="{{list}}" wx:key="index">
{{index}} : {{item}}
</view>
<!-- wx:for 循环,自定义 item 和 index 名称 -->
<view wx:for="{{list}}" wx:for-item="a_item" wx:for-index="a_index" wx:key="a_index">
{{a_index}} - {{a_item}}
</view>

demo.js

1
2
3
4
5
6
Page({
data: {
list: ["jerry", "tom", "spike"]
},
...
})

4.4 条件渲染 wx:if

官方文档:https://developers.weixin.qq.com/miniprogram/dev/reference/wxml/conditional.html

wx:if

wx:if 使用 wx:if 来判断是否需要渲染该代码块。

也可以用 wx:elifwx:else 来添加一个 else 块

demo.wxml

1
2
3
4
5
<view wx:if="{{isCreated}}">动态创建和删除</view>

<view wx:if="{{length > 5}}"> 1 </view>
<view wx:elif="{{length > 2}}"> 2 </view>
<view wx:else> 3 </view>

demo.js

1
2
3
4
5
6
7
Page({
data: {
isCreated: true,
length: 4,
},
...
})

hidden

hidden 会创建出节点,在 hidden=”false” 的时候,设置 DOM 节点为 display: none 以隐藏,反之则显示。

1
<view hidden="{{isHidden}}">动态隐藏和显示</view>
1
2
3
4
5
6
Page({
data: {
isHidden: false
},
...
})

4.5 事件绑定 bindtap

bindtap

官方文档:https://developers.weixin.qq.com/miniprogram/dev/framework/view/wxml/event.html

1
2
3
4
5
6
7
8
9
10
11
<!-- bindtap 与 catchtap -->
<button type="primary" bindtap="handleTap">click1</button>
<view id="outer" bindtap="handleTap1">
outer view
<view id="middle" catchtap="handleTap2">
middle view - 阻止冒泡,只会触发自己的事件
<view id="inner" bindtap="handleTap3">
inner view - 会冒泡到上一层,会触发 handleTap2和3
</view>
</view>
</view>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Page({
handleTap() {
console.log("click", this.data.myname);
this.setData({
myname: "tom",
isCreated: !this.data.isCreated
})
},
handleTap1() {
console.log("handleTap1");
},
handleTap2() {
console.log("handleTap2");
},
handleTap3() {
console.log("handleTap3");
},
})

evt.target & evt.currentTarget

evt.target:指向实际触发事件的那个元素(事件的「触发源」),是用户真实点击 / 操作的节点;

evt.currentTarget:指向绑定了该事件的那个元素(事件的「绑定者」),是写了bindtap/catchtap等事件的节点;

只有事件绑定元素本身被直接触发时,二者才指向同一个元素,取值一致;若触发的是绑定元素的【子元素】,二者指向不同,取值可能不同

1
2
3
4
<!-- 事件绑定在view上(currentTarget),text是view的子元素(target可能是它) -->
<view bindtap="getParam" data-num="100" style="padding:20rpx; background:#f0f0f0;">
<text>点击我(子元素)</text>
</view>
1
2
3
4
5
6
7
8
9
Page({
// 事件处理函数
getParam(evt) {
const targetParam = evt.target.dataset.num; // 获取触发源的参数
const currentTargetParam = evt.currentTarget.dataset.num; // 获取事件绑定者的参数
console.log('evt.target.dataset.num:', targetParam); // undefined
console.log('evt.currentTarget.dataset.num:', currentTargetParam); // 100
}
});

eg:todolist - evt.target

  • bindinput="func" 绑定输入事件方法,通过 evt.detail.value 拿到输入值
  • value="{{xxx}}" 绑定 input 输入框中的值,以按需置空
  • data-x="{{v}}" 事件传参方式使用 data- 拼接自定义变量名,该参数名纯小写
  • evt.target.dataset.x 事件接参方式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!--pages/1-todolist/1-todolist.wxml-->
<view class="box">
<!-- bindinput 输入事件获取输入内容: https://developers.weixin.qq.com/miniprogram/dev/component/input.html -->
<!-- value : 绑定输入内容 -->
<input type="text" bindinput="handleInput" value="{{mytext}}" />
<button type="primary" size="mini" bindtap="handleTapAdd">添加</button>
</view>

<view wx:for="{{datalist}}" wx:key="index" class="list">
<text>{{item}}</text>
<!-- 事件传参 data-x="{{v}}" -->
<button type="warn" size="mini" bind:tap="handleDelete" data-myid="{{index}}">删除</button>
</view>

<view wx:if="{{datalist.length === 0}}">暂无待办事项</view>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/* pages/1-todolist/1-todolist.wxss */
.box {
display: flex;
}
input {
border: 1px solid gray;
height: 30px;
line-height: 30px;
border-radius: 30px;
padding-left: 10px;
flex: 1;
}
.list {
display: flex;
flex-direction: row;
}
.list text{
width: 300px;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// pages/1-todolist/1-todolist.js
Page({

/**
* 页面的初始数据
*/
data: {
mytext: "",
datalist: ["aaa", "bbb", "ccc"],
},

handleInput(evt) {
//evt.detail.value 拿到输入框的值
//console.log("input:", evt.detail.value);
this.setData({
mytext: evt.detail.value
})
},

handleTapAdd() {
console.log(this.data.mytext);
this.setData({
datalist: [...this.data.datalist, this.data.mytext],
mytext: "",
})
},
// 接收参数方式:evt.target.dataset.x
handleDelete(evt) {
console.log("delete:", evt.target.dataset.myid);
this.data.datalist.splice(evt.target.dataset.myid, 1)
this.setData({
datalist: this.data.datalist
})
},
...
})

image-20260202182134064

eg:高亮切换 - evt.currentTarget

  • evt.currentTarget.dataset.x 拿到绑定事件的事件源,即接参,可用于高亮或其他
1
2
3
4
5
6
7
8
9
10
<!--pages/2-heightlight/2-heightlight.wxml-->
<view class="box">
<!-- 点击的索引等于当前元素,则高亮 -->
<view wx:for="{{datalist}}" wx:key="index" class="item {{ current === index ? 'active' : ''}}" bindtap="handleTap" data-currentid="{{index}}">
<!-- 未包裹 <text> 时,通过 evt.target.dataset.x 接参 -->
<!-- {{item}} -->
<!-- 包裹 <text> 时,通过 evt.currentTarget.dataset.x 接参 -->
<text>{{item}}</text>
</view>
</view>
1
2
3
4
5
6
7
8
9
10
11
12
/* pages/2-heightlight/2-heightlight.wxss */
.box {
display: flex;
flex-direction: row;
}
.box .item{
flex: 1;
text-align: center;
}
.active {
color: red;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// pages/2-heightlight/2-heightlight.js
Page({
data: {
datalist: ['衣服', '裤子', '鞋帽', '手套'],
current: 0
},
handleTap(evt) {
// 未包裹 <text>
//console.log(evt.target.dataset.currentid);
// 包裹 <text>,拿到绑定事件的事件源
console.log(evt.currentTarget.dataset.currentid);
this.setData({
current: evt.currentTarget.dataset.currentid
})
},
...
})

5. wxss 样式

官方文档:https://developers.weixin.qq.com/miniprogram/dev/framework/view/wxss.html

定义在 app.wxss 中的样式为全局样式,作用于每一个页面。在 page 的 wxss 文件中定义的样式为局部样式,只作用在对应的页面,并会覆盖 app.wxss 中相同的选择器。

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

  • rpx(responsive pixel): 可以根据屏幕宽度进行自适应。1rpx = 0.5px = 1物理像素

即按照量取的像素值,1:1 设置 rpx 值。

1
<view class="box"></view>
1
2
3
4
5
6
/* 官方建议:以 iPhone6 为准 */
.box{
width: 750rpx; /* 以 iPhone6 为基准,rpx 单位,等比缩放适配所有机型 */
height: 400rpx;
background-color: yellow;
}

6. wxs 语法

官方文档:https://developers.weixin.qq.com/miniprogram/dev/framework/view/wxs/

语法文档:https://developers.weixin.qq.com/miniprogram/dev/reference/wxs/

1
2
3
4
5
<!--pages/4-wxs/4-wxs.wxml-->
<!-- 引入定义的 wxs 脚本方法 -->
<wxs src="./date.wxs" module="handleDate"/>
<!-- 调用方法,传参,并执行方法 -->
<text>{{handleDate(startTime)}}</text>
1
2
3
data: {
startTime: 1664229867258,
},
1
2
3
4
5
6
7
8
<!--pages/4-wxs/date.wxs-->
function handleDate(time) {
console.log(time);
var date = getDate(time)
return date.getFullYear() + "-" + (date.getMonth() + 1) + "-" + date.getDate()
}

module.exports = handleDate

案例:

1
2
3
4
5
6
7
<wxs src="./goodFilter.wxs" module="goodFilter"/>
<view>
<input bindinput="handleInput" value="{{mytext}}" />
<view wx:for="{{goodFilter(goodsList, mytext)}}" wx:key="index">
{{item}}
</view>
</view>
1
2
3
4
data: {
goodsList: ["aaa", "bbb", "ccc", "abc", "acc", "bcc", "abb", "cbb"],
mytext: ""
},
1
2
3
4
5
6
7
8
9
<!--pages/4-wxs/goodFilter.wxs-->
function goodFilter(list, text) {
// 不能使用 => 箭头函数,只能显式的写函数
return list.filter(function(item) {
return item.indexOf(text) > -1
})
}

module.exports = goodFilter

7. 数据请求

7.1 前提条件

请求报错:https://xxx.com 不在以下 request 合法域名列表中,请参考文档:https://developers.weixin.qq.com/miniprogram/dev/framework/ability/network.html

方式一:用于开发

配置位置:在微信开发工具中【本地设置】-【不校验合法域名、web-view、TLS版本以及HTTPS证书】打开,但上线时记得关掉

只能在微信开发工具中看到,无法手机预览 和 上线查看到。

image-20260202201051152

image-20260202201204851

方式二:用于上线

配置位置:服务器域名请在 「小程序后台-【开发与服务】-【开发设置】-【服务器域名】」 中进行配置 安全域名。

配置生效:检查项目配置中的合法域名、并重新编译项目。

image-20260202193018479

7.2 wx.request({ })

  • wx.request({...}) 微信小程序中的请求方式,没有跨域限制,但需要配置安全域名
1
2
3
4
5
6
<!--pages/5-request/5-request.wxml-->
<button type="primary" bindtap="handleAjax">ajax</button>

<view wx:for="{{datalist}}" wx:key="id">
{{item.nm}}
</view>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// pages/5-request/5-request.js
Page({
data: {
datalist: []
},
//请求数据:没有跨域限制,但需要配置安全域名
handleAjax() {
wx.request({
url: 'https://m.maoyan.com/ajax/movieOnInfoList',
method: 'get',
data: {},
success: (res) => {
console.log("success:", res.data.movieList);
this.setData({
datalist: res.data.movieList
})
},
fail: () => {
console.log("fail");
}
})
},
...
})

image-20260202194941693

8. 组件

8.1 image

官方文档:https://developers.weixin.qq.com/miniprogram/dev/component/image.html

image 标签组件,主要是 mode 属性。

1
2
3
4
5
6
7
<!--pages/5-request/5-request.wxml-->
<button type="primary" bindtap="handleAjax">ajax</button>
<view wx:for="{{datalist}}" wx:key="id" class="list">
<!-- 如 widthFix:缩放模式,宽度不变,高度自动变化,保持原图宽高比不变 -->
<image src='{{item.img}}' mode="widthFix"/>
<view>{{item.nm}}</view>
</view>
1
2
3
4
5
6
7
8
9
10
11
12
13
/* pages/5-request/5-request.wxss */
.list {
display: flex;
flex-direction: row;
margin: 20rpx;
}
.list image{
width: 200rpx;
}
.list view{
flex: 1;
margin-left: 10px;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// pages/5-request/5-request.js
Page({
data: {
datalist: []
},
//请求数据:没有跨域限制,但需要配置安全域名
handleAjax() {
wx.request({
url: 'https://m.maoyan.com/ajax/movieOnInfoList',
method: 'get',
data: {},
success: (res) => {
console.log("success:", res.data.movieList);
this.setData({
datalist: res.data.movieList
})
},
fail: () => {
console.log("fail");
}
})
},
...
})

image-20260202200605637

8.2 swiper

官方文档:https://developers.weixin.qq.com/miniprogram/dev/component/swiper.html

swiper 滑块视图容器(轮播图)。其中只可放置swiper-item组件,否则会导致未定义的行为。

image-20260202202624168

swiper 组件会有默认的 150px 固定高度,需要单独给 swiper 进行高度 rpx 值。

1
2
3
swiper {
height: 480rpx; /* 图片实际高度(240px) * 2 的 rpx 值 */
}
1
2
3
4
5
6
7
<!--pages/7-swiper/7-swiper.wxml-->
<button type="primary" bindtap="handleTap">ajax-swiper</button>
<swiper indicator-dots="{{true}}" circular="{{true}}" autoplay="{{true}}" interval="{{2000}}">
<swiper-item wx:for="{{datalist}}" wx:key="index">
<image src="{{item.banner_pic}}" mode="widthFix" style="width:100%" ></image>
</swiper-item>
</swiper>
1
2
3
4
/* pages/7-swiper/7-swiper.wxss */
swiper {
height: 480rpx; /* 图片实际高度(240px) * 2 的 rpx 值 */
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// pages/7-swiper/7-swiper.js
Page({
data: {
datalist: []
},
handleTap() {
wx.request({
url: 'https://gw.juooo.com/gw/main/pageConfig?version=6.3.11&referer=2',
method: 'post',
success: (res) => {
console.log(res.data.data.result.banner_list.home_banner);
this.setData({
datalist: res.data.data.result.banner_list.home_banner
})
}
})
},
...
})

image-20260202203045142

8.3 scroll-view

组件内下拉刷新

官方文档:https://developers.weixin.qq.com/miniprogram/dev/component/scroll-view.html

scroll-view 可滚动视图区域。使用竖向滚动时,需要给scroll-view一个固定高度,通过 WXSS 设置 height。组件属性的长度单位默认为px,2.4.0起支持传入单位(rpx/px)。

  • 可以实现非页面级别的 下拉刷新
  • 水平方向滚动需要2个条件,标签组件上 enable-flex="{{true}}" 以及样式 flex 弹性盒布局中需要设置收缩属性为0 flex-shrink: 0;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!--pages/8-scrollview/8-scrollview.wxml-->
<view>水平方向</view>
<scroll-view class="box_shuiping" enable-flex="{{true}}" scroll-x="{{true}}" bindscrolltolower="handleScrollToRight">
<view class="item_shuiping">aaaa</view>
<view class="item_shuiping">bbbb</view>
<view class="item_shuiping">cccc</view>
</scroll-view>

<view>垂直方向</view>
<!-- scroll-view 示例 -->
<scroll-view class="box" scroll-y="{{true}}" bindscrolltolower="handleScrollToLower" refresher-enabled="{{true}}" bindrefresherrefresh="handleRefresh" refresher-triggered="{{isRefresh}}">
<view class="item">1111</view>
<view class="item">2222</view>
<view class="item">3333</view>
</scroll-view>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/* pages/8-scrollview/8-scrollview.wxss */
.box{
height: 300rpx;
}
.item{
height: 300rpx;
background-color: yellow;
}

.box_shuiping{
height: 200px;
width: 100vw;
display: flex;
flex-direction: row;
}
.box_shuiping .item_shuiping {
width: 300px;
background-color: blue;
flex-shrink: 0; /* 伸缩属性设置为 0 - 配合水平滑动 */
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// pages/8-scrollview/8-scrollview.js
Page({
data: {
isRefresh: false
},
handleScrollToLower() {
console.log("到底了...");
},
handleScrollToRight() {
console.log("到右边了...");
},
handleRefresh() {
console.log("容器级别下拉刷新");
setTimeout(() => {
this.setData({
isRefresh: false //停止下拉刷新
})
}, 1000)
},
...
})

全局下拉刷新

如 pages/home/home.json

  • "enablePullDownRefresh": true 放在具体的 page json 配置里,就对应的 page 支持下拉刷新
1
2
3
4
5
{
"usingComponents": {},
"navigationBarTitleText": "首页",
"enablePullDownRefresh": true //开启【首页】的下拉刷新
}

app.json

  • "enablePullDownRefresh": true 放在 app.json 的 window 属性中,全局所有页面都支持下拉刷新
1
2
3
4
5
6
7
8
{
"pages": [...],
"window": {
"backgroundTextStyle": "dark", //背景文本样式,需设置为 dark 否则看不到
...
},
...
}

image-20260203163530441

下拉刷新触发事件:都需要在具体的 page.js 中处理 onPullDownRefresh() 下拉逻辑。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// pages/home/home.js
Page({
...
/**
* 页面相关事件处理函数--监听用户下拉动作
*/
onPullDownRefresh() {
setTimeout(() => {
//更新数据
console.log("下拉更新数据了");
wx.stopPullDownRefresh() //停止下拉刷新
}, 1000) //eg: 1s时间
},
...
})

8.4 其他组件

ifcon

icon 组件:https://developers.weixin.qq.com/miniprogram/dev/component/icon.html

1
2
3
4
5
<!-- icon -->
<icon class="icon-box-img" type="success" size="50"></icon>
<icon class="icon-box-img" type="info" size="50"></icon>
<icon class="icon-box-img" type="warn" size="50" color="#C9C9C9"></icon>
<icon class="icon-box-img" type="warn" size="50"></icon>

checkbox

checkbox 组件:https://developers.weixin.qq.com/miniprogram/dev/component/checkbox.html

1
2
3
4
5
6
7
8
9
10
11
12
13
<!--pages/9-checkbox/9-checkbox.wxml-->
<wxs src="./sum.wxs" module="sum"/>
<view wx:for="{{checkList}}" wx:key="index" style="display: flex; justify-content: space-around; padding: 10px;">
<checkbox bind:tap="handleCheckBoxTap" data-index="{{index}}" checked="{{item.isChecked}}" />
<view>
<view>商品:{{item.name}}</view>
<view>价格:¥{{item.price}}</view>
</view>
<view>{{item.number}}</view>
</view>
<view>
金额计算:{{sum(checkList)}}
</view>
1
2
3
4
5
6
7
8
9
10
11
<!--pages/9-checkbox/9-checkbox.wxs-->
function sum(list) {
var total = 0
for (var i = 0; i < list.length; i++) {
if (list[i].isChecked) {
total += list[i].number * list[i].price
}
}
return total
}
module.exports = sum
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// pages/9-other/9-other.js
Page({
data: {
checkList: [
{
id: 1,
name: "aaa",
price: 100,
number: 11,
isChecked: true
},
{
id: 2,
name: "bbb",
price: 200,
number: 12,
isChecked: false
},
{
id: 3,
name: "ccc",
price: 300,
number: 13,
isChecked: false
},
]
},

handleCheckBoxTap(evt) {
console.log("handleCheckBoxTap index=", evt.target.dataset.index);
var index = evt.target.dataset.index
this.data.checkList[index].isChecked = !this.data.checkList[index].isChecked
console.log(this.data.checkList);
this.setData({
checkList: [...this.data.checkList]
})
},
...
})

image-20260203102352328

form

form 组件:https://developers.weixin.qq.com/miniprogram/dev/component/form.html

等等…

8.5 自定义组件

官方文档:https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/

定义与使用

根目录下创建 components/,以 navbar 为例,在 components/ 目录下创建 navbar/ 目录,再右键 新建 Component,输入 navbar 即可生成4文件结构:

1
2
3
4
5
6
<!--components/navbar/navbar.wxml-->
<view class="box">
<view wx:for="{{list}}" wx:key="index" class="item {{current === index ? 'active' : ''}}" bindtap="handleTap" data-index="{{index}}">
{{item}}
</view>
</view>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/* components/navbar/navbar.wxss */
.box{
display: flex;
flex-direction: row;
justify-content: space-around;
}
.box .item{
flex: 1;
text-align: center;
height: 50px;
line-height: 50px;
}
.active {
color: red;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// components/navbar/navbar.js
Component({ // 注册一个组件
properties: {},
data: {
list: ["正在热映", "即将上映"],
current: 0
},
methods: {
handleTap(evt) {
// console.log(evt.currentTarget.dataset.index);
this.setData({
current: evt.currentTarget.dataset.index
})
}
}
})

其他组件引入/复用该组件:

1
2
3
4
5
6
<!--pages/10-selfcomponent/10-selfcomponent.json-->
{
"usingComponents": {
"navbar": "../../components/navbar/navbar" //引入自定义组件
}
}
1
2
3
<!--pages/10-selfcomponent/10-selfcomponent.wxml-->
<view>父组件中使用自定义组件:</view>
<navbar></navbar>

image-20260203104730120

父传子

父组件:

1
2
3
4
5
6
<!--pages/10-selfcomponent/10-selfcomponent.json-->
{
"usingComponents": {
"navbar": "../../components/navbar/navbar" //引入自定义组件
}
}
1
2
3
4
5
6
7
8
9
10
<!--pages/10-selfcomponent/10-selfcomponent.wxml-->
<view>父组件中使用自定义组件(传递参数 list, current 给 navbar):</view>
<navbar list="{{cateItems}}" current="{{current}}"></navbar>

<swiper bindchange="handleSwiperChange">
<swiper-item wx:for="{{cateList}}" wx:key="index">
{{item}}
</swiper-item>
</swiper>
<view>当前轮播索引:{{current}}</view>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// pages/10-selfcomponent/10-selfcomponent.js
Page({
data: {
cateItems: ['衣服', '裤子', '帽子', '手套'],
cateList: ['衣服内容', '裤子内容', '帽子内容', '手套内容'],
current: 0
},
handleSwiperChange(evt) {
console.log(evt.detail.current);
this.setData({
current: evt.detail.current
})
},
...
})

子组件:navbar

1
2
3
4
5
6
<!--components/navbar/navbar.wxml-->
<view class="box">
<view wx:for="{{list}}" wx:key="index" class="item {{current === index ? 'active' : ''}}" bindtap="handleTap" data-index="{{index}}">
{{item}}
</view>
</view>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/* components/navbar/navbar.wxss */
.box{
display: flex;
flex-direction: row;
justify-content: space-around;
}
.box .item{
flex: 1;
text-align: center;
height: 50px;
line-height: 50px;
}
.active {
color: red;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// components/navbar/navbar.js
Component({
// 接收并校验父传过来的属性,value 为默认值,接收 list、current
properties: {
list: {
type: Array,
value: ["正在热映", "即将上映"] //默认值
},
current: {
type: Number,
value: 0 //默认值
}
},
data: {},
methods: {
handleTap(evt) {
// console.log(evt.currentTarget.dataset.index);
this.setData({
current: evt.currentTarget.dataset.index
})
}
}
})

效果:当前是滑动 swiper 可以从父传子,给到 navbar 同步 current 索引值,高亮 navbar(但是点击 navbar 去切换 swiper 需要子传父)。

image-20260203111230836

子传父

官方文档:https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/events.html

子传父使用的是 事件 方式。(父传子 代码基础上进行优化,子组件最好不要自行修改属性值)

父组件:

  • 定义 bindParentEvent 事件回调函数 - 监听函数
  • swiper 组件自带的 current 属性控制滑块位置,父与子之间的 current 完全同步通信
1
2
3
4
5
6
7
8
9
10
11
<!--pages/10-selfcomponent/10-selfcomponent.wxml-->
<view>父组件中使用自定义组件:</view>
<navbar list="{{cateItems}}" current="{{current}}" bindParentEvent="handleChangeEvent"></navbar>

<!-- swiper 组件上的 current 控制滑块位置,父与子之间的 current 完全同步通信 -->
<swiper bindchange="handleSwiperChange" current="{{current}}">
<swiper-item wx:for="{{cateList}}" wx:key="index">
{{item}}
</swiper-item>
</swiper>
<view>当前轮播索引:{{current}}</view>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// pages/10-selfcomponent/10-selfcomponent.js
Page({
data: {
cateItems: ['衣服', '裤子', '帽子', '手套'],
cateList: ['衣服内容', '裤子内容', '帽子内容', '手套内容'],
current: 0
},
handleSwiperChange(evt) {
console.log(evt.detail.current);
this.setData({
current: evt.detail.current
})
},
// 自定义父组件事件名(bind开头)和回调函数 bindParentEvent="handleChangeEvent"
handleChangeEvent(evt) {
console.log("父组件 handleChangeEvent evt=", evt.detail);
this.setData({
current: evt.detail //evt.detail 是 子组件传入的事件参数
})
},
...
})

子组件:

  • 自定义组件触发事件时,需要使用 triggerEvent 方法,指定事件名、detail对象和事件选项
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// components/navbar/navbar.js
Component({
properties: {
// 接收并校验父传过来的属性,value 为默认值
list: {
type: Array,
value: ["正在热映", "即将上映"] //默认值
},
current: {
type: Number,
value: 0 //默认值
}
},
data: {},
methods: {
handleTap(evt) {
console.log(evt.currentTarget.dataset.index);
//通知父组件触发父组件回调函数: bindParentEvent 并携带参数 index
this.triggerEvent("ParentEvent", evt.currentTarget.dataset.index)
}
}
})

效果:当前滑动 swiper 可以从父传子,给到 navbar 同步 current 索引值,高亮 navbar。而且点击 navbar 也可以同步切换 swiper 内容。

image-20260203111230836

slot 插槽

官方文档:slot组件wxml的slot

父:

1
2
3
4
5
6
{
"usingComponents": {
"top-header": "../../components/topheader/topheader",
"footer": "../../components/footer/footer"
}
}
1
2
3
4
5
6
7
8
<!--pages/11-slot/11-slot.wxml-->
<top-header>
<button slot="left">返回</button>
<button slot="right">菜单</button>
</top-header>
<!-- 插槽可以一定程度避免父子通信,更便捷 -->
<button bindtap="handleShowFooter">显示Footer</button>
<footer wx:if="{{isShowFooter}}"></footer>
1
2
3
4
5
6
7
8
9
10
11
12
// pages/11-slot/11-slot.js
Page({
data: {
isShowFooter: false
},
handleShowFooter() {
this.setData({
isShowFooter: !this.data.isShowFooter
})
},
...
})

子:footer

1
2
<!--components/footer/footer.wxml-->
<view class="footer">footer</view>
1
2
3
4
5
6
7
8
9
/* components/footer/footer.wxss */
.footer {
width: 100%;
height: 200rpx;
position: fixed;
left: 0;
bottom: 0;
background-color: yellow;
}

image-20260203115548746

生命周期-自定义组件

官方文档:https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/lifetimes.html

父:

1
2
3
4
5
{
"usingComponents": {
"count": "../../components/count/count"
}
}
1
2
3
<!--pages/12-lifecycle/12-lifecycle.wxml-->
<view>抢购倒计时</view>
<count wx:if="{{isCreated}}" bindMyEvent="handleMyEvent"></count>
1
2
3
4
5
6
7
8
9
10
11
12
// pages/12-lifecycle/12-lifecycle.js
Page({
data: {
isCreated: true
},
handleMyEvent() {
this.setData({
isCreated: !this.data.isCreated
})
},
...
})

子:components/count/count.xx

1
2
<!--components/count/count.wxml-->
<text>{{count}}</text>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// components/count/count.js
Component({
lifetimes: {
attached: function() {
// 在组件实例进入页面节点树时执行
console.log("attached");
this.intervalId = setInterval(() => {
if (this.data.count === 0) {
this.triggerEvent("MyEvent") //通知父组件移除自己
return
}
this.setData({
count: --this.data.count
})
}, 1000)
},
detached: function() {
// 在组件实例被从页面节点树移除时执行
console.log("detached");
clearInterval(this.intervalId) // 销毁定时器
},
},
...
})

9. 页面生命周期

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
// pages/request/request.js
Page({
/**
* 页面的初始数据
*/
data: {
datalist: []
},
//请求数据:没有跨域限制,但需要配置安全域名
handleAjax() {
wx.request({
url: 'https://m.maoyan.com/ajax/movieOnInfoList',
method: 'get',
data: {},
success: (res) => {
console.log("success:", res.data.movieList);
this.setData({
datalist: res.data.movieList
})
},
fail: () => {
console.log("fail");
}
})
},
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
console.log("onLoad");
this.handleAjax()
},
/**
* 生命周期函数--监听页面初次渲染完成
*/
onReady() {
console.log("onReady");
},
/**
* 生命周期函数--监听页面显示
*/
onShow() {
console.log("onShow");
},
/**
* 生命周期函数--监听页面隐藏
*/
onHide() {
console.log("onHide");
},
/**
* 生命周期函数--监听页面卸载
*/
onUnload() {
console.log("onUnload");
},
/**
* 页面相关事件处理函数--监听用户下拉动作
*/
onPullDownRefresh() {
console.log("onPullDownRefresh");
},
/**
* 页面上拉触底事件的处理函数
*/
onReachBottom() {
console.log("onReachBottom");
},
/**
* 用户点击右上角分享
*/
onShareAppMessage() {
console.log("onShareAppMessage");
}
})

01-wechat微信小程序基础
https://janycode.github.io/2022/05/22/04_大前端/10_小程序/01-wechat微信小程序基础/
作者
Jerry(姜源)
发布于
2022年5月22日
许可协议