文章目录
- 账号注册
- 完善小程序信息
- 获取id
- 增加成员
- 微信开发者工具
- 创建项目
- 小程序目录结构和文件介绍
- 调试小程序
- 新建页面
- 小程序配置
- rpx
- 组件
- 事件绑定和事件对象
- 绑定并阻止事件冒泡
- 事件传参-data-
- 事件传参-mark
- 声明和绑定数据
- 声明和修改数据
- 修改对象类型数据
- 修改数组类型数据
- 数据绑定-简易双向绑定
- 列表渲染-基本使用
- 列表渲染-使用进阶
- 条件渲染
- 运行机制
- 更新机制
- 生命周期介绍
- 应用级别生命周期
- 页面级别生命周期
- 组件生命周期
- 生命周期两个细节补充说明
- 总结
- API 基础
- 网络请求
- 界面交互
- 本地存储
- 事件监听-上拉加载更多
- 事件监听-下拉刷新
- 增强 scroll-view
- 创建-注册-使用组件
- 自定义组件-数据和方法
- 自定义组件-属性
- 组件 wxml 的 slot
- 组件样式以及注意事项
- 组件样式隔离
- 拓展-小程序修改checkbox样式
- 数据监听器
- 拓展:使用 Component 构造页面
- 拓展:behaviors
- 拓展:外部样式类
账号注册
在开发小程序之前,要去微信开发平台注册一个账号
1.打开 微信公众平台,点击立即注册
2.选择注册的帐号类型,在这里我们需要 选择小程序
3.输入账号信息
4.邮箱激活,需要进入邮箱进行激活
点击链接,进行激活
5.信息登记,注册类型 (需要选择中国大陆和个人,企业其他需要资质认证)
6.主体信息登记与确认
在进行管理员身份验证的时候,推荐使用自已的微信进行扫码,
将自已设置为小程序账号的管理员,方便以后对小程序进行开发、成员等相关的设置
7.注册之后,进入后台
完善小程序信息
在完成小程序账号的注册后,需要补充小程序的基本信息,如名称、图标、描述等。
注:在填写小程序类目时不要选择游戏类型
点击 前往设置 , 设置小程序类目信息
1.点击右上角添加类目
2.管理员授权
3.手机微信进行认证
4.添加小程序类目,**注意:选择类目的时候不要选择小游戏类目 **
获取id
小程序的开发者账号是免费的,只要开发者满足开发资质就可以免费注册,并且会获得对应的开发者 ID
一个完整的开发者 ID 由 小程序 ID(AppID)和一个 小程序密钥(AppSecret)组成。
小程序 ID 即 AppId 是小程序在整个微信账号体系内的唯一身份凭证,后续在很多地方都会用到,例如:新建小程序项目、真机调试、发布小程序等操作时,必须有小程序 ID。
小程序密钥 是开发者对小程序拥有所有权的凭证,在进行 微信登录、微信支付,或进行发送消息等高级操作时会使用到。
在微信公众后台,单击左侧开发标签,选择 “开发管理”,在新的页面中点击 “开发设置”,就可以看到开发者 ID 信息。请妥善保管你的小程序 ID 和小程序密钥,在后续的开发中会经常使用到,获取位置见下图:
增加成员
小程序提供了两种不同的成员角色:项目成员 和 体验成员
项目成员:参与小程序开发、运营的成员,可登陆小程序管理后台,包括运营者、开发者及数据分析者。管理员可在“成员管理”中添加、删除项目成员,并设置项目成员的角色。
体验成员:参与小程序内测体验的成员,可使用体验版小程序,但不属于项目成员。管理员及项目成员均可添加、删除体验成员。
微信开发者工具
微信官方提供了 微信开发者工具,利用开发者工具可以很方便地进行小程序开发、代码查看以及编辑、预览和发布等功能。
在 微信开发者工具 下载页面,可以看到微信开发者工具包含三个版本:
-
稳定版:稳定性高,开发中一般推荐大家使用稳定版本
-
预发布版:稳定性尚可,一般包含新的、大的特性,通过了内部测试
-
开发版:稳定性差,主要用于尽快修复缺陷和敏捷上线小的特性,如果想体验新特性,可以使用这个版本
选择合适的版本进行下载,在下载完成后,双击下载好的微信开发者工具安装包,根据引导点击下一步、我接受、直至安装完成。第一次使用微信开发者工具的时候,需要使用手机微信扫码登录,登录成功即可进入项目选择界面。
创建项目
1.打开微信开发者工具,左侧选择小程序,点击 + 号即可新建项目
2.在弹出的新页面,填写项目信息
- 项目名称:输入项目名称
- 目录:选择小程序开发文件夹,注意:小程序的目录建议是空目录,否则官方会有提示,见下图
- AppID:填写自己申请的小程序 AppID
- 开发模式:选择小程序
- 后端服务:选择不使用云服务
- 模板选择:选择不使用模板
3.点击确定,如果能够看到小程序的开发主界面,说明小程序项目已经创建成功
小程序目录结构和文件介绍
一个完整的小程序项目分为两个部分:主体文件、页面文件
主体文件 又称小程序全局文件,顾名思义,全局文件能够作用于整个小程序,影响到小程序的每个页面,且主体文件必须放到项目的根目录下,主要由三部分组成:
页面文件 是每个页面所需的文件,小程序页面文件都存放在 pages 目录下,一个页面一个文件夹,每个页面通常由四个文件组成,每个文件只对当前页面有效:
- 页面文件,wxss、json 文件能够覆盖主体文件中的样式和配置
- 强烈建议:页面文件夹名称和页面文件名称要保持一致 感叹号
调试小程序
微信开发者工具缓存非常严重❗❗❗❗
如果发现代码和预期不一样,先点击编译❗
编译后还是没有达到预期的效果,就需要清除缓存❗ 甚至重启项目才可以❗
新建页面
第一种方式:
-
在 pages 目录上 点击右键
新建文件夹
,输入页面目录的名称,例如:list -
在 list 目录上 点击右键
点击 page
,输入页面文件的名称,例如:list
📌 注意事项:
- 在输入页面文件名称的时候,不要输入后缀名 ❗
- 新建页面成功以后,会在 app.json 的 pages 选项中新增页面路径
第二种方式:
在 app.json 的 pages 选项中,新增页面路径即可
在新增页面目录以后,会自动在 pages 目录下生成页面文件
小程序配置
小程序中常见的配置文件有以下几种:
- 全局配置文件:
app.json
➡ 小程序全局配置文件,用于配置小程序的一些全局属性和页面路由。 - 局部配置文件:
页面.json
➡ 小程序页面配置文件,用于配置当前页面的窗口样式、页面标题等 - 项目配置文件:
project.config.json
➡ 小程序项目的配置文件,用于保存项目的一些配置信息和开发者的个人设置 - 搜索配置文件:
sitemap.json
➡ 配置小程序及其页面是否允许被微信索引,提高小程序在搜索引擎搜索到的概率
全局配置
小程序 app.json
文件是小程序的全局配置文件,用于配置小程序的一些全局属性和页面路由。
当小程序启动时,会自动读取 app.json
文件中的配置,根据配置生成对应的页面和组件、界面表现、网络超时时间、底部 tab,在小程序运行过程中起着关键的作用。
pages
pages
字段:用于指定小程序由哪些页面组成,用于为了让小程序知道由哪些页面组成以及页面定义在哪个目录,每一项都对应一个页面的 路径(含文件名) 信息。
{
"pages": [
"pages/index/index",
"pages/list/list"
]
}
📌 注意事项:
- 文件名不需要写文件后缀框架会自动去寻找对应位置的
.json
,.js
,.wxml
,.wxss
四个文件进行处理- 小程序中新增/减少页面,都需要对 pages 数组进行修改。
- 未指定
entryPagePath
时,数组的第一项代表小程序的初始页面(首页)
有时候可以直接配置生成需要的页面
window
window
字段: 用于设置小程序的状态栏、导航条、标题、窗口背景色。
更多配置属性
tabBar
tabBar
字段:定义小程序顶部、底部 tab
栏,如果小程序是一个多 tab 应用,例如:可以在客户端窗口的底部或顶部通过 tab 栏可以切换页面,可以通过 tabBar 配置项指定 tab 栏的表现,以及 tab 切换时显示的对应页面。
tabBar
配置项
属性 | 描述 | 类型 | 默认值 |
---|---|---|---|
color | tab 上的文字默认颜色,仅支持十六进制颜色 | HexColor | |
selectedColor | tab 上的文字选中时的颜色,仅支持十六进制颜色 | HexColor | |
backgroundColor | tab 的背景色,仅支持十六进制颜色 | HexColor | |
borderStyle | tabbar 上边框的颜色, 仅支持 black / white | string | black |
list | tab 的列表,详见 list 属性说明,最少 2 个、最多 5 个 tab | ||
position | tabBar 的位置,仅支持 bottom / top | string | bottom |
List 配置项:list 是一个数组,只能配置最少 2 个、最多 5 个 tab,tab 按数组的顺序排序,每个项都是一个对象
属性 | 描述 | 类型 | 是否必填 |
---|---|---|---|
pagePath | 页面路径,必须在 pages 中先定义 | string | 是 |
text | tab 上按钮文字 | string | 是 |
iconPath | 图片路径,icon 大小限制为 40kb, 建议尺寸为 81px * 81px, | string | 是 |
selectedIconPath | 选中时的图片路径,icon 大小限制为 40kb, 建议尺寸为 81px * 81px,不支持网络图片。 | string | 是 |
📌 注意事项:
- list 是一个数组,只能配置最少 2 个、最多 5 个 tab
- 当
position
为top
时,不显示 icon
{
"tabBar": {
"color": "#252933",
"selectedColor": "#FF734C",
"backgroundColor": "#ffffff",
"borderStyle": "black",
"list": [
{
"text": "首页",
"iconPath": "/assets/tabbar/home.png",
"selectedIconPath": "/assets/tabbar/home-active.png",
"pagePath": "pages/home/home"
},
{
"text": "列表",
"iconPath": "/assets/tabbar/list.png",
"selectedIconPath": "/assets/tabbar/list-active.png",
"pagePath": "pages/list/list"
},
{
"text": "购物车",
"iconPath": "/assets/tabbar/cart.png",
"selectedIconPath": "/assets/tabbar/cart-active.png",
"pagePath": "pages/cart/cart"
},
{
"text": "我的",
"iconPath": "/assets/tabbar/my.png",
"selectedIconPath": "/assets/tabbar/my-active.png",
"pagePath": "pages/my/my"
}
]
}
}
页面配置
小程序的页面配置,也称局部配置,每一个小程序页面也可以使用自己的 .json 文件来对本页面的窗口表现进行配置
需要注意的是:页面配置文件的属性和 全局配置文件中的 window 属性几乎一致
只不过这里不需要额外指定 window 字段,因此如果出现相同的配置项,页面中配置项 会覆盖全局配置文件中相同的配置项。
项目配置文件
小程序开发者工具在每个项目的根目录都会生成一个 project.config.json
,你在工具上做的任何配置都会写入到这个文件,当你重新安装工具或者换电脑工作时,你只要载入同一个项目的代码包,开发者工具就自动会帮你恢复到当时你开发项目时的个性化配置,其中会包括编辑器的颜色、代码上传时自动压缩等等一系列选项
project.config.json
文件中配置公共的配置,
project.private.config.json
配置个人配置
project.private.config.json
文件同样可以对项目进行配置
project.private.config.json
中的相同设置优先级高于project.config.json
支持使用 sass/less
在 project.config.json
文件中,修改 setting
下的 useCompilerPlugins
字段为 ["sass"]
,即可开启工具内置的 sass 编译插件。 如需同时开启 typescript 编译插件,可将该字段修改为 ["typescript ", “sass”]。 目前支持三个编译插件:typescript、less、sass
```json
{
"setting": {
+ "useCompilerPlugins": [
+ "sass"
+ ]
}
}
我们在配置的时候,编译插件的名称是 sass,但是文件的后缀名是 .scss
这是因为 Sass 是 Css 预处理语言,scss 是 Sass 的一个语法版本
## sitemap.json
微信现已开放小程序内搜索,当开发者允许微信索引时,微信会通过爬虫的形式,为小程序的页面内容建立索引。当用户的搜索词条触发该索引时,小程序的页面将可能展示在搜索结果中。
可以通过 `sitemap.json` 配置,或者管理后台页面收录开关来配置其小程序页面是否允许微信索引。语法如下:
```json
{
"rules": [
{
"action": "allow",
"page": "*"
}
]
}
常用配置项:
属性 | 属性说明 | 属性值 |
---|---|---|
action | 是否允许被搜索 | 可选项有:allow 、disallow |
page | 允许/不允许被搜索的页面 | 页面路径或者通配符 |
// 所有页面都会被微信索引(默认情况)
{
"rules": [
{
"action": "allow",
"page": "*"
}
]
}
// 配置 path/to/page 页面不被索引,其余页面允许被索引
{
"rules":[
{
"action": "disallow",
"page": "path/to/page"
}
]
}
📌 注意事项:
- 没有 sitemap.json 则默认所有页面都能被索引
- sitemap.json 文件中默认的设置,是优先级最低的默认规则,所有页面都会被微信索引
rpx
小程序运行在手机移动端,宿主环境是微信,因为手机尺寸的不一致,在写 CSS
样式时,开发者需要考虑到手机设备的屏幕会有不同的宽度和设备像素比,会采用一些技巧来算像素单位从而实现页面的适配。而 WXSS
在底层支持新的尺寸单位 rpx
,开发者可以免去换算的烦恼,只要交给小程序底层来换算即可。
rpx
: 小程序新增的拓展单位,可以根据屏幕宽度进行自适应,小程序规定任何型号手机:屏幕宽都为 750 rpx。
代码:
pages/index/index.wxml
<!-- 需求:绘制一个盒子,让盒子的宽度占据屏幕的一半 -->
<!-- view 是小程序提供的组件,是容器组件,类似于 div,也是一个块级元素,占据一行 -->
<!-- 如果想实现需求,不能使用 px,px 使固定单位,不能实现自适应,需要使用小程序提供的 rpx -->
<!-- 微信小程序规定,不管是什么型号的手机,屏幕的宽度都是 750rpx -->
<!-- rpx 单位能够实现自适应的 -->
<view class="box">尚硅谷</view>
pages/index/index.wxss
/* 通过演示使用 css 作为单位,px 是不具有响应式的 */
/* image {
width: 375px;
height: 600px;
background-color: lightgreen;
} */
.box {
width: 375rpx;
height: 600rpx;
background-color: lightgreen;
}
组件
了解全部组件请点击跳转官方文档
下面展示几种常用的组件
swiper 组件
在进行网页开发的时候,实现轮播图的时候,我们通常先使用 HTML 、CSS 实现轮播图的样式结构,然后使用 JS 控制轮播图的效果,或者直接使用插件实现轮播图的功能,而在小程序中实现小程序功能则相对简单很多。
在小程序中,提供了 swiper
和 swiper-item
组件实现轮播图:
swiper
:滑块视图容器,常用来实现轮播图,其中只可放置 swiper-item 组件,否则会导致未定义的行为swiper-item
:仅可放置在swiper组件中,宽高自动设置为100%,代表swiper
中的每一项
我们可以使用 swiper
组件提供的属性,实现轮播图的订制,常见属性如下:
属性 | 说明 | 类型 |
---|---|---|
indicator-dots | 是否显示面板指示点 | boolean (默认 false) |
indicator-color | 指示点颜色 | color (默认:rgba(0, 0, 0, .3)) |
indicator-active-color | 当前选中的指示点颜色 | color (默认:#000000) |
autoplay | 是否自动切换 | boolean (默认 false) |
interval | 自动切换时间间隔 | number (默认 5000) |
circular | 是否采用衔接滑动 | boolean (默认 false) |
其他属性… |
代码:
➡️ pages/index/index.wxml
<!-- 轮播图区域 -->
<view class="swiper">
<!-- swiper 组件实现轮播图区域的绘制 -->
<!-- swiper 组件,滑块视图容器 -->
<swiper
circular
autoplay
indicator-dots
interval="2000"
indicator-color="#efefef"
indicator-active-color="#ccc"
>
<!-- swiper 组件内部不能写其他组件或内容 -->
<!-- 在 swiper 组件内部只能写 swiper-item 组件 -->
<!-- swiper-item 组件只能放到 swiper 组件中,宽高自动设置为 100% -->
<swiper-item>
第一张轮播图
</swiper-item>
<swiper-item>
第二张轮播图
</swiper-item>
<swiper-item>
第三张轮播图
</swiper-item>
</swiper>
</view>
➡️ pages/index/index.scss
page {
height: 100vh;
background-color: #efefef !important;
}
swiper {
swiper-item {
// 在 Sass 拓展语言中,& 符号表示父选择器的引用。它用于在嵌套的选择器中引用父选择器
// 下面这段代码在编译以后,生成的代码是 swiper-item:first-child
&:first-child {
background-color: skyblue;
}
&:nth-child(2) {
background-color: lightcoral;
}
&:last-child {
background-color: lightseagreen;
}
}
}
image 组件
在小程序中没有 img 标签,添加图片需要使用小程序提供的image
组件,image
组件的语法如下:
<!-- src:图片资源地址 -->
<!-- mode:图片裁剪、缩放的模式 -->
<!-- lazy-load:图片懒加载,在即将进入一定范围(上下三屏)时才开始加载 -->
<image src="/assets/tom.png" mode="heightFix" lazy-load=”{{ true }}“ />
代码
➡️ pages/index/index.wxml
<!-- 轮播图区域 -->
<view class="swiper">
<swiper
circular
autoplay
indicator-dots
interval="2000"
indicator-color="#efefef"
indicator-active-color="#ccc"
>
<swiper-item>
<!-- 在小程序中图片不能使用 img 标签,使用后不会生效 -->
<!-- <img src="../../assets/banner/banner-1.png" alt=""/> -->
<!-- 需要使用 images 组件 -->
<!-- image 组件不给 src 属性设置默认值,也占据宽和高 -->
<!-- image 默认具有宽度,宽是 320px 高度是 240px -->
<!-- mode 属性:用来设置图片的裁切模式、纵横比例、显示的位置 -->
<!-- show-menu-by-longpress 属性:长按转发给朋友、收藏、保存图片 -->
<image src="../../assets/banner/banner-1.png" mode="aspectFill" show-menu-by-longpress />
</swiper-item>
<swiper-item>
<image src="../../assets/banner/banner-2.png" />
</swiper-item>
<swiper-item>
<image src="../../assets/banner/banner-3.png" />
</swiper-item>
</swiper>
</view>
➡️ pages/index/index.scss
/** index.wxss **/
page {
height: 100vh;
background-color: #efefef !important;
}
swiper {
height: 360rpx;
swiper-item {
image {
width: 100%;
height: 100%;
}
// 在 Sass 拓展语言中,& 符号表示父选择器的引用。它用于在嵌套的选择器中引用父选择器
// 下面这段代码在编译以后,生成的代码是 swiper-item:first-child
// &:first-child {
// background-color: skyblue;
// }
// &:nth-child(2) {
// background-color: lightcoral;
// }
// &:last-child {
// background-color: lightseagreen;
// }
}
}
text 组件
1.内联文本只能用 text 组件,不能用 view,如
2.新增 span 组件用于内联文本和图片,如
<view class="info">
<!-- text 是文本组件,类似于 span,是行内元素 -->
<!-- user-select:文本是否可选 -->
<!-- space:是否连续展示空格 -->
<!-- <text user-select space="ensp">1 2</text> -->
<text>3</text>
</view>
navigator跳转
在网页开发中,如果想实现页面的跳转需要使用 a 标签,在小程序中如果想实现页面的跳转则需要使用 navigator 组件,语法如下:
<!-- url:当前小程序内的跳转链接 -->
<navigator url="/../..">
在小程序中,如果需要进行跳转,需要使用 navigation 组件,常用的属性有 2 个:
-
url :当前小程序内的跳转链接
-
open-type :跳转方式
- navigate:保留当前页面,跳转到应用内的某个页面。但是不能跳到 tabbar 页面
- redirect: 关闭当前页面,跳转到应用内的某个页面。但不能跳转到 tabbar 页面
- switchTab:跳转到 tabBar 页面,并关闭其他所有非 tabBar 页面
- reLaunch:关闭所有页面,打开到应用内的某个页面
- navigateBack:关闭当前页面,返回上一页面或多级页面
📌 注意事项:
路径后可以带参数。参数与路径之间使用 ? 分隔,参数键与参数值用 = 相连,不同参数用 & 分隔
例如:/list?id=10&name=hua
,在onLoad(options)
生命周期函数 中获取传递的参数属性
open-type="switchTab"
时不支持传参
scroll-view 横向滚动
使用横向滚动时,需要添加 scroll-x 属性,然后通过 css 进行结构绘制,实现页面横向滚动
代码:
➡️ pages/index/index.wxml
:
<!-- 商品推荐区域 -->
<view class="hot">
<scroll-view class="scroll-x" scroll-x>
<view>1</view>
<view>2</view>
<view>3</view>
</scroll-view>
</view>
➡️ pages/index/index.wxss
:
.hot {
margin-top: 16rpx;
.scroll-x {
width: 100%;
white-space: nowrap;
background-color: lightblue;
view{
display: inline-block;
width: 50%;
height: 80rpx;
&:last-child{
background-color: lightseagreen;
}
&:first-child{
background-color: lightpink;
}
}
}
}
scroll-view 纵向滚动
落地代码:
➡️ pages/index/index.wxml:
<!-- 商品推荐区域 -->
<view class="hot">
<scroll-view class="scroll-x" scroll-x>
<view>1</view>
<view>2</view>
<view>3</view>
</scroll-view>
<scroll-view class="scroll-y" scroll-y>
<view>1</view>
<view>2</view>
<view>3</view>
</scroll-view>
</view>
➡️ pages/index/index.wxss
:
.hot {
margin-top: 16rpx;
.scroll-x {
width: 100%;
white-space: nowrap;
background-color: lightblue;
view{
display: inline-block;
width: 50%;
height: 80rpx;
&:last-child{
background-color: lightseagreen;
}
&:first-child{
background-color: lightcoral;
}
}
}
+ .scroll-y {
+ height: 400rpx;
+ background-color: lightsalmon;
+ margin-top: 60rpx;
+
+ view {
+ height: 400rpx;
+
+ &:nth-child(odd) {
+ background-color: lightseagreen;
+ }
+
+ &:nth-child(even) {
+ background-color: lightcoral;
+ }
+ }
+ }
}
背景图片的使用
当编写小程序的样式文件时,我们可以使用 background-image
属性来设置一个元素的背景图像,但是小程序的 background-image
不支持本地路径。
在使用了本地资源图片以后,微信开发者工具提供的提示:
本地资源图片无法通过 WXSS 获取,可以使用网络图片,或者 base64,或者使用<image/>标签
.image {
width: 100%;
height: 400rpx;
/* 本地资源图片无法通过 WXSS 获取 */
/* background-image: url('../../static/微信.jpg'); */
/* 使用网络图片 */
/* background-image: url('http://8.131.91.46:6677/TomAndJerry.jpg'); */
/* 使用 base64 格式展示图片 */
/* base64 编码的文件很长,这个地址老师在这边进行了简写,在测试的时候,需要自己将这里转成完成的 64 编码 */
background-image: url("data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAeAB4AAD/.....")
background-position: center;
background-size: cover;
}
事件绑定和事件对象
小程序中绑定事件与在网页开发中绑定事件几乎一致,只不过在小程序不能通过 on
的方式绑定事件,也没有 click
等事件,小程序中绑定事件使用 bind
方法,click
事件也需要使用 tap
事件来进行代替,绑定事件的方式有两种:
第一种方式:bind:事件名
,bind 后面需要跟上冒号,冒号后面跟上事件名
<button bind:tap="handler">按钮</button>
第二种方式:bind事件名
,bind 后面直接跟上事件名
<button bindtap="handler">按钮</button>
事件处理函数需要写到 .js
文件中,在 .js
文件中需要调用小程序提供的 Page
方法来注册小程序的页面,我们可以直接在 Page
方法中创建事件处理函数。例如:
// pages/cate/cate.js
Page({
handler(){
console.log(1111);
}
})
当组件触发事件时,绑定的事件的处理函数会收到一个事件对象,用来记录事件发生时的相关信息。在触发事件时,事件处理程序会主动的给我们传入一个参数 —— event
(事件对象)
可以打印一下
handler (event) {
// console.log('我被触发了~~~')
console.log(event)
}
绑定并阻止事件冒泡
事件分为冒泡事件和非冒泡事件:
- 冒泡事件:当一个组件上的事件被触发后,该事件会向父节点传递。
- 非冒泡事件:当一个组件上的事件被触发后,该事件不会向父节点传递。
使用 bind
绑定的事件,会触发事件冒泡,如果想阻止事件冒泡,可以使用 catch
来绑定事件。
<view bindtap="parentHandler">
<!-- 使用 bind 绑定的事件,会产生事件冒泡 -->
<!-- <button bindtap="handler">按钮</button> -->
<!-- 使用 catcht 绑定的事件,会阻止事件冒泡 -->
<button catchtap="handler">按钮</button>
</view>
Page({
// 页面的初始数据
data: {},
// 事件处理程序
handler (event) {
console.log('我是子绑定的事件 ~~~')
},
parentHandler () {
console.log('我是父绑定的事件 ~~~')
}
// 其他 coding...
})
WXML
中冒泡事件列表如下表:
类型 | 触发条件 |
---|---|
touchstart | 手指触摸动作开始 |
touchmove | 手指触摸后移动 |
touchcancel | 手指触摸动作被打断,如来电提醒,弹窗 |
touchend | 手指触摸动作结束 |
tap | 手指触摸后马上离开 |
longpress | 手指触摸后,超过350ms再离开,如果指定了事件回调函数并触发了这个事件,tap事件将不被触发 |
longtap | 手指触摸后,超过350ms再离开(推荐使用 longpress 事件代替) |
transitionend | 会在 WXSS transition 或 wx.createAnimation 动画结束后触发 |
animationstart | 会在一个 WXSS animation 动画开始时触发 |
animationiteration | 会在一个 WXSS animation 一次迭代结束时触发 |
animationend | 会在一个 WXSS animation 动画完成时触发 |
touchforcechange | 在支持 3D Touch 的 iPhone 设备,重按时会触发 |
📌 注意事项:
除上表之外的其他组件自定义事件,如无特殊声明都是非冒泡事件
例如 form 的submit
事件,input 的input
事件
事件传参-data-
在小程序中,可以通过事件传参的方式,将数据传递给事件处理函数。常见的事件包括点击事件、输入事件等。
在组件节点中可以通过 data-
的方式传递一些自定义数据,传递的数据可以通过事件对象的方式进行获取
📌 注意事项:
使用
data-
方法传递参数的时候,多个单词由连字符-
连接 连字符写法会转换成驼峰写法,而大写字符会自动转成小写字符
例如:
data-element-type
,最终会呈现为event.currentTarget.dataset.elementType
data-elementType
,最终会呈现为event.currentTarget.dataset.elementtype
在 wxml 文件中,使用 data-*
属性将数据传递给事件处理函数。例如:
<view bindtap="parentHandler" data-parent-id="1" data-parentName="tom">
<!-- 如果需要进行事件传参,需要再组件上通过 data- 的方式传递数据 -->
<!-- <button bindtap="btnHandler" data-id="1" data-name="tom">按钮</button> -->
<button data-id="1" data-name="tom">按钮</button>
</view>
在 js 文件中,可以通过 event.currentTarget.dataset 获取传递的数据
Page({
// 按钮触发的事件处理函数
btnHandler (event) {
// currentTarget 事件绑定者,也就是指:哪个组件绑定了当前事件处理函数
// target 事件触发者,也就是指:哪个组件触发了当前事件处理函数
// currentTarget 和 target 都是指按钮,因为是按钮绑定的事件处理函数,同时点击按钮触发事件处理函数
// 这时候通过谁来获取数据都可以
console.log(event.currentTarget.dataset.id)
console.log(event.target.dataset.name)
},
// view 绑定的事件处理函数
parentHandler (event) {
// 点击蓝色区域(不点击按钮)
// currentTarget 事件绑定者:view
// target 事件触发者:view
// currentTarget 和 target 都是指 view,如果想获取 view 身上的数据,使用谁都可以
// 点击按钮(不点击蓝色区域)
// currentTarget 事件绑定者:view
// target 事件触发者:按钮
// 如果想获取 view 身上的数据,就必须使用 currentTarget 才可以
// 如果想获取的是事件触发者本身的数据,就需要使用 target
console.log(event)
// 在传递参数的时候,如果自定义属性是多个单词,单词与单词直接使用中划线 - 进行连接
// 在事件对象中会被转换为小托峰写法
console.log(event.currentTarget.dataset.parentId)
// 在传递参数的时候,如果自定义属性是多个单词,单词如果使用的是小托峰写法
// 在事件对象中会被转为全部小写的
console.log(event.currentTarget.dataset.parentname)
}
})
事件传参-mark
小程序进行事件传参的时候,除了使用 data-*
属性传递参数外,还可以使用 mark
标记传递参数
mark
是一种自定义属性,可以在组件上添加,用于来识别具体触发事件的 target 节点。同时 mark
还可以用于承载一些自定义数据(类似于 dataset
)
mark
和 dataset
很相似,主要区别在于:
mark
会包含从触发事件的节点到根节点上所有的 mark:
属性值 (事件委托的)
dataset
仅包含触发事件那一个节点的 data-
属性值。
在 wxml 文件中,使用 mark:自定义属性
的方式将数据传递给事件处理函数
<!-- pages/index/index.wxml -->
<view bindtap="parentHandler" mark:parentid="1" mark:parentname="tom">
<!-- 如果需要使用 mark 进行事件传参,需要使用 mark:自定义属性的方式进行参数传递 -->
<!-- <button bindtap="btnHandler" mark:id="1" mark:name="tom">按钮</button> -->
<button mark:id="1" mark:name="tom">按钮</button>
</view>
Page({
// 按钮绑定的事件处理函数
btnHandler (event) {
console.log(event.mark.id)
console.log(event.mark.name)
},
// view 绑定的事件处理函数
parentHandler (event) {
// 先点击蓝色区域 (不点击按钮)
// 通过事件对象获取的是 view 身上绑定的数据
// 先点击按钮 (不点击蓝色区域)
// 通过事件对象获取到的是 触发事件的节点 已经 父节点身上所有的 mark 数据
console.log(event)
}
})
声明和绑定数据
小程序页面中使用的数据均需要在 Page() 方法的 data 对象中进行声明定义
在将数据声明好以后,需要在 WXML 中绑定数据,数据绑定最简单的方式是使用 Mustache 语法(双大括号)将变量包起来。
在 {{ }} 内部可以做一些简单的运算,支持如下几种方式:
-
算数运算
-
三元运算
-
逻辑判断
-
其他…
在 {{ }} 语法中,只能写表达式,不能写语句,也不能调用 js 相关的方法
定义数据:
Page({
// 页面的初始数据
data: {
num: 1
}
// coding...
}
使用数据:
<!-- 如果需要展示数据,在 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>
<!-- 在双大括号写法内部,只能写表达式,不能写语句,也不能调用 js 的方法 -->
<!-- <view>{{ if (id === 1) {} }}</view> -->
<!-- <view>{{ for (const i = 0; i <= 10; i++) {} }}</view> -->
<view>{{ obj.name.toUpperCase() }}</view>
pages/index/index.js
// index.js
Page({
// 在小程序页面中所需要使用的数据均来自于 data 对象
data: {
id: 1,
isChecked: false,
school: '尚硅谷',
obj: {
name: 'tom'
}
}
})
声明和修改数据
小程序中修改数据并不能直接进行赋值,而是要通过调用 this.setData
方法才能实现
将需要修改的数据以 key:value
的形式传给 this.setData
方法。
this.setData
方法有两个作用:
- 更新数据
- 驱动视图更新
Page({
// 页面的初始数据
data: {
num: 1
},
updateNum() {
this.setData({
// key 是需要修改的数据
// value 是最新值
num: this.data.num + 1
})
}
// coding...
}
修改对象类型数据
在实际开发中,我们经常会在 data
中声明对象类型的数据,小程序中通过调用 setData 方法可以修改页面的数据,包括对象类型的数据。下面是修改对象类型数据的方法:
1.定义一个对象
Page({
// 定义页面中使用的数据
data: {
userInfo: {
name: 'Tom',
age: 10,
gender: '男'
}
}
}
2.修改对象中的单个属性
this.setData({
'userInfo.name': 'Jerry'
})
-
修改对象中的多个属性
// 修改对象中的多个属性 this.setData({ 'userInfo.name': 'Jerry', 'userInfo.age': 100 })
-
使用 ES6 的展开运算符
在修改对象类型的数据时,可以使用 ES6 的展开运算符先复制对象,然后利用新值对旧值覆盖的方式修改
const userInfo = { ...this.data.userInfo, name: 'Jerry', age: 100 } // 修改对象中的多个属性 this.setData({ userInfo })
-
使用解构赋值修改对象属性
在修改对象类型的数据时,可以使用解构赋值来修改对象属性
// 将 userInfo 从 data 中进行解构 const { userInfo } = this.data // 产生一份新数据 const newUserInfo = { ...userInfo, name: 'Jerry', age: 100 } // 修改对象中的多个属性 this.setData({ userInfo: newUserInfo })
-
使用 Object.assign 方法合并对象
在修改对象类型的数据时,可以使用 Object.assign 方法将多个对象合并为一个对象
// 使用 Object.assign 方法将多个对象合并为一个对象 const userInfo = Object.assign( this.data.userInfo, { name: 'Jerry' }, { age: 100 } ) // 修改对象中的多个属性 this.setData({ userInfo })
-
删除对象中的属性
在删除对象中的属性时,不能使用
delete
操作符,因为小程序的数据绑定机制不支持监听 delete 操作// 使用展开运算符拷贝一份数据,产生一个新对象 const newUser = { ...this.data.userInfo } // 使用 delete 删除新对象中的属性 delete newUser.age this.setData({ // 将新的对象进行赋值 userInfo: newUser })
📌 注意事项:
小程序的数据绑定机制只能监听到 setData 方法中修改的数据,无法监听到直接删除属性的操作,所以在删除对象属性时,需要先将对象复制一份再进行操作,然后再调用 setData 方法更新数据。
修改数组类型数据
数组类型数据也是经常会使用的数据格式之一,下面是修改数组类型数据的方法:
-
定义一个数组
Page({ // 定义页面中使用的数据 data: { animalList: ['Tom', 'Jerry', 'Spyke'] } // coding... }
-
使用数组的 concat 方法合并数组
在修改数组类型的数据时,可以使用数组的 concat 方法来合并数组
// 使用 concat 方法来合并数组 const newList = this.data.animalList.concat('Tyke') // 使用 setData 进行赋值 this.setData({ animalList: newList })
-
使用数组的 push 方法新增属性
在修改数组类型的数据时,可以使用数组的 push 方法来添加元素
// 使用数组的 push 方法来添加元素 this.data.animalList.push('Tyke') // 使用 setData 进行赋值 this.setData({ animalList: this.data.animalList })
-
使用 ES6 的展开运算符
在数组类型的数据时,可以使用 ES6 的展开运算符先复制数组,然后进行合并
// 使用 ES6 的展开运算符先复制数组,然后进行合并 const newList = [...this.data.animalList, 'Tyke'] // 使用 setData 进行赋值 this.setData({ animalList: newList })
-
修改数组中某个元素的值:
利用索引的方式进行修改,但必须使用 wx:for 来进行渲染数组,否则会出现错误
this.setData({ 'animalList[2]': 'Tyke' })
-
使用数组的 filter 方法删除元素
在修改数组类型的数据时,可以使用数组的 filter 方法来删除元素
// 使用数组的 filter 方法来删除元素 const newList = this.data.animalList.filter(item => item !== 'Tom') // 使用 setData 进行赋值 this.setData({ animalList: newList })
数据绑定-简易双向绑定
在 WXML 中,普通属性的绑定是单向的,例如:
<input value="{{ num }}" />
如果使用 this.setData
来更新 num
,num
和输入框的中显示的值都会被更新为值。但如果用户修改了输入框里的值,却不会同时改变 data
中的 num
。
如果需要在用户输入的同时也将 data
中的数据修改 ,需要借助简易双向绑定机制。此时可以在对应项目之前加入 model:
前缀即可,例如:
<input model:value="{{ value }}" />
如果使用 this.setData
来更新 num
,num
和输入框的中显示的值都会被更新为值。
如果输入框的值被改变了, data
的数据也会随着改变。
同时, WXML 中所有绑定了数据的位置也会被一同更新
📌 注意事项:
简易双向绑定的属性值如下限制:
只能是一个单一字段的绑定,例如:错误用法:
尚不能写 data 路径,也就是不支持数组和对象,例如:错误用法:
落地代码:
<!-- 单向绑定:数据能够影响页面,但是页面更新不会影响到数据 -->
<!-- <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.value }}" /> -->
列表渲染-基本使用
基本使用
列表渲染:就是指通过循环遍历一个数组或对象,将其中的每个元素渲染到页面上
只需要在组件上使用 wx:for
属性绑定一个数组,即可使用数组中各项的数据重复渲染该组件
默认数组当前项的变量名默认为 item
默认数组的当前项的下标变量名默认为 index
在使用 wx:for
对数组进行遍历的时候,建议加上 wx:key
属性,如不提供 wx:key
,会报一个 warning
, 如果明确知道该列表是静态,即以后数据不会改变,或者不必关注其顺序,可以选择忽略。
wx:key
的值以两种形式提供:
- 字符串:代表需要遍历的 array 中 item 的某个 property,该 property 的值需要是列表中唯一的字符串或数字,且不能动态改变
- 保留关键字
*this
代表在 for 循环中的 item 本身,当 item 本身是一个唯一的字符串或者数字时可以使用
当数据改变触发渲染层重新渲染的时候,会校正带有 key 的组件,框架会确保他们被重新排序,而不是重新创建,以确保使组件保持自身的状态,并且 提高列表渲染时的效率 。
📌 注意事项:
在使用
wx:for
对数组进行遍历的时候,建议加上wx:key
属性,否则控制台会报警告
落地代码:
<!-- 如果需要进行列表渲染,需要使用 wx:for 属性 -->
<!-- 属性值需要使用双大括号进行包裹 -->
<!-- 每一项的变量名默认是 item -->
<!-- 每一项下标(索引)的变量名默认是 index -->
<!-- 如果渲染的是数组,item:数组的每一项,index:下标 -->
<!-- <view wx:for="{{ numList }}">{{ item }} - {{ index }}</view> -->
<!-- 如果渲染的是对象,item:对象属性的值,index:对象属性 -->
<!-- <view wx:for="{{ obj }}">{{ item }} - {{ index }}</view> -->
<!-- ------------------------ 关于 Key --------------------------------- -->
<!-- wx:key 提升性能 -->
<!-- wx:key 的属性值不需要使用双大括号进行包裹,直接写遍历的数组 中 item 的某个属性 -->
<!-- wx:key 属性值有两种添加形式 -->
<!-- 字符串,需要是遍历的数组 中 item 的某个属性,要求该属性是列表中唯一的字符串或者数字,不能进行动态改变 -->
<view wx:for="{{ fruitList }}" wx:key="id">{{ item.name }}</view>
<view wx:for="{{ fruitList }}" wx:key="index">{{ item.name }}</view>
<!-- 保留关键字 *this,*this 代表的是 item 本身,item 本身是唯一的字符串或者数字 -->
<view wx:for="{{ numList }}" wx:key="*this">{{ item }}</view>
// profile.js
Page({
data: {
numList: [1, 2, 3],
fruitList: [
{ id: 1, name: '🍎', price: 66 },
{ id: 2, name: '🍋', price: 77 },
{ id: 3, name: '🍅', price: 88 }
],
obj: {
name: 'tom',
age: 10
}
}
})
列表渲染-使用进阶
修改默认下标和变量名:
如果需要对默认的下标和变量名进行修改,可以使用 wx:for-item
和 wx:for-index
-
使用
wx:for-item
可以指定数组当前元素的变量名 -
使用
wx:for-index
可以指定数组当前下标的变量名
<view wx:for="{{ animal }}" wx:for-item="itemName" wx:for-index="i">
{{ itemName.name }} - {{ itemName.avatar }} - {{ i }}
</view>
渲染多节点结构块:
如果需要渲染一个包含多节点的结构块,可以使用一个 <block/>
标签将多个组件包装起来
<block wx:for="{{ animal }}">
<view>
<span>{{ item.name }}</span>
<span>{{ item.avatar }}</span>
</view>
</block>
注意: <block/>
并不是一个组件,它仅仅是一个包装元素,不会在页面中做任何渲染,只接受控制属性。
条件渲染
知识点:
条件渲染主要用来控制页面结构的展示和隐藏,在微信小程序中实现条件渲染有两种方式:
- 使用
wx:if
、wx:elif
、wx:else
属性组 - 使用
hidden
属性
<view wx:if="{{condition}}"> True </view>
<view wx:if="{{length > 5}}"> 1 </view>
<view wx:elif="{{length > 2}}"> 2 </view>
<view wx:else> 3 </view
<view hidden="{{condition}}"> True </view>
wx:if
和 hidden
二者的区别:
wx:if
:当条件为true
时将内容渲染出来,否则元素不会进行渲染,通过移除/新增节点的方式来实现hidden
:当条件为true
时会将内容隐藏,否则元素会显示内容,通过display
样式属性来实现的
落地代码:
<!-- 使用 wx:if、wx:elif、wx:else 属性组控制元素的隐藏和控制 -->
<view wx:if="{{ num === 1 }}">num 等于 {{ num }}</view>
<view wx:elif="{{ num === 2 }}">num 等于 {{ num }}</view>
<view wx:else>大于 2</view>
<view hidden="{{ num !== 1 && num !== 2 && num !== 3 && num < 3}}">
{{ num < 3 ? 'num 等于' + num : '大于 2' }}
</view>
<button type="primary" bindtap="updateNum">修改数据</button>
Page({
// 页面的初始数据
data: {
num: 1
},
// 更新数据
updateNum() {
this.setData({
num: this.data.num + 1
})
}
// coding...
}
运行机制
用一张图简要概述一下小程序的运行机制
冷启动与热启动:
小程序启动可以分为两种情况,一种是冷启动,一种是热启动
冷启动:如果用户首次打开,或小程序销毁后被用户再次打开,此时小程序需要重新加载启动
热启动:如果用户已经打开过某小程序,然后在一定时间内再次打开该小程序,此时小程序并未被销毁,只是从后台状态进入前台状态
前台 以及 后台状态:
小程序启动后,界面被展示给用户,此时小程序处于「前台」状态。
当用户「关闭」小程序时,小程序并没有真正被关闭,而是进入了「后台」状态,当用户再次进入微信并打开小程序,小程序又会重新进入「前台」状态
切后台的方式包括但不限于以下几种:
- 点击右上角胶囊按钮离开小程序
- 点击返回键离开小程序
- 屏幕左侧右滑离开小程序
挂起:
小程序进入「后台」状态一段时间后(5 秒),微信停止小程序 JS 线程执行,小程序进入「挂起」状态,当开发者使用了后台播放音乐、后台地理位置等能力时,小程序可以在后台持续运行,不会进入到挂起状态
销毁:
如果用户很久没有使用小程序,或者系统资源紧张,小程序会被销毁,即完全终止运行。
当小程序进入后台并被「挂起」后,如果很长时间(目前是 30 分钟)都未再次进入前台,小程序会被销毁
当小程序占用系统资源过高,可能会被系统销毁或被微信客户端主动回收。
更新机制
在访问小程序时,微信会将小程序代码包缓存到本地。
开发者在发布了新的小程序版本以后,微信客户端会检查本地缓存的小程序有没有新版本,并进行小程序代码包的更新。
小程序的更新机制有两种:启动时同步更新 和 启动时异步更新
启动时异步更新
启动时同步更新:微信运行时,会定期检查最近使用的小程序是否有更新。如果有更新,下次小程序启动时会同步进行更新,更新到最新版本后再打开小程序。如果 用户长时间未使用小程序时,会强制同步检查版本更新
启动时异步更新
启动时异步更新:在启动前没有发现更新,小程序每次 冷启动 时,都会异步检查是否有更新版本。如果发现有新版本,将会异步下载新版本的代码包,将新版本的小程序在下一次冷启动进行使用,当前访问使用的依然是本地的旧版本代码
在启动时异步更新的情况下,如果开发者希望立刻进行版本更新,可以使用 wx.getUpdateManager API 进行处理。在有新版本时提示用户重启小程序更新新版本。
App({
/**
* 当小程序初始化完成时,会触发 onLaunch(全局只触发一次)
*/
onLaunch: function () {
const updateManager = wx.getUpdateManager()
updateManager.onCheckForUpdate(function (res) {
// 请求完新版本信息的回调
console.log(res.hasUpdate)
})
updateManager.onUpdateReady(function () {
wx.showModal({
title: '更新提示',
content: '新版本已经准备好,是否重启应用?',
success(res) {
if (res.confirm) {
// 新的版本已经下载好,调用 applyUpdate 应用新版本并重启
updateManager.applyUpdate()
}
}
})
})
updateManager.onUpdateFailed(function () {
// 新版本下载失败
})
}
})
代码:
// app.js
App({
// onLaunch 是小程序的钩子函数,这个钩子函数在冷启动时肯定会执行到
// 当小程序冷启动时,会自动微信后台请求新版本的信息,如果有新版本,会立即进行下载
onLaunch () {
// 使用 wx.getUpdateManager 方法监听下载的状态
const updateManager = wx.getUpdateManager()
// 当下载完成新版本以后,会触发 onUpdateReady 回调函数
updateManager.onUpdateReady(function () {
// 在回调函数中给用户提示,
wx.showModal({
title: '更新提示',
content: '新版本已经准备好,是否重启应用?',
success(res) {
if (res.confirm) {
// 强制当前小程序使用新版本并且会重启当前小程序
updateManager.applyUpdate()
}
}
})
})
}
})
生命周期介绍
关于生命周期,官方的流程图是这样的
看起来复杂难懂,下面让我们慢慢了解
应用生命周期是指应用程序进程从创建到消亡的整个过程
小程序的生命周期指的是 小程序从启动到销毁的整个过程
在打开一个小程序应用的时候都需要经历一系列的初始化步骤,比如页面是否加载完成、页面是否初次渲染完成等等。
在此过程中,小程序也会运行被称为生命周期钩子的函数,这些函数由小程序框架本身提供,被称为生命周期函数,生命周期函数会按照顺序依次自动触发调用。帮助程序员在特定的时机执行特定的操作,辅助程序员完成一些比较复杂的逻辑。让开发者有机会在特定阶段运行自己的代码。
小程序的生命周期分类三类:应用级别、页面级别和组件级别 3 种类型
应用级别生命周期
知识点:
应用生命周期通常是指一个小程序从 启动 → 运行 → 销毁的整个过程
应用生命周期伴随着一些函数,我们称为 应用生命周期函数,应用生命周期函数需要 在 app.js 文件的 App() 方法中定义
当整个小程序应用运行到某个时机的时候,我们需要做一些事情。例如:当小程序启动成功之后,我们要获取小程序的一些信息,就可以在小程序启动成功时的钩子函数中写代码获取我们想要的信息。
生命周期 | 必填 | 说明 |
---|---|---|
onLaunch | 否 | 监听小程序初始化,全局只会执行 1 次 |
onShow | 否 | 监听小程序启动或切前台 |
onHide | 否 | 监听小程序切后台 |
📌 注意事项:
从小程序生命周期的角度来看,我们一般讲的「启动」专指冷启动,热启动一般被称为后台切前台。
App() 必须在
app.js
中调用,必须调用且只能调用一次。不然会出现无法预期的后果。
小程序启动后,后台会首先完成小程序的初始化,该过程只会触发一次;之后会完成显示的工作,用户可以操作小程序从前台进入后台以及从后台回复到前台显示;小程序在后台运行一段时间,当系统资源不足时会被注销。
代码:
➡️ app.js
App({
/**
* 当小程序初始化完成时,会触发 onLaunch(全局只触发一次)
*/
onLaunch: function () {
// 监听小程序初始化
console.log('onLaunch: 当小程序初始化完成时,会触发 onLaunch')
},
/**
* 当小程序启动,或从后台进入前台显示,会触发 onShow
*/
onShow: function (options) {
// 监听小程序的显示
console.log('onShow: 当小程序启动,或从后台进入前台显示')
},
/**
* 当小程序从前台进入后台,会触发 onHide
*/
onHide: function () {
// 监听小程序的隐藏
console.log('onHide: 小程序从前台进入后台')
}
})
页面级别生命周期
知识点:
页面生命周期就是指小程序页面从 加载 → 运行 → 销毁的整个过程
当某个页面运行到某个时机的时候,我们需要做一些事情,例如: 当某个页面加载完毕之后,需要发请求获取当前页面所需的数据,就可以在对应的页面加载完成后的钩子函数中执行发送请求的代码。
小程序中的一个页面都需要在对应页面的 .js
文件中调用 Page()
方法来注册。Page()
接受一个 Object
类型参数,其指定页面的初始数据、生命周期回调、事件处理函数等。
生命周期 | 必填 | 说明 |
---|---|---|
onLoad | 否 | 页面加载时触发 (一个页面只会调用一次) |
onShow | 否 | 页面显示时触发,页面显示/切入前台时触发 |
onReady | 否 | 页面初次渲染完成时触发(一个页面只会调用一次) 代表页面已经准备妥当,可以和视图层进行交互 |
onHide | 否 | 页面隐藏/切入后台时触发 |
onUnload | 否 | 页面卸载时触发 |
代码:
// pages/home/home.js
Page({
// coding...
// 生命周期函数--监听页面加载
onLoad: function (options) {
console.log('页面加载完毕')
},
// 生命周期函数--监听页面显示
onShow: function () {
console.log('监听页面显示,此时页面处于显示状态')
},
// 生命周期函数--监听页面初次渲染完成
onReady: function () {
console.log('页面初次渲染已经完成')
},
// 生命周期函数--监听页面隐藏
onHide: function () {
console.log('当前页面处于隐藏状态')
},
// 生命周期函数--监听页面卸载
onUnload: function () {
console.log('页面卸载时触发')
}
})
组件生命周期
组件的生命周期,指的是组件自身的一些函数,这些函数在特殊的时间点或遇到一些特殊的框架事件时被自动触发。
组件的生命周期函数需要在 lifetimes
字段内进行声明
最重要的生命周期是 created
attached
detached
包含一个组件生命周期流程的最主要时间点
- 组件实例刚刚被创建好时, created 生命周期被触发。此时,组件数据 this.data 就是在 Component构造器中定义的数据 data 。 此时还不能调用 setData 。 通常情况下,这个生命周期只应该用于给组件 this添加一些自定义属性字段。
- 在组件完全初始化完毕,进入页面节点树后, attached 生命周期被触发。此时, this.data
已被初始化为组件的当前值。这个生命周期很有用,绝大多数初始化工作可以在这个时机进行。 - 在组件离开页面节点树后, detached 生命周期被触发。退出一个页面时,如果组件还在页面节点树中,则 detached 会被触发
代码:
Component({
lifetimes: {
created: function () {
// 在组件实例刚刚被创建时执行,注意此时不能调用 setData
// 一般用来为组件添加一些自定义属性字段。
},
attached: function() {
// 在组件实例进入页面节点树时执行
},
detached: function() {
// 在组件实例被从页面节点树移除时执行
},
},
// 以下是旧式的定义方式,可以保持对 <2.2.3 版本基础库的兼容
attached: function() {
// 在组件实例进入页面节点树时执行
},
detached: function() {
// 在组件实例被从页面节点树移除时执行
},
// ...
})
生命周期 | 必填 | 说明 |
---|---|---|
created | 否 | 在组件实例刚刚被创建时执行 |
attached | 否 | 在组件实例进入页面节点树时执行 |
ready | 否 | 在组件在视图层布局完成后执行 |
moved | 否 | 在组件实例被移动到节点树另一个位置时执行 |
detached | 否 | 在组件实例被从页面节点树移除时执行 |
组件还有一些特殊的生命周期,这类生命周期和组件没有很强的关联
主要用于组件内部监听父组件的展示、隐藏状态,从而方便组件内部执行一些业务逻辑的处理
组件所在页面的生命周期有 4 个: show、 hide、 resize、 routeDone,需要在 pageLifetimes
字段内进行声明
Component({
// coding...
// 组件所在页面的生命周期
pageLifetimes: {
// 监听组件所在的页面展示(后台切前台)状态
show () {
console.log('组件所在的页面被展示')
},
// 监听组件所在的页面隐藏(前台切后台、点击 tabBar)状态
hide () {
console.log('组件所在的页面被隐藏')
}
}
})
生命周期两个细节补充说明
-
tabBar 页面之间相互切换,页面不会被销毁
-
点击左上角,返回上一个页面,会销毁当前页面(被打开页面)
总结
小程序冷启动,钩子函数执行的顺序
保留当前页面(navigate) 以及 关闭当前页面(redirect)
切后台 以及 切前台(热启动)
API 基础
小程序开发框架提供丰富的微信原生 API
,可以方便的调起微信提供的能力,如获取用户信息,本地存储,支付功能等,几乎所有小程序的 API
都挂载在 wx
对象底下,例如:wx.chooseMedia()
、wx.request()
, wx
对象实际上就是小程序的宿主环境微信所提供的全局对象
通常,在小程序 API 有以下几种类型:
- 事件监听 API:约定以
on
开头 API 用来监听某个事件是否触发,例如:wx.onThemeChange()
- 同步 API:约定以
Sync
结尾的 API 都是同步 API,例如:wx.setStorageSync()
- 异步 API:大多数 API 都是异步 API,例如:
wx.setStorage()
异步 API 支持 callback & Promise 两种调用方式:
-
当接口参数 Object 对象中不包含 success/fail/complete 时将默认返回 Promise
-
部分接口如 request, uploadFile 本身就有返回值,因此不支持 Promise 风格的调用方式,它们的 promisify 需要开发者自行封装。
网络请求
知识点:
在微信小程序中,如果需要发起 HTTPS 网络请求需要使用:wx.request()
,语法如下:
wx.request({
// 接口地址,仅为示例,并非真实的接口地址
url: 'example.php',
// 请求的参数
data: { x: '' },
// 请求方式
method: 'GET|POST|PUT|DELETE',
success (res) {
console.log(res.data)
},
fail(err) {
console.log(err)
}
})
注意:wx.request() 请求的域名需要在小程序管理平台进行配置,如果小程序正式版使用wx.request请求未配置的域名,在控制台会有相应的报错。
这时候就需要在小程序管理后台进行设置请求的域名,打开微信公众后台:点击左侧 开发 → 开发管理 → 开发设置 → 服务器域名。域名只支持 https
而且要求已备案
但一般我们在开发阶段时,处于开发阶段的服务器接口可能还没部署到对应的域名下,经常会通过另一个域名来进行开发调试,考虑到这一点,为了方便开发者进行开发调试,开发者工具、小程序的开发版和小程序的体验版在某些情况下允许 wx.request
请求任意域名 (只适用于开发环境,只能在小程序开发者工具中生效),在开发工具中设置步骤如下:
将 不校验合法域名、web-view (业务域名)、TLS版本以及HTTPS证书 勾选上:
📌 注意事项:
这两种方式只适用于开发者工具、小程序的开发版和小程序的体验版
项目上线前必须在小程序管理平台进行合法域名的配置
代码:
Page({
// 页面的初始数据
data: {},
// 获取数据
getPostInfo() {
wx.request({
url: 'https://jsonplaceholder.typicode.com/posts/1',
method: 'GET',
success(res) {
console.log(res)
},
fail(err) {
console.log(err)
}
})
},
// coding...
}
界面交互
小程序还提供了一些用于界面交互的 API,如消息提示框、模态对话框、 loading 提示框等等
loading 提示框
知识点:
小程序提供了一些用于界面交互的 API,例如: loading 提示框、消息提示框、模态对话框等 API。
loading 提示框常配合网络请求来使用,用于增加用户体验,对应的 API 有两个:
wx.showLoading
显示加载提示框wx.hideLoading
隐藏加载提示框
语法如下:
wx.showLoading({
title: '提示内容', // 提示的内容
mask: true, // 是否显示透明蒙层,防止触摸穿透
success() {}, // 接口调用成功的回调函数
fail() {} // 接口调用失败的回调函数
})
官方文档:
代码:
Page({
data: {
list: []
},
// 获取数据
getData () {
+ // 显示 loading 提示框
+ wx.showLoading({
+ // 用来显示提示的内容
+ // 提示的内容不会自动换行,如果提示的内容比较多,因为在同一行展示
+ // 多出来的内容就会被隐藏
+ title: '数据加载中...',
+ // 是否展示透明蒙层,防止触摸穿透
+ mask: true
+ })
// 如果需要发起网络请求,需要使用 wx.request API
wx.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: (res) => {
// console.log(res)
+ // 关掉 loading 提示框
+ // hideLoading 和 showLoading 必须结合、配对使用才可以
+ wx.hideLoading()
}
})
}
})
模态对话框以及消息提示框
知识点:
wx.showToast()
:消息提示框用来根据用户的某些操作来告知操作的结果,如退出成功给用户提示,提示删除成功等,语法如下:
wx.showToast({
title: '标题', // 提示的内容
duration: 2000, // 提示的延迟时间
mask: true, // 是否显示透明蒙层,防止触摸穿透
icon: 'success', // 图标
success() {}, // 接口调用成功的回调函数
fail() {} // 接口调用失败的回调函数
})
wx.showModal()
模态对话框也是在项目中频繁使用的一个小程序 API
,通常用于向用户询问是否执行一些操作,例如:点击退出登录,显示模态对话框,询问用户是否真的需要退出等等
wx.showModal({
title: '提示', // 提示的标题
content: '您确定执行该操作吗?', // 提示的内容
confirmColor: '#f3514f',
// 接口调用结束的回调函数(调用成功、失败都会执行)
success({ confirm }) {
confirm && consle.log('点击了确定')
}
})
官方文档:
代码:
Page({
// coding...
// 删除商品
async delHandler () {
// showModal 显示模态对话框
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
})
}
}
// coding...
})
本地存储
小程序中也能够像网页一样支持本地数据缓存,本地数据缓存是小程序存储在当前设备上硬盘上的数据,本地数据缓存有非常多的用途,我们可以利用本地数据缓存来存储用户在小程序上产生的操作,在用户关闭小程序重新打开时可以恢复之前的状态。我们还可以利用本地缓存一些服务端非实时的数据提高小程序获取数据的速度,在特定的场景下可以提高页面的渲染速度,减少用户的等待时间。其包含以下 8个主要的 API
同步 API | 异步 API | 作用 |
---|---|---|
wx.setStorageSync | wx.setStorage | 将数据存储在本地缓存中指定的 key 中 |
wx.getStorageSync | wx.getStorage | 从本地缓存中同步获取指定 key 的内容 |
wx.removeStorageSync | wx.removeStorage | 从本地缓存中移除指定 key |
wx.clearStorageSync | wx.clearStorage | 清理本地数据缓存 |
异步方式的 API
,在调用的时候都需要传入对象类型的参数
同步方式执行的 API 在使用时简洁比较好,缺点是同步会阻塞程序执行,执行效率上相较异步版本要差一些。
📌 注意事项:
- 对象类型的数据,可以直接进行存储,无需使用
JSON.stringify
转换- 对象类型的数据存的时候没有使用转换,因此获取的时候也不需要使用
JSON.parse
转换
代码:
<button type="primary" bindtap="handler" size="mini" plain bindtap="setData">
存储数据
</button>
<button type="primary" bindtap="handler" size="mini" plain bindtap="getData">
获取数据
</button>
<button type="warn" bindtap="handler" size="mini" plain bindtap="delData">
删除数据
</button>
<button type="warn" bindtap="handler" size="mini" plain bindtap="clearData">
移除数据
</button>
Page({
// 将数据存储到本地
setStorage () {
// 第一个参数:本地存储中指定的 key
// 第二个参数:需要存储的数据
// wx.setStorageSync('num', 1)
// 在小程序中
// 如果存储的是对象类型数据,不需要使用 JSON.stringify 和 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 () {
// 从本地存储的数据中获取指定 key 的数据、内容
// const num = wx.getStorageSync('num')
// const obj = wx.getStorageSync('obj')
// console.log(num)
// console.log(obj)
// ------------------- 异步 API -------------------
const { data } = await wx.getStorage({
key: 'obj'
})
console.log(data)
},
// 删除本地存储的数据
removeStorage () {
// 从本地移除指定 key 的数据、内容
// wx.removeStorageSync('num')
// ------------------- 异步 API -------------------
wx.removeStorage({
key: 'num'
})
},
// 清空本地存储的全部数据
clearStorage () {
// wx.clearStorageSync()
// ------------------- 异步 API -------------------
wx.clearStorage()
},
})
###路由与通信
知识点:
在小程序中实现页面的跳转,有两种方式:
- 声明式导航:
navigator
组件 - 编程式导航:使用小程序提供的
API
wx.navigateTo()
:保留当前页面,跳转到应用内的某个页面,但是不能跳到 tabbar 页面wx.redirectTo()
:关闭当前页面,跳转到应用内的某个页面。但是不允许跳转到 tabbar 页面wx.switchTab()
:跳转到 tabBar 页面,路径后不能带参数wx.navigateBack()
:关闭当前页面,返回上一页面或多级页面
- 路径后可以带参数,参数需要在跳转到的页面的
onLoad
钩子函数中通过形参进行接收- 参数与路径之间使用
?
分隔 - 参数键与参数值用
=
相连 - 不同参数用
&
分隔 - 例如
path?key=value&key2=value2
- 参数与路径之间使用
代码:
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'
})
},
switchTab() {
// 跳转到 tabBar 页面,不能跳转到 非 tabBar 页面,路径后面不能传递参数
wx.switchTab({
// url: '/pages/list/list'
url: '/pages/cate/cate?id=1&name=tom'
})
},
reLaunch() {
// 关闭所有的页面,然后跳转到应用中某一个页面
wx.reLaunch({
url: '/pages/list/list?id=1&name=tom'
// url: '/pages/cate/cate?id=1&name=tom'
})
}
})
// list.js
Page({
navigateBack() {
// 关闭当前页面,返回上一页或者返回多级页面
// 默认返回上一页
wx.navigateBack({
delta: 1
})
},
onLoad(options) {
console.log(options)
}
})
事件监听-上拉加载更多
上拉加载是小程序中常见的一种加载方式,当用户滑动页面到底部时,会自动加载更多的内容,以便用户继续浏览
小程序中实现上拉加载的方式:
1.在 app.json 或者 page.json 中配置距离页面底部距离: onReachBottomDistance;默认 50px
2.在 页面.js 中定义 onReachBottom 事件监听用户上拉加载
代码:
<view wx:for="{{ numList }}" wx:key="*this">{{ item }}</view>
/* pages/market/market.wxss */
view {
height: 400rpx;
display: flex;
align-items: center;
justify-content: center;
}
view:nth-child(odd) {
background-color: lightskyblue;
}
view:nth-child(even) {
background-color: lightsalmon;
}
Page({
data: {
numList: [1, 2, 3]
},
// 监听用户上拉加载
onReachBottom() {
// console.log('监听用户上拉加载')
// 产品需求:
// 当用户上拉,需要数字进行累加
// 当用户上拉加载时,需要对数字进行累加,每次累加 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()
}, 1500)
}
})
事件监听-下拉刷新
下拉刷新是小程序中常见的一种刷新方式,当用户下拉页面时,页面会自动刷新,以便用户获取最新的内容。
小程序中实现上拉加载更多的方式:
1.在 app.json 或者 page.json 中开启允许下拉,同时可以配置 窗口、loading 样式等
2.在 页面.js 中定义 onPullDownRefresh 事件监听用户下拉刷新
落地代码:
<view wx:for="{{ numList }}" wx:key="*this">{{ item }}</view>
/* pages/market/market.wxss */
view {
height: 400rpx;
display: flex;
align-items: center;
justify-content: center;
}
view:nth-child(odd) {
background-color: lightskyblue;
}
view:nth-child(even) {
background-color: lightsalmon;
}
Page({
data: {
numList: [1, 2, 3]
},
// 监听用户上拉加载
onReachBottom() {
// coding...
},
// 监听用户下拉刷新
onPullDownRefresh () {
// console.log('监听用户下拉刷新')
// 产品需求:
// 当用户上拉加载更多以后,如果用户进行了下拉刷新
// 需要将数据进行重置
this.setData({
numList: [1, 2, 3]
})
// 在下拉刷新以后,loading 效果有可能不会回弹回去
if (this.data.numList.length === 3) {
wx.stopPullDownRefresh()
}
}
})
增强 scroll-view
scroll-view 上拉加载
知识点:
bindscrolltolower:滚动到底部/右边时触发
lower-threshold:距底部/右边多远时,触发 scrolltolower 事件
enable-back-to-top:让滚动条返回顶部,iOS 点击顶部状态栏、安卓双击标题栏时,只支持竖向
代码:
<scroll-view
class="scroll-y"
scroll-y
lower-threshold="100"
bindscrolltolower="getMore"
enable-back-to-top
>
<view wx:for="{{ arr }}" wx:key="*this">{{ item }}</view>
</scroll-view>
// index.js
Page({
data: {
arr: [1, 2, 3]
},
// 上拉加载更多
getMore() {
wx.showLoading({
title: '数据正在加载中...'
})
setTimeout(() => {
// 记录当前数组的最后一个元素
let lastNum = this.data.arr[this.data.arr.length - 1]
// 最后一个元素加 1
lastNum++
// 每次向数组中新增三项
const newArr = [lastNum, lastNum + 1, lastNum + 2]
this.setData({
arr: [...this.data.arr, ...newArr]
})
// 数据返回,隐藏 Loading
wx.hideLoading()
wx.showToast({
title: '数字请求完毕,上滑继续浏览',
icon: 'none'
})
}, 1000)
}
})
croll-view 下拉刷新
知识点:
refresher-enabled:开启自定义下拉刷新
refresher-default-style自定义下拉刷新默认样式支持设置 black | white | none
, none 表示不使用默认样式
refresher-background:自定义下拉刷新区域背景颜色
bindrefresherrefresh:自定义下拉刷新状态回调
refresher-triggered:设置当前下拉刷新状态,(true 下拉刷新被触发,false 表示下拉刷新未被触发,用来关闭下拉效果)
代码:
<scroll-view
class="scroll-y"
scroll-y
lower-threshold="100"
bindscrolltolower="getMore"
enable-back-to-top
refresher-enabled
refresher-default-style="black"
refresher-background="#f7f7f8"
refresher-triggered
bindrefresherrefresh="onrefresh"
refresher-triggered="{{ triggered }}"
>
<view wx:for="{{ arr }}" wx:key="*this">{{ item }}</view>
</scroll-view>
// index.js
Page({
data: {
triggered: false, // 控制 scroll-view 下拉刷新效果
arr: [1, 2, 3]
},
// scroll-view 下拉刷新回调函数
onrefresh() {
wx.showLoading({
title: '数据正在加载中...'
})
// 定时器模拟网络请求,1 秒后数据返回
setTimeout(() => {
// 重置数据
this.setData({
arr: [1, 2, 3]
})
// 数据返回,隐藏 Loading
wx.hideLoading()
wx.showToast({
title: '下拉刷新完成,数据已重置...',
icon: 'none'
})
// 数据返回,关闭 scroll-view 下拉刷新效果
this.setData({
triggered: false
})
}, 1000)
}
})
创建-注册-使用组件
组件介绍
小程序目前已经支持组件化开发,可以将页面中的功能模块抽取成自定义组件,以便在不同的页面中重复使用;
也可以将复杂的页面拆分成多个低耦合的模块,有助于代码维护。
开发中常见的组件有两种:
- 公共组件:将页面内的功能模块抽象成自定义组件,以便在不同的页面中重复使用
- 页面组件:将复杂的页面拆分成多个低耦合的模块,有助于代码维护
如果是公共组件,建议将其放在小程序的目录下的 components
文件夹中
如果是页面组件,建议将其放在小程序对应页面目录下,当然你也可以放到页面的 components
文件夹中
同时建议:一个组件一个文件夹,文件夹名称和组件名称保持一致
📌 注意事项:
- 自定义组件的需要在
json
文件中需要配置component
字段设为true
- 自定义组件通过
Component
构造器进行构建,在构造器中可以指定组件的属性、数据、方法等
创建自定义组件:
创建组件的步骤很简单,以公共组件为例,创建的步骤如下:
-
在小程序的目录下新建
components
文件夹 -
在
components
文件夹上,点击右键,选择新建文件夹
,然后输入文件夹名称,我们建议文件夹的名称和组件的名称保持一致,这样方便后期对组件进行维护。我们这里新的的组件名称叫做:custom-checkbox
-
在新建的组件文件夹上,点击右键,选择
新建 Component
,然后输入组件的名称,组件的名称建议和文件夹保持一致 -
此时就已经创建了一个功能组件
使用自定义组件
开发中常见的组件主要分为 公共组件 和 页面组件 两种,因此注册组件的方式也分为两种:
- 全局注册:在
app.json
文件中配置usingComponents
节点进行引用声明,注册后可在任意组件使用 - 局部注册:在页面的
json
文件中配置usingComponents
节点进行引用声明,只可在当前页面使用
在配置 usingComponents
节点进行引用声明时,需要提供自定义组件的标签名和对应的自定义组件文件路径,语法如下:
{
"usingComponents": {
"自定义组件的标签名": "自定义组件文件路径"
}
}
这样,在页面的 wxml
中就可以像使用基础组件一样使用自定义组件。节点名即自定义组件的标签名,节点属性即传递给组件的属性值。
{
"usingComponents": {
"custom-checkbox": "/components/custom-checkbox/custom-checkbox"
}
}
<!--pages/index/index.wxml-->
<view>
<!-- 将导入的自定义组件当成标签使用 -->
<custom-checkbox
/>
</view>
自定义组件-数据和方法
在组件的 .js 中,需要调用 Component
方法创建自定义组件,Component
中有以下两个属性:
data
数据:组件的内部数据
methods
方法:在组件中事件处理程序需要写到 methods
中才可以
代码:
➡️ components/custom-checkbox/custom-checkbox.wxml
<!--components/custom-checkbox/custom-checkbox.wxml-->
<!-- <text>我是自定义组件</text> -->
<view class="custom-checkbox-container">
<view class="custom-checkbox-box">
<checkbox checked="{{ isChecked }}" bindtap="updateChecked" />
</view>
</view>
➡️ components/custom-checkbox/custom-checkbox.wxss
/* components/custom-checkbox/custom-checkbox.wxss */
.custom-checkbox-container {
display: inline-block;
}
➡️ components/custom-checkbox/custom-checkbox.js
Component({
/**
* 组件的初始数据:用来定义当前组件内部所需要使用的数据
*/
data: {
isChecked: false
},
/**
* 组件的方法列表:在组件中,所有的事件处理程序都需要写到 methods 方法中
*/
methods: {
// 更新复选框的状态
updateChecked () {
this.setData({
isChecked: !this.data.isChecked
})
console.log(this.data.isChecked)
}
}
})
➡️ app.json
{
"usingComponents": {
"custom-checkbox": "./components/custom-checkbox/custom-checkbox"
}
}
➡️ index.wxml
<custom-checkbox />
<view class="line"></view>
<custom-checkbox />
自定义组件-属性
属性 Properties 是指组件的对外属性,主要用来接收组件使用者传递给组件内部的数据,和 data 一同用于组件的模板渲染
📌 注意事项:
- 设置属性类型需要使用 type 属性,属性类型是必填项,value 属性为默认值
- 属性类型可以为 String、Number、Boolean、Object、Array ,也可以为 null 表示不限制类型
落地代码:
➡️ index.wxml
<!-- label 文本显示的内容 -->
<!-- position 控制文本显示的位置 -->
<custom-checkbox label="我已阅读并同意 用户协议 和 隐私协议" position="right" />
<view class="line"></view>
<custom-checkbox label="匿名提交" position="left" />
➡️ components/custom-checkbox/custom-checkbox.wxml
<!--components/custom-checkbox/custom-checkbox.wxml-->
<!-- <text>我是自定义组件</text> -->
<view class="custom-checkbox-container">
+ <view class="custom-checkbox-box {{ position === 'right' ? 'right' : 'left' }}">
+ <checkbox class="custom-checkbox" checked="{{ isChecked }}" bindtap="updateChecked" />
+
+ <view>
+ <text>{{ label }}</text>
+ </view>
</view>
</view>
➡️ components/custom-checkbox/custom-checkbox.wxss
/* components/custom-checkbox/custom-checkbox.wxss */
.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;
}
➡️ components/custom-checkbox/custom-checkbox.js
Component({
+ /**
+ * 组件的属性列表:组件的对外属性,主要用来接收组件使用者传递给组件内部的属性以及数据
+ */
+ properties: {
+ // 如果需要接收传递的属性,有两种方式:全写、简写
+ // label: String
+
+ label: {
+ // 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,
+ // label: '在组件内部也可以修改 properties 中的数据'
})
+ // 在 JS 中可以访问和获取 properties 中的数据
+ // 但是一般情况下,不建议修改,因为会造成数据流的混乱
+ // console.log(this.properties.label)
// console.log(this.data.isChecked)
}
}
})
组件 wxml 的 slot
在使用基础组件时,可以给组件传递子节点传递内容,从而将内容展示到页面中,自定义组件也可以接收子节点内容
只不过在组件模板中需要定义 <slot />
节点,用于承载组件引用时提供的子节点
默认情况下,一个组件的 wxml 中只能有一个 slot 。需要使用多 slot 时,可以在组件 js 中声明启用。
同时需要给 slot 添加 name 来区分不同的 slot,给子节点内容添加 slot 属性来将节点插入到 对应的 slot 中
知识点讲解:
➡️ custom01.html
<view>
<slot name="slot-top" />
<!-- slot 就是用来接收、承载子节点内容 -->
<!-- slot 只是一个占位符,子节点内容会将 slot 进行替换 -->
<!-- 默认插槽 -->
<view><slot /></view>
<slot name="slot-bottom" />
</view>
➡️ custom01.js
// components/custom01/custom01.js
Component({
options: {
// 启用多 slot 支持
multipleSlots: true
}
})
➡️ cart.wxml
<custom01>
<text slot="slot-top">我需要显示到顶部</text>
<!-- 默认情况下,自定义组件的子节点内容不会进行展示 -->
<!-- 如果想内容进行展示,需要再组件模板中定义 slot 节点 -->
我是子节点内容
<text slot="slot-bottom">我需要显示到低部</text>
</custom01>
组件样式以及注意事项
选择器使用注意事项:
类似于页面,自定义组件拥有自己的 wxss
样式,组件对应 wxss
文件的样式,只对组件wxml内的节点生效。
编写组件样式时,需要注意以下几点:
-
app.wxss 或页面的 wxss 中使用了标签名(view)选择器(或一些其他特殊选择器)来直接指定样式
这些选择器会影响到页面和全部组件,通常情况下这是不推荐的做法 -
组件和引用组件的页面不能使用 id 选择器(#a)、属性选择器([a]) 和 标签名选择器,请改用 class 选择器
-
组件和引用组件的页面中使用后代选择器(.a .b)在一些极端情况下会有非预期的表现,如遇,请避免使用
-
子元素选择器(.a>.b)只能用于 view 组件与其子节点之间,用于其他组件可能导致非预期的情况。
-
继承样式,如 font 、 color ,会从组件外继承到组件内。
-
除继承样式外, 全局中的样式、组件所在页面的的样式对自定义组件无效 (除非更改组件样式隔离选项)
#a { } /* 在组件中不能使用 */
[a] { } /* 在组件中不能使用 */
button { } /* 在组件中不能使用 */
.a > .b { } /* 除非 .a 是 view 组件节点,否则不一定会生效 */
代码:
➡️ custom02.wxml
<text id="content" class="content son">
<text class="label">给自定义组件设置样式</text>
</text>
➡️ custom02.wxss
/* components/custom02/custom02.wxss */
/* 第一个注意事项:在自定义的 wxss 文件中,不允许使用标签选择器,ID 选择器,属性选择器 */
/* 请改为 class 选择器 */
/* text {
color: lightseagreen;
} */
/* #content {
color: lightseagreen;
} */
/* [id=content] {
color: lightseagreen;
} */
/* .content {
color: lightseagreen;
} */
/* 第二个注意事项:子选择器,只能用于 view 和 子组件,用于其他组件可能会出现样式失效的问题 */
/* .content > .label {
color: lightseagreen;
} */
/* 第三个注意事项:继承样式,例如:color\font 都会从组件外继承 */
/* 第四个注意事项:全局样式、组件所在页面的样式文件中的样式都对自定义组件无效 */
/* 第五个注意事项:官方不推荐做法 */
/* 不建议在 全局样式文件 以及 父级页面之间使用标签选择器设置样式 */
/* 如果是在全局样式文件中设置样式,会影响项目中全部的相同组件 */
/* 如果是再页面样式文件中设置样式,会影响当前页面所有的相同组件 */
/* 第六个注意事项: */
/* 组件和组件使用者,如果使用了后代选择器,可能会出现一些非预期情况 */
/* 如果出现,请避免 */
➡️ cate.wxml
<view class="custom parent">
<view>
<custom02 />
<view class="son test">我是父级页面中的结构</view>
</view>
</view>
➡️ cate.wxss
/* pages/cate/cate.wxss */
/* .custom {
color: lightseagreen;
font-size: 50rpx;
} */
/* .label {
color: lightseagreen;
} */
/* text {
color: lightseagreen;
} */
.parent .son.test {
color: lightsalmon;
}
➡️ app.wxss
/* .label {
color: lightseagreen;
} */
/* text {
color: lightseagreen;
} */
组件样式隔离
默认情况下,自定义组件的样式只受到自定义组件 wxss 的影响。除非以下两种情况:
-
app.wxss
或页面的wxss
中使用了标签名(view)选择器(或一些其他特殊选择器)来直接指定样式,这些选择器会影响到页面和全部组件。通常情况下这是不推荐的做法。 -
指定特殊的样式隔离选项
styleIsolation
Component({ options: { styleIsolation: 'isolated' } })
styleIsolation
选项它支持以下取值:
isolated
表示启用样式隔离,在自定义组件内外,使用 class 指定的样式将不会相互影响(一般情况下的默认值);apply-shared
表示页面 wxss 样式将影响到自定义组件,但自定义组件 wxss 中指定的样式不会影响页面;shared
表示页面 wxss 样式将影响到自定义组件,自定义组件 wxss 中指定的样式也会影响页面和其他设置了apply-shared
或shared
的自定义组件。
代码:
➡️ custom03.wxml
<!--components/custom03/custom03.wxml-->
<text class="label">演示组件样式隔离</text>
➡️ custom03.wxss
/* components/custom03/custom03.wxss */
.test {
color: lightseagreen;
font-size: 50rpx;
}
➡️ custom03.js
// components/custom03/custom03.js
Component({
options: {
// styleIsolation:配置组件样式隔离
// isolated:开启样式隔离,默认值
// 在默认情况下,自定义组件和组件使用者如果存在相同的类名,类名不会相互影响
// apply-shared:表示组件使用者、页面的 wxss 样式能够影响到自定义组件
// 但是自定义组件的样式不会影响组件使用者、页面的 wxss 样式
// styleIsolation: "apply-shared"
// shared:表示组件使用者、页面的 wxss 样式能够影响到自定义组件
// 自定义组件的样式会影响组件使用者、页面的 wxss 样式
// 和其他使用了 apply-share 以及 share 属性的自定义组件
styleIsolation: 'shared'
}
})
➡️ cate.wxml
<custom03 />
➡️ cate.wxss
.label {
color: lightsalmon;
}
拓展-小程序修改checkbox样式
知识点:
技巧:在官方文档,找到官方提供的案例,审查元素,就能看到对应的类名
📌 注意事项
- .custom-checkbox .wx-checkbox-input {}:复选框没有选中时默认的样式
- .custom-checkbox .wx-checkbox-input-checked {}: 复选框选中时默认的样式
- .custom-checkbox .wx-checkbox-input.wx-checkbox-input-checked:before {}:复选框选中时 √ 样式
这几个类名,在全局样式文件、页面样式文件都可以对修改复选框样式,
但是在自定义组件内部使用的时候,需要添加 styleIsolation: 'shared' 属性
代码:
➡️ components/custom-checkbox/custom-checkbox.wxss
/* 复选框组件是公共组件 */
/* 以后需要再多个页面或者需要再多个项目中进行使用 */
/* 所以呢,需要先给复选框组件准备、设置一些默认样式 */
/* 如果在其他页面或者其他项目中使用的时候,发现样式不符合产品需求 */
/* 可以进行修改、对默认的样式进行修改 */
/* 1. 需要给复选框设置默认样式 */
/* 需要先找到小程序给复选框提供的类名,通过小程序给提供的类名修改才可以 */
/* 需要先打开小程序开发文档,找到复选框文档,审查元素,进行查找 */
/* 在自定义组件中,不能直接修改复选框样式 */
/* 如果需要进行修改,需要设置 styleIsolation 才可以 */
/* shared:修改其他页面的样式、组件使用者的样式、以及其他使用了 share 以及 apply-share 的组件 */
/* 这时候,不是想要的结果 */
/* 需求是:只想影响当前组件,可以添加命名空间 */
/* 复选框没有选中时默认的样式 */
.custom-checkbox .wx-checkbox-input {
width: 24rpx !important;
height: 24rpx !important;
border-radius: 50% !important;
border: 1px solid #fda007 !important;
margin-top: -6rpx;
}
/* 复选框选中时默认的样式 */
.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;
}
/* 2. 组件使用者也能够修改默认的样式 */
➡️ components/custom-checkbox/custom-checkbox.js
Component({
options: {
styleIsolation: 'shared'
}
})
➡️ index.wxss
/* 组件使用者修改复选框的样式 */
.custom .custom-checkbox .wx-checkbox-input {
border: 1px solid lightseagreen !important;
}
.custom .custom-checkbox .wx-checkbox-input-checked {
background-color: lightseagreen !important;
}
数据监听器
知识点:
数据监听器可以用于监听和响应任何属性和数据字段的变化,有时,需要在一些数据字段被 setData 设置时,需要执行一些操作。那么就可以使用 observers
数据监听器来实现。语法如下:
Component({
data: {
num: 10,
count: 1,
obj: { name: 'Tom', age: 10 },
arr: [1, 2, 3]
},
observers: {
// key 是需要检测数据
// value 是一个函数,函数接收一个形参作为参数,是最新的值
num: function(newNum) {
console.log(newNum)
},
// 数据监听器支持监听属性或内部数据的变化,可以同时监听多个
'num, count': function (newNum, newCount) {
console.log(newNum, newCount)
}
// 监听器可以监听子数据字段
'obj.age': function(newAge) {
console.log(newAge)
},
// 如果需要监听所有子数据字段的变化,可以使用通配符 **
'obj.**': function(newAge) {
console.log(newAge)
},
'arr[0]': function (val) {}
}
})
拓展:使用 Component 构造页面
Component 方法用于创建自定义组件
小程序的页面也可以视为自定义组件,因此页面也可以使用 Component 方法进行创建,从而实现复杂的页面逻辑开发
📌 注意事项:
要求对应 json 文件中包含 usingComponents 定义段
页面使用 Component 构造器创建,需要定义与普通组件一样的字段与实例方法
页面 Page 中的一些生命周期方法(如 onLoad() 等以“on”开头的方法),在 Component 中要写在 methods 属性中才能生效
组件的属性 Properties 可以用于接收页面的参数,在 onLoad() 中可以通过 this.data 拿到对应的页面参数
代码:
Component({
// 为什么需要使用 Component 方法进行构造页面
// Component 方法功能比 Page 方法强大很多
// 如果使用 Component 方法构造页面,可以实现更加复杂的页面逻辑开发
// 小程序页面也可以使用 Component 方法进行构造
// 注意事项:
// 1. 要求 .json 文件中必须包含 usingComponents 字段
// 2. 里面的配置项需要和 Component 中的配置项保持一致
// 3. 页面中 Page 方法有一些钩子函数、事件监听方法,这些钩子函数、事件监听方法必须方法 methods 对象中
// 4. 组件的属性 properties 也可以接受页面的参数,在 onLoad 钩子函数中可以通过 this.data 进行获取
properties: {
id: String,
title: String
},
data: {
name: 'tom'
},
// onLoad () {
// console.log('页面加载 - 1')
// },
methods: {
// 更新 name
updateName() {
this.setData({
name: 'jerry'
})
},
onLoad (options) {
// console.log('页面加载 - 2')
// console.log(options)
console.log(this.data.id)
console.log(this.data.title)
console.log(this.properties.id)
},
}
})
拓展:behaviors
小程序的 behaviors 方法是一种代码复用的方式,可以将一些通用的逻辑和方法提取出来,然后在多个组件中复用,从而减少代码冗余,提高代码的可维护性。
如果需要 behavior 复用代码,需要使用 Behavior() 方法,每个 behavior 可以包含一组属性、数据、生命周期函数和方法
组件引用它时,它的属性、数据和方法会被合并到组件中,生命周期函数也会在对应时机被调用。
注册 behavior:
如果需要注册一个 behavior
,需要借助 Behavior()
方法,接受一个 Object
类型的参数
// my-behavior.js
module.exports = Behavior({
behaviors: [],
properties: {
myBehaviorProperty: {
type: String
}
},
data: {
myBehaviorData: 'my-behavior-data'
},
created: function () {
console.log('[my-behavior] created')
},
attached: function () {
console.log('[my-behavior] attached')
},
ready: function () {
console.log('[my-behavior] ready')
},
methods: {
myBehaviorMethod: function () {
console.log('[my-behavior] log by myBehaviorMehtod')
},
}
})
使用 behavior:
// my-component.js
const myBehavior = require('my-behavior')
Component({
behaviors: [myBehavior]
// coding...
})
组件和它引用的 behavior 中可以包含同名的字段,对这些字段的处理方法如下:
-
如果有同名的属性或方法,采用 “就近原则”,组件会覆盖 behavior 中的同名属性或方法
-
如果有同名的数据字段且都是对象类型,会进行对象合并,其余情况会 采用 “就近原则” 进行数据覆盖
-
生命周期函数和 observers 不会相互覆盖,会是在对应触发时机被逐个调用,也就是都会被执行
详细的规则:同名字段的覆盖和组合规则
拓展:外部样式类
默认情况下,组件和组件使用者之间如果存在相同的类名不会相互影响,组件使用者如果想修改组件的样式,需要就解除样式隔离,但是解除样式隔离以后,在极端情况下,会产生样式冲突、CSS 嵌套太深等问题,从而给我们的开发带来一定的麻烦。
外部样式类:在使用组件时,组件使用者可以给组件传入 CSS 类名,通过传入的类名修改组件的样式。
如果需要使用外部样式类修改组件的样式,在 Component 中需要用 externalClasses 定义若干个外部样式类。
外部样式类的使用步骤:
1.在 Component 中用 externalClasses 定义段定义若干个外部样式类
2.自定义组件标签通过 属性绑定 的方式提供一个样式类,属性是 externalClasses 定义的元素,属性值是传递的类名
3.将接受到的样式类用于自定义组件内部
📌注意事项:
在同一个节点上使用普通样式类和外部样式类时,两个类的优先级是未定义的
因此需要添加 !important 以保证外部样式类的优先级
代码:
➡️ custom09.js
// components/custom09/custom09.js
Component({
// 组件接受的外部样式类
externalClasses: ['extend-class']
})
➡️ custom09.wxml
<!-- 在同一个节点上,如果存在外部样式类 和 普通的样式类 -->
<!-- 两个类的优先级是未定义的 -->
<!-- 建议:在使用外部样式类的时,样式需要通过 !important 添加权重 -->
<view class="extend-class box">通过外部样式类修改组件的样式</view>
➡️ custom09.wxss
.box {
color: lightseagreen;
}
➡️ profile.wxml
<!-- 属性是在 externalClasses 里面定义的元素 -->
<!-- 属性值必须是一个类名 -->
<custom09 extend-class="my-class" />
➡️ profile.wxss
/* pages/index/index.wxss */
.my-class {
color: lightsalmon !important;
}