尚硅谷微信小程序开发教程,2024最新版微信小程序项目实战!
一、小程序基础
1. 初始小程序
微信小程序是一种运行在微信内部的 轻量级 应用程序。
使用小程序时 不需要下载,用户 扫一扫 或 搜一下 即可打开应用,它也体现了 “用完即走” 的理念,用户不用关心太多应用的问题,它实现了应用“触手可及”的梦想,应用无处不在,随时可用,但又 无需安装卸载。
使用 App 过程:王者荣耀 → 应用商店 → 下载 + 安装 → 5 v 5 → 卸载…
使用小程序过程:羊了个羊 → 扫、搜 → 直接使用 → 关掉…
小程序的四大特性:无需安装 、用完即走 、无需卸载 、触手可及
2. 注册微信小程序
小程序开发与网页开发不一样,在开始微信小程序开发之前,需要访问 微信公众平台,注册一个微信小程序账号。
有了小程序的账号以后,我们才可以开发和管理小程序,后续需要通过该账号进行 开发信息的设置、开发成员的添加,也可以用该账号查看小程序的运营数据。
在申请账号之前,我们需要先 准备一个邮箱,该邮箱要求:
- 未被微信公众平台注册
- 未被微信开发平台注册
- 未被个人微信号绑定过,如果被绑定了需要解绑 或 使用其他邮箱
注:因流程较多,详细步骤可参考笔记https://developers.weixin.qq.com/miniprogram/dev/framework/quickstart/getstart.html#%E7%94%B3%E8%AF%B7%E8%B4%A6%E5%8F%B7
3. 完善小程序账号信息
在完成小程序账号的注册后,需要打开微信公众平台对小程序账号进行一些设置,这是因为小程序在 上线阶段 - 提交审核 的时候,小程序账号信息是必填项,因此注册小程序以后,需要补充小程序的基本信息,如名称、图标、类目等。同时需要进行小程序备案和微信认证
4. 项目成员和体验成员
小程序提供了两种不同成员角色:项目成员 和 体验成员
项目成员:表示参与小程序开发、运营的成员,包括运营者、开发者及数据分析者,项目成员可登陆微信公众后台,管理员可以在成员管理中添加、删除项目成员,并设置项目成员的角色。
体验成员:表示参与小程序内测体验的成员,可使用体验版小程序,但不属于项目成员。管理员及项目成员均可添加、删除体验成员。
5. 小程序开发者ID
微信小程序账号只要开发者满足开发资质都可以进行注册,并且会获得对应的 开发者 ID;一个完整的开发者 ID 由 小程序ID(AppID) 和 小程序密钥(AppSecret) 组成。
小程序 ID 是小程序在整个微信账号体系内的唯一身份凭证,后续在很多地方都会用到,例如:新建小程序项目、真机调试、发布小程序等操作时,必须有小程序 ID。
小程序密钥 是开发者对小程序拥有所有权的凭证,在进行 微信登录、微信支付,或进行发送消息等高级开发时回使用到。
6. 微信开发者工具下载
微信开发者工具官网:https://developers.weixin.qq.com/miniprogram/dev/devtools/download.html
为了帮助开发者简单和高效的开发和调式微信小程序,微信官方提供了 微信开发者工具,利用开发者工具可以很方便的进行小程序开发、代码查看、以及编辑、预览和发布等。
微信开发者工具包含三个版本:
- 稳定版:稳定性高,开发中一般推荐大家使用稳定版本
- 预发布版:稳定性尚可,一般包含新的、大的特性,通过了内部测试
- 开发版:稳定性差,主要用于尽快修复缺陷和敏捷上线小的特性
注意事项:微信开发者工具必须联网使用
7. 创建小程序项目
- 打开微信开发者工具,左侧选择小程序,点击 + 号即可新建项目
- 在弹出的新页面,填写项目信息
8. 文件和目录结构介绍
一个完整的小程序项目分为两个部分:主体文件、页面文件
主体文件 又称 全局文件,能够作用于整个小程序,影响到小程序的每个页面,主体文件必须放到项目的根目录下
主体文件由三部分组成:
- app.js:小程序入口文件
- app.json:小程序的全局配置文件
- app.wxss:小程序的全局样式
注意事项:主体文件的名字必须是app,app.js 和 app.json 文件是必须的
页面文件 是每个页面所需的文件,小程序页面文件都存放在pages目录下,一个页面一个文件夹
每个页面通常由四个文件组成,每个文件只对当前页面有效:
.js
:页面逻辑.wxml
:页面结构.wxss
:页面样式.json
:小页面配置
注意事项:
.js
文件和.wxml
文件是必须的
9. 如何新建页面 以及 调试基础库
1)新建页面
- 方式一
在项目目录中找到 pages 文件夹,点击右键选中新建文件夹,输入文件名,再次选中新建的文件名,右键选择新建Page,输入page名称(名字与文件夹名一致即可,不需要后缀名),微信开发者工具会自动生成相应的.js
、.wxml
、.wxss
、.json
等文件。 - 方式二
在app.json
中新增对应的路由并保存,对应的文件及文件夹会自动生成。
2) 调试基础库
小程序调试基础库是指 微信开发者工具中可以选择的微信基础库版本。
微信基础库是指小程序的运行环境,给小程序提供了运行所需的各种API工具,以及基础框架和运行逻辑等。
小程序开发者可以在微信开发者工具中选择所需的微信基础库版本,作为运行和调试小程序时的运行环境。
每个小程序有自己所允许使用的基础库最低版本要求,开发者需要选择要兼容的基础库版本,从而确保小程序的功能正常运行。
10.如何调试小程序
在进行项目开发的时候,不可避免的需要进行调试,那么如何调试小程序呢?
注意事项:微信开发者工具缓存非常重要;如果发现代码和预期不一样,先点击编译;编译后还是没有达到预期的效果,就需要清除缓存,甚至重启项目才可以。
二、配置文件
1. 配置文件介绍
JSON 是一种轻量级的数据格式,常用于前后端的数据交互,但是在小程序中,JSON扮演的配置项的角色,用于配置项目或者页面属性和行为,每个页面或组件也都有一个对应的 json 文件。
小程序中常见的配置文件有以下几种:
app.json
:小程序全局配置文件,用于配置小程序的一些全局属性和页面路由。页面.json
:小程序页面配置文件,也称为局部配置文件,用于配置当前页面的窗口样式、页面标题等。project.config.json
:小程序项目的配置文件,用于保存项目的一些配置信息和开发者的个人设置。sitemap.json
:配置小程序及其页面是否允许被微信索引(即被微信搜索),提高小程序在搜索引擎搜索到的概率。project.private.config.json
:可选,用于保存开发者自定义的配置信息,例如小程序的第三方 API 密钥、版本号等敏感信息,这些信息不应该被公开或共享,因此需要保存在本地进行保护。同时,由于这个文件不会被上传到服务器或共享给其他开发者,因此不同开发者之间也可以使用不同的 private.config.json 文件,并且可以独立配置;由于 project.private.config.json 包含敏感信息,一般不会被提交到代码仓库中,而是由开发人员在本地进行管理。因此,在拉取代码时,project.private.config.json 文件可能会出现冲突,因为不同开发人员的私有配置信息可能不同。另外,由于project.private.config.json 文件在本地管理,可能会被误删除或修改,也会导致冲突的出现。开发人员可以在项目中加入一些约定,比如将project.private.config.json 文件放在一个独立的目录下,并在.gitignore
文件中将该目录排除在代码仓库之外。这样可以更好地管理私有配置信息,避免冲突的出现。
原文链接:https://blog.csdn.net/snowball_li/article/details/134553156
2. 全局配置 - pages配置
pages 字段:用来指定小程序由哪些页面组成,用于让小程序知道由哪些页面组成以及页面定义在哪个目录,每一项都对应一个页面的路由信息。
在配置 pages字段时,有以下注意事项:
- 页面路由不需要些后缀名,框架会自动去寻找对应位置的四个文件进行处理
- 小程序中新增/减少页面,都需要对pages数组进行修改
- 未指定 enterPagePath 时,数组的第一项代表小程序的初始页面(首页)
3. 全局配置 - window 配置
window 字段:用于设置小程序的状态栏、导航条、标题、窗口背景色。
官方文档:window 配置
4. 全局配置 - tabbar 配置
tabbar 字段:定义小程序顶部、底部 tab 栏,用以实现页面之间的快速切换,可以通过 tabBar配置项指定 tab 栏的表现,以及 tab 切换时显示的对应页面。
注意事项:tab按数组的顺序排序,list 配置最少2个、最多5个tab
官方文档:tabbar 配置
5. 页面配置
小程序的页面配置,也称为局部配置,每一个小程序页面也可以使用自己的 .json
文件来对本页面的窗口进行配置
需要注意的是:页面配置文件的属性和全局配置文件中的 window 属性几乎一致的,只不过这里不需要额外指定 window 字段,因此如果出现相同的配置项,页面中配置项 会覆盖全局配置文件中相同的配置项。
官方文档:页面配置
6. 项目配置文件和配置sass
在创建项目的时候,每个项目的根目录生成两个 config.json
文件,用于保存开发者在工具上做的个性化配置,例如和编译有关的配置。
当重新安装微信开发者工具或者换电脑工作时,只要载入同一个项目的代码包,开发者工具就会自动恢复到当时开发项目时的个性化配置。
项目根目录中 project.config.json
和 project.private.config.json
文件都可以对项目进行配置
project.config.json
:项目配置文件,常用来进行配置公共的配置
project.private.config.json
:项目私有配置,常用来配置个人的配置
注意事项:
project.private.config.json
写到.gitignore
避免版本管理的冲突- **与最终编译结果有关的设置 必须设置到 **
project.config.json
中
官方文档:项目配置项文件
7. sitemap.json 文件
sitemap.json 文件:配置小程序及其页面是否允许被微信索引,提高小程序在微信内部被用户搜索到的概率
微信现已开放小程序内搜索,开发者可以通过 sitemap.json 配置来设置小程序页面是否允许微信索引。当开发者允许微信索引时,微信会通过爬虫的形式,为小程序的页面内容建立索引。当用户的搜索词条触发该索引时,小程序的页面将可能展示在搜索结果中。
注意事项:
- 注:没有 sitemap.json 则默认所有页面都能被索引
{ "action": "allow", "page": "*" }
是优先级最低的默认规则,未显示指明 “disallow” 的都默认被索引
三、样式
1. 小程序的样式和组件介绍
在开发 Web 网站的时候:页面的结构由 HTML 进行编写,例如:经常会用到 div、p、span、img、a 等标签
页面的样式由 CSS 进行编写,例如:经常会采用 .class、 #id、element 等选择器
但是在小程序中不能使用 HTML 标签,也没有 DOM 和 BOM,CSS 也仅仅支持部分选择器。
小程序提供了 WXML 进行页面结构编写,同时提供了 WXSS 进行页面的样式编写。
WXML 提供了 view、text、image、navigator 等标签来构建页面结构,只不过在小程序中将标签称为 组件
WXSS 对 CSS 扩充和修改,新增了尺寸单位 rpx、提供了全局的样式和局部样式,另外需要注意的是 WXSS 仅支持部分 CSS 选择器
官方文档:选择器
2. 尺寸单位 rpx
随着智能手机的发展,手机设备的宽度也逐渐多元化,这就需要开发者在开发的时候,需要适配不同屏幕宽度的手机。为了解决屏幕适配的问题,微信小程序推出了 rpx 单位。
rpx:是小程序新增的自适应单位,它可以根据不同设备的屏幕宽度进行自适应缩放
小程序规定任何型号手机:屏幕宽度都为 750rpx
开发建议:
- 开发微信小程序时设计师可以用 iPhone6 作为视觉稿的标准,iPhone6 的设计稿一般是 750px
- 如果 iPhone6 作为视觉稿的标准量取多少 px,直接写多少 rpx 即可,开发起来更方便,也能够适配屏幕的宽度
设计稿宽度是 750px,而 iPhone6 的手机设备宽度是 375rpx,设计稿想完整展示到手机中,就需要缩小一倍,
在 iPhone6 下,px 和 rpx 的换算关系是:1rpx = 0.5px,750rpx = 375px,刚好能够填充满整个屏幕的宽度。
<!-- 需求:绘制一个盒子,让盒子的宽度占据屏幕的一半view是小程序提供的组件,是容器组件,类似于 div,也是一个块级元素,占据一行如果想实现需求,不能使用 px,px是固定的单位,不能实现自适应,需要使用小程序提供的 rpx微信小程序规定,不管是什么型号的手机,屏幕单位都是 750rpxrpx 是可以实现自适应的
-->
<view class="box">尚硅谷</view>
.box {width: 375rpx;height: 600rpx;background-color: lightgreen;
}
3. 全局样式和局部样式
在进行网页开发时,我们经常创建 global.css、base.css 或者 reset.css 作为全局样式文件进行重置样式或者样式统一,然后在每个页面或者组件中写当前页面或组件的局部样式,小程序中也存在全局样式和局部样式。
全局样式:指在 app.wxss 中定义的样式规则,作用于每一个页面,例如:设置字号、背景色、宽高等全局样式,
局部样式:指在 page.wxss 中定义的样式规则,只作用于相应的页面,并会覆盖 app.wxss 中相同的选择器。
四、组件
1. 组件案例演示
小程序常用的组件:
- view 组件
- swiper 和 swiper-item 组件
- image 组件
- text 组件
- navigator 组件
- scroll-view 组件
- 字体图标
使用小程序常用的组件实现项目首页的效果图
1.1 轮播图区域绘制
在进行网页开发的时候,实现轮播图的时候,我们通常先使用 HTML、CSS 实现轮播图的结构样式,然后使用 JS 控制轮播图的效果,或者直接使用插件实现轮播图的功能,而在小程序中实现小程序功能则相对简单很多。
在小程序中,提供了 swiper 和 swiper-item 组件实现轮播图:
swiper:滑块视图容器,其中只能放置swiper-item 组件
swiper-item:只可放置在 swiper 组件中,宽高自动设置为100%,代表 swiper 中的每一项
官方文档: swiper 和 swiper-item
<!-- 轮播区域 -->
<view class="swiper"><swiperautoplaycircularindicator-dotsinterval="2000" indicator-color="#fff"indicator-active-color="#f3514f"><swiper-item>1</swiper-item><swiper-item>2</swiper-item><swiper-item>3</swiper-item></swiper>
</view>
// 轮播图区域样式
.swiper {swiper {height: 360rpx;background-color: skyblue;swiper-item {/*& 在 Sass 中代表的是父选择器,引用的意思相当于:swiper-item:first-child {}*/&:first-child {background-color: lightsalmon;}&:last-child {background-color: lightseagreen;}}}
}
1.2 轮播图图片添加
在小程序中,如果需要渲染图片,需要使用image 组件,常用的属性有4个:
- src 属性:图片资源地址
- mode:图片裁剪、缩放的模式
- show-menu-by-longpress:长按图片显示菜单
- lazy-load:图片懒加载
注意事项:
- image 默认具有宽度和高度,宽是 320rpx 高度是 240rpX
- image 组件不给 src 属性设置图片地址,也占据宽和高
官方文档:image 组件
<!-- 轮播区域 -->
<view class="swiper"><swiperautoplaycircularindicator-dotsinterval="2000" indicator-color="#fff"indicator-active-color="#f3514f"><!-- src:图片的资源地址mode:图片的裁剪和缩放模式show-menu-by-longpress:长按展示菜单,菜单中有转发给好友、收藏、保存等功能lazy-load:图片懒加载功能,在滑动到一定的距离(上下三屏)以后展示图片--><swiper-item><image src="../../assets/banner/banner-1.png" mode="aspectFit" show-menu-by-longpress lazy-load /></swiper-item><swiper-item><image src="../../assets/banner/banner-2.png" mode="aspectFit" show-menu-by-longpress lazy-load /></swiper-item><swiper-item><image src="../../assets/banner/banner-3.png" mode="aspectFit" show-menu-by-longpress lazy-load /></swiper-item></swiper>
</view>
// 轮播图区域样式
.swiper {swiper {height: 360rpx;swiper-item {image{width: 100%;height: 100%;}}}
}
1.3 绘制公司信息区域
在小程序中,如果需要渲染文本,需要使用 text 组件,常用的属性有 2 个:
- user-select:文本是否可选,用于长按选择文本
- space:显示连续空格
注意事项:
- 除了文本节点以外的其他节点都无法长按选中
- text 组件内只支持 text 嵌套
官方文档:text 组件
<!-- 公司信息 -->
<view class="info"><!-- text 不可嵌套其他组件user-select:长按以后选中文本 boolean 文本是否可选,该属性会使文本节点显示为 inline-block space:显示连续空格 可选值:ensp 中文字符空格一半大小 emsp 中文字符空格大小 nbsp 根据字体设置的空格大小--><text space="ensp">同城配送</text><text space="ensp">行业龙头</text><text space="ensp">半小时送达</text><text space="ensp">100% 好评</text>
</view>
page {height: 100vh;background-color: #efefef !important;padding: 16rpx;box-sizing: border-box;display: flex;flex-direction: column;> view {&:nth-child(n+2) {margin-top: 16rpx;}}
}// 轮播图区域样式
.swiper {border-radius: 10rpx;overflow: hidden;swiper {height: 360rpx;swiper-item {image{width: 100%;height: 100%;}}}
}.info {display: flex;justify-content: space-between;background-color: #fff;padding: 16rpx;border-radius: 10rpx;font-size: 24rpx;
}
1.4 商品导航区域
- veiw:视图容器
- image:图片组件
- text:文本组件
<!-- 商品导航 -->
<view class="good-nav"><view class="nav-item"><image src="../../assets/category/cate-1.png" mode=""/><text>鲜花玫瑰</text></view><view class="nav-item"><image src="../../assets/category/cate-2.png" mode=""/><text>鲜花玫瑰</text></view><view class="nav-item"><image src="../../assets/category/cate-3.png" mode=""/><text>鲜花玫瑰</text></view><view class="nav-item"><image src="../../assets/category/cate-4.png" mode=""/><text>鲜花玫瑰</text></view><view class="nav-item"><image src="../../assets/category/cate-5.png" mode=""/><text>鲜花玫瑰</text></view>
</view>
page {height: 100vh;background-color: #efefef !important;padding: 16rpx;box-sizing: border-box;display: flex;flex-direction: column;> view {&:nth-child(n+2) {margin-top: 16rpx;}}
}// 轮播图区域样式
.swiper {border-radius: 10rpx;overflow: hidden;swiper {height: 360rpx;swiper-item {image{width: 100%;height: 100%;}}}
}// 公司信息
.info {display: flex;justify-content: space-between;background-color: #fff;padding: 20rpx 16rpx;border-radius: 10rpx;font-size: 24rpx;
}// 商品导航区域
.good-nav {display: flex;justify-content: space-between;background-color: #fff;padding: 20rpx 16rpx;border-radius: 10rpx;.nav-item {display: flex;flex-direction: column;align-items: center;image {width: 80rpx;height: 80rpx;}text {font-size: 24rpx;margin-top: 12rpx;}}
}
1.5 跳转到商品列表
在小程序中,如果需要进行跳转,需要使用 navigation 组件,常用的属性有 2 个:
- url:当前小程序内的跳转链接
- open-type:跳转方式
- navigate:保留当前页面,跳转到应用内的某个页面但是不能跳到 tabbar 页面
- redirect:关闭当前页面,跳转到应用内的某个页面。但不能跳转到 tabbar 页面
- switchTab:跳转到 tabBar 页面,并关闭其他所有非 tabBar 页面
- reLaunch:关闭所有页面,打开到应用内的某个页面
- navigateBack:关闭当前页面,返回上一页面或多级页面
注意事项:
- 路径后可以带参数。参数与路径之间使用 ? 分隔,参数键与参数值用 = 相连,不同参数用 & 分隔
例如:/list?id=10&name=hua,在 onLoad(options) 生命周期函数 中获取传递的参数- open-type=“switchTab" 时不支持传参
官方文档:navigator
<!-- view 小程序提供的容器组件,可以直接当成 div 使用即可 -->
<!-- 轮播区域 -->
<view class="swiper"><swiperautoplaycircularindicator-dotsinterval="2000" indicator-color="#fff"indicator-active-color="#f3514f"><!-- src:图片的资源地址mode:图片的裁剪和缩放模式show-menu-by-longpress:长按展示菜单,菜单中有转发给好友、收藏、保存等功能lazy-load:图片懒加载功能,在滑动到一定的距离(上下三屏)以后展示图片--><swiper-item><image src="../../assets/banner/banner-1.png" mode="aspectFit" show-menu-by-longpress lazy-load /></swiper-item><swiper-item><image src="../../assets/banner/banner-2.png" mode="aspectFit" show-menu-by-longpress lazy-load /></swiper-item><swiper-item><image src="../../assets/banner/banner-3.png" mode="aspectFit" show-menu-by-longpress lazy-load /></swiper-item></swiper>
</view><!-- 公司信息 -->
<view class="info"><!-- text 不可嵌套其他组件user-select:长按以后选中文本 boolean 文本是否可选,该属性会使文本节点显示为 inline-block space:显示连续空格 可选值:ensp 中文字符空格一半大小 emsp 中文字符空格大小 nbsp 根据字体设置的空格大小--><text space="ensp">同城配送</text><text space="ensp">行业龙头</text><text space="ensp">半小时送达</text><text space="ensp">100% 好评</text>
</view><!-- 商品导航 -->
<view class="good-nav"><view class="nav-item"><!-- 在进行页面跳转时,需要在路径的前面添加 / 斜线,否则跳转不成功open-type string类型 默认值 navigate 跳转方式参数列表:navigate 对应 wx.navigateTo 或 wx.navigateToMiniProgram 的功能只能跳转到非 tabbar 页面,不能跳转到 tabbar 页面,保留上级页面redirect 对应 wx.redirectTo 的功能只能跳转到非 tabbar 页面,不能跳转到 tabbar 页面,关闭上级页面switchTab 对应 wx.switchTab 的功能 只能跳转到 tabbar 页面,不能跳转到非 tabbbar 页面reLaunch 对应 wx.reLaunch 的功能关闭所有页面,打开到应用内的某个页面navigateBack 对应 wx.navigateBack 或 wx.navigateBackMiniProgram (基础库 2.24.4 版本支持)的功能关闭当前页面,返回上一页面或多级页面 默认返回上一页 当返回多级页面时,需要添加 delta 属性,表示返回的层级 默认为 1exit 退出小程序,target="miniProgram"时生效--><navigator url="/pages/list/list?id=10&num=hua"><image src="../../assets/category/cate-1.png" mode=""/><text>鲜花玫瑰</text></navigator></view><view class="nav-item"><navigator url="/pages/list/list?id=10&num=hua"><image src="../../assets/category/cate-2.png" mode=""/><text>鲜花玫瑰</text></navigator></view><view class="nav-item"><navigator url="/pages/list/list?id=10&num=hua"><image src="../../assets/category/cate-3.png" mode=""/><text>鲜花玫瑰</text></navigator></view><view class="nav-item"><navigator url="/pages/list/list?id=10&num=hua"><image src="../../assets/category/cate-4.png" mode=""/><text>鲜花玫瑰</text></navigator></view><view class="nav-item"><navigator url="/pages/list/list?id=10&num=hua"><image src="../../assets/category/cate-5.png" mode=""/><text>鲜花玫瑰</text></navigator></view>
</view><!-- 推荐商品 -->
<view class="good-hot"></view>
page {height: 100vh;background-color: #efefef !important;padding: 16rpx;box-sizing: border-box;display: flex;flex-direction: column;> view {&:nth-child(n+2) {margin-top: 16rpx;}}
}// 轮播图区域样式
.swiper {border-radius: 10rpx;overflow: hidden;swiper {height: 360rpx;swiper-item {image{width: 100%;height: 100%;}}}
}// 公司信息
.info {display: flex;justify-content: space-between;background-color: #fff;padding: 20rpx 16rpx;border-radius: 10rpx;font-size: 24rpx;
}// 商品导航区域
.good-nav {display: flex;justify-content: space-between;background-color: #fff;padding: 20rpx 16rpx;border-radius: 10rpx;.nav-item {navigator {display: flex;flex-direction: column;align-items: center;image {width: 80rpx;height: 80rpx;}text {font-size: 24rpx;margin-top: 12rpx;}}}
}
1.6 推荐商品区域 - 滚动效果
在微信想小程序中如果想实现内容滚动,需要使用 scroll-view 组件
scroll-view:可滚动视图区域,适用于需要滚动展示内容的场景,用于在小程序中实现类似于网页中的滚动条效果,用户可以通过手指滑动或者点击滚动条来滚动内容。
先来学习两个属性:
- scroll-x:允许横向滚动
- scroll-y:允许纵向滚动
<!-- 推荐商品 -->
<view class="good-hot"><scroll-view class="scroll-x" scroll-x="true"><view>1</view><view>2</view><view>3</view></scroll-view>
</view>
// 推荐商品
.good-hot {.scroll-x {width: 100%;white-space: nowrap;background-color: skyblue;view {display: inline-block;width: 300rpx;height: 80rpx;&:last-child {background-color: slateblue;}&:first-child {background-color: springgreen;}}}
}
横向滚动时,需要把宽度固定,纵向滚动时,需要把纵向滚动固定
1.7 推荐商品区域 - 实现结构样式
<!-- view 小程序提供的容器组件,可以直接当成 div 使用即可 -->
<!-- 轮播区域 -->
<view class="swiper"><swiperautoplaycircularindicator-dotsinterval="2000" indicator-color="#fff"indicator-active-color="#f3514f"><!-- src:图片的资源地址mode:图片的裁剪和缩放模式show-menu-by-longpress:长按展示菜单,菜单中有转发给好友、收藏、保存等功能lazy-load:图片懒加载功能,在滑动到一定的距离(上下三屏)以后展示图片--><swiper-item><image src="../../assets/banner/banner-1.png" mode="aspectFit" show-menu-by-longpress lazy-load /></swiper-item><swiper-item><image src="../../assets/banner/banner-2.png" mode="aspectFit" show-menu-by-longpress lazy-load /></swiper-item><swiper-item><image src="../../assets/banner/banner-3.png" mode="aspectFit" show-menu-by-longpress lazy-load /></swiper-item></swiper>
</view><!-- 公司信息 -->
<view class="info"><!-- text 不可嵌套其他组件user-select:长按以后选中文本 boolean 文本是否可选,该属性会使文本节点显示为 inline-block space:显示连续空格 可选值:ensp 中文字符空格一半大小 emsp 中文字符空格大小 nbsp 根据字体设置的空格大小--><text space="ensp">同城配送</text><text space="ensp">行业龙头</text><text space="ensp">半小时送达</text><text space="ensp">100% 好评</text>
</view><!-- 商品导航 -->
<view class="good-nav"><view class="nav-item"><!-- 在进行页面跳转时,需要在路径的前面添加 / 斜线,否则跳转不成功open-type string类型 默认值 navigate 跳转方式参数列表:navigate 对应 wx.navigateTo 或 wx.navigateToMiniProgram 的功能只能跳转到非 tabbar 页面,不能跳转到 tabbar 页面,保留上级页面redirect 对应 wx.redirectTo 的功能只能跳转到非 tabbar 页面,不能跳转到 tabbar 页面,关闭上级页面switchTab 对应 wx.switchTab 的功能 只能跳转到 tabbar 页面,不能跳转到非 tabbbar 页面reLaunch 对应 wx.reLaunch 的功能关闭所有页面,打开到应用内的某个页面navigateBack 对应 wx.navigateBack 或 wx.navigateBackMiniProgram (基础库 2.24.4 版本支持)的功能关闭当前页面,返回上一页面或多级页面 默认返回上一页 当返回多级页面时,需要添加 delta 属性,表示返回的层级 默认为 1exit 退出小程序,target="miniProgram"时生效--><navigator url="/pages/list/list?id=10&num=hua"><image src="../../assets/category/cate-1.png" mode=""/><text>鲜花玫瑰</text></navigator></view><view class="nav-item"><navigator url="/pages/list/list?id=10&num=hua"><image src="../../assets/category/cate-2.png" mode=""/><text>鲜花玫瑰</text></navigator></view><view class="nav-item"><navigator url="/pages/list/list?id=10&num=hua"><image src="../../assets/category/cate-3.png" mode=""/><text>鲜花玫瑰</text></navigator></view><view class="nav-item"><navigator url="/pages/list/list?id=10&num=hua"><image src="../../assets/category/cate-4.png" mode=""/><text>鲜花玫瑰</text></navigator></view><view class="nav-item"><navigator url="/pages/list/list?id=10&num=hua"><image src="../../assets/category/cate-5.png" mode=""/><text>鲜花玫瑰</text></navigator></view>
</view><!-- 推荐商品 -->
<view class="good-hot"><scroll-view class="scroll-x" scroll-x><view><view class="good-item"><image src="../../assets/floor/1.png" mode=""/><text>鲜花玫瑰</text><text>66</text></view></view><view><view class="good-item"><image src="../../assets/floor/2.png" mode=""/><text>鲜花玫瑰</text><text>99</text></view></view><view><view class="good-item"><image src="../../assets/floor/3.png" mode=""/><text>鲜花玫瑰</text><text>100</text></view></view><view><view class="good-item"><image src="../../assets/floor/4.png" mode=""/><text>鲜花玫瑰</text><text>105</text></view></view><view><view class="good-item"><image src="../../assets/floor/5.png" mode=""/><text>鲜花玫瑰</text><text>88</text></view></view></scroll-view>
</view>
page {height: 100vh;background-color: #efefef !important;padding: 16rpx;box-sizing: border-box;display: flex;flex-direction: column;> view {&:nth-child(n+2) {margin-top: 16rpx;}}
}// 轮播图区域样式
.swiper {border-radius: 10rpx;overflow: hidden;swiper {height: 360rpx;swiper-item {image{width: 100%;height: 100%;}}}
}// 公司信息
.info {display: flex;justify-content: space-between;background-color: #fff;padding: 20rpx 16rpx;border-radius: 10rpx;font-size: 24rpx;
}// 商品导航区域
.good-nav {display: flex;justify-content: space-between;background-color: #fff;padding: 20rpx 16rpx;border-radius: 10rpx;.nav-item {navigator {display: flex;flex-direction: column;align-items: center;image {width: 80rpx;height: 80rpx;}text {font-size: 24rpx;margin-top: 12rpx;}}}
}// 推荐商品
.good-hot {background-color: #fff;border-radius: 10rpx;padding: 16rpx;font-size: 24rpx;.scroll-x {width: 100%;white-space: nowrap;view {display: inline-block;width: 320rpx;height: 440rpx;margin-right: 16rpx;.good-item {display: flex;flex-direction: column;justify-content: space-between;image {width: 100%;height: 320rpx;}text {&:nth-of-type(1) {font-weight: bold;}}}&:last-child {margin-right: 0;}}}
}
1.8 字体图标的使用
在项目中使用到的小图标,一般由公司设计师进行设计,设计好以后上传到 阿里巴巴矢量图标库,然后方便程序员来进行使用。
小程序中的字体图标使用方式与 Web 开发中的使用方式一样的。
在 阿里巴巴矢量图标库 中搜索或上传自己需要的图标,添加到项目,生成对应的样式文件,在微信小程序项目目录创建,字体图标 iconfont 文件夹,然后新建 iconfont.scss 样式文件,导入到 app.scss文件中,即可使用。
样式文件推荐使用 Base64 格式的字体文件,在项目设置中设置,如下图:
iconfont.scss 文件中
@font-face {font-family: "iconfont"; /* Project id 4569815 */src: url('data:application/x-font-woff2;charset=utf-8;base64,d09GMgABAAAAAAYwAAsAAAAAC7gAAAXjAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHFQGYACDMgqKPIh4ATYCJAMUCwwABCAFhGcHUxslClGULlKC7Mdh7DwJwwiNSoHG4pHKh7t33+Ph+WO85776XZbpUfPRaLymgQ2jaOQTKh5VW/RQnTaFPCnlLBTsBoV8foHpyXwBHtS5FK41Ey/D5cMvtwriFxfsRpM3eSvD9VO7xCv///1KJ9L+ANYF8PumqizD/gDVBvDYWnSBDPieFadVwc2lmOo1XiPQbVss5dPU7GKgf40vFwNrqszJIKmOazTJB5qCQ2kQhU5z63pqEQ9AJd1UvgiA+8Hvx0cQGJokNTOvcvZCCgaOPlN6dlql+z8iPwfehIDdEyoy1jfmiuvNgWtUIt16YOiuCqg6PBzVKZFJN3J7Jnp2+v9/Es3RTN3WPzyKLAmiMQ29xD0sFt4oOYrE6AabDKMINgnG01A9i6F6p3RjBIjHAKRTYFrmp9y0ydq4AFeYURpwYFlDoYDX7mhgbTZJlhcUyJwVGZ0K1ZaCz+Jb3a6LpL826BUyUij30HxTvwcZqmFVtKQtM1FVWZM/rXrOVYl/v+Ix3+SJ7F/WUwzBsHNKQIGgz5U1hqGcxlkW/8H7t0lm0Gz7Ek1Taryixq1Vv+QdVD4lKDucJRk6rw8rsOOFi7uHQQZrEAJ0QI8YJtiaKgDI29uRhR1uLyu3Jb5n7eQWADRK+IE2JMzrpZJlUVg0zx4gpoktixoG0vU+sg/GmMCHMJXl1W2/TBQJqkftbkcCCxkHNG8vTlscJatE24clazI93VHMQLy+xNipha41KAZVlYvGGlVAlKAsRLaHHddENkfOzjwa6mBJ76R4Wr7LsofXvOsXXNMZDnoAANpn2dUv8hHvKVpUwJVf+p8EdOQ7v9cYpBlnBQLL4HFyjx/oHbp1hI9TJsLX4NAGwm8GY+ZErzCaB2eeJdmpkjdPmJ4oV+UO+ZQyuN+oZPzcdo05I6DJLcOB4JNBZdp0gk7tt/RBdUapzHNkAInfa7I3nj/QXOpOKqk/NP2mU5ugTQeWeWcOVOZrGcKrJlu09hvNa20x+WAI87VKNinWepCt4d/jLYH+ox8MuSR4Z5rvskqrBzmrsi8lHxQDiUEDio9hqmieNwKMx5rv8sp0TQB/QzBtowkmvbYgmFFvUm8V6PGseHqCjoAysjb6pDa1JnWhc1xV8+nBNe0GS6xYeMHhPTwAWadE1cocnywvryyfnF221SszxrR2F1kzvUhZEGZlHlZZk9Gekd42l97enj7Xlq5tNZfR1pZxol/PSM9+ch3Pc/GekwKfFzG+tp9xLt5nrSh2Zvqry5kV3jNR0zHLmXTwf56qdatynZhwrXKrvWqjjzGmZ9e6Xa3YXL3vfDsTQ0ZHkzHMDxt9jDFN/0N6JBbwKtcfE2vE6f4L/tMNeJrVX7KyBDSuBqsr9ObVvdZ5PYUaobFUI1n98mSNZd5mBd0Pj12wo0ye2aRoLDFcopHiZOT0WHPYcFgzxtrsofkGs0dmdHFXdhd2BIOjmG7RQBdx2Rx6BMXli2gnQ+cNvM3ZW0otKN++JPFvZBplY9sUbjGGDKKQYGvQDOD/Xe5XiQvEDzlPJX5vVEC+AZC/Kl/8nnbzi/4G6wyiv2rq/MfWvegapUjTvdgpwSu+HuP5jrRFI2QsVYOOooO0uquFEt9I3+cSGfZ4GveIKX2hM6PkIekwA1mnWWbhrkLVYxeaTnvQbU3K2T1GcFRE6cKivh+EQQpI+j2CbNAZZuHegWrcc2gG/YVu50Lvcj0WA02S4nw5jkGkBdIoKyZsszxYtBAnG4R8aVm2FJfKaKRhgK9/vpyGi3HpGEfIGslAuZyAhJQVwVTubrhQyEJOyjI4Kvel5HIu0s+PqPoRX5QVgSRSOD45HAZCtIBoKJYY4dHLs3+vEI7UQIhP2pFZ6pOSMXJaHArgy78DM80g7jR0I40yjUiB5OQjQEcnpZIIlCogdEK+wIK46hwDh6oBX9SAHCeSn0VHdDX4Lm8SvWDlf3akeb0aKXKUqNF0XOKzHC0mVS//EDVUyKICQzkrJlEKd2McTssSOwAA') format('woff2'),url('//at.alicdn.com/t/c/font_4569815_u50pb2x14v.woff?t=1717164520149') format('woff'),url('//at.alicdn.com/t/c/font_4569815_u50pb2x14v.ttf?t=1717164520149') format('truetype');
}.iconfont {font-family: "iconfont" !important;font-size: 16px;font-style: normal;-webkit-font-smoothing: antialiased;-moz-osx-font-smoothing: grayscale;
}.icon-haoping:before {content: "\e6c6";
}.icon-icon:before {content: "\e66d";
}.icon-clock:before {content: "\e627";
}.icon-tongchengpeisong:before {content: "\e601";
}
app.scss 文件中
// 导入样式文件以后,必须以分号结尾,否则会出现异常
@import "./iconfont/iconfont.scss";
index.scss 文件中
// 公司信息
.info {display: flex;justify-content: space-between;background-color: #fff;padding: 20rpx 16rpx;border-radius: 10rpx;font-size: 24rpx;.iconfont {font-size: 24rpx;}
}
index.wxml 文件中
<!-- 公司信息 -->
<view class="info"><!-- text 不可嵌套其他组件user-select:长按以后选中文本 boolean 文本是否可选,该属性会使文本节点显示为 inline-block space:显示连续空格 可选值:ensp 中文字符空格一半大小 emsp 中文字符空格大小 nbsp 根据字体设置的空格大小--><text space="ensp"><text class="iconfont icon-tongchengpeisong"></text>同城配送</text><text space="ensp"><text class="iconfont icon-icon"></text>行业龙头</text><text space="ensp"><text class="iconfont icon-clock"></text>半小时送达</text><text space="ensp"><text class="iconfont icon-haoping"></text>100% 好评</text>
</view>
注意事项:
使用字体图标可能会报错:[渲染层网络层错误] Failed to load font.………该错误可忽略
但在控制台出现错误,会影响开发调试,解决方案是:将字体图标转换成 base64 的格式
2. 背景图片的使用
当编写小程序的样式文件时,我们可以使用 background-image
属性来设置元素的背景图像
注意事项:
小程序的
background-image
不支持本地路径! 需要使用网络图片,或者 base64 ,或者使用<image />
组件
五、事件系统
1. 事件绑定和事件对象
小程序中绑定事件与网页开发中绑定事件几乎一致,只不过在小程序中不能通过 on 的方式绑定事件,也没有 click 等事件,小程序中绑定事件使用 bind 方法,click 事件也需要使用 tap 事件来进行代替,绑定事件的方式有两种:
第一种方式:bind:事件名,bind 后面需要跟上冒号,冒号后面跟上事件名,例如:<view bind:tap="fnName"></view>
第二种方式:bind事件名,bind 后面直接跟上事件名,例如:<view bindtap="fnName"></view>
事件处理函数需要写到 .js 文件中,在 .js 文件中需要调用小程序提供的 Page 方法来注册小程序的页面,我们可以直接在 Page 方法中创建事件处理函数。
<button type="primary" size="mini" bind:tap="handleTap">绑定事件</button>
<button type="warn" size="mini" bindtap="handleTap">绑定事件</button><!-- 微信小程序input输入框默认不带边框 -->
<input type="text" bindinput="getInputValue"/>
微信小程序input输入框默认不带边框
Page({handleTap(event) {console.log(event)},getInputValue(event) {console.log(event.detail.value)}
})
2. 事件分类以及阻止事件冒泡
事件分为 冒泡事件 和 非冒泡事件:
冒泡事件:当一个组件的事件被触发后,该事件会向父节点传递
非冒泡事件:当一个组件的事件被触发后,该事件不会向父节点传递
使用 bind 绑定的事件,会触发事件冒泡,如果想阻止事件冒泡,可以使用 catch 来绑定事件。
<view class="catch" bindtap="parentHander"><button catchtap="btnHander">按钮</button>
</view>
Page({parentHander () {console.log("父组件绑定的事件")},btnHander () {console.log("子组件绑定的事件")}
})
以上代码可以组织事件冒泡。
3. 事件传参 - data-*
自定义数据
事件传参:在触发事件时,将一些数据作为参数传递给事件处理函数的过程,就是事件传参
在微信小程序中,我们经常会在组件上添加一些自定义数据,然后在事件外理函数中获取这些自定义数据,从而完成业务逻辑的开发
在组件上 通过 data-*
的方式 定义需要传递的数据,其中 是自定义的属性,例如:<view data-id=“100"bindtap="handler”/>
然后通过事件对象进行获取自定义数据
<button catchtap="btnHander" data-id="1" data-name="tom">按钮</button><view class="catch" bindtap="parentHander" data-parent-id="1" data-parent-name="tom"><button data-id="1" data-name="tom">按钮</button>
</view>
Page({// 按钮触发的事件处理函数btnHander (event) {/*** currentTarget 事件绑定者 也就是指:哪个组件绑定了当前事件处理函数* target 事件触发者,也就死指:哪个组件触发了当前事件处理函数* currentTarget 和 target 都是指按钮,因为是按钮绑定的事件处理函数,同时点击按钮触发事件处理函数,这时候通过谁来获取数据都可以。*/console.log(event.currentTarget.dataset) // {id: "1", name: "tom"}console.log(event.target.dataset) // {id: "1", name: "tom"}},// view 触发的事件处理函数parentHander (event) {/*** 点击蓝色区域(不点击按钮)* currentTarget 事件绑定者 view* target 事件触发者,view* currentTarget 和 target 都是指 view,通过谁来获取数据都可以。* * 点击按钮(不点击蓝色区域)* currentTarget 事件绑定者 view* target 事件触发者,按钮* 如果想获取view 身上的数据,就必须使用 currentTarget;如果想获取事件本身的数据,就需要使用 target。* * 在传递参数的时候,自定义属性是多个单词:* 如果单词与单词之间使用中划线 - 进行连接,在事件对象中会被转换为小驼峰写法;* 如果使用的是小驼峰写法,在事件对象中会被转为全部小写的*/// 点击蓝色区域时(不能获取按钮的传参)console.log(event.currentTarget.dataset) // {parentId: "2", parentName: "tom"}console.log(event.target.dataset) // {parentId: "2", parentName: "tom"}// 点击按钮时(通过 currentTarget 获取父节点传参)console.log(event.currentTarget.dataset) // {parentId: "2", parentName: "tom"}console.log(event.target.dataset.id) // {id: "1", name: "tom"}}
})
点击父节点不能获取子节点的参数,点击子节点可以通过 currentTarget 获取父节点的参数。
注意事项:
- event.target 是指事件触发者,event.currentTarget 是指事件绑定者
- 使用 data- 方法传递参数的时候,多个单词由连字符-连接,连字符写法会转换成驼峰写法
- 使用 data- 方法传递参数的时候,而大写字符会自动转成小写字符
4. 事件传参 - mark 自定义数据
小程序进行事件传参的时候,除了使用 data-* 属性传递参数外,还可以 使用 mark 标记传递参数
mark 是一种自定义属性,可以在组件上添加,用于来识别具体触发事件的 target 节点。同时 mark 还可以用于承载一些自定义数据
在组件上使用 mark:自定义属性 的方式将数据传递给事件处理函数,例如:<view mark:id=“100" bindtap="handler”/>
,然后通过事件对象进行获取自定义数据。
mark 和 data-* 很相似,主要区别在于:*
mark 包含从触发事件的节点到根节点上所有的 mark:属性值
currentTarget.dataset 或者 target.dataset 只包含事件绑定者 或者 事件触发者那一个节点的 data-* 值
<!-- 如果需要使用 mark 进行事件传参,需要使用 mark:自定义属性的方式进行参数传递 -->
<button bindtap="markBtnHander" mark:id="1" mark:name="tom">按钮</button><view class="catch" bindtap="markParentHander" mark:parentId="2" mark:parentName="char"><button mark:id="1" mark:name="tom">按钮</button>
</view>
Page({// 使用 mark:自定义属性 传参markBtnHander(event) {console.log(event.mark) // {id: "1", name: "tom"}},markParentHander(event) {/*** 点击蓝色区域(不点击按钮)* 通过事件对象获取的是 view 身上绑定的数据* * 点击按钮(不点击蓝色区域)* 通过事件对象获取的是 触发事件的节点 以及 父节点身上所有的 mark 数据*/console.log(event.mark) // {id: "1", name: "tom", parentId: "2", parentName: "char"}}
})
六、wxml 语法
1. 声明和绑定数据
小程序页面中使用的数据均需要在 Page()
方法的 data 对象中进行声明定义
在将数据声明好以后,在 WXML 使用 Mustache 语法(双大括号{{ }}) 将变量包起来,从而将数据绑定
在 {{ }} 内部可以做一些简单的运算,支持如下几种方式:
- 算数运算
- 三元运算
- 逻辑判断
- 其他…
注意事项:在 {{ }} 语法中,只能写表达式,不能写语句,也不能调用 JavaScript 相关的方法
<!-- 如果需要展示数据,在 wxml 中需要使用双大括号写法将变量进行包裹 -->
<!-- 展示内容 -->
<view>{{ school }}</view>
<view>{{ obj.name }}</view><!-- 绑定属性值,如果需要动态绑定一个变量,属性值也需要使用双大括号进行包裹 -->
<view id="{{ id }}"></view>
<!-- 如果属性值是布尔值,也需要使用双大括号进行包裹 -->
<checkbox checked="{{ isChecked }}"/><!-- 算术运算 -->
<view>{{ id + 1 }}</view>
<view>{{ id - 1 }}</view>
<!-- 三元运算 -->
<view>{{ id === 1 ? "等于" : "不等于" }}</view><!-- 逻辑判断 -->
<view>{{ id === 1 }}</view><!-- 在双大括号写法内部,只能写表达式,不能写语句,也不能调用 JavaScript 的方法 -->
<!-- 以下写法报错 -->
<!-- <view>{{ if(id === 1) {} }}</view> -->
<!-- <view>{{ for(const i =0; i <= 10; i++) {} }}</view> -->
<!-- 以下代码没有报错,但也不会生效 -->
<!-- <view>{{ obj.name.toUpperCase }}</view> -->
Page({/*** 在小程序页面中所需要使用的数据均来自于 data*/data: {id: 1,isChecked: false,school: "尚硅谷",obj: {name: "tom"}}
})
2. setData()
修改数据
小程序中修改数据不推荐通过赋值的方式进行修改,通过赋值的方式修改数据无法改变页面的数据
而是要通过调用 setData()
方法进行修改,setData()
方法接收对象作为参数,key 是需要修改的数据,value 是最新的值。
setData()
方法有两个作用:
- 更新数据
- 驱动视图更新
<view>{{ num }}</view>
<button bindtap="handlerUpdata">更新 num</button>
Page({/*** 在小程序页面中所需要使用的数据均来自于 data*/data: {num: 1},handlerUpdata(event) {/*** 通过赋值的方式直接修改数据:* 能够修改数据,但是不能更新页面上的数据,例如:this.data.num += 1* * 通过 this.setData({ key: value }) 方式修改数据:1. 更新数据,2.驱动视图(页面)更新* key:是需要更新的数据* value:是最新的值*/this.setData({ num: this.data.num + 1 })}
})
3. setData()
- 修改对象类型数据
- 新增 单个/多个属性
- 修改 单个/多个属性
- 删除 单个/多个属性
<view>{{ userInfo.name }}</view>
<view>{{ userInfo.age }}</view>
<button bindtap="handlerUpdataUserInfo">更新 userInfo</button>
Page({/*** 在小程序页面中所需要使用的数据均来自于 data*/data: {userInfo: {name: "Tom",age: 10,test: "aaa"}},// 更新userInfohandlerUpdataUserInfo() {/*** 新增单个 / 多个属性* 如果给对象新增属性,可以将 key 写成数据路径的方式 a.b.c*/this.setData({"userInfo.name": "Tom","userInfo.age": 10})/*** 修改单个 / 多个属性* 如果需要修改对象属性,可以将 key 写成数据路径的方式 a.b.c*/this.setData({"userInfo.name": "jerry","userInfo.age": 18})/*** 优化方案* 目前进行新增和修改都是使用数据路径,如果新增和修改的数据量比较小,还可以;* 如果修改的数据很多,每次都写数据路径,就太麻烦了,可以使用 ES6 提供的展开运算符 和 Object.assign()*/// 通过展开运算符,能够将对象中的属性复制给另外一个对象,后面的属性会覆盖前面的属性const userInfo = {...this.data.userInfo,name: "Tom",age: 20}this.setData({userInfo})// Object.assign() 将多个对象合并为一个对象const userInfo = Object.assign(this.data.userInfo, { name: 'jerry', age: 8 })this.setData({ userInfo })// 删除单个属性delete this.data.userInfo.age;this.setData({ userInfo: this.data.userInfo })// 删除多个属性 rest 剩余参数const {age, test, ...rest} = this.data.userInfothis.setData({ userInfo: rest })}
})
4. setData()
- 修改数组类型数据
- 新增数组元素
- 修改数组元素
- 删除数组元素
<!-- setData() 修改数组类型数据 -->
<view wx:for="{{ list }}" wx:key="index">{{ item }}</view>
<button bindtap="handlerUpdataList">修改数组类型数</button>
Page({/*** 在小程序页面中所需要使用的数据均来自于 data*/data: {list: [1, 2, 3]},// 修改数组类型数据handlerUpdataList() {// 新增数组元素// 如果直接使用 push 方法,可以更新 data, 但是不能更新 页面中的数组,需要调用 setData() 更新页面this.data.list.push(4);this.setData({list: this.data.list})const newList = this.data.list.concat(4);this.setData({list: newList})const newList = [...this.data.list, 4];this.setData({list: newList})// 修改数组元素,使用 数据路径的方式修改 如:{ "list[1].name": 6 }this.setData({"list[1]": 6})// 删除数组元素this.data.list.splice(1, 1)this.setData({list: this.data.list})const newList = this.data.list.filter(item => item != 2)this.setData({list: newList})}
})
5. 简易双向数据绑定
在 WXML中,普通属性的绑定是单向的,例如:<input value="{{value}}"/>
如果希望用户输入数据的同时改变 data 中的数据,可以借助简易双向绑定机制。在对应属性之前添加 mode: 前缀即可:
例如:<input model:value="{{value}}" />
注意事项:
简易双向绑定的属性值如下限制:
- 只能是一个单一字段的绑定,例如:错误用法:
<input model:value="值为{{value}}"/>
- 尚不能写 data 路径,也就是不支持数组和对象,例如:错误用法:
<input model:value="{{a.b}}"/>
<!-- 单向绑定:数据能够影响页面,但是页面更新不会影响到数据 -->
<input type="text" value="{{ value }}" />
<!-- 双向绑定:数据能够影响页面,页面更新也能够影响数据,实现简易的双向绑定,需要在对应的属性之前添加 model: -->
<input type="text" model:value="{{ value }}" />
<!-- 如果需要获取复选框的选中效果,需要给 checked 添加 model: -->
<checkbox model:checked="{{ isChecked }}"/>是否同意该协议
<!-- 注意事项1:属性值只能是一个单一字段的绑定 -->
<input type="text" model:value="值为{{ value }}" /> <!-- 错误写法 -->
<!-- 注意事项2:属性值不能写成数据路径,也不支持对象和数组 -->
<input type="text" model:value="{{ obj.name }}"/> <!-- 错误写法 -->
Page({/*** 在小程序页面中所需要使用的数据均来自于 data*/data: {isChecked: false,obj: {name: "tom"},list: [1, 2, 3],value: 123}
})
6. 列表渲染 - 基本使用
列表渲染 就是指通过循环遍历一个数组或对象,将其中的每个元素渲染到页面上
在组件上使用 wx:for
属性绑定一个数组或对象,既可使用每一项数据重复渲染当前组件
每一项的变量名默认为 item,下标变量名默认为 index
在使用 wx:for
进行遍历的时候,建议加上 wx:key
属性, wx:key
的值以两种形式提供:
- 字符串:代表需要遍历的
array
中item
的某个属性,该属性的值需要是列表中唯一的字符串或数字,且不能动态改变 - 保留关键字
*this
代表在for
循环中的item
本身,当item
本身是一个唯一的字符串或者数字时可以使用
注意事项:
- 如果不加
wx:key
,会报一个warning
,如果明确知道该列表是静态,即以后数据不会改变,或者不必关注其顺序,可以选择忽略。 - 在给
wx:key
添加属性值的时候,不需要使用双大括号语法,直接使用遍历的array
中item
的某个属性
<!-- 如果需要进行列表渲染,需要使用 wx:for 属性,属性需要使用双大括号进行包裹,每一项的变量名默认是 item,每一项下标的变量名默认是 index -->
<!-- wx:key 提升性能wx:key 属性值有两种添加形式:1. 字符串,需要是遍历的数组中 item 的某个属性,要求该属性是列表中唯一的字符串或者数字,不能进行动态改变2. 保留关键字 *this, *this 代表的是 item 本身,item 本身是唯一的字符串或者数字wx:key 的属性值不需要使用大括号进行包裹,直接写遍历的数组中 item 的某个属性
-->
<!-- 如果渲染的是数组,item:数组的每一项,index:下标 -->
<view wx:for="{{ numList }}" wx:key="*this">{{ item }} - {{ index }}</view>
<!-- 如果渲染的是对象,item:对象的值,index:对象的属性 -->
<view wx:for="{{ obj }}" wx:key="index">{{ item }} - {{ index }}</view>
<view wx:for="{{ fruitList }}" wx:key="index">{{ item.id }} - {{ item.name }}</view>
<view wx:for="{{ fruitList }}" wx:key="id">{{ item.id }} - {{ item.name }}</view>
Page({/*** 在小程序页面中所需要使用的数据均来自于 data*/data: {obj: {name: "tom",age: 12},numList: [1, 2, 3],fruitList: [{ id: 1, name: "🍎" },{ id: 2, name: "🍋" },{ id: 3, name: "🍅" }]}
})
7. 列表渲染 - 进阶用法
- 如果需要对默认的变量名和下标进行修改,可以使用
wx:for-item
和wx:for-index
- 使用
wx:for-item
可以指定数组当前元素的变量名 - 使用
wx:for-index
可以指定数组当前下标的变量名
- 使用
- 将
wx:for
用在<block />
标签上,以渲染一个包含多个节点的结构块<block />
并不是一个组件,它仅仅是一个包装元素,不会在页面中做任何渲染,只接受控制属性<block />
标签在 wxml 中可以用于组织代码结构,支持列表渲染、条件渲染等
<!-- 如果需要修改默认的变量名,需要使用 wx:for-item 属性;如果需要修改默认的下标变量名,需要使用 wx:for-index 属性。两个属性需要和 wx:for 写到同一组件上在重命名、修改以后,需要使用最新的变量名-->
<!-- 数组 -->
<view wx:for="{{ fruitList }}" wx:key="id" wx:for-item="fruitItem" wx:for-index="i">
{{ fruitItem.name }}
</view>
<!-- 对象 -->
<view wx:for="{{ obj }}" wx:key="key" wx:for-item="value" wx:for-index="key">
{{ value }} - {{ key }}
</view><view wx:for="{{ fruitList }}" wx:key="id" wx:for-item="fruitItem" wx:for-index="i"><view>名字:{{ fruitItem.name }}</view><view>价格:{{ fruitItem.price }}</view>
</view><!-- block 不是一个组件,只是渲染元素,也就是只是包装元素,可以组织代码,支持列表渲染,block 不会在页面中做任何渲染,只接受控制属性
-->
<block wx:for="{{ fruitList }}" wx:key="id" wx:for-item="fruitItem" wx:for-index="i"><view>名字:{{ fruitItem.name }}</view><view>价格:{{ fruitItem.price }}</view>
</block>
Page({/*** 在小程序页面中所需要使用的数据均来自于 data*/data: {fruitList: [{ id: 1, name: "🍎", price: 66 },{ id: 2, name: "🍋", price: 77 },{ id: 3, name: "🍅", price: 77 }]}
})
8. 条件渲染
条件渲染主要用来控制页面结构的展示和隐藏,在微信小程序中实现条件渲染有两种方式:
- 使用
wx:if
、wx:elif
、wx:else
属性组 - 使用
hidden
属性
wx:if
和 hidden
二者的区别:
wx:if
:当条件为 true 时将结构展示出来,否则结构不会进行展示,通过 移除/新增节点 的方式来实现hidden
:当条件为 true 时会将结构隐藏,否则结构会展示出来,通过 display 样式属性 来实现的
<view class="line">条件渲染</view>
<!-- wx:if 属性组,包含 wx:if wx:elif wx: else只有对应的条件成立,属性所在的组件才会进行展示 wx:elif wx: else 不能单独使用,在使用的时候,必须结合 wx:if 使用使用了 wx:if 属性组的组件不能被打断,组件必须连贯才可以,即中间不能参杂没有使用 wx:if 属性组的组件-->
<view wx:if="{{ num === 1 }}">num 等于 {{ num }}</view>
<view wx:elif="{{ num === 2 }}">num 等于 {{ num }}</view>
<view wx:else>num 大于2,目前 num 等于{{ num }}</view><!-- 使用了 wx:if 属性组的组件不能被打断,组件必须连贯才可以,即中间不能参杂没有使用 wx:if 属性组的组件 -->
<!-- <view wx:if="{{ num === 1 }}">num 等于 {{ num }}</view>
<view></view>
<view wx:elif="{{ num === 2 }}">num 等于 {{ num }}</view>
<view wx:else>num 大于2,目前 num 等于{{ num }}</view> --><!-- hidden 属性hidden 属性 属性值,如果是 true,就会隐藏结构,如果是 false,才会展示结构-->
<view hidden="{{ !isFlag }}">如果 isFlag 是 true,展示结构,否则隐藏结构</view><!-- wx:if 和 hidden 的区别:wx:if 控制结构的展示和隐藏,是通过新增和移除结构来实现的hidden 属性控制结构的 展示和隐藏,是通过 css 的 display 属性来实现--><button type="warn" bind:tap="handlerUpdata">更新num</button>
Page({/*** 在小程序页面中所需要使用的数据均来自于 data*/data: {num: 1,isFlag: true},// 更新 numhandlerUpdata(event) {/*** 通过赋值的方式直接修改数据:* 能够修改数据,但是不能更新页面上的数据,例如:this.data.num += 1* * 通过 this.setData({ key: value }) 方式修改数据:1. 更新数据,2.驱动视图(页面)更新* key:是需要更新的数据* value:是最新的值*/this.setData({ num: this.data.num + 1 })}
})
七、生命周期
1. 小程序运行机制
小程序启动可以分为两种情况,一种是冷启动,一种是热启动
冷启动:如果用户首次打开,或小程序销毁后被用户再次打开,此时小程序需要重新加载启动
热启动:如果用户已经打开过某小程序,然后在一定时间内再次打开该小程序,此时小程序并未被销毁,只是从后台状态进入前台状态
前台 和 后台状态
小程序启动后,界面被展示给用户,此时小程序处于**「前台」**状态。
当用户「关闭」小程序时,小程序并没有真正被关闭,而是进入了**「后台」**状态,当用户再次进入微信并打开小程序,小程序又会重新进入「前台」状态
挂起:小程序进入「后台」状态一段时间后(5 秒),微信停止小程序 JavaScript 线程执行,小程序进入**「挂起」**状态当开发者使用了后台播放音乐、后台地理位置等能力时,小程序可以在后台持续运行,不会进入到挂起状态
销毁:如果用户很久没有使用小程序,或者系统资源紧张,小程序会被销毁,即完全终止运行。
当小程序进入后台并被「挂起」后,如果很长时间(目前是 30 分钟)都未再次进入前台,小程序会被销毁当小程序占用系统资源过高,可能会被系统销毁或被微信客户端主动回收。
2. 小程序更新机制
在访问小程序时,微信会将小程序代码包缓存到本地。
开发者在发布了新的小程序版本以后,微信客户端会检查本地缓存小程序有没有新版本,并进行小程序代码包的更新。
小程序的更新机制有两种:启动时同步更新 和 启动时异步更新
启动时同步更新:微信运行时,会定期检查最近使用的小程序是否有更新。如果有更新,下次小程序启动时会同步进行更新,更新到最新版本后再打开小程序。如果 用户长时间未使用小程序时,会强制同步检查版本更新
启动时异步更新:在启动前没有发现更新,小程序每次 冷启动 时都会异步检查是否有更新版本。如果发现有新版本,将会异步下载新版本的代码包,将新版本的小程序在下一次冷启动进行使用,当访问使用的依然是本地的旧版本代码
在启动时异步更新的情况下,如果开发者希望立刻进行版本更新,可以使用 wx.getUpdateMannager
API 进行处理。在有新版本时提示用户重启小程序更新新版本。
App({/*** onLaunch 是小程序的钩子函数,这个钩子函数在冷启动时肯定会执行到* 当小程序冷启动时,会自动微信后台请求新版本信息,如果有新版本,会立即进行下载*/onLaunch() {// 使用 wx.getUpdateManager() 方法监听下载的状态const updateMannager = wx.getUpdateManager()// 当下载完成新版本以后,会触发 onUpdateReady 回调函数updateMannager.onUpdateReady(function() {// 在回调函数中给用户提示wx.showModal({title: '更新提示',content: '新版本已经准备好,是否重启应用?',complete: (res) => {if (res.confirm) {// 强制当前小程序使用新版本,并且会重启当前小程序updateMannager.applyUpdate()}}})})}
})
模拟更新设置:
3. 小程序生命周期介绍
应用生命周期是指应用程序进程 从创建到消亡的整个过程
小程序的生命周期指的是 小程序从启动到销毁的整个过程
小程序完整的生命周期由 应用生命周期、页面生命周期 和 组件生命周期 三部分来组成。
小程序生命周期伴随着一些函数,这些函数由小程序框架本身提供,被称为 生命周期函数,生命周期函数会按照顺序依次触发调用,帮助程序员在特定的时机执行特定的操作,辅助程序员完成一些比较复杂的逻辑。
4. 应用生命周期
应用生命周期通常是指一个小程序从 启动 → 运行 → 销毁的整个过程
应用生命周期伴随着一些函数,我们称为 应用生命周期函数,应用生命周期函数需要 在 app.js
文件的 App()
方法中进行定义,App()
方法必须在 app.js
中进行调用,主要用来注册小程序。
应用生命周期函数由 onLaunch
、onShow
、onHide
三个函数组成
从小程序生命周期的角度来看,我们一般讲的 「启动」专指冷启动,热启动一般被称为后台切前台。
App({/*** 当小程序初始化完成时,会触发 onLaunch(全局只触发一次)*/onLaunch: function () {/*** 当进行冷启动时,才会触发 onLaunch 钩子函数* 如果时热启动,不会触发 onLaunch 钩子函数,会触发 onShow 钩子函数* 因此,onLaunch(全局只触发一次)*/console.log("onLaunch 小程序初始化完成时")},/*** 当小程序启动,或从后台进入前台显示,会触发 onShow*/onShow: function (options) {console.log("onShow 当小程序启动,或从后台进入前台显示")},/*** 当小程序从前台进入后台,会触发 onHide*/onHide: function () {console.log("onHide 当小程序从前台进入后台")},/*** 当小程序发生脚本错误,或者 api 调用失败时,会触发 onError 并带上错误信息*/onError: function (msg) {}
})
5. 页面生命周期
页面生命周期通常是指一个小程序从 加载 → 运行 → 销毁的整个过程
页面生命周期函数需要在 Page()
方法进行定义
<!-- redirect:销毁当前页面,跳转到下一个页面 -->
<navigator url="/pages/list/list" open-type="redirect">跳转到列表页面-redirect</navigator>
<!-- navigate:保留当前页面,跳转到下一个页面 -->
<navigator url="/pages/list/list" open-type="navigate">跳转到列表页面-navigate</navigator>
Page({/*** 页面的初始数据*/data: {},/*** 生命周期函数--监听页面加载--一个页面只会调用只会调用一次*/onLoad: function (options) {console.log("onLoad 页面创建的时候");},/*** 生命周期函数--监听页面初次渲染完成--一个页面只会调用只会调用一次*/onReady: function () {console.log("onReady 页面初次渲染完成,代表页面已经准备妥当,可以和视图层进行交互");},/*** 生命周期函数--监听页面显示--如果从后台进入前台*/onShow: function () {console.log("onShow 页面在前台展示的时候");},/*** 生命周期函数--监听页面隐藏--在当前小程序进入后台是,也会触发执行*/onHide: function () {console.log("onHide 页面隐藏");},/*** 生命周期函数--监听页面卸载*/onUnload: function () {console.log("onHide 页面卸载、销毁");},/*** 页面相关事件处理函数--监听用户下拉动作*/onPullDownRefresh: function () {},/*** 页面上拉触底事件的处理函数*/onReachBottom: function () {},/*** 用户点击右上角分享*/onShareAppMessage: function () {}
})
onLoad、onReady 两个钩子函数,一个页面只会触发一次
6. 生命周期两个细节
- tabBar 页面之间相互切换,页面不会被销毁
- 点击左上角,返回上一个页面,会销毁当前页面
八、小程序 API
1. 小程序 API介绍
小程序开发框架提供丰富的微信原生 API,可以方便的调起微信提供的能力,例如:获取用户信息、微信登录、微信支付等,小程序提供
的 API 几乎都挂载在 wx 对象下,例如:wx.request()
、wx.setStorage()
等,wx 对象实际上就是小程序的宿主环境微信所提供的全局对象
异步 API 支持 callback & Promise 两种调用方式:
- 当接口参数 Object 对象中不包含 success/fail/complete 时将默认返回 Promise
- 部分接口如 request, uploadFile 本身就有返回值,因此不支持 Promise 风格的调用方式,它们的 promisify 需要开发者自行封装。
2. 网络请求
发起网络请求获取服务器的数据,需要使用 wx,request()
接口 API
wx,request()
请求的域名必须在微信公众平台进行配置,如果使用 wx,request()
请求未配置的域名,在控制台会有相应的报错。
跳过域名的校验的开发:
- 在微信开发者工具中,点击详情按钮,切换到本地详情,将不校验合法域名、web-view(业务域名)、TLS版本以及HTTPS证书 勾选上
- 在真机上,需要点击胶囊区域的分析按钮,在弹框中选择 开发调试,重启小程序后即可
注意事项:
这两种方式只适用于开发者工具、小程序的开发版和小程序的体验版,项目上线前必须在小程序管理平台进行合法域名的配置。
<button type="warn" bind:tap="getData">获取数据</button>
Page({/*** 页面的初始数据*/data: {list: []},// 获取数据getData() {// 如果需要发起网络情趣,需要使用 wx.request APIwx.request({// 接口地址url: 'https://gmall-prod.atguigu.cn/mall-api/index/findBanner',// 请求方式method: 'GET',// 请求参数data: {},// 请求头header: {},// API 调用成功以后,执行的回调success: (res) => {console.log(res);if(res.data.code === 200) {this.setData({list: res.data.data})}},// API 调用失败以后,执行的回调fail: (err) => {console.log(err);},// API 不管调用成功还是失败,执行的回调complete: () => {console.log("complete");}})}
})
3. 界面交互 - loading 提示框
小程序提供了一些用于界面交互的 API,例如:loading 提示框、消息提示框、模态对话框等 API
loading 提示框常配合网络请求来使用,用于增加用户体验,对应的API有两个:
wx.showLoading()
显示 loading 提示框wx.hideLoading()
关闭 loading 提示框
Page({/*** 页面的初始数据*/data: {list: []},// 获取数据getData() {// 显示 loading 提示框wx.showLoading({/*** title 用来显示提示的内容* 提示的内容不会自动换行,如果提示的内容比较多,因为在同一行展示,多出来的内容会被隐藏*/title: "数据加载中....",// 是否显示透明蒙层,防止触摸穿透mask: true});// 如果需要发起网络情趣,需要使用 wx.request APIwx.request({// 接口地址url: 'https://gmall-prod.atguigu.cn/mall-api/index/findBanner',// 请求方式method: 'GET',// 请求参数data: {},// 请求头header: {},// API 调用成功以后,执行的回调success: (res) => {console.log(res);if(res.data.code === 200) {this.setData({list: res.data.data})}},// API 调用失败以后,执行的回调fail: (err) => {console.log(err);},// API 不管调用成功还是失败,执行的回调complete: () => {console.log("complete");// 关掉 loading 提示框,通常放在 complete 回调函数里面wx.hideLoading()}})}
})
注意:
wx.showLoading()
和wx.hideLoading()
必须结合,配对使用。
4. 界面交互 - 模态对话框 - 消息提示框
wx.showModal()
:模态对话框,常用于询问用户是否执行一些操作
例如:询问用户是否退出登录、是否删除该商品 等
wx.showToast()
:消息提示框,根据用户的某些操作来告知操作的结果
例如:退出成功给用户提示,提示删除成功等
<button type="warn" bind:tap="delHandler">删除商品</button>
Page({async delHandler() {// showModal 显示模态对话框 是一个异步函数,返回 一个 Promise 对象const { confirm } = await wx.showModal({// 提示标题title: '提示',// 提示内容content: '是否删除该商品?'})if (confirm) {// showToast 消息提示框wx.showToast({title: '删除成功',icon: 'none',duration: 2000,})} else {wx.showToast({title: '取消删除',icon: 'error',duration: 2000,})}}
})
5. 本地存储
小程序本地存储是指在小程序中使用 API 将数据存储在用户的设备上,以便小程序运行时和下次启动时快速地读取这些数据
注意事项: 对象类型的数据,可以直接进行存储获取,无需使用
JSON.stringfy()
、JSON.parse()
转换
同步 API 示例:
<button size="mini" plain type="warn" bindtap="setStorage">存储</button>
<button size="mini" plain type="primary" bindtap="getStorage">获取</button>
<button size="mini" plain type="warn" bindtap="removeStorage">删除</button>
<button size="mini" plain type="primary" bindtap="clearStorage">清空</button>
Page({// 将数据存储到本地setStorage() {// 第一个参数:本地存储中指定的 key,第二个参数:需要存储的数据wx.setStorageSync('num', 1)// 在小程序中,如果对象类型的数据,可以直接进行存储获取,无需使用 JSON.stringfy()、JSON.parse() 转换wx.setStorageSync('obj', {name: 'Tom', age: 10})},// 获取本地存储的数据getStorage() {// 从本地存储的数据中获取指定 key 的数据、内容const num = wx.getStorageSync('num')const obj = wx.getStorageSync('obj')console.log(num);console.log(obj);},// 删除本地存储的数据removeStorage() {// 从本地移除指定 key 的数据、内容wx.removeStorageSync('num')},// 清空本地存储的全部数据clearStorage() {wx.clearStorageSync()}
})
异步 API 示例:
<button size="mini" plain type="warn" bindtap="setStorage">存储</button>
<button size="mini" plain type="primary" bindtap="getStorage">获取</button>
<button size="mini" plain type="warn" bindtap="removeStorage">删除</button>
<button size="mini" plain type="primary" bindtap="clearStorage">清空</button>
Page({// 将数据存储到本地setStorage() {/*----------------------------- 同步 API -------------------------------------------*/ // 第一个参数:本地存储中指定的 key,第二个参数:需要存储的数据// wx.setStorageSync('num', 1)// 在小程序中,如果对象类型的数据,可以直接进行存储获取,无需使用 JSON.stringfy()、JSON.parse() 转换// wx.setStorageSync('obj', {name: 'Tom', age: 10})/*----------------------------- 异步 API -------------------------------------------*/ wx.setStorage({key: 'num',data: 1})wx.setStorage({key: 'obj',data: {name: 'Jerry', age: 18}})},// 获取本地存储的数据async getStorage() {/*----------------------------- 同步 API -------------------------------------------*/ // 从本地存储的数据中获取指定 key 的数据、内容// const num = wx.getStorageSync('num')// const obj = wx.getStorageSync('obj')/*----------------------------- 异步 API -------------------------------------------*/ const num = await wx.getStorage({ key: 'num' })const obj = await wx.getStorage({ key: 'obj' })console.log(num);console.log(obj);},// 删除本地存储的数据removeStorage() {/*----------------------------- 同步 API -------------------------------------------*/ // 从本地移除指定 key 的数据、内容// wx.removeStorageSync('num')/*----------------------------- 异步 API -------------------------------------------*/ wx.removeStorage({ key: 'num' })},// 清空本地存储的全部数据clearStorage() {/*----------------------------- 同步 API -------------------------------------------*/ // wx.clearStorageSync()/*----------------------------- 异步 API -------------------------------------------*/ wx.clearStorage()}
})
6. 路由与通信
在小程序中的页面跳转,有两种方式:
- 声明式导航:
navigator
组件 - 编程式导航:使用小程序提供的 API
路径后可以带参数,参数与路劲之间使用 ?
分隔,参数键与参数值用 =
相连,不同参数用 &
分隔,例如:path?key=value&key2=value2
参数需要在跳转到的页面的 onLoad 钩子函数 中通过形参进行接收
<button plain size="mini" type="warn" bindtap="navigateTo">navigateTo</button>
<button plain size="mini" type="primary" bindtap="redirectTo">redirectTo</button>
<button plain size="mini" type="warn" bindtap="switchTo">switchTo</button>
<button plain size="mini" type="primary" bindtap="relaunchTo">relaunchTo</button>
Page({navigateTo() {// 保留当前页面,跳转到应用中其他页面,不能跳转到 tabbar 页面wx.navigateTo({url: '/pages/list/list?id=1&name=Tom',// url: '/pages/cate/cate',})},redirectTo() {// 销毁当前页面,跳转到应用中其他页面,不能跳转到 tabbar 页面wx.redirectTo({url: '/pages/list/list?id=1&name=Tom',// url: '/pages/cate/cate',})},switchTo() {// 跳转到 tabBar 页面,不能跳转到非 tabBar 页面,路劲后面不能传递参数wx.switchTab({// url: '/pages/list/list',url: '/pages/cate/cate',})},relaunchTo() {// 关闭所用的页面,然后跳转到应用中某一个页面wx.reLaunch({url: '/pages/list/list?id=1&name=Tom'})}
})
<button plain size="mini" type="warn" bindtap="navigateBack">navigateBack</button>
Page({navigateBack() {// 关闭当前页面,返回上一级或者返回多级页面wx.navigateBack({delta: 1})},onLoad(options) {console.log(options);}
})
7. 页面处理函数 - 上拉加载
上拉加载是小程序中常见的一种加载方式,当用户滑动页面到底部时,回家再更多的内同,以便用户继续浏览
小程序中实现上拉加载的方式:
- 在
app.json
或者page.json
中配置距离页面底部距离:onReachBottomDistance
;默认 50px - 在 页面.js 中定义
onReachBottom
事件监听用户上拉加载
<view wx:for="{{ numList }}" wx:key="*this">{{ item }}</view>
{"usingComponents": {},"onReachBottomDistance": 100
}
view {height: 400rpx;display: flex;align-items: center;justify-content: center;
}view:nth-child(odd) {background-color: lightcyan;
}
view:nth-child(even) {background-color: coral;
}
Page({/*** 页面的初始数据*/data: {numList: [1, 2, 3]},/*** 监听用户上拉加载*/onReachBottom() {/*** 产品需求* 当用户上拉,需要数字进行累加* 分析:* 当用户上拉加载时,需要对数字进行累加,每次加 3 个数字,目前时 [1, 2, 3], [1, 2, 3, 4, 5, 6]* 怎么追加?* 获取当前数组的最后一项 n, n + 1, n + 2, n + 3*/wx.showLoading({title: '数组加载中...'})setTimeout(() => {// 获取数组的最后一项const lastNum = this.data.numList[this.data.numList.length - 1];// 定义需要追加的元素const newArr = [lastNum + 1, lastNum + 2, lastNum + 3];this.setData({numList: [...this.data.numList, ...newArr]})wx.hideLoading()}, 3000)}
})
8.页面处理函数 - 下拉刷新
下拉刷新是小程序中常见的一种刷新方式,当用户下拉页面时,页面会自动刷新,以便用户获取最新的内容。
小程序中实现上拉加载更多的方式:
- 在 app.json 或者 page.json 中开启允许下拉,同时可以配置 窗口、loading 样式等
- 在 页面.js 中定义 onPullDownRefresh 事件监听用户下拉刷新
<view wx:for="{{ numList }}" wx:key="*this">{{ item }}</view>
{"usingComponents": {},"onReachBottomDistance": 100,"enablePullDownRefresh": true,"backgroundColor": "#efefef","backgroundTextStyle": "light"
}
view {height: 400rpx;display: flex;align-items: center;justify-content: center;
}view:nth-child(odd) {background-color: lightcyan;
}
view:nth-child(even) {background-color: coral;
}
Page({/*** 页面的初始数据*/data: {numList: [1, 2, 3]},/*** 监听用户下拉刷新*/onPullDownRefresh() {/*** 产品需求:* 当用户上拉加载更多以后,如果用户进行了下拉刷新,需要将数据进行重置*/this.setData({numList: [1, 2, 3]})// 在下拉刷新以后,loading 效果有可能不会回弹回去if (this.data.numList.length === 3) {wx.stopPullDownRefresh()}},/*** 监听用户上拉加载*/onReachBottom() {/*** 产品需求* 当用户上拉,需要数字进行累加* 分析:* 当用户上拉加载时,需要对数字进行累加,每次加 3 个数字,目前时 [1, 2, 3], [1, 2, 3, 4, 5, 6]* 怎么追加?* 获取当前数组的最后一项 n, n + 1, n + 2, n + 3*/wx.showLoading({title: '数组加载中...'})setTimeout(() => {// 获取数组的最后一项const lastNum = this.data.numList[this.data.numList.length - 1];// 定义需要追加的元素const newArr = [lastNum + 1, lastNum + 2, lastNum + 3];this.setData({numList: [...this.data.numList, ...newArr]})wx.hideLoading()}, 3000)}
})
9. 增强 scroll-view
使用 scroll-view
实现上拉加载更多和下拉刷新功能
<scroll-viewscroll-yclass="scroll-y"lower-threshold="100"bindscrolltolower="getMore"enable-back-to-toprefresher-enabledrefresher-default-style="white"refresher-background="#f7f7f8"bindrefresherrefresh="refreshHandler"refresher-triggered="{{ isTriggered }}"
><view wx:for="{{ numList }}" wx:key="*this">{{ item }}</view>
</scroll-view>
.scroll-y {height: 100vh;background-color: #efefef;
}
view {height: 400rpx;display: flex;align-items: center;justify-content: center;
}view:nth-child(odd) {background-color: lightcyan;
}
view:nth-child(even) {background-color: coral;
}
Page({/*** 页面的初始数据*/data: {numList: [1, 2, 3],isTriggered: false},// scroll-view 上拉加载更多事件的逻辑getMore() {/*** 产品需求* 当用户上拉,需要数字进行累加* 分析:* 当用户上拉加载时,需要对数字进行累加,每次加 3 个数字,目前时 [1, 2, 3], [1, 2, 3, 4, 5, 6]* 怎么追加?* 获取当前数组的最后一项 n, n + 1, n + 2, n + 3*/wx.showLoading({title: '数组加载中...'})setTimeout(() => {// 获取数组的最后一项const lastNum = this.data.numList[this.data.numList.length - 1];// 定义需要追加的元素const newArr = [lastNum + 1, lastNum + 2, lastNum + 3];this.setData({numList: [...this.data.numList, ...newArr]})wx.hideLoading()}, 3000)},refreshHandler() {wx.showToast({title: '下拉刷新...',})this.setData({numList: [1, 2, 3],isTriggered: false})}
})
九、自定义组件
1. 创建和注册组件
小程序目前已经支持组件化开发,可以将页面中的功能模块抽取成自定义组件,以便在不同的页面中重复使用;
也可以将复杂的页面拆分成多个低耦合的模块,有助于代码维护。
开发中常见的组件有两种:
- 公共组件:将页面内的功能模块抽取成自定义组件,以便在不同的页面中重复使用
- 页面组件:将复杂的页面拆分成多个低耦合的模块,有助于代码维护
如果是公共组件,建议放在项目根目录的 components 文件夹中
如果是页面组件,建议放在对应页面的目录下
建议:一个组件一个文件夹
开发中常见的组件主要分为 公共组件 和 页面组件 两种,因此注册组件的方式也分为两种:
- 全局注册:在 app.json 文件中配置 usingComponents 进行注册,注册后可以在任意页面使用
- 局部注册:在页面的 json 文件中配置 usingComponents 进行注册,注册后只能在当前页面使用
在 usingComponents 中进行组件注册时,需要提供 自定义组件的组件名 和 自定义组件文件路径
在将组件注册好以后,直接将 自定义组件的组件名 当成 组件标签名 使用即可
2. 组件的数据以及方法
组件数据和方法需要在 组件.js
的 Component 方法中进行定义, Component 创建自定义组件
- data:定义组件的内部数据
- methods:在组件中事件处理程序需要写到 methods 中才可以
3. 组件的属性 - properties
Properties 是指组件的对外属性,主要用来接收组件使用者传递给组件内部的数据,和 data 一同用于组件的模板渲染
注意事项:
设置属性类型需要使用 type 属性,属性类型是必填项,value 属性为默认值
属性类型可以为 String、Number、Boolean、Object、Array ,也可以为 null 表示不限制类型
4. 组件 wxml 的 slot - 插槽
在使用基础组件时,可以在组件中间写子节点,从而将子节点的内容展示到页面中,自定义组件也可以接收子节点
只不过在组件模板中需要定义 <slot />
节点,用于承载组件中的子节点
默认情况下,一个组件的 wxml 中只能有一个 slot(默认插槽)。需要使用多 slot 时,可以在组件 js 中声明启用。
同时需要给 slot 添加 name 属性来区分不同的 slot(具名插槽),然后给子节点内容添加 slot 属性,属性值是对应 slot 的 name 名称,从而将内容插入到对应的 slot 中。
"usingComponents": {"custom-checkbox": "./components/custom-checkbox/custom-checkbox","custom-slot": "./components/custom-slot/custom-slot"
}
<custom-checkbox lable="我已阅读并同意 用户协议 和 隐私协议" position="right">我已阅读并同意 用户协议 和 隐私协议
</custom-checkbox><custom-checkbox lable="匿名提交" position="left">匿名提交
</custom-checkbox><custom-slot><text slot="slot-top">我需要显示到顶部</text><!-- 默认情况下,自定义组件的子节点内容不会进行展示 --><!-- 如果想内容进行展示,需要在组件模板中定义 slot 节点 -->我是子节点内容<text slot="slot-bottom">我需要显示到底部</text>
</custom-slot>
<view class="custom-checkbox-container"><view class="custom-checkbox-box {{ position === 'right' ? 'right' : 'left' }}"><checkbox class="custom-checkbox" checked="{{ isChecked }}" bindtap="updateChecked" /><view><!-- lable 和 子节点内容都进行了展示要么展示 lable 属性,要么展示 子节点内容,如果用户传递了 lable 属性,要么展示 lable如果用户没有传递了 lable 属性,要么展示 子节点内容--><text wx:if="{{ lable !== '' }}">{{ lable }}</text><slot wx:else /></view></view>
</view>
.custom-checkbox-container {display: inline-block;
}.custom-checkbox-box {display: flex;align-items: center;
}.custom-checkbox-box.left {flex-direction: row-reverse;
}.custom-checkbox-box.right {flex-direction: row;
}.custom-checkbox {margin-left: 10rpx;
}
Component({/*** 组件的属性列表:组件的对外属性,主要用来接收组件使用者传递给组件内部的属性以及数据*/properties: {// 如果需要接收传递的属性,有两种方式:全写、简写// lable: String, // 简写lable: {// type 组件使用者传递过来的数据类型 包含:String、Number、Boolean、Object、Array,也可以设置为:null 表示不显示类型type: String, value: ''},position: {type: String,value: 'right'}},/*** 组件的初始数据:用来定义当前组件内部所需要使用的数据*/data: {isChecked: false},/*** 组件的方法列表:在组件中,所有的事件处理程序都需要写到 methods 方法中*/methods: {// 更新复选框的状态updateChecked() {this.setData({isChecked: !this.data.isChecked,/*** 在 JS 中可以访问和获取 properties 中的数据,但一般情况下,不建议修改,因为会造成数据流的混乱*/lable: '在组件内也可以修改 properties 中的数据'})}}
})
<view><!-- 具名插槽 --><slot name="slot-top" /><!-- slot 就是用来接收、承载子节点内容,slot 只是一个占位符,子节点内容会将 slot 进行替换 --><!-- 默认插槽 --><view><slot /></view><!-- 具名插槽 --><slot name="slot-bottom" />
</view>
Component({options: {// 启用多 slot 支持multipleSlots: true}
})
5. 组件样式以及注意事项
自定义组件拥有自己的 wxss 样式,组件 wxss 文件的样式,默认只对当前组件生效
编写组件样式时,需要注意以下几点:
- 组件和引用组件的页面不能使用 id 选择器(#a)、属性选择器([a]) 和 标签名选择器,请改用 class 选择器,
// 标签名选择器
text {color: lightgreen;
}// id 选择器
#content {color: lightgreen;
}// 属性选择器
[id=content] {color: lightgreen;
}
使用 id 选择器 和 属性选择器 直接不生效,标签名选择器 官方不允许使用,样式会生效。推荐使用 class 选择器
- 子元素选择器(.a > .b)只能用于 view 组件与其子节点之间,用于其他组件可能导致非预期的情况,即子选择器,只能用于 view 和 子组件,用于其他组件可能会出现样式失效问题。
- 继承样式,如 font、color ,会从组件外继承到组件内,即继承样式,例如:color\font 都会从组件外继承。
- 除继承样式外,全局中的样式、组件所在页面的的样式对自定义组件无效(除非更改组件样式隔离选项)
- 官方不推荐做法,不建议 在
app.wxss
或页面.wxss
中使用 标签名(view)选择器(或一些其他特殊选择器)设置样式;如果是在 全局样式文件 中设置样式,会影响项目中全部的相同组件;如果是在页面样式文件中设置样式,会影响当前页面所有的相同组件。 - 组件和引用组件的页面中使用后代选择器(.a.b)在一些极端情况下会有非预期的表现,如果出现,请避免使用;解决方案:需要具体到使用的位置。
6. 组件样式隔离
默认情况下,自定义组件的样式只受自身 wxss 的影响,但是有时我们需要组件使用者的样式能够影响到组件,这时候就需要指定特殊的样式隔离选项 stylelsolation,选择它支持以下取值:
- isolated:表示启用样式隔离,在自定义组件内外,使用 class 指定的样式将不会相互影响(一般情况下的默认值);
- apply-shared:表示页面 wxss 样式将影响到自定义组件,但自定义组件 wxss 中指定的样式不会影响页面;
- apply-sharedshared:表示页面 wxss 样式将影响到自定义组件,自定义组件 wxss 中指定的样式也会影响页面和其他设置了 apply-shared 或 shared 的自定义组件。
options: {/*** styleIsolation:配置组件样式隔离* * styleIsolation: "isolated",开启样式隔离,默认值;* 在默认情况下,自定义组件和组件使用者如果存在相同的类名,类名不会相互影响;* * styleIsolation: "apply-shared",表示组件使用者、页面的 wxss 样式能够影响到自定义组件,* 但是自定义组件的样式不会影响组件使用者、页面的 wxss 样式;* * styleIsolation: "shared",表示组件使用者、页面的 wxss 样式能够影响到自定义组件,* 自定义组件的样式会影响组件使用者、页面的 wxss 样式和其他使用了 apply-shared 和 shared 属性的自定义组件*/styleIsolation: "shared"},
7. 件样式隔离 - 小程序修改 checkbox 样式
<custom-checkbox lable="我已阅读并同意 用户协议 和 隐私协议" position="right">我已阅读并同意 用户协议 和 隐私协议
</custom-checkbox><view></view><view class="custom"><custom-checkbox lable="匿名提交" position="left">匿名提交</custom-checkbox>
</view>
/* 2.组件使用者也能修改默认样式 */
/* 可以通过在加外层的方式进行提升样式的权重 */
/* 复选框没有选中时默认的样式 */
.custom .custom-checkbox .wx-checkbox-input {border: 1px solid skyblue;
}
/* 复选框选中时默认的样式 */
.custom .custom-checkbox .wx-checkbox-input-checked {background-color: skyblue !important;
}
Component({options: {/*** styleIsolation:配置组件样式隔离* * styleIsolation: "isolated",开启样式隔离,默认值;* 在默认情况下,自定义组件和组件使用者如果存在相同的类名,类名不会相互影响;* * styleIsolation: "apply-shared",表示组件使用者、页面的 wxss 样式能够影响到自定义组件,* 但是自定义组件的样式不会影响组件使用者、页面的 wxss 样式;* * styleIsolation: "shared",表示组件使用者、页面的 wxss 样式能够影响到自定义组件,* 自定义组件的样式会影响组件使用者、页面的 wxss 样式和其他使用了 apply-shared 和 shared 属性的自定义组件*/styleIsolation: "shared"},/*** 组件的属性列表:组件的对外属性,主要用来接收组件使用者传递给组件内部的属性以及数据*/properties: {// 如果需要接收传递的属性,有两种方式:全写、简写// lable: String, // 简写lable: {// type 组件使用者传递过来的数据类型 包含:String、Number、Boolean、Object、Array,也可以设置为:null 表示不显示类型type: String, value: ''},position: {type: String,value: 'right'}},/*** 组件的初始数据:用来定义当前组件内部所需要使用的数据*/data: {isChecked: false},/*** 组件的方法列表:在组件中,所有的事件处理程序都需要写到 methods 方法中*/methods: {// 更新复选框的状态updateChecked() {this.setData({isChecked: !this.data.isChecked,/*** 在 JS 中可以访问和获取 properties 中的数据,但一般情况下,不建议修改,因为会造成数据流的混乱*/// lable: '在组件内也可以修改 properties 中的数据'})}}
})
<view class="custom-checkbox-container"><view class="custom-checkbox-box {{ position === 'right' ? 'right' : 'left' }}"><checkbox class="custom-checkbox" checked="{{ isChecked }}" bindtap="updateChecked" /><view class="content"><!-- lable 和 子节点内容都进行了展示要么展示 lable 属性,要么展示 子节点内容,如果用户传递了 lable 属性,要么展示 lable如果用户没有传递了 lable 属性,要么展示 子节点内容--><text wx:if="{{ lable !== '' }}">{{ lable }}</text><slot wx:else /></view></view>
</view>
.custom-checkbox-container {display: inline-block;
}.custom-checkbox-box {display: flex;align-items: center;
}.custom-checkbox-box.left {flex-direction: row-reverse;
}.custom-checkbox-box.right {flex-direction: row;
}.custom-checkbox {margin-left: 10rpx;
}.content {font-size: 24rpx;
}/* 复选框组件是公共组件,以后需要在多个页面或者多个项目中使用,所以需要先给复选框组件准备、设置一些默认样式如果在其他页面或者项目中使用的时候,发现样式不符合产品需求,可以进行修改、对默认样式进行修改*//* 1. 需要给复选框设置默认样式,需要先找到小程序给复选框提供的类名,通过小程序给提供的类名才可以修改,需要到小程序开发者文档,找到复选框文档,审查元素,进行查找在自定义组件中,不能直接修改复选框样式,如果需要进行修改,需要设置 styleIsolation: "shared" shared:修改其他页面的样式、组件使用者的样式、以及其他使用了 share 以及 apply-share 的组件如果只想影响当前组件,可以添加命名空间*//* 复选框没有选中时默认的样式 */
.custom-checkbox .wx-checkbox-input {width: 24rpx !important;height: 24rpx !important;border-radius: 50% !important;border: 1px solid #fda007;
}/* 复选框选中时默认的样式 */
.custom-checkbox .wx-checkbox-input-checked {background-color: #fda007 !important;}/* 复选框选中时 √ 样式 */
.custom-checkbox .wx-checkbox-input.wx-checkbox-input-checked::before {font-size: 22rpx;color: #fff;
}
8.数据监听器
数据监听器主要用于监听和响应任何属性(properties)和 数据(data)的变化,当数据发生变化时就会触发对应回调函数,从而方便开发者进行业务逻辑的处理
在组件中如果需要进行数据监听 需要使用 observers 字段
<custom-slot label="标题"></custom-slot>
<view><view>{{ num }}</view><view>{{ count }}</view><view>{{ obj.name }}</view><view>{{ arr[1] }}</view><view>{{ label }}</view><button type="warn" plain bindtap="updateData">更新数据</button>
</view>
Component({/*** 组件的属性列表*/properties: {label: {type: String,value: "测试"}},/*** 组件的初始数据*/data: {num: 10,count: 100,obj: { name: 'Tom', age: 22 },arr: [1, 2, 3]},// 用来监听数据以及属性是否发生了变化observers: {/*** 监听单个属性* key: 需要监听的数据* value:就是一个回调函数,形参:最新的数据*/num: function(newNum) {// 对 data 中的数据进行监听,如果数据没有发生改变,监听器不会执行console.log(newNum);},// count: function(newCount) {// console.log(newCount);// }/*** 同时监听多个数据*/// "num,count": function(newNum, newCount) {// console.log(newNum);// console.log(newCount);// }// 支持监听属性以及内部数据的变化// 'obj.name': function(newName) {// console.log(newName);// },// 'arr[1]': function(newItem) {// console.log(newItem);// },// 使用通配符监听对象中所有属性的变化// 'obj.**': function(newObj) {// console.log(newObj);// }label: function(newlabel) {// 组件使用者传递了参数,在监听器中就能获取传递的数据,即监听器立即就执行了console.log(newlabel);},},/*** 组件的方法列表*/methods: {// 更新数据updateData() {this.setData({num: this.data.num + 1,// count: this.data.count - 1// 'obj.name': 'jerry',// 'arr[1]': 666label: "最新的标题"})}}
})
observers:对 data 中的数据进行监听,如果数据没有发生改变,监听器不会执行;组件使用者(父级)传递了参数,在监听器中就能获取传递的数据,即监听器立即就执行了。
9.组件通信 - 父往子传值
父组件如果需要向子组件传递数据,只需要两个步骤:
- 在父组件 WXML 中使用 数据绑定 的方式向子组件传递动态数据
- 子组件内部使用 properties 接收父组件传递的数据即可
<custom-checkbox lable="我已阅读并同意 用户协议 和 隐私协议" position="right" checked="{{ isChecked }}">我已阅读并同意 用户协议 和 隐私协议
</custom-checkbox><view></view><view class="custom"><custom-checkbox lable="匿名提交" position="left">匿名提交</custom-checkbox>
</view>
Page({data: {isChecked: true}
})
/* 2.组件使用者也能修改默认样式 */
/* 可以通过在加外层的方式进行提升样式的权重 */
/* 复选框没有选中时默认的样式 */
.custom .custom-checkbox .wx-checkbox-input {border: 1px solid skyblue;
}
/* 复选框选中时默认的样式 */
.custom .custom-checkbox .wx-checkbox-input-checked {background-color: skyblue !important;
}
<view class="custom-checkbox-container"><view class="custom-checkbox-box {{ position === 'right' ? 'right' : 'left' }}"><checkbox class="custom-checkbox" checked="{{ isChecked }}" bindtap="updateChecked" /><view class="content"><!-- lable 和 子节点内容都进行了展示要么展示 lable 属性,要么展示 子节点内容,如果用户传递了 lable 属性,要么展示 lable如果用户没有传递了 lable 属性,要么展示 子节点内容--><text wx:if="{{ lable !== '' }}">{{ lable }}</text><slot wx:else /></view></view>
</view>
.custom-checkbox-container {display: inline-block;
}.custom-checkbox-box {display: flex;align-items: center;
}.custom-checkbox-box.left {flex-direction: row-reverse;
}.custom-checkbox-box.right {flex-direction: row;
}.custom-checkbox {margin-left: 10rpx;
}.content {font-size: 24rpx;
}/* 复选框组件是公共组件,以后需要在多个页面或者多个项目中使用,所以需要先给复选框组件准备、设置一些默认样式如果在其他页面或者项目中使用的时候,发现样式不符合产品需求,可以进行修改、对默认样式进行修改*//* 1. 需要给复选框设置默认样式,需要先找到小程序给复选框提供的类名,通过小程序给提供的类名才可以修改,需要到小程序开发者文档,找到复选框文档,审查元素,进行查找在自定义组件中,不能直接修改复选框样式,如果需要进行修改,需要设置 styleIsolation: "shared" shared:修改其他页面的样式、组件使用者的样式、以及其他使用了 share 以及 apply-share 的组件如果只想影响当前组件,可以添加命名空间*//* 复选框没有选中时默认的样式 */
.custom-checkbox .wx-checkbox-input {width: 24rpx !important;height: 24rpx !important;border-radius: 50% !important;border: 1px solid #fda007;
}/* 复选框选中时默认的样式 */
.custom-checkbox .wx-checkbox-input-checked {background-color: #fda007 !important;}/* 复选框选中时 √ 样式 */
.custom-checkbox .wx-checkbox-input.wx-checkbox-input-checked::before {font-size: 22rpx;color: #fff;
}
Component({options: {/*** styleIsolation:配置组件样式隔离* * styleIsolation: "isolated",开启样式隔离,默认值;* 在默认情况下,自定义组件和组件使用者如果存在相同的类名,类名不会相互影响;* * styleIsolation: "apply-shared",表示组件使用者、页面的 wxss 样式能够影响到自定义组件,* 但是自定义组件的样式不会影响组件使用者、页面的 wxss 样式;* * styleIsolation: "shared",表示组件使用者、页面的 wxss 样式能够影响到自定义组件,* 自定义组件的样式会影响组件使用者、页面的 wxss 样式和其他使用了 apply-shared 和 shared 属性的自定义组件*/styleIsolation: "shared"},/*** 组件的属性列表:组件的对外属性,主要用来接收组件使用者传递给组件内部的属性以及数据*/properties: {// 如果需要接收传递的属性,有两种方式:全写、简写// lable: String, // 简写lable: {// type 组件使用者传递过来的数据类型 包含:String、Number、Boolean、Object、Array,也可以设置为:null 表示不显示类型type: String, value: ''},position: {type: String,value: 'right'},/*** 复选框组件是公共组件* 需要在多个页面、在多个项目中进行使用,在使用的时候,有的地方默认是选中的效果,有的地方是没有选中的效果* 怎么处理?* 首先让复选框默认还是没有被选中的效果* 如果希望复选框默认被选中,传递属性(checked=true)到复选框组件*/checked: {type: Boolean,value: false}},observers: {// 如果需要将 properties 中的数据赋值给 data,可以使用 observers 进行处理checked: function(newChecked) {this.setData({isChecked: newChecked})}},/*** 组件的初始数据:用来定义当前组件内部所需要使用的数据*/data: {isChecked: false},/*** 组件的方法列表:在组件中,所有的事件处理程序都需要写到 methods 方法中*/methods: {// 更新复选框的状态updateChecked() {this.setData({isChecked: !this.data.isChecked,/*** 在 JS 中可以访问和获取 properties 中的数据,但一般情况下,不建议修改,因为会造成数据流的混乱*/// lable: '在组件内也可以修改 properties 中的数据'})}}
})
10.组件通信 - 子往父传值
子组件如果需要向父组件传递数据,可以通过小程序提供的事件系统实现,可以传递任意数据。
- 自定义组件内部使用 triggerEvent 方法发射一个自定义的事件,同时可以携带数据
- 自定义组件标签上通过 bind 方法监听 发射的事件,同时绑定事件处理函数,在事件函数函数中通过事件对象获取传递的数据
<view class="custom-checkbox-container"><view class="custom-checkbox-box {{ position === 'right' ? 'right' : 'left' }}"><checkbox class="custom-checkbox" checked="{{ isChecked }}" bindtap="updateChecked" /><view class="content"><!-- lable 和 子节点内容都进行了展示要么展示 lable 属性,要么展示 子节点内容,如果用户传递了 lable 属性,要么展示 lable如果用户没有传递了 lable 属性,要么展示 子节点内容--><text wx:if="{{ lable !== '' }}">{{ lable }}</text><slot wx:else /></view></view>
</view>
Component({options: {/*** styleIsolation:配置组件样式隔离* * styleIsolation: "isolated",开启样式隔离,默认值;* 在默认情况下,自定义组件和组件使用者如果存在相同的类名,类名不会相互影响;* * styleIsolation: "apply-shared",表示组件使用者、页面的 wxss 样式能够影响到自定义组件,* 但是自定义组件的样式不会影响组件使用者、页面的 wxss 样式;* * styleIsolation: "shared",表示组件使用者、页面的 wxss 样式能够影响到自定义组件,* 自定义组件的样式会影响组件使用者、页面的 wxss 样式和其他使用了 apply-shared 和 shared 属性的自定义组件*/styleIsolation: "shared"},/*** 组件的属性列表:组件的对外属性,主要用来接收组件使用者传递给组件内部的属性以及数据*/properties: {// 如果需要接收传递的属性,有两种方式:全写、简写// lable: String, // 简写lable: {// type 组件使用者传递过来的数据类型 包含:String、Number、Boolean、Object、Array,也可以设置为:null 表示不显示类型type: String, value: ''},position: {type: String,value: 'right'},/*** 复选框组件是公共组件* 需要在多个页面、在多个项目中进行使用,在使用的时候,有的地方默认是选中的效果,有的地方是没有选中的效果* 怎么处理?* 首先让复选框默认还是没有被选中的效果* 如果希望复选框默认被选中,传递属性(checked=true)到复选框组件*/checked: {type: Boolean,value: false}},observers: {// 如果需要将 properties 中的数据赋值给 data,可以使用 observers 进行处理checked: function(newChecked) {this.setData({isChecked: newChecked})}},/*** 组件的初始数据:用来定义当前组件内部所需要使用的数据*/data: {isChecked: false},/*** 组件的方法列表:在组件中,所有的事件处理程序都需要写到 methods 方法中*/methods: {// 更新复选框的状态updateChecked() {this.setData({isChecked: !this.data.isChecked,/*** 在 JS 中可以访问和获取 properties 中的数据,但一般情况下,不建议修改,因为会造成数据流的混乱*/// lable: '在组件内也可以修改 properties 中的数据'})/*** 目前复选框组件的状态是存储在复选框组件内部的、存储在自定义组件内部的,* 在实际开发中,组件使用者、父组件有时候也需要获取到复选框内部的状态,* 需要使用自定义组件内部发射一个自定义事件,如果组件使用者、父组件需要使用数据,绑定自定义事件进行获取即可。* * 将数组传递给父组件* 如果需要将数据传递给父组件,需要使用 triggerEvent 发射自定义事件,第二个参数是携带得参数*/this.triggerEvent("changeChecked", this.data.isChecked)}}
})
<!-- 需要在自定义组件标签上通过 bind 方法绑定自定义事件,同时绑定事件处理函数 -->
<custom-checkboxlable="我已阅读并同意 用户协议 和 隐私协议"position="right"checked="{{ isChecked }}"bind:changeChecked="getData">我已阅读并同意 用户协议 和 隐私协议
</custom-checkbox><view></view><view class="custom"><custom-checkbox lable="匿名提交" position="left">匿名提交</custom-checkbox>
</view>
Page({data: {isChecked: true},getData(event) {// 可以通过 事件对象.detail 获取子组件传递给父组件的数据console.log(event.detail);if (event.detail) {console.log("提交");} else {console.log("请同意协议!");}}
})
11.组件通信 - 获取组件实例
父组件可以 通过 this.selectComponent 方法,获取子组件实例对象,这样就可以直接访问子组件的任意数据和方法
this.selectComponent 方法在调用时需要传入一个匹配选择器 selector
<custom-checkboxclass="child"id="child"lable="我已阅读并同意 用户协议 和 隐私协议"position="right"checked="{{ isChecked }}"bind:changeChecked="getData"
>我已阅读并同意 用户协议 和 隐私协议
</custom-checkbox><button type="primary" plain bindtap="getChild">获取子组件实例对象</button>
Page({data: {isChecked: true},getData(event) {// 可以通过 事件对象.detail 获取子组件传递给父组件的数据console.log(event.detail);if (event.detail) {console.log("提交");} else {console.log("请同意协议!");}},// 获取子组件的实例对象getChild() {/*** this.selectComponent 方法获取子组件实例对象(参数 可传 id 选择器 和 类选择器)* 获取到实例对象以后,就能获取子组件所有的数据,也能获取子组件的方法*/const res = this.selectComponent('#child')console.log(res.data);}
})
12.组件生命周期
组件的生命周期:指的是组件自身的一些钩子函数,这些函数在特定的时间节点时被自动触发
组件的生命周期函数需要在 **lifetimes **字段内进行声明
组件的生命周期函数有 5 个:created、attached、ready、moved、detached
<custom06 wx:if="{{ num === 1 }}" />
<button type="warn" plain bindtap="handler">销毁自定义组件</button>
Page({data: {num: 1},handler() {this.setData({num: this.data.num + 1})}
})
<text>{{ name }}</text>
Component({data: {name: 'Tom'},// 组件生命周期函数lifetimes: {/*** 组件实例被创建好以后执行*/created() {console.log('组件 created');// 在 created 钩子函数中不能调用 setData,可以给组件添加一些自定义属性,可以通过 this 的方式进行添加// this.setData({// name: 'Jerry'// })this.test = '测试'},/*** 组件被初始化完毕、模板解析完成,已经把组件挂在到页面上*/attached() {console.log('组件 attached');// 一般页面的交互会在 attached 钩子函数中进行执行this.setData({name: 'Jerry'}),console.log(this.test);},/*** 组件被销毁时*/detached() {console.log('组件 detached');}}
})
注意事项:created 钩子函数中不能调用 setData
13.组件所在页面的生命周期
组件还有一些特殊的生命周期,这类生命周期和组件没有很强的关联
主要用于组件内部监听父组件的展示和隐藏状态,从而方便组件内部执行一些业务逻辑的处理
组件所在页面的生命周期有 4 个:show、hide、resize、routeDone,需要在 pageLifeTimes 字段内进行声明
// 组件所在页面的生命周期
pageLifetimes: {// 监听组件所在的页面展示(后台切前台)状态show() {console.log('组件 show');},// 监听组件所在的页面隐藏(前台切后台,点击 tabbar)状态hide() {console.log('组件 hide');},resize() {},routeDone() {}
}
14.小程序生命周期总结
一个小程序完整的生命周期由 应用生命周期、页面生命周期 和 组件生命周期 三部分来组成
- 小程序冷启动,钩子函数执行的顺序
- 保留当前页面,进入下一个页面,钩子函数执行的顺序
- 销毁当前页面,进入下一个页面,钩子函数执行的顺序
- 小程序热启动,钩子函数执行的顺序
15.拓展 - 使用 Component 构造页面
Component 方法用于创建自定义组件
小程序的页面也可以视为自定义组件,因此页面也可以使用 Component 方法进行创建,从而实现复杂的页面逻辑开发
<navigator url="/pages/detail/detail?id=10&title=测试">跳转到详情页面</navigator>
<view>{{ name }}</view>
<button type="warn" plain bind:tap="updateName">更新名字</button>
{"usingComponents": {}
}
Component({/*** 小程序页面也可以使用 Component 方法进行构造* * 为什么需要使用 Component 方法进行构造页面* Component 方法功能比 Page 方法强大很多,如果使用 Component 方法构造页面,可以实现更加复杂的页面逻辑开发,例如:属性监听等方法* * 注意事项:* 1. 要求 .json 文件中必须要有 usingComponents 字段* 2. 里面的配置项需要和 Component 中的配置项保持一致* 3. 页面中 Page 方法有一些钩子函数、事件监听方法,这些钩子函数和事件监听方法必须放到 methods 对象中才可以* 4. 组件的属性 properties 也可以接收页面的参数,在 onLoad 钩子函数中,可以通过 this.data 进行获取*/data: {name: 'Tom'},properties: {id: String,title: String},methods: {onLoad(options) {console.log('Component onload');// 以下三种方式都可以获取上级页面传递过来的参数// console.log(options);// console.log(this.data.id, this.data.title);// console.log(this.properties.id, this.properties.title);},// 更新nameupdateName() {this.setData({name: 'Jerry'})}}})
为什么需要使用 Component 方法进行构造页面?
Component 方法功能比 Page 方法强大很多,如果使用 Component 方法构造页面,可以实现更加复杂的页面逻辑开发,例如:属性监听等方法
注意事项:
- 要求对应 .json 文件中包含 usingComponents 字段;
- 页面使用 Component 构造器,需要定义语普通组件一样的字段与实例方法;
- 页面 Page 中的一些生命周期方法(如 onLoad() 等以“on”开头的方法),在 Component 中要写在 methods 属性中才能生效;
- 组件的属性 properties 也可以用于接收页面的参数,在 onLoad() 中可以通过 this.data 拿到对应的页面参数;
16.拓展 - 组件的复用机制 behaviors
小程序的 behaviors 方法是一种代码复用的方式,可以将一些通用的逻辑和方法提取出来,然后再多个组件中复用,从而减少代码冗余,提高代码的可维护性。
如果需要 behavior 复用代码,需要使用 Behavior() 方法,每个 behavior 可以包含一组属性,数据,生命周期函数和方法
组件引用它时,它的属性、数据和方法会被合并到组件中,生命周期函数也会再对应世纪被调用
组件和它引用的 behavior 中可以包含同名的字段,对这些字段的处理方法如下:
- 如果有同名的属性或方法,采用“就近原则”,组件会覆盖 behavior 中的同名属性或方法
- 如果有同名的数据字段且都是对象类型,会进行对象合并,其余情况会 采用“就近原则"进行数据覆盖
- 生命周期函数和 observers 不会相互覆盖,会是在对应触发时机被逐个调用,也就是都会被执行
<view>{{label}}</view>
<view>{{name}}</view>
<view>{{obj.name}} - {{obj.age}}</view><button type="primary" plain bind:tap="updateName">更新数据</button>
const behavior = Behavior({/*** 组件的属性列表*/properties: {label: {type: String,value: "我已同意该协议"}},/*** 组件的初始数据*/data: {name: "Tome",obj: {name: "Tyke"}},/*** 组件的方法列表*/methods: {updateName() {this.setData({name: "Jerry"})console.log("我是组建的函数~~~");}},lifetimes: {attached() {console.log("我是组建的生命周期函数~~~");}}
})export default behavior
import behavior from './behavior'
Component({behaviors: [behavior],/*** 在以后的开发中,使用 behaviors 进行代码复用的时候,组件 和 behaviors 可能存在相同的字段*/// 如果存在相同的 properties,采用就近原则,使用组件内部的数据properties: {label: {type: String,value: "匿名提交"}},// 如果存在相同的 data,如果是对象类型,属性会进行合并,如果不是对象类型的数据,就近原则,展示以组件内部为准data: {name: "组件中的 name",obj: {age: 100}},// 如果存在相同的方法,就近原则,展示以组件内部为准methods: {updateName() {console.log("我是组件内部的方法!!!");}},// 如果存在相同的生命周期函数,生命周期函数都会被触发,先执行 behavior 里面的,后执行 组件内里面的lifetimes: {attached() {console.log("我是组件内部调用的生命周期!!!");}}
})
17.拓展 - 外部样式类
默认情况下,组件和组件使用者之间如果存在相同的类名不会相互影响,组件使用者如果想修改组件的样式,就需要解除样式隔离,但是解除样式隔离以后,在极端情况下,会产生样式冲突、CSS 嵌套太深等问题,从而给我们的开发带来了一定的麻烦。
外部样式类:在使用组件时,组件使用者可以给组件传入 CSS 类名,通过传入的类名修改组件的样式。
如果需要使用外部样式类修改组件的样式,在 Component 中需要用 externalClasses 定义若干个外部样式类。
外部样式类的使用步骤:
- 在 Component 中用 extemmalClasses 定义段定义若干个外部样式类
- 自定义组件标签通过 属性绑定 的方式提供一个样式类,属性是 externalClasses 定义的元素,属性值是传递的类名
- 将接受到的样式类用于自定义组件内部
注意事项:在同一个节点上使用普通样式类和外部样式类时,两个类的优先级是未定义的,因此需要添加 !imporant 以保证外部样式类的优先级
<!-- 属性是在 externalClasses 里面定义的属性,属性值必须是一个类名 -->
<custom09 extend-class="my-class" />
.my-class {color: coral !important;
}
<!-- 在同一个节点上,如果存在 外部样式类 和 普通的样式类两个类的优先级是未定义的,建议:在使用外部样式类时,样式通过 !important 添加权重-->
<view class="extend-class box">通过外部样式类修改组件的样式</view>
.box {color: cornflowerblue;
}
Component({// 组件接受的外部样式类externalClasses: ['extend-class']
})
18.完善复选框案例以及总结自定义组件
- 组件基本使用:数据、属性、方法、插槽
- 组件样式使用:组件样式、注意事项、样式隔离、外部样式类
- 组件通信传值:父往子传值、子往父传值、获取组件实例
- 组件生命周期:组件的生命周期、组件所在页面的生命周期、总结了小程序全部的生命周期
- 组件数据监听器:observers
- 组件拓展:使用 Component构造页面、组件复用机制 behaviors 等
如果 styleIsolation 属性值是 shared 时,externalClasses 选项会失效
十、使用 npm 包
1.使用 npm 包
目前小程序已经支持使用 npm 安装第三方包,因为 node modules 目录中的包不会参与小程序项目的编译、上传和打包,因此在小程序项目中要使用的 npm 包,必须走一遍 构建 npm 的过程。
在构建成功以后,默认 会在小程序项目根目录,
也就是 node_modules 同级目录下生成 miniprogram_npm 目录,
里面存放着构建完成以后的 npm 包,也就是小程序运行过程中真正使用的包。
注意事项:
小程序运行在微信内部,因为运行环境的特殊性,这就导致 并不是所有的包都能够在小程序使用
我们在小程序中提到的包指专为小程序定制的包,简称小程序 npm 包,在使用之前需要先确定该包是否支持小程序
开发者如果需要发布小程序包,需要参考官方规范:npm 支持 | 微信开放文档
2.自定义构建 npm
在实际的开发中,随着项目的功能越来越多、项目越来越复杂,文件目录也变的很繁琐,为了方便进行项目的开发,开发人员通常会对目录结构进行调整优化,例如:将小程序源码放到 miniprogram 目录下
这时候需要开发者在 project.config.json 中 指定 node_moudles 的位置 和 目标 miniprogram_npm 的位置
具体配置如下:
- 配置 project.config.json 的 miniprogramRoot 指定小程序源码的目录
- 配置 project.config.json 的 setting.packNpmManually 为 true,开启自定义 node_modules 和 miniprogram_npm 位置的构建 npm 方式
- 配置 project.config.json 的 seting.packNpmRelationList 项,指定 packagesonPath 和 miniprogramNpmDistDir 的位置
3.Vant Weapp 组件库的使用
Vant Weapp 是有赞前端团队开源的小程序 UI 组件库,基于微信小程序的自定义组件开发,可以用来快速搭建小程序项目。
演示:image 图片组件
{..."usingComponents": {"van-image": "@vant/weapp/image/index","van-loading": "@vant/weapp/loading/index"}
}
<!-- 在使用 van-image 图片组件时,如果需要渲染本地图片是,不能使用 ../,需要相对于小程序源码的目录来查找图片才可以 -->
<van-imageround width="100"height="100" src="/assets/Jerry.png"bind:click="imageHandler"use-loading-slotuse-error-slotcustom-class="custom-class"
><van-loading slot="loading" type="spinner" size="20" vertical /><text slot="error">加载失败</text>
</van-image>
Page({imageHandler() {console.log("点击图片");}
})
.custom-class {border: 10rpx solid lightgreen !important;
}
在使用 Vant 提供的组件时,只需要两个步骤:
- 将组件在 app.json 中进行全部注册 或者 index.json 中进行局部注册
- 在引入组件后,可以在 wxml 中直接使用组件
注意事项:将 app.json 中的 “style”: “v2” 去除,小程序的新版基础组件强行加上了许多样式,难以覆盖,不关闭将造成部分组件样式混乱。
4.Vant Weapp 组件样式覆盖
Vant Weapp 基于微信小程序的机制,为开发者提供了 3 种修改组件样式的方法:
- 解除样式隔离:在页面中使用 Vant Weapp 组件时,可直接在页面的样式文件中覆盖样式
- 使用外部样式类:需要注意的是普通样式类和外部样式类的优先级是未定义的,使用时需要添加 !important 保证外部样式类的优先级
- 使用 CSS 变量:在页面或全局对多个组件的样式做批量修改以进行主题样式的定制
十一、分包加载
1. 小程序分包加载
小程序的代码通常是由许多页面、组件以及资源等组成,随着小程序功能的增加,代码量也会逐渐增加,体积过大就会导致用户打开速度变慢,影响用户的使用体验。
分包加载是一种小程序优化技术。将小程序不同功能的代码,分别打包成不同的子包,在构建时打包成不同的分包,用户在使用时按需进行加载,在构建小程序分包项目时,构建会输出一个或多个分包。每个使用分包小程序必定含有一个主包
主包:包含默认启动页面/TabBar 页面 以及 所有分包都需用到公共资源的包
分包:根据开发者的配置进行划分出来的子包
分包后加载顺序:在小程序启动时,默认会下载主包并启动主包内页面,当用户进入分包内某个页面时,微信客户端会把对应分包下载下来,下载完成后再进行展示。
目前小程序分包大小有以下限制:
- 整个小程序所有分包大小不超过 20MB(大小可能调整)
- 单个分包/主包大小不能超过 2MB
2. 配置分包加载以及打包、引用原则
小程序如果需要进行分包加载,需要在 app.json 中,通过 subPackages 或者 subpackages 定义分包结构
每个分包结构含三个常用字段:
- root:分包的根目录,该目录下的所有文件都会被打包成一个独立的包
- name:分包的别名,用于在代码中引用该分包
- pages:指定当前分包中包含哪些页面
例:配置 商品模块 分包,分包包含:商品列表、商品详情两个页面
"subPackages": [{"root": "modules/goodModule","name": "goodModule","pages": ["pages/list/list", "pages/detail/detail"]}],
在跳转到分包页面时,需要加上分包路径:
<navigator url="/modules/goodModule/pages/list/list" open-type="navigate">跳转到列表页面</navigator>
打包原则:
- tabBar 页面必须在主包内
- 最外层的 pages 字段,属于主包的包含的页面
- 按 subpackages 配置路径进行打包,配置路径外的目录将被打包到主包中
- 分包之间不能相互嵌套,subpackage 的根目录不能是另外一个 subpackage 内的子目录
引用原则:
- 主包不可以引用分包的资源,但分包可以使用主包的公共资源
- 分包与分包之间资源无法相互引用,分包异步化时不受此条限制
3. 独立分包
独立分包:是指能够独立于主包和其他分包运行的包
从独立分包中页面进入小程序时,不需要下载主包,当用户进入普通分包或主包内页面时,主包才会被下载
开发者可以将功能相对独立的页面配置到独立分包中,因为独立分包不依赖主包就可以运行,可以很大程度上提升分包页面的启动速度。
给 subPackages 定义的分包结构添加 independent 字段,即可声明对应分包为独立分包。
"subPackages": [{"root": "modules/goodModule","name": "goodModule","pages": ["pages/list/list", "pages/detail/detail"]},{"root": "modules/markModule","name": "markModule","independent": true,"pages": ["pages/mark/mark"]}],
注意事项:
- 独立分包中不能依赖主包和其他分包中的资源
- 主包中的 app.wxss 对独立分包无效
- App 只能在主包内定义,独立分包中不能定义 App,会造成无法预期的行为
4. 分包预下载
分包预下载是指访问小程序某个页面时,预先下载其他分包中的代码和资源,当用户需要访问分包中的页面时,已经预先下载的代码和资源,因此可以直接使用,从而提高用户的使用体验。
小程序的分包预下载需要在 app.json 中通过 preloadRule 字段设置预下载规则
preloadRule 是一个对象,对象的 key 表示访问哪个路径时进行预加载,value 是进入此页面的预下载配置,具有两个配置项:
- packages:进入页面后预下载分包的 root 或 name,APP 表示主包。
- network:在指定网络下预下载,可选值为:all(不限网络)、wifi(仅wifi下预下载)
"preloadRule": {"pages/index/index": {"network": "all","packages": ["goodModule", "modules/markModule"]},"modules/markModule/pages/mark/mark": {"network": "all","packages": ["__APP__"]}},
十二 、开放能力
1. 获取微信头像
当小程序需要让用户完善个人资料时,我们可以通过微信提供的头像、昵称填写能力快速完善
想使用微信提供的头像填写能力,需要两步:
- 将 button 组件 open-type 的值设置为 chooseAvatar
- 通过 bindchooseavatar 事件回调获取到头像信息的临时路径。
<view><button class="btn" open-type="chooseAvatar" bindchooseavatar="chooseAvatar"><image class="avatar" src="{{ avatar }}" mode=""/></button>
</view>
.btn {background-color: transparent;
}
.btn::after {border: none;
}.avatar {width: 200rpx;height: 200rpx;border-radius: 50%;
}
Page({/*** 页面的初始数据*/data: {avatar: "../../assets/Jerry.png"},chooseAvatar(event) {// 目前获取的微信头像是临时路径,临时路径是有失效时间的,在实际开发中,需要将临时路径上传到公司的服务器const { avatarUrl } = event.detail;this.setData({avatar: avatarUrl})}
})
2. 获取微信昵称
当小程序需要让用户完善个人资料时,我们可以通过微信提供的头像、昵称填写能力快速完善
想使用微信提供的昵称填写能力,需要三步:
- 通过 form 组件中包裹住 input 以及 form-type 为 submit 的 button 组件
- 需要将 input 组件 type 的值设置为 nickname,当用户输入框输入时,键盘上方会展示微信昵称
- 给 form 绑定 submit 事件,在事件处理函数中通过事件对象获取用户昵称
<!-- 需要使用 form 组件包裹住 input 以及 button 组件 -->
<form bindsubmit="onSubmit"><!-- input 输入框组件的 type 属性设置为 nickname,用户点击输入框,键盘上方才会显示昵称 --><!-- 如果添加了 name 属性,from 组件就会自动收集 name 属性的表单元素的值--><input type="nickname" name="nickname" placeholder="请输入昵称" /><!-- 如果将 from-type="submit",就将按钮变为提交按钮,在点击提交按钮的时候,会触发 表单的 bindsubmit 提交事件 --><button type="primary" plain form-type="submit">点击获取昵称</button>
</form>
input {border: 1px solid #179c16;margin: 20rpx;height: 60rpx;border-radius: 20rpx;padding-left: 20rpx;
}
Page({// 获取微信昵称onSubmit(event) {// console.log(event);const { nickname } = event.detail.value;console.log(nickname);}
})
3. 转发功能
转发功能,主要帮助用户更流畅地与好友分享内容和服务
想实现转发功能,有两种方式:
- 页面is 文件 必须声明 onShareAppMessage 事件监听函数,并自定义转发内容只有定义了此事件处理函数,右上角菜单才会显示“转发”按钮
- 通过 给 button 组件设置属性 open-type=“share”,可以在用户点击按钮后触发 Page.onShareAppMessage 事件监听函数
<!-- 页面设置转发按钮 -->
<button plain type="primary" open-type="share">转发</button>
Page({/*** 监听页面按钮的转发以及右上角的转发按钮*/onShareAppMessage: function (obj) {console.log(obj);return {title: "这是一个神奇的页面~~~",page: "/miniprogram/pages/index/index",imageUrl: "../../assets/Jerry.png"}}
})
通过页面转发,形参 obj 的 target 有值
{from: "button", target: {…}}
,通过右上角 “转发”按钮转发,形参 obj 的 target 没有值,{from: "menu", target: undefined}
4. 分享到朋友圈
小程序页面默认不能被分享到朋友圈,开发者需主动设置“分享到朋友圈”才可以,实现分享到朋友圈需满足两个条件:
- 页面 必须 设置允许“发送给朋友”,页面 js 文件声明 onShareAppMessage 事件监听函数
- 页面 必须 需设置允许“分享到朋友圈”,页面 js 文件声明 onShareTimeline 事件监听函数
Page({/*** 监听页面按钮的转发以及右上角的转发按钮*/onShareAppMessage: function (obj) {console.log(obj);return {title: "这是一个神奇的页面~~~",page: "/miniprogram/pages/index/index",imageUrl: "../../assets/Jerry.png"}},/*** 监听右上角 分享到朋友圈 按钮*/onShareTimeline: function() {return {title: "帮我砍一刀~~",query: "id=1",imageUrl: "../../assets/Jerry.png"}}
})
5. 手机号验证组件
手机验证组件,用于帮助开发者向用户发起手机号申请,必须经过用户同意后,才能获得由平台验证后的手机号,进而为用户提供相应服务
手机号验证组件分为两种:手机号快速验证组件 以及 手机号实时验证组件
- 手机号快速验证组件:平台会对号码进行验证,但不保证是实时验证
<button open-type="getPhoneNumber" bindgetphonenumber="getPhoneNumber" />
- 手机号实时验证组件:在每次请求时,平台均会对用户选择的手机号进行实时验证。
<button open-type="getRealtimePhoneNumber" bindgetrealtimephonenumber="getrealtimephonenumber" />
注意事项:
- 目前该接口针对非个人开发者,且完成了认证的小程序开放(不包含海外主体)
- 两种验证组件需要付费使用,每个小程序账号将有 1000 次体验额度
<buttontype="primary"plainopen-type="getPhoneNumber"bindgetphonenumber="getPhoneNumber"
>快速验证组件</button>
<buttontype="warn"plainopen-type="getRealtimePhoneNumber"bindgetrealtimephonenumber="getRealtimePhoneNumber"
>实时验证组件</button>
Page({// 手机号快速验证getPhoneNumber(event) {/*** 通过事件对象,可以看到,在 event.detail 中可以获取到 code,code 动态令牌,可以使用 code 换取用户的手机号,* 需要将 code 发送给后端,后端在接收到 code 以后,也需要调用 API,换取用户的真正手机号,在换取成功以后,会将手机号返回给前端*/console.log(event);},// 手机号实时验证getRealtimePhoneNumber(event) {console.log(event);}
})
6. 客户功能
小程序为开发者提供了客服能力,同时为客服人员提供移动端、网页端客服工作台便于及时处理消息使用方式:
- 需要将 button 组件 open-type 的值设置为 contact,当用户点击后就会进入客服会话
- 在微信公众后台,绑定后的客服账号,可以登陆 网页端客服 或 移动端小程序 客服接收、发送客服消息
<button type="warn" plain open-type="contact">联系客服</button>
十三、补充
1. 框架接口 - getApp()
在小程序中,可以通过 getApp() 方法获取到小程序全局唯一的 App 实例,因此在 App() 方法中添加全局共享的数据、方法,从而实现页面、组件的数据传值。
App({// 全局共享的数据globalData: {token: ''},// 全局共享的方法setToken(token) {// 如果想获取 token,可以使用 this 的方式进行获取this.globalData.token = token// 在 App() 方法中,如果想获取 App() 实例,可以通过 this 的方式进行获取,不能通过 getApp() 方法获取}
})
<button type="primary" plain bind:tap="login">登录</button>
// getApp() 方法用来获取全局唯一的 App() 实例
const appInstance = getApp();
Page({// 登录login() {// 不要通过 app 实例 调用钩子函数console.log(appInstance);appInstance.setToken("adadafasdfsfafdafasfdafasfdafaf")}
})
const appInstance = getApp();
Page({onLoad() {console.log(appInstance.globalData.token); // adadafasdfsfafdafasfdafasfdafaf}
})
注意事项:
- 不要在 App() 方法中使用 getApp(),使用 this 就可以拿到 app 实例
- 通过 getpp() 获取实例之后,不要私自调用生命周期函数
2. eventChannel - 页面间通信
如果一个页面通过 wx.navigateTo 打开一个新页面,这两个页面间将建立-条数据通道
- 在 wx.navigateTo 的 success 回调中通过 EventChannel 对象发射事件
- 被打开的页面可以通过 this.getopenerEventChannel() 方法来获得一个 Eventchannel 对象,进行监听、发射事件
- wx.navigateTo 方法中可以定义 events 配置项接收被打开页面发射的事件
<button type="warn" plain bind:tap="handler">跳转到列表页面</button>
Page({handler() {wx.navigateTo({url: '/pages/list/list',events: {/*** key:被打开页面通过 eventChannel 发射的事件* value:回调函数,为事件添加一个监听器,获取被打开页面传递给当前页面的数据*/currentEvent: (res) => {console.log(res);}},success(res) {console.log(res);// 通过 success 回调函数的形参,可以获取 eventChannel 对象,eventChannel 对象给提供了 emit 方法,可以发射事件,同时携带参数res.eventChannel.emit("myEvent", { name: 'Tom' })}})}
})
Page({navigateBack() {// 关闭当前页面,返回上一级或者返回多级页面wx.navigateBack({delta: 1})},onLoad(options) {// 通过 this.getOpenerEventChannel() 可以获取 EventChannel 对象const EventChannel = this.getOpenerEventChannel();// 通过 EventChannel 提供的 on 方法监听页面发射的自定义事件EventChannel.on("myEvent", (res) => {console.log(res);})// 通过 EventChannel 提供的 emit 方法,也可以向上一级页面传递数据,需要使用 emit 定义自定义事件,携带需要传递的数据EventChannel.emit("currentEvent", {age: 10})}
})
3. 小程序组件通信 - 事件总线
随着项目功能的增加,业务逻辑也会变的很复杂,一个页面可能是由多个组件进行构成,并且这些组件之间需要进行数据的传递。这时候如果使用之前学习的组件传值方式进行数据的传递,就会比较麻烦
事件总线是对发布-订阅模式的一种实现,是一种集中式事件处理机制,允许不同的组件之间进行彼此通信,常用于两个非父子关系组件和兄弟组件之间通讯。我们可以借助第三方的 发布订阅 JS 包,来实现事件总线的功能,PubsubJs
<view><text>父组件,子组件 A 和 子组件 B 是兄弟关系</text><custom06 /><custom09 />
</view>
<view class="son1"><text>子组件 A</text><button bind:tap="setData">传递数据给兄弟</button>
</view>
import PubSub from 'pubsub-js'
Component({data: {name: 'Tom'},methods: {setData() {/*** publish 发布、发射自定义事件* 第一个参数:自定义事件的名称* 第二个参数:需要传递的数据*/PubSub.publish("myEvent", { name: this.data.name, age: 10 })}}
})
<view class="son2"><text>子组件 B</text><view>{{ name }}</view>
</view>
import PubSub from 'pubsub-js'
Component({/*** 组件的初始数据*/data: {name: ''},lifetimes: {attached() {/*** subscribe 订阅、监听自定义事件* 第一个参数:需要订阅、监听的自定义事件名称* 第二个参数:回调函数,回调函数有两个参数:msg:自定义事件的名称 data:传递过来的参数*/PubSub.subscribe("myEvent", (msg, data) => {console.log(msg, data);this.setData({name: data.name})})}}
})
4. 自定义导航栏
小程序默认的导航栏与 APP 一样都位于顶部固定位置。但是默认导航栏可能会影响小程序整体风格,且无法满足特定的设计需求,这时候,就需要进行自定义导航栏。
在 app.json 或者 page.json 中,配置 navigationStyle 属性为 custom,即可 自定义导航栏,在设置以后,就会移除默认的导航栏,只保留右上角胶囊按钮。
{"usingComponents": {},"navigationStyle": "custom"
}
<swiperindicator-dotsautoplayinterval="2000"class="custom-swiper"
><swiper-item><image src="../../assets/banner/banner-1.png" mode=""/></swiper-item><swiper-item><image src="../../assets/banner/banner-2.png" mode=""/></swiper-item><swiper-item><image src="../../assets/banner/banner-3.png" mode=""/></swiper-item>
</swiper>
.custom-swiper {height: 440rpx;
}.custom-swiper image {width: 100%;height: 100%;
}
5. 小程序上线发布
假设我们目前已经完成了小程序的开发,并且通过了本地测试,这时候小程序就需要上线发布。
开发版本:点击开发者工具上传后的版本,开发版本只保留每人最新的一份上传的代码,是供开发者和团队测试和调试的版本
体验版本:小程序开发者可以将开发版本转换为体验版本,由测试人员以及产品经理进行测试与体验,确认没问题可提交审核
审核版本:小程序开发者可以将开发版本转换为审核版本,由微信的审核团队进行审核,审核周期为 1~7 天,审核通过可提交发布
线上版本:通过微信小程序平台审核,并由开发者提交发布的正式版本,线上版本是用户可以正常使用的小程序版本