做传统网站
前端开发的同学初次接触小程序
,会有许多困惑
:为什么没有div,view 是什么、怎么没有 ajax,wx.request 为什么是回调方式、预览怎么要用小程序开发者工具、APPID有什么用、安装npm包怎么还要构建、tabBar 是什么、语法怎么和vue很像但是有的部分又不同、@import 用法怎么和 css 中不同...
本篇通过微信小程序(发布较早,影响力较大)来介绍小程序,帮助你快速认识小程序
,并解决以上困惑。主要介绍:
网站
的差异vue
语法差异运行机制
开发流程
基本语法
:wxsl、wxss、js(wxs)API的Promise化
生命周期
和页面生命周期路由和状态
管理小李虽然会一些react、vue、js,但是移动端开发做的比较少,几乎不会小程序
开发。
下一阶段的任务是移动端开发
,涉及H5、小程序、公司内部自建的移动端的框架、调试工具、TS等。
如何快速上手这部分工作,经过三分钟的思考,决定出大致要加强的方向:
小程序类似网站
,只是网站在浏览器中打开,而小程序通过小程序平台(微信、支付宝)打开。
两者相似点
:
两者不同点
:
有人说小程序比 Vue 简单多了。我们来对比两者异同,会发现小程序在语法上有许多和vue相似
。
相同点
:
{{}}
进行数据绑定,类似 vue.js 模板语法不同点
:
手机微信
是微信小程序的宿主环境,支付宝
是支付宝小程序的宿主环境
小程序借助宿主环境提供的能力,可以完成许多普通网页无法完成的功能,如:微信登录、微信支付、微信扫码、地理定位...
通过宿主环境,小程序提供的能力包含:通信模型
、运行机制
、组件
和API
小程序通信主体包含:渲染层
和逻辑层
小程序中的通信模型分两部分:
小程序启动过程
:
Tip:上面是冷启动,对于已经打开过的小程序,再次进入可能就会有热启动的情况。比如代码下载可能就会被跳过
页面渲染过程
:
小程序官方把API分三类:
事件监听API
:以 on 开头,监听某些事件。例如 wx.onWindowResize(callback)(小程序中没有windown) 监听窗口尺寸变化同步API
:以Sync结尾的都是同步API,通过函数直接获取,如果执行出错会抛出异常。例如wx.setStorageSync('key', 'value') 向本地缓存写入数据小程序开发流程不同于传统网站
。
传统网站开发:vscode编写代码,浏览器预览效果,git提交到代码。
小程序开发步骤大致如下(以微信小程序三方
开发为例):
Tip:一方开发通常指的是由小程序的所有者的开发,也是官方开发; 三方开发,是指由第三方开发者为小程序提供功能或服务;
注册小程序账号,主要是为了获得APPID。
APPID
是小程序唯一标识,用于在微信上识别和区分不同的小程序,在注册过程中,需要填写一些基本信息,如小程序名称、小程序介绍、小程序类别等。完成这些,微信会为你生成一个APPID。APPID将用于开发、发布和运营小程序各种操作,包含开发工具的配置等
大致流程如下:
https://mp.weixin.qq.com/cgi-bin/wx
),点击“注册”注册后就可以登录到小程序后台管理界面,在“开发”导航中就能找到APPID。
小程序你不用特定工具怎么预览效果?浏览器又不认识小程序
微信开发者工具提供如下功能:
找到稳定版下载安装成功,在桌面会看到一个二维码,用自己的微信扫一扫,登录后就能打开“微信开发者工具”。
创建项目:可以指定项目所在目录、后端服务是否选择云开发、语言有javascript或 TypeScript。
小程序工具主界面
分五个部分:
构建 npm
、插件)编译
、真机调试
页面参数
)console控制台
、Network、Storage通过小程序工具,普通编译
会从小程序首页开始,而平时我们修改某页面逻辑,保存后想立刻看到效果,而不是又从首页切换好几次才到该页面。这是,我们可以使用“自定义编译条件”。
点击“普通编译”下的“添加编译模式”,选择要启动的页面,还可以传参,新建即可。下次就选择这个页面编译即可。
一个页面可以创建多个编译页面,比如有参的、无参的...
小程序通常不是一个人完成的。
微信小程序成员管理(三方
)体现在管理员对小程序项目成员及体验成员的管理
开发者的权限有:
Tip:之所以有这些角色,因为小程序的开发流程不同于网站开发,小程序的代码由小程序平台管理。
小程序发布流程大致如下:上传代码到开发版本,多次迭代开发版本,根据开发版本生成体验版本,验证通过后提交审核,审核通过后发布。
开发版本
:使用开发者工具,可将代码上传到开发版本中。开发版本只保留每人最新的一份上传的代码。点击提交审核,可以将代码提交审核。开发版本删除,不影响线上版本和审核中的版本。体验版本
:选择某个开发版本作为体验版审核中版本
:只能有一份代码处于审核中。有审核结果后可以发布到线上,也可以直接重新提交审核,覆盖原审核版本线上版本
:线上所有用户使用的代码版本Tip:微信小程序和支付宝小程序都提供了多版本开发和管理功能。体验版只能同时根据其中一个开发版本生成。
发布后就需要推广
推广可以基于微信码和小程序码。
小程序码的优势:
小程序可以通过后台查看运营数据
,也可以使用“小程序数据助手”(微信搜索)查看已发布小程序相关数据:访问分析、实时同级、用户画像...
小程序也可以使用第三方埋点工具,例如:友盟友、神策数据...
微信小程序支持 NPM 包,但小程序能用的 Npm 包却不多
。
下面是一些限制和注意事项:
创建一个微信小程序项目,目录结构
如下:
- pages: 存放所有小程序的页面
- utils:存放工具性质的模块
- app.js:小程序入口文件
- app.json:小程序全局配置文件。包含小程序所有页面路径、窗口外观、界面表现(所有页面的背景色、文字颜色、小程序组件所使用的样式版本)、底部tab
- project.config.json:项目配置文件。记录我们对小程序开发工具做的个性化配置,如项目名、小程序账号ID、编译相关配置(ES6转ES5、上传代码时自动压缩混淆、上传代码时样式自动补全)
- sitemap.json:配置小程序及其页面是否允许被微信索引。微信现已开放了小程序内搜索,类似网页的SEO。
小程序官方建议所有小程序页面都放在 pages
目录中,以单独文件夹存放:
- pages
- index
- index.js 页面脚本
- index.wxml 页面结构
- index.wxss 页面样式
- index.json 当前页面的配置,如窗口的外观
- pageb
- pageb.js
- pageb.wxml
- pageb.wxss
- pageb.json
Tip:小程序中有4种json配置文件
(具体作用后面会介绍)
在 app.json->pages 中新增页面存放路径,ctrl+s保存,工具会自动
创建对应页面文件。
{
pages: [
"pages/index/index",
"pages/pageb/pageb",
+ "pages/pageb/pagec"
]
}
只需调整
app.json->pages 数组中页面路径的顺序
,小程序会把排在第一位的页面,当做项目首页渲染。
小程序根目录下的 app.json
是小程序全局配置文件。
常用配置:
示例:
{
"pages": [
"pages/index/index",
"pages/logs/logs"
],
"window": {
"navigationBarTitleText": "小程序示例",
"navigationBarBackgroundColor": "#ffffff",
"navigationBarTextStyle": "black",
"backgroundColor": "#eeeeee",
"backgroundTextStyle": "light",
"enablePullDownRefresh": true,
"onReachBottomDistance": 50
},
"tabBar": {
"color": "#7A7E83",
"selectedColor": "#3cc51f",
"borderStyle": "black",
"backgroundColor": "#ffffff",
"list": [
{
"pagePath": "pages/index/index",
"text": "首页",
"iconPath": "images/icon_home.png",
"selectedIconPath": "images/icon_home_active.png"
},
{
"pagePath": "pages/logs/logs",
"text": "日志",
"iconPath": "images/icon_logs.png",
"selectedIconPath": "images/icon_logs_active.png"
}
]
},
"style": "v2"
}
小程序窗口组成部分(从上到下):
导航栏区域
:包含时间、电量、微信标题background
背景区域,默认不可见,下拉才显示页面主体区域
,用了显示 wxml 中布局windown节点常用配置项:
导航栏标题
文字 字符串Tip:下拉刷新
,通常做法是在页面中单独开启,而非在这里全局开启。下拉刷新开启后,若要实现刷新,还得在 onPullDownRefresh 方法中处理下来刷新逻辑,这个方法会在用户触发下拉刷新操作时被调用。
注
:模拟器不能百分之百还原真机。例如下拉刷新,在模拟器中,下拉后,过了3秒,下拉自动合上;而在真机中,不会自动合上
小程序中 tabBar 是导航组件
。特性有:
以下是一个典型的 app.json 中 tabBar 配置示例
:
{
"tabBar": {
"color": "#999999",
"selectedColor": "#1c1c1b",
"backgroundColor": "#ffffff",
"borderStyle": "black",
"list": [
{
"pagePath": "pages/home/index",
"text": "首页",
"iconPath": "/images/icon_home.png",
"selectedIconPath": "/images/icon_home_active.png"
},
{
"pagePath": "pages/search/index",
"text": "搜索",
"iconPath": "/images/icon_search.png",
"selectedIconPath": "/images/icon_search_active.png"
},
{
"pagePath": "pages/profile/index",
"text": "我的",
"iconPath": "/images/icon_profile.png",
"selectedIconPath": "/images/icon_profile_active.png"
}
]
}
}
注
:tabBar 只能配置最少2个,最多5个。当渲染顶部tabBar 时,不显示 icon
,只显示文本。说tabBar 中的页面要放在 pages 前面,否则显示不出。
tabBar 有6个组成部分:
tabBar 节点配置项:
最少2个,最多5个
每个 tab 项配置选项:
在小程序中,全局配置和页面配置可以定义页面的外观和行为
。当全局配置和页面配置冲突时,确实遵循就近原则,最终效果通常以页面配置为准。这意味着页面特定的配置会覆盖全局配置。这样可以确保页面的定制化效果。
页面配置中常用配置项:
微信小程序的 wxml 类似网页中的 html。支付宝小程序中是 axml。
wxml 和 html 区别
:
<a href="http://www.baidu.com">百度</a>
<navigator url="http://www.baidu.com">百度</navigator>
在 data 中定义数据,在 wxml 中使用。例如:
Page({
data: {
name: '张三',
age: 18,
url: 'http://....png',
randomNum: Math.random() * 10,
}
})
用Mustache语法({{}}
)将变量包起来即可:
<view>{{ name }}</view>
<view>{{ randomNum > 5 ? '大于5': '小于或等于5' }}</view>
动态绑定属性不同于 vue 的 v-bind,小程序的动态绑定属性是直接在标签上写(写法不同而已,死记即可
),例如:
<image src="{{ url }}"></image>
Tip: 数据在小程序开发工具控制台的 AppData tab中可以看到。
小程序和vue中条件渲染对比:
用法:在 wxml 中使用 wx:if、wx:elif、wx:else 标签,在 data 中定义变量,在 wx:if 中使用变量。
<view>
<view wx:if="{{ age > 18 }}">
你成年了
</view>
<view wx:elif="{{ age < 18 }}"> 你未成年
</view>
<view wx:else>
你很少年
</view>
</view>
小程序和vue中列表渲染对比:
用法:在 wxml 中使用 wx:for 标签,在 data 中定义数组,在 wx:for 中使用数组。
默认当前循环项索引是 index,当前循环项是 item:
<view>
<block wx:for="{{items}}" wx:key="index">
<view>
<text>{{index}}: {{item}}</text>
</view>
</block>
</view>
Page({
data: {
items: ['Item 1', 'Item 2', 'Item 3']
}
});
wx:for-item 和 wx:for-index 用于自定义变量名,使得代码更加清晰和可读。
<view>
<block wx:for="{{items}}" wx:for-item="user" wx:for-index="idx" wx:key="id">
<view>
<text>Index: {{idx}}</text>
<text>ID: {{user.id}}</text>
<text>Name: {{user.name}}</text>
</view>
</block>
</view>
Page({
data: {
items: [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' },
{ id: 3, name: 'Charlie' }
]
}
});
注
: 小程序的 key,直接是循环项中的属性,且不需要 {{}}
。如果是vue,得通过循环项找到
<template v-for="item in items" :key="item.id">
小程序的样式,类似网页的 css。
wxss 具备 css 大部分特定,wxss 还对 css 进行了扩充和修改,以适应小程序的开发
wxss 和 css 区别:
rpx
(responsive pixel,响应式像素)尺寸单位
@media
@import
引入其他 .wxss,不支持 @import url() 形式引入外部 cssrpx 原理非常简单,把所有设备的屏幕从宽度上等分 750 份
Tip:rem和 rpx 在实现响应式布局中,主要关注的是宽度的自适应。高度需要自行处理,比如等比扩展,或者限制最高高度。
iphone 屏幕宽度是 375px(逻辑像素),共有 750个像素点(物理像素),1个逻辑像素等于2个物理像素,等分750rpx。则:
开发举例:根据设计稿来,有的要求是1:1,有的是1:2,宽100px200px的盒子,转成rpx 就是 200rpx400rpx。
@import 后根需要导入的外联样式的相对路径,用;表示结束。示例:
@import "demo.wxss";
.box{
}
使用时是 class 而非 className。
Tip:微信小程序支持使用 less,不过需要进行一些配置。
定义在 app.wxss 中的样式是全局样式,作用于每一个页面
定义在页面的 .wxss 中的样式是局部样式,只作用于当前页面。
注
:当局部样式和全局样式冲突,和 css 中一样:哪个权重高用哪个,如果权重相同,则使用就近原则(采取局部样式)
Tip:把鼠标放到小程序工具中选择器上,会有选中提示,例如:Selector Specificity:(0, 1, 0)
Tip:小程序中的 js 分3大类
wxs在微信小程序中的作用类似 Vue.js中的过滤器
(vue3 已废除过滤器)
小程序中的 wxs 和 javascript 是两种语言,区别有:
Tip: 在 ios 中,小程序内的 wxs 比 js 块 2~20倍;在安卓上无差异。
wxs的语法基于JavaScript,这意味着如果你熟悉JavaScript,学习wxs会相对容易:
<wxs>
标签中,就像js写在 <script>
中,wxs 必须提供 module 属性,用来指定当前 wxs 模块名称外联wxs用法(src必须是相对路径):
<!-- index.wxml -->
<wxs module="utils" src="../../utils/utils.wxs"/>
<view>
<text>{{utils.formatDate(new Date())}}</text>
</view>
// utils.wxs
module.exports = {
formatDate: function(date) {
var year = date.getFullYear();
var month = date.getMonth() + 1;
var day = date.getDate();
return [year, month, day].map(this.formatNumber).join('-');
},
formatNumber: function(n) {
n = n.toString();
return n[1] ? n : '0' + n;
}
};
小程序中网络数据,处于安全考虑,小程序官方对数据接口做了如下限制:
只能请求 https 类型接口
假如希望在自己的微信小程序中,希望请求https://www.xxx.com/域名下的接口。配置步骤:登录微信小程序后台->开发->开发设置->服务器域名->修改request合法域名。注意:
Tip: 如果后端仅提供http协议接口,为不耽误开发进度,可以在微信开发者工具中,临时开启「开发环境不校验请求域名、TLS版本及HTTPS证书」,跳过 request 合法域名校验,仅限在开发和调试阶段使用。
在微信小程序中,您可以使用 wx.request
方法来发起 HTTP GET 和 POST 请求。这个方法提供了一种简单的方式来与服务器进行数据交互:
请看示例:
wx.request({
url: 'https://api.example.com/data',
method: 'GET',
data: {
key1: 'value1',
key2: 'value2'
},
header: {
'content-type': 'application/json'
},
success: function(res) {
},
fail: function(err) {
}
});
wx.request({
url: 'https://api.example.com/submit',
method: 'POST',
data: {
key1: 'value1',
key2: 'value2'
},
header: {
'content-type': 'application/json'
},
success: function(res) {
},
fail: function(err) {
}
});
小程序没有常规的跨域问题,但本质上还是涉及一些
。但是对于前端开发,则无需处理跨域。
跨域(Cross-Origin Resource Sharing,简称 CORS)是指一个域名下的文档或脚本尝试请求另一个域名下的资源时,由于浏览器的同源策略(Same-origin policy)限制而导致的请求被阻拦的行为。这里的“同源”指的是协议、域名和端口号完全相同。同源策略是一种安全措施,旨在防止恶意网站通过脚本读取另一个网站的敏感数据。
跨域的本质是指浏览器出于安全考虑,实施的一种同源策略(Same-origin policy)
小程序的主体不是浏览器,而是小程序平台,所以没有常规的跨域问题。
因为小程序需要配置受信任域名,其实也在一定程度上有了安全保障,小程序的服务端也会涉及到CORS的配置
Ajax核心依赖浏览器中的 XMLHttpRequest 对象,而小程序的宿主环境是微信客户端,所以小程序不叫”发起ajax请求“,而叫”发起网络请求“
微信小程序没有直接使用 ajax 这个术语,但提供了类似异步HTTP请求能力,主要通过 wx.request 接口来完成 Get 或 Post 请求,这一过程和Ajax非常类似,都是异步获取数据并更新界面而不阻塞页面。所以小程序中不说”ajax“,但实际上具备异步获取数据的能力
wx.request 和 ajax 功能相似,运营环境和实现机制不同。
请看示例:
wx.request({
url: 'https://api.example.com/data',
method: 'GET',
data: {
key1: 'value1',
key2: 'value2'
}, // 请求参数
header: {
'content-type': 'application/json'
},
success: function(res) {
},
fail: function(err) {
}
});
wx.request 可以与 async/await 和 Promise.all 配合使用:
const request = (options) => {
return new Promise((resolve, reject) => {
wx.request({
...options,
success: res => resolve(res),
fail: err => reject(err)
});
});
};
Page({
async onLoad() {
try {
const response = await request({
url: 'https://example.com/api/data',
method: 'GET'
});
console.log('Data:', response.data);
} catch (err) {
console.error('Error:', err);
}
}
});
在开发微信小程序时,许多原生 API 是基于回调函数
的,这在现代 JavaScript 编程中可能不太方便。为了更好地处理异步操作,我们可以将这些回调函数形式的 API 转换为 Promise
形式
一种是手动封装
一种是用库(例如miniprogram-api-promise)。例如:
安装,构建后,在你的项目中配置并使用:
// app.js
import wxp from 'miniprogram-api-promise';
App({
onLaunch() {
// 把所有 wx 函数 promise 化
wxp.init();
}
});
在页面或组件中使用:
// pages/index/index.js
Page({
data: {},
async onLoad() {
try {
const response = await wx.p.request({
url: 'https://api.example.com/data',
method: 'GET',
});
console.log(response.data);
} catch (error) {
console.error(error);
}
}
});
小程序有两类生命周期:
应用生命周期
:小程序从启动->运行->销毁页面生命周期
:小程序中每个页面的加载->渲染->销毁页面的生命周期范围小,应用程序的生命周期范围大:
小程序启动-> 页面A的生命周期 -> 页面B的生命周期 ->页面C的生命周期 -> 小程序结束
应用生命周期函数需要写在 app.js中:
App({
onLaunch: function(opts) {},
onShow: function(opts){},
hoHide: function(){},
})
Tip:微信开发者工具有一个选项“切后台”,就可以模拟切到后台
每个小程序页面也有其独立的生命周期,主要用于控制页面的加载、渲染、显示、隐藏和卸载等过程。主要生命周期包括:
一个页面只调用一次
)。适合初始化页面数据、获取页面参数等。一个页面只调用一次
)。此时可以进行一些DOM操作(虽然一般推荐使用setData来改变界面)。Tip:后台进入前台,先执行全局的 onShow,再执行页面的 onShow。
启用下拉刷新有全局开启下拉和局部开启下拉,实际开发,推荐使用局部开启下来,也就是为需要的页面单独开启下拉。
这里说一下实现:
{
"enablePullDownRefresh": true
}
Page({
onPullDownRefresh: function() {
// 这里写你的数据重新加载或更新逻辑
console.log('正在刷新...');
// 模拟异步数据加载过程,实际情况中可能是发起网络请求获取新数据
setTimeout(() => {
// 数据加载完毕,停止下拉刷新的动画
wx.stopPullDownRefresh();
console.log('刷新完成');
}, 1000); // 延迟时间仅作为示例,实际应根据你的数据加载时间调整
},
// 页面的其他生命周期函数和方法...
})
前面提到配置中默认是距离底部50px时触发,没有特别要求不用改
现在要实现上拉触底逻辑,只需要在 onReachBottom 中编码即可:
Page({
data: {
itemList: [], // 初始数据列表
page: 1, // 当前页数,用于分页加载
hasMore: true // 是否还有更多数据
},
onReachBottom: function() {
// 当用户滑动到底部时触发此函数
if (this.data.hasMore) {
this.loadMoreData();
} else {
wx.showToast({
title: '没有更多数据了',
icon: 'none'
});
}
},
小程序 behaviors 和 vue 中 mixins
类似。相似点有:
mixins 有一些问题:
Vue 3 的组合 API(Composition API)在很多情况下可以替代 mixins,并且解决了某些 mixins 的不足之处,比如命名冲突和代码组织不清晰等问题
小程序 hehaviors 和 vue 中 mixins 区别:
事件是渲染层到逻辑层的通讯
:事件将用户在渲染层产生的动作,反馈到逻辑层进行处理。
小程序中常用事件:
tap
,绑定方式是 bindtap或bind:tap,手指触摸后马上离开,类似html中的click事件在微信小程序中,推荐使用tap,而非传统html中的 click,因为小程序为了优化移动端触摸体验,特别设计了 tap 事件来处理用户点击。相对click,有几个优势:
请看示例:
Page({
data: {
message: '按钮尚未被点击'
},
// 方法不像vue需要写在 methods 中,和 data 同级即可。
handleTap: function (e) {
this.setData({
message: '按钮被点击了!'
});
wx.showToast({
title: '你点击了按钮',
icon: 'none'
});
}
});
<view>
<button bindtap="handleTap">点击我</button>
<view>{{message}}</view>
</view>
除了tap事件,小程序还提供了一些常见的触摸事件:longpress(长按)、touchstart(触摸开始)、touchemove(触摸移动)、touchend(触摸结束)、touchcancel(触摸取消)等
Tip:小程序中其他事件有
事件类型 | 事件 | 说明 |
---|---|---|
触摸事件 | touchstart | 手指触摸动作开始 |
touchmove | 手指触摸后移动 | |
touchend | 手指触摸动作结束 | |
touchcancel | 手指触摸动作被打断,如来电提醒 | |
tap | 手指触摸后马上离开 | |
longpress | 手指触摸后,超过350ms再离开 | |
longtap | 手指触摸后,超过350ms再离开(别名) | |
表单事件 | submit | 表单提交 |
reset | 表单重置 | |
input | 输入框输入时触发 | |
focus | 输入框获得焦点时触发 | |
blur | 输入框失去焦点时触发 | |
媒体事件 | play | 开始播放 |
pause | 暂停播放 | |
ended | 播放结束 | |
timeupdate | 播放进度更新 | |
error | 播放错误 | |
waiting | 正在加载中 | |
图片事件 | load | 图片加载完成时触发 |
error | 图片加载错误时触发 | |
滚动事件 | scroll | 滚动时触发 |
scrolltoupper | 滚动到顶部/左边时触发 | |
scrolltolower | 滚动到底部/右边时触发 | |
开放能力事件 | contact | 用户点击客服按钮时触发 |
getuserinfo | 获取用户信息事件 | |
getphonenumber | 获取用户手机号事件 |
当事件回调触发时,会有一个事件对象 event,其详细属性有:
常用
)常用
)Tip: target 和 currentTarget 的区别类似 web 中target 和 currentTarget。target 是触发改事件的源,CurrentTarget 则是当前事件绑定的组件。比如点击 view 中的 button,e.target 是按钮,而 e.currentTarget 是 view。
<view bind:tap="callback">
<button>btn</button>
</view>
小程序事件传参不同于 vue
。
在Vue中可以这么写:<button @click="handleClick(123)">Button 1</button>
但小程序会将 bindtap 属性值统一当做事件名处理,相当于调用 handleClick(123) 的事件处理函数。
微信小程序:通过 data-*
属性传递参数,使用 event.currentTarget(或target).dataset 获取参数。请看示例:
<view>
<button data-id="1" data-name="button1" bindtap="handleTap">Button 1</button>
<button data-id="2" data-name="button2" bindtap="handleTap">Button 2</button>
</view>
Page({
handleTap: function(event) {
const { id, name } = event.currentTarget.dataset; // 获取多个参数
console.log('Button clicked:', id, name);
}
});
在微信小程序中,this.setData
是用于更新页面数据的主要方法。当数据改变时,视图会自动更新。this.setData 可以用来修改 Page 对象中的数据,并将数据的变化反映到界面上。
<!-- example.wxml -->
<view>
<text>计数值: {{count}}</text>
<button bindtap="incrementCount">增加</button>
<button bindtap="decrementCount">减少</button>
<input placeholder="输入内容" bindinput="handleInput"/>
<text>输入内容: {{inputValue}}</text>
</view>
// example.js
Page({
data: {
count: 0,
inputValue: ''
},
// 增加计数
incrementCount: function () {
this.setData({
count: this.data.count + 1
});
},
// 减少计数
decrementCount: function () {
this.setData({
count: this.data.count - 1
});
},
// 处理输入事件
handleInput: function (e) {
this.setData({
inputValue: e.detail.value
});
}
});
Tip:
Page({
data: {
count: 0,
inputValue: ''
},
incrementCount: function () {
this.setData({
count: this.data.count + 1
});
},
handleInput: function (e) {
this.setData({
inputValue: e.detail.value
});
}
});
对于文本框和数据的同步,小程序和vue实现原理类似。
vue中可以通过 v-model 实现双向绑定,但是 v-model 的本质
是 value 的属性以及 @input 事件
<input type="text" v-model="message" placeholder="Enter text"/>
或
<input type="text" :value="message" @input="updateMessage" placeholder="Enter text"/>
new Vue({
el: '#app',
data: {
message: ''
},
methods: {
updateMessage(event) {
this.message = event.target.value;
}
}
});
用微信小程序是这样:
<input type="text" value="{{inputValue}}" bindinput="handleInput" placeholder="Enter text"/>
Page({
data: {
inputValue: '',
errorMsg: ''
},
handleInput: function(event) {
const value = event.detail.value;
let errorMsg = '';
if (value.length < 3) {
errorMsg = 'Input must be at least 3 characters long';
}
this.setData({
inputValue: value,
errorMsg: errorMsg
});
}
});
小程序中的组件也由宿主环境提供,开发者可以基于组件搭建出漂亮的页面。小程序的组件分类有:
Tip: 微信小程序 vs 支付宝小程序常用组件对比。感觉几乎相同
功能/类别 | 微信小程序组件 | 支付宝小程序组件 | 备注 |
---|---|---|---|
视图容器 | view |
view |
基本视图容器 |
scroll-view |
scroll-view |
可滚动视图容器 | |
swiper |
swiper |
滑块视图容器 | |
movable-view |
movable-view |
可移动的视图容器 | |
cover-view |
cover-view |
覆盖在原生组件上的视图容器 | |
list |
列表视图容器 | ||
基础内容 | text |
text |
文本标签 |
icon |
icon |
图标组件 | |
rich-text |
rich-text |
富文本组件 | |
progress |
progress |
进度条 | |
表单组件 | form |
form |
表单,用于收集数据 |
input |
input |
单行输入框 | |
textarea |
textarea |
多行输入框 | |
checkbox |
checkbox |
复选框 | |
radio |
radio |
单选按钮 | |
switch |
switch |
开关选择器 | |
slider |
slider |
滑动选择器 | |
picker |
picker |
选择器 | |
picker-view |
picker-view |
嵌入页面的滚动选择器 | |
label |
label |
标签,用于表单控件的说明 | |
导航组件 | navigator |
navigator |
页面导航 |
媒体组件 | image |
image |
图片组件 |
video |
video |
视频组件 | |
audio |
audio |
音频组件 | |
camera |
camera |
相机组件 | |
live-player |
live-player |
实时音视频播放组件 | |
live-pusher |
live-pusher |
实时音视频推流组件 | |
地图组件 | map |
map |
地图组件 |
画布组件 | canvas |
canvas |
画布组件,用于绘制图形 |
开放能力 | open-data |
contact-button |
微信开放数据组件和支付宝客服按钮 |
web-view |
web-view |
嵌入网页内容 | |
ad |
ad |
广告组件 | |
official-account |
lifestyle |
微信公众号组件和支付宝生活号组件 | |
login |
button |
登录按钮(不同场景使用) | |
pay-button |
button |
支付按钮(不同场景使用) | |
无障碍访问 | aria-role |
aria-role |
无障碍角色 |
aria-label |
aria-label |
无障碍标签 |
问
:为什么不用 div ,而要创建 view?
答
:微信小程序选择使用 view 等自定义组件而不是原生 HTML 标签,如 div,主要出于以下几个原因:
问
:有了 view,为什么还得单独为了滚动创建 scroll-view?
答
:尽管 view 组件已经提供了基本的容器功能,但 scroll-view 组件作为专门的滚动容器,具有以下显著优势:
问
:就不能将 scroll-view 合并到 view?
答
:尽管将 scroll-view 的功能合并到 view 组件中在理论上是可行的,但在实践中会引入许多复杂性和技术挑战。微信小程序选择将 scroll-view 与 view 分开实现,是为了:
text
: 长按选中(selectable属性)只能使用 text,放在 view 中的不可以。
rich-tex
t:通过其nodes 属性节点,可以把HTML字符串渲染成对应的UI结构
button
:按钮组件,功能比html中的 button 按钮丰富(主色调、大按钮小按钮、警告按钮...),通过 open-type属性可以调用微信提供的各种功能(客服、转发、获取用户信息)image
:图片组件,image组件默认宽度约300px,高度约240px。mode 属性可以用来指定图片的裁剪和缩放,aspectFill类似 cover,aspectFit类似contain。其中差异需要自己品味navigator
:导航组件,类似html中的a标签,用于页面跳转小程序开发者工具也提供了创建组件的便捷方式,右键“新建 Component”
组件从引用方式分为:
局部引用
:组件只能在当前被引用的页面中使用全局引用
:组件每个小程序页面都可以使用components/
my-component/
my-component.wxml
my-component.wxss
my-component.js
my-component.json
{
"usingComponents": {
"my-component": "/components/my-component/my-component"
}
}
<view>
<my-component text="Welcome to My Component"></my-component>
</view>
// my-component.json
{
"component": true
}
components/
my-global-component/
my-global-component.wxml
my-global-component.wxss
my-global-component.js
my-global-component.json
在 app.json 中进行全局引用配置
{
"pages": [
"pages/index/index",
"pages/logs/logs"
],
"window": {
"backgroundTextStyle": "light",
"navigationBarBackgroundColor": "#fff",
"navigationBarTitleText": "WeChat",
"navigationBarTextStyle": "black"
},
"usingComponents": {
"my-global-component": "/components/my-global-component/my-global-component"
}
}
在小程序中,页面和组件在开发解构和使用方式上有许多相似之处,但他们用途和特性有所不同
特性 | 页面(Page) | 组件(Component) |
---|---|---|
功能 | 用户交互的独立视图 | 可复用的功能模块或UI元素 |
组成文件 | .wxml , .wxss , .js , .json |
.wxml , .wxss , .js (调用Component()函数、事件需要定义到 methods中), .json (需要 "component": true ) |
生命周期 | onLoad , onShow , onReady , onHide , onUnload |
created , attached , ready , moved , detached |
路由和导航 | 支持路由和导航 API | 不支持路由和导航 |
组合和嵌套 | 不能嵌套在其他页面中 | 可以嵌套在页面或其他组件中 |
复用性 | 通常独立使用 | 高,可在多个页面中引用 |
在微信小程序中,组件的样式是默认隔离
的。这意味着组件的样式不会影响到外部页面或其他组件,而外部样式也不会影响到组件内部。这种样式隔离机制有助于提高组件的独立性和可复用性
如果希望外界影响到组件,也可以通过设置 `"styleIsolation" 来修改。微信小程序的样式隔离有三种模式:
Tip:说只有class选择器会有样式隔离效果,id选择器、属性选择器、标签选择器不会受样式隔离影响
组件中的数据、方法和属性,请看示例:
Component({
properties: {
max: {
type: Number,
value: 10
}
},
data: {
name: 'pjl'
},
methods: {
handleFn() {
// true
console.log(this.data === this.properties);
// 使用 setData 修改 properties 值
this.setData({max: this.properties.max + 1})
}
}
})
Tip:说小程序中properties 属性和 data 数据用法相同,都是可读可写
微信小程序中的 observers
和 Vue.js 中的 watch 功能相似,都是用于监听数据变化并做出响应。然而,Vue.js 的 watch 提供了更多选项和更大的灵活性,适用于更复杂的监听需求。微信小程序的 observers 则较为简单和直接。
语法:
Compoment({
observers: {
'字段A, 字段B': function(字段A的新值, 字段B的新值) {
}
}
})
observers: {
'countA, countB': function(newCountA, newCountB) {
console.log(`CountA has changed to: ${newCountA}, CountB has changed to: ${newCountB}`);
this.setData({
sum: newCountA + newCountB
});
}
)
observers: {
'obj.v1, obj.v2': function(newFirstElement, newSecondElement) {
console.log(`First element has changed to: ${newFirstElement}, Second element has changed to: ${newSecondElement}`);
}
}
observers: {
'obj.**': function(newObj) {
}
微信小程序有纯数据字段
,其主要作用:
在 options 中使用 pureDataPattern
。请看示例:
Component({
// 组件的属性列表
properties: {
initialValue: {
type: Number,
value: 0
}
},
// 组件的初始数据
data: {
displayResult: 0,
__internalCache: 0 // 纯数据字段,不会被传递到视图层
},
// 定义纯数据对象的匹配模式
options: {
pureDataPattern: /^__/
},
小程序组件,生命周期可以直接定义在 Component 构造器一级参数中,也可以写在 lifetimes 字段内(推荐方式,优先级更高)
Component({
// 低优先级
error(err) {
},
lifetimes: {
error(err) {
}
}
});
有时,自定义组件行为依赖于页面状态的变化,这时就得用到组件所在页面的生命周期。比如每当触发页面的 show 声明周期时,希望重新生成一个数。
组件所在页面的生命周期有3个:
例如:
Component({
pageLifetimes: {
show() {
console.log('Component in page show');
// 页面显示时执行的逻辑
}
}
});
和 vue 中类似,没有作用域插槽。
有单个插槽和多个插槽
微信小程序中组件通信,和vue中类似,父传子用属性,子传父用事件。
Tip:微信小程序还有父组件通过 this.selectComponent() 获取组件实例(应该要少用)
子组件向父组件传递数据示例:
// 子组件
Component({
methods: {
incrementCount() {
// 触发自定义事件,传递数据
this.triggerEvent('countChange', { count: this.data.count + 1 });
}
}
});
<view>
<button bindtap="incrementCount">Increment</button>
</view>
// 父组件
<view>
<my-component bind:countChange="handleCountChange"></my-component>
</view>
Page({
handleCountChange(e) {
// e.detail获取子组件传递的数据
const newCount = e.detail.count;
}
});
vant weapp 是一套小程序UI组件库。
小程序使用npm 包的和传统网站有一些不同。比如:
微信小程序安装 vant weapp,大概步骤(详细看vant官网):
Tip:小程序比较特殊,每安装一个包都得构建才能使用。建议先删除 miniprogram_npm 这个包在构建,否则容易构建失败等问题
导航就是页面中相互跳转,浏览器中有 <a>
、location.href
vue 的单页面中有编程式导航
和命令行导航
,在微信小程序中也有编程式导航和命令行导航
先说一下传统的单页应用:
微信小程序在某种程度上与单页应用有相似的用户体验和部分技术实现,但在严格技术定义来看,它并不是单页应用
。
微信小程序采用多页面框架,每个页面独立存在。切换页面的时候就可以和原生一致,可以做到滑动的效果。
小程序和单页的相似:
小程序和单页的差异:
vue 中编程式导航和命令式导航,就像这样:
this.$router.push({ path: '/some/path', query: { key: 'value' } });
<router-link :to="{ name: 'routeName', params: { userId: 123 } }">Go to User</router-link>
微信小程序中的命令式导航主要通过页面的 WXML 文件中的 <navigator>
组件实现,类似于 HTML 的 <a>
标签。
<navigator url="/pages/somePath/somePath">Go to Some Path</navigator>
<navigator url="/pages/tabPage/tabPage" open-type="switchTab">Go to Tab Page</navigator>
微信小程序中的编程式导航通过 wx.navigateTo、wx.redirectTo、wx.switchTab 和 wx.reLaunch 等方法实现。这些方法允许开发者在 JavaScript 代码中进行页面跳转
// 保留当前页面,跳转到应用内的某个页面
wx.navigateTo({
url: '/pages/somePath/somePath'
});
// 关闭当前页面,跳转到应用内的某个页面
wx.redirectTo({
url: '/pages/somePath/somePath'
});
// 跳转到 tabBar 页面,并关闭其他所有非 tabBar 页面
wx.switchTab({
url: '/pages/tabPage/tabPage'
});
// 关闭所有页面,打开到应用内的某个页面
wx.reLaunch({
url: '/pages/somePath/somePath'
});
编程式导航:
命令式导航:
<router-link>
组件进行导航,语义化强,结构清晰,易读。<navigator>
组件进行导航,功能类似 <router-link>
,但没有 Vue 的路由命名和参数传递功能,需要通过 URL 进行导航。参数传递:
适用场景:
通过对比,可以看出 Vue Router 在单页应用中的复杂导航管理方面更强大
,而微信小程序的导航设计则更加简洁
和快速,符合小程序快速开发的需求
<router-link>
,微信小程序使用 <navigator>
。两者功能类似,都是用于声明式地定义导航结构,但 Vue Router 提供了更强大的路由命名和参数传递功能。tabBar页面
: url 必须以 / 开头;open-type表示跳转方式,必须为 switchTab。请看示例:<navigator url="/pages/page1/page1" open-type="switchTab">导航到page1</navigator>
tabBar页面
:url 必须以 / 开头;open-type表示跳转方式,必须为 navigate(可省略)。请看示例:<navigator url="/pages/page1/page1" open-type="navigate">导航到page1</navigator>
<navigator open-type="navigateBack" delta="1">返回上一页</navigator>
wx.switchTab({
url: '/pages/tabBar/home/home',
success: function(res) {
// 成功回调
},
fail: function(err) {
// 失败回调
}
});
wx.navigateTo({
url: '/pages/page1/page1',
success: function(res) {
// 成功回调
},
fail: function(err) {
// 失败回调
}
});
wx.navigateBack({
delta: 1,
success: function(res) {
// 成功回调
},
fail
<navigator url="/pages/page1/page1?name=pjl&age=18">导航到page1</navigator>
wx.navigateTo({
url: '/pages/detail/detail?itemId=123&itemName=ExampleItem'
});
新页面接收参数:
Page({
onLoad: function(options) {
// options 对象包含了传递的参数
console.log(options.itemId); // 输出: 123
console.log(options.itemName); // 输出: ExampleItem
}
});
全局数据共享有:vuex、mobx、Redux等
小程序中可以使用 mobx
管理小程序状态。大概步骤:
// store/index.js
import { observable, action } from 'mobx-miniprogram';
export const store = observable({
// 定义状态
count: 0,
// 定义计算属性
get doubleCount() {
return this.count * 2;
},
// 定义动作
increment: action(function() {
this.count += 1;
}),
decrement: action(function() {
this.count -= 1;
})
});
将 Store 注入小程序
使用 MobX 绑定页面:在页面中使用 mobx-miniprogram-bindings 库来绑定 MobX store
// pages/index/index.js
import { createStoreBindings } from 'mobx-miniprogram-bindings';
import { store } from '../../store';
Page({
// 初始化 Store Bindings
onLoad() {
this.storeBindings = createStoreBindings(this, {
store,
fields: ['count', 'doubleCount'],
actions: ['increment', 'decrement']
});
},
// 销毁 Store Bindings
onUnload() {
this.storeBindings.destroyStoreBindings();
}
});
通常建议每个页面都有自己的 Store
全局store和页面store混合使用也是一种很好的实践。
小程序中的分包
(subpackage)是指将小程序的代码分割成多个子包(subpackage),每个子包可以独立开发、测试、发布,最终合并成一个完整的小程序
分包的优点
:
分包中三种包:
分包后,小程序项目:1个主包+多个分包
小程序启动时,默认下载主包并启动主包内页面,当用户进入分包某页面时,客户端会把对应分包下载下来后再展示
假设我们有一个主包和两个分包 subpackageA 和 subpackageB。
项目目录结构如下:
├── app.js
├── app.json
├── app.wxss
├── pages
│ ├── index
│ └── logs
├── subpackageA
│ ├── pages
│ │ ├── pageA1
│ │ │ ├── pageA1.js
│ │ │ ├── pageA1.json
│ │ │ ├── pageA1.wxml
│ │ │ └── pageA1.wxss
│ │ ├── pageA2
│ │ ├── pageA2.js
│ │ ├── pageA2.json
│ │ ├── pageA2.wxml
│ │ └── pageA2.wxss
├── subpackageB
│ ├── pages
│ │ ├── pageB1
│ │ │ ├── pageB1.js
│ │ │ ├── pageB1.json
│ │ │ ├── pageB1.wxml
│ │ │ └── pageB1.wxss
│ │ ├── pageB2
│ │ ├── pageB2.js
│ │ ├── pageB2.json
│ │ ├── pageB2.wxml
│ │ └── pageB2.wxss
在 app.json 中配置分包信息配置(subPackages)如下:
// app.json
{
"pages": [
"pages/index/index",
"pages/logs/logs"
],
"subPackages": [
{
"root": "subpackageA",
"pages": [
"pages/pageA1/pageA1",
"pages/pageA2/pageA2"
]
},
{
"root": "subpackageB",
"pages": [
"pages/pageB1/pageB1",
"pages/pageB2/pageB2"
]
}
]
}
Tip:分包的体积是有一定限制的,分包体积可以在“小程序开发者工具”中查看。
独立分包是微信小程序提供的一种特殊分包形式,允许某些分包独立于主包运行。这对于需要快速启动的模块尤其有用,例如登录模块、功能独立的插件模块等。使用独立分包可以显著提高小程序的启动速度和用户体验。
独立分包的特点:
将分包配置成独立分包,只需要一个配置:independent。请看示例
// app.json
{
"pages": [
"pages/index/index",
"pages/logs/logs"
],
"subPackages": [
{
"root": "subpackageA",
"pages": [
"pages/pageA1/pageA1",
"pages/pageA2/pageA2"
],
"independent": true
}
]
}
分包预下载:是指进入小程序某页面时,框架自动下载可能需要的包。
例如进入 tabBar pageB 页面时下载 packageA。
通过 preloadRule 配置。就像这样:
{
"pages": [
"pages/pageA/index",
"pages/pageB/index"
],
"tabBar": {
"list": [
{
"pagePath": "pages/pageB/index",
"text": "PageB"
}
]
},
"subPackages": [
{
"root": "packageA/",
"pages": [
"pageC/index"
]
}
],
"preloadRule": {
"pages/pageB/index": {
// wifi、2g、3g、4g。wifi 不包含 2g。
"network": "all",
"packages": ["packageA"]
}
}
}