目录
- 前言
- 初始化项目
- 文件介绍
- 基本介绍
- JS
- WXML
- WXSS
- 常见组件
- 基础组件
- 视图容器
- match-media
- movable-area/view
- page-container
- scroll-view
- swiper
- 表单组件
- 自定义组件
- 模板语法
- 数据绑定
- 单向数据绑定
- 双向数据绑定
- 列表渲染
- 条件渲染
- 模板引用
- 事件系统
- 事件类型
- 事件绑定
- 阻止冒泡
- 互斥事件
- 事件参数
- 页面切换
- 生命周期
- 数据通信
- 全局通信
- localStorage(本地存储)
- globData(小程序全局存储)
- 父 => 子
- 父 => 父
- 子 => 父
- triggerEvent
- selectComponent
- 子 => 子
- 网络请求
- 第三方组件
- Vant/weapp 1.11
- Tailwind
- 拓展功能
- 提问改善
前言
当前教程的示例代码已经在 Github 开源,大家开源下载简易的示例对照学习,https://github.com/bosombaby/wechat_tutorial
在进行一个完整的小程序项目开始时,建议阅读一遍小程序开发的设计指南。
微信小程序设计指南 | 微信开放文档
微信小程序的编码规范链接如下:
https://juejin.cn/post/7047299287264264206
初始化项目
简单介绍一下初始化项目文件、文件夹的用法和配置
- 打开微信开发者工具,通过目录打开本地文件的空文件夹(项目名称识别为目录名称)
- AppID 获取流程参考第一周的学习内容
- 后端服务学习阶段选择不使用云服务
- 模板选择我们工作中使用的 JS 基础模板
文件介绍
- 微信开发者工具的使用教程可跳转到第一章查看
- 初始化不同文件的含义对比图参见上图
- 鼠标右键可以添加页面和组件,同时需要确保在 app.json 配置中被顺利引入
基本介绍
JS
和 js的用法一致,注意的是响应式机制和 vue 前端框架有所不同。
1.在这里定义数据
data: {msg:'Hello WeChat'
}2.获取数据
this.data.msg3.更新数据
this.setData({msg:'更新数据'
})
WXML
https://developers.weixin.qq.com/miniprogram/dev/framework/view/wxml/
和原生的 html 标签的逻辑一致,但是有部分标签不可用,小程序也内置了相关的标签组件。
HTML通用标签:<div>, <span>, <a>, <img>, <p>, <h1> 到 <h6>, <strong>, <em>, <ul>, <ol>, <li>
WXML专用标签:<view>, <text>, <image>, <button>, <navigator>, <swiper>, <scroll-view>, <picker>, <form>
WXSS
https://developers.weixin.qq.com/miniprogram/dev/framework/view/wxss.html
rpx是微信小程序中css的尺寸单位,可以根据屏幕宽度进行自适配。
规定屏幕宽度为750px,譬如iphone6,屏幕宽度为375px,共有750个物理像素,则1rpx = 0.5px,后续可以扩展讲解一下 em rem px rpx 逻辑像素 物理像素 等原理,目前只需要了解在标准开发下 1rpx = 0.5px 就行。
其余样式的用法和原生 css 差不多,只是只有一个标签名和类名定义变量
常见组件
基础组件
最常用的就是 view text,相当于html 的 div span 标签
视图容器
match-media
https://developers.weixin.qq.com/miniprogram/dev/component/match-media.html
相当于前端的媒体查询,页面宽高在一定的范围内才会触发,注意这里是 px 不是 rpx
示例代码如下:
<match-media min-width="300" max-width="600"><view>当页面宽度在 300 ~ 500 px 之间时展示这里</view>
</match-media>
<match-media min-width="1200"><view>当页面宽度在 300 ~ 500 px 之间时展示这里</view>
</match-media>
movable-area/view
https://developers.weixin.qq.com/miniprogram/dev/component/movable-area.html
可以移动拖拽的区域或者视图组件,通常用在验证码、需要交互拖拽的场景中,比如购物车的左滑移动删除
示例代码:
<movable-area class="movable-area"><movable-view x="{{x}}" y="{{y}}" direction="all" class="movable-view" bindtap="handleAreaMove"></movable-view>
</movable-area>this.setData({x: 30,y: 30,
});
page-container
主要是底部弹出的对话框,效果类似于 popup 弹出层。
https://developers.weixin.qq.com/miniprogram/dev/component/page-container.html
- tip: 当前页面最多只有 1 个容器,若已存在容器的情况下,无法增加新的容器
- tip: wx.navigateBack 无法在页面栈顶调用,此时没有上一级页面
示例代码:
<page-container show="{{isShowPage}}" position="bottom" bind:afterleave="afterLeave"><view class="poup-content">这里是弹窗内容,哈哈哈哈哈</view>
</page-container>
scroll-view
可以滚动的视图区域
https://developers.weixin.qq.com/miniprogram/dev/component/scroll-view.html
示例代码:
<scroll-view scroll-y="true" class="scroll-view-list" scroll-into-view="{{scrollToView}}" scroll-top="{{scrollTop}}" scroll-with-animation="true"><view id="demo1" class="scroll-view-item" style="background-color: cadetblue;">1</view><view id="demo2" class="scroll-view-item" style="background-color:chartreuse;">2</view><view id="demo3" class="scroll-view-item" style="background-color:crimson;">3</view>
</scroll-view><view class="flex-bc"><button type="primary" bindtap="scrollToTop">顶部</button><button type="primary" bindtap="scrollToTarget">特定元素</button>
</view>
swiper
小程序相关的轮播图教程,可以使用微信小程序官方提供的** api(推荐)**或者其他的第三方组件库
<swiper class="scroll-view-list" indicator-dots="true" autoplay="true" interval="500" circular="true"><swiper-item id="demo1" class="scroll-view-item" style="background-color: cadetblue;">1</swiper-item><swiper-item id="demo2" class="scroll-view-item" style="background-color:chartreuse;">2</swiper-item><swiper-item id="demo3" class="scroll-view-item" style="background-color:crimson;">3</swiper-item>
</swiper>
表单组件
https://developers.weixin.qq.com/miniprogram/dev/component/button.html
和原生的 html 标签差不多,用到的话可以去查
自定义组件
https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/
https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/wxml-wxss.html
- 自定义组件的流程:定义 => 注册 => 引入
- 比普通的 Page 的生命周期不同,多了一个 properties 传参
Slot | 微信开放文档
插槽 slot 用法如下:
1. 单个插槽
<my-component parentName="常见组件学习"><view style="color:cornflowerblue">我是单个插槽</view>
</my-component>2. 多个插槽
<view slot="before">组件slot name="before"</view>
<view slot="after">组件slot name="after"</view>
模板语法
WXML是框架设计的一套标签语言,结合常见组件、事件系统 可以构建出页面的结构。
数据绑定
单向数据绑定
概述:data=>页面
<view>{{message}}</view>data: {message: "你好,数据绑定",
}
和 VUE 的插值表达式一致,允许你将JavaScript表达式的值嵌入到模板字符串中,并且小程序会自动更新DOM以反映该表达式值的变化。
双向数据绑定
概述:data<=>页面
简易双向绑定 | 微信开放文档
<input model:value="{{num}}" class="input-num" />
<view>{{num}}</view>
在 WXML 中,普通的属性的绑定是单向的,一般都监听数值的变化然后通过方法操作。但是目前微信小程序双向绑定只支持单一字段、不支持对象,可以在组件中自定义双向绑定。
列表渲染
<text class="second-level-title">2.列表渲染</text>
<view wx:for="{{arrayList}}" wx:key="id" wx:for-index="idx" wx:for-item="myItem">{{idx}} -- {{myItem.content}}
</view>arrayList: [{ id: "1-1", content: "测试数据1" },{ id: "1-2", content: "测试数据2" },{ id: "1-3", content: "测试数据3" },{ id: "1-4", content: "测试数据4" },{ id: "1-5", content: "测试数据5" },
]
https://blog.csdn.net/qq_53673551/article/details/127167029
WXML 的列表渲染语法需要提供为唯一值 key ,它是虚拟 DOM 比较的标识
条件渲染
在框架中,使用 wx:if=“” 来判断是否需要渲染该代码块,如果要一次性判断多个组件标签,可以使用 仅仅是一个包装元素,不会在页面中做任何渲染,只接受控制属性。
<text class="second-level-title">3.条件渲染</text>
<input model:value="{{score}}" class="input-num" />
<view wx:if="{{score >= 90}}"> 优秀 </view>
<view wx:elif="{{ score >= 70}}"> 良好 </view>
<view wx:elif="{{ score >= 60}}"> 及格 </view>
<view wx:else> 不及格 </view>
wx:if 和 hidden 的区别就是 v-if 和 v-show 的区别
模板引用
模板 template 相当于简易的自定义组件,感觉有点像 VUE 里面的插槽(slot),示例如下:
<text class="second-level-title">4.模板引用</text>
<template name="odd"><view>{{item}} is 奇数 </view>
</template>
<template name="even"><view>{{item}} is 偶数 </view>
</template><block wx:for="{{[1, 2, 3, 4, 5]}}"><template is="{{item % 2 == 0 ? 'even' : 'odd'}}" data="{{item}}" />
</block>
跨文件使用的导入方式如下:
- 单个导入 template ,一层关系依赖,不可用递归引入
<template name="header"><text style="color: red;"> 头部区域模板 {{score}}</text>
</template><import src="./header.wxml" />
<template is="header" data="{{score}}" />
- 导入除了 外的整个代码,相当于拷贝,后续可以专注于 wxml 组件的拆分(局部组件)
<include src="./header.wxml" />
<div>这里是内容区域</div>
<include src="./footer.wxml" />
事件系统
事件类型
事件分为冒泡事件和非冒泡事件:
类型 | 触发条件 | 版本 |
---|---|---|
touchstart | 手指触摸动作开始 | |
touchmove | 手指触摸后移动 | |
touchcancel | 手指触摸动作被打断,如来电提醒,弹窗 | |
touchend | 手指触摸动作结束 | |
tap | 手指触摸后马上离开 | |
longpress | 手指触摸后,超过350ms再离开,如果指定了事件回调函数并触发了这个事件,tap事件将不被触发 | 1.5.0 |
longtap | 手指触摸后,超过350ms再离开(推荐使用longpress事件代替) | |
transitionend | 会在 WXSS transition 或 wx.createAnimation 动画结束后触发 | |
animationstart | 会在一个 WXSS animation 动画开始时触发 | |
animationiteration | 会在一个 WXSS animation 一次迭代结束时触发 | |
animationend | 会在一个 WXSS animation 动画完成时触发 | |
touchforcechange | 在支持 3D Touch 的 iPhone 设备,重按时会触发 | 1.9.90 |
注:除上表之外的其他组件自定义事件如无特殊声明都是非冒泡事件,如 form 的submit事件,input** 的input事件,scroll-view 的scroll事件,(详见各个**组件)
事件绑定
1. bind:[事件名称]="[事件绑定函数名称]"
2. 事件绑定函数名称 是一个字符串,指向在 .ts/.js 文件的 Page 函数中定义的处理函数名。
<button bind:tap="openDialog">点击弹窗</button>
阻止冒泡
除 bind 外,也可以用 catch 来绑定事件。与 bind 不同, catch 会阻止事件向上冒泡。
<view id="outer" bind:tap="handleTap1">outer view<view id="middle" catch:tap="handleTap2">middle view<view id="inner" catch:tap="handleTap3">inner view</view></view>
</view>
互斥事件
除了 bind 和 catch 外,还可以使用 mut-bind 来绑定互斥事件
所有 mut-bind 绑定都是互斥的,只会触发其中一个绑定函数
不会影响bind 和 catch 绑定的事件
事件参数
和 VUE 直接向方法函数传参不同外,他的事件系统需要自定义属性去获取,示例如下:
target 触发事件的源组件,currentTarget 事件绑定的当前组件,比如冒泡事件举例:
<view id="outer" data-name="outer" data-msg="hello" bind:tap="handleTap4">outer view<view id="middle">middle view<view id="inner">inner view</view></view>
</view>handleTap4(event) {const name = event.currentTarget.dataset.name;const msg = event.currentTarget.dataset.msg;console.log("outer", event);console.log(name, msg);
}
页面切换
https://juejin.cn/post/7079045451948916744
https://blog.csdn.net/weixin_47124112/article/details/126466315
https://bosombaby.blog.csdn.net/article/details/122012979
- 获取当前小程序应用的页面栈使用 getCurrentPages ,后续页面栈(后进先出,也就是数组)过多可以通过这个条件进行清除。
- 使用 navigateTo 跳转,页面最上方会出现返回上一级的按钮,可以便捷调用 navigateBack
在微信小程序中的实践结果如下图所示:
- 动态记录和管理用户在小程序中页面访问顺序和跳转关系的栈结构,支持导航、参数传递、生命周期管理,并具有容量限制。
- 入栈即使路由路径相同也会添加,本质是由微信小程序定义的 **wxExparserNodeId**唯一
- wx.navigateTo**(添加新的页面栈,新加入的页面从头渲染,之前的页面 onHide 隐藏,为了使用 navigateBack 走缓存 onShow )**
- wx.redirectTo(原理和 navigateTo一致,但是这样就没法走 navigateBack 缓存返回)
生命周期
https://juejin.cn/post/7151402790823133215
https://developers.weixin.qq.com/miniprogram/dev/framework/app-service/page-life-cycle.html
跟vue、react框架一样,微信小程序框架也存在生命周期,实质也是一堆会在特定时期执行的函数
小程序中,生命周期主要分成了三部分:
- 应用的生命周期
- 页面的生命周期
- 组件的生命周期
一般页面渲染流程(navigateTo 方法,即页面栈只增加的清空下,A B 代表不同页面):
- onLaunch(App) => onShow(App)
- onLoad(A)=> onShow(A)=> onReady(A)=> onHide(A)
- onLoad(B)=> onShow(B)=> onReady(B)=> onUnload(B)
- onShow(A)
一般组件的渲染流程(navigateTo 方法,即页面栈只增加的清空下,A B C 代表不同页面和组件):
- created(C)=> attached(C)
- onLoad(A)=> onShow(A)=> ready(C)=> onReady(A)=> onUnload(A)=> detached(C)
子组件先完成渲染,父组件先完成卸载
数据通信
https://juejin.cn/post/6995875058224726030
这里父一般指 Page(页面),子一般指组件(Component),通信有很多的技术实现方案,但是自己只列举出最常用的几种方式:
全局通信
localStorage(本地存储)
setStorageSync和setStorage的区别-CSDN博客
wx.setStorageSync("index-data", "哈哈哈哈");const storageData = wx.getStorageSync("index-data");
if (storageData) {console.log("当前存储的数据", storageData);
}
优点:简单操作,易理解
缺点:调用到 storage,有可能设置失败;且设置后是持久缓存,可能污染原逻辑,应及时删除
globData(小程序全局存储)
const app = getApp();本质是 app.js 看作一个大的对象,通过 getApp() 获取实例并调用
app.调用具体的方法、变量
优点:简单操作,易理解;直接操作globalData对象,相比于storage执行效率更高
缺点:设置后是小程序生命周期内都可访问,可能污染原逻辑,应及时删除
父 => 子
父到子传值一般采用 property 的方式,具体实现代码如下:
<ty-child-a parentName="{{name}}" />properties: {parentName: "父组件传值",
}
父 => 父
EventChannel | 微信开放文档
通过 wx.navigateTo 中的events和eventChannel实现页面间的通信,因为 navigateTo 会加入新页面到页面栈,所以从头开始渲染,能够触及到 onLoad 的 options 方法。
// A 页面
wx.navigateTo({url: link + "?a=1&b=2",events: {calculateResult(data) {console.log("计算的结果为", data);},},success: function (res) {res.eventChannel.emit("multiplication", { result: 100 });},
});// B 页面
const a = Number(options.a);const b = Number(options.b);const eventChannel = this.getOpenerEventChannel();if (Object.keys(eventChannel).length) {eventChannel.emit("calculateResult", { result: a + b });eventChannel.on("multiplication", function (data) {console.log("相乘之后的结果", data);});}// 计算的结果为 {result: 3}
// 相乘之后的结果 {result: 100}
- 上面代码主要利用 事件频道进行出发操作,B页面触发A页面的事件,进行传递参数
- B 页面也可以监听自己的事件,然后等待 A 页面触发
- 这种方式有一个坏处就是单独刷新页面会导致** eventChannel 为空,所以相关的事件可能会出问题。而同驿/优采**的做法就只是传递参数,这里需要主要传递的参数都会变成 **字符串 **类型。
子 => 父
triggerEvent
<my-component parentName="常见组件学习" bind:send-msg="sendMsg" />sendMsg(data) {console.log("来自子组件的数据", data);
}this.triggerEvent("send-msg", { id: 111, content: "这里是子组件的内容" });
selectComponent
// 引入组件的时候需要定义好 id 名称
<my-component id="my-component" parentName="常见组件学习" />const child = this.selectComponent("#my-component");
console.log("子组件实例", child);
- 这种方式直接让父组件获取到子组件的所有数据和方法,会让父子组件耦合在一起,代码很容易出现bug,一般不推荐这种方式,除非数据交互非常频繁的场景下
子 => 子
这种情况一般很少出现,两个子组件相互通信的概率很少,可以利用全局数据通信或者上述的方法进行通信,使用方法一致
网络请求
// 设置请求的统一配置
const defaultOptions = {method: "GET", // 默认请求方法data: {}, // 请求参数header: {}, // 请求头complete: function () {}, // 完成回调
};function request(url, options) {const newOptions = { ...defaultOptions, ...urloptions };newOptions.header = {"Content-Type": "application/json",Authorization: "Bearer your-token-here", // 示例,根据需要设置...newOptions.header,};return new Promise((resolve, reject) => {wx.request({url: url,method: newOptions.method,data: newOptions.data,header: newOptions.header,success: function (res) {// 统一处理响应数据if (res.statusCode === 200) {resolve(res.data);} else {reject(err);}resolve(res.data);},fail: function (err) {// 统一处理请求失败reject(err);},complete: function () {// 请求完成的统一处理},});});
}function get(url, data) {return request(url, {method: "GET",data,});
}function post(url, data) {return request(url, {method: "POST",data,});
}export { get, post };
微信小程序有自己的一套请求机制,使用 wx.request ,但是实际场景使用的时候需要利用 Promise 异步优化一下,这里大家可以先了解,后续进行项目开发的时候根据应用场景详细讲解一下。
第三方组件
Vant/weapp 1.11
https://juejin.cn/post/7229891190900277285
Tailwind
https://weapp-tw.icebreaker.top/
引入成本过高,暂时没必要
拓展功能
后续的分包内存优化、导航栏配置、打包上线流程留到下次课程项目实战去讲解。
提问改善
Q1:xxxxx?