Skip to main content

小程序开发

从vue到小程序开发

教程:

1 数据绑定&事件绑定

数据

<view>{{text}}</view>                   // 绑定值
<image src="{{imgSrc}}" /> // 绑定属性
<view>{{no > 0 ? '大于': '小于'}}</view> // 三元表达式
<view>{{random * 10}}</view> // 算术表达式

事件

名称描述
tapclick
input输入
change状态改变
<button bindtap="handleClick"}>点击</button>

Page({
handleClick(){
console.log('handleClick')
}
})

事件传参

<button bindtap="handleClick"} data-info="{{2}}">点击</button>

Page({
handleClick(event){
console.log('[params]:', event.target.dataset.info);
//=>> 2
}
})

文本框更新示例

<input value="{{msg}}" bindinput="handleInput"}>点击</button>

Page({
data: {
msg: '',
},
handleInput(event){
console.log('[value]:', event.detail.value);
this.setData({
msg: event.detail.value
})
}
})

问:this.setData会更新所有data对象的值吗?还是只更新msg的值?

2 条件渲染&列表渲染

条件

<block hidden="{{true}}">我将被隐藏,但会创建dom</block>
<block wx:if="{{false}}">我将被隐藏,且不会创建dom</block>

列表

<view wx:for="{{items}}"} wx:key="{{item.id}}">
{{index}}-{{item}} // 默认索引变量为index,item为值
</view>

Page({
data: {
items: [1, 2, 3]
}
})
  • wx:for-index 可以改变默认索引变量名
  • wx-for-item 可以改变默认值变量名

3 wxss

image-20221010214200492

4 配置

image-20221010214708058

window可以配置导航区、背景区

5 页面导航|路由

组件

<navigator url="/home" open-type="navigate">首页</navigator>
<navigator open-type="navigateBack" delta="{{1}}">后退一层</navigator>

逻辑

wx.navigateTo({
url: '',
success: () => void 0,
fail: () => void 0,
complete: () => void 0,
})
// 后退
wx.navigateBack({
delta: 1,
})

// 重定向
wx.redirect({
url: '',
})

获取当前路由

const pages = getCurrentPages();

const currentPage = pages[pages.length - 1];
const url = `/${currentPage.route}`;

https://developers.weixin.qq.com/miniprogram/dev/reference/api/getCurrentPages.html

6 下拉刷新和上拉触底

两步:1.设置中开启 2.声明周期函数

7 WXS

但有computed,所以wxs可不用

WXML中无法调用js函数,而wxs就是解决该问题的,但wxs是类js语言,与js环境隔离,一般用做 过滤器

内联wxs

// app.wxml

<view>{{m1.toUpper(username)}}</view>
//=>> ZHANGSAN

<wxs module="m1">
module.exports.toUpper = function(str){
return str.toUpper();
}
</wxs>

Page({
data: {
username: 'zhangsan'
}
});

外联wxs

// m1.wxs
module.exports.toUpper = function(str){
return str.toUpper();
}

// app.wxml
<view>{{m1.toUpper(username)}}</view>
<wxs src="./m1.wxs" module="m1" />

8 自定义组件

// header.json
{
"component": true,
"usingComponents" : {}
}

// header.js
Component({
data: {},
methods: {
handleClick(){}, // 组件处理函数必须放到methods中
_getName(){}, // 建议自定义方法以_开头
},
})

使用组件:1.在.json中注册组件(全局|局部) 2.使用

// home.json
{
"usingComponents" : {
"header": "/components/header/header"
}
}

// home.wxml
<header />

组件样式

  • 组件间样式是隔离的

  • app.wxss全局样式的类选择器对组件无效,但id选择器、属性选择器、标签选择器有效

    所以建议只使用类选择器

外部样式类

枚举外部可以使用的类样式

Props

// header.js
Component({
properties: {
max: Number, // Number|String|Boolean
max: {
type: Number,
value: 0, // 缺省值
}
},
methods: {
handleClick() {
console.log("[最大值]:", this.properties.max);
}
}
})

<header max="{{100}}" />

监听Watch

// header.js
Component({
data: {
firstName: '',
lastName: '',
fullName: '',
},
observers: {
'firstName, lastName': function(newValueA, newValueB) {
this.setData({
fullName: newValueA + newValueB
})
}

// 还支持对象属性监听
'obj.a, obj.b': function(newValueA, newValueB) {
this.setData({
fullName: newValueA + newValueB
})
}
},
methods: {
handleClick(){
this.setData({
firstName: 'san',
lastName: 'zhang'
})
//=>> fullName:zhangsan
}
}
})

computed

[1][computed](https://github.com/wechat-miniprogram/computed)

组件中设置页面声明周期监听

// header.js
Component({
pageLifetimes:{
show(){} // show|hide|resize
}
})

插槽

// nav.wxml
<view>
<slot name="left"></slot>
<slot></slot> // 默认插槽
<slot name="right"></slot>
</view>

// app.wxml
<nav>
<view slot="left">左边插槽</view>
<view slot="right">右边插槽</view>
</nav>

父子组件通讯

  • Props
  • 事件
  • 拿到实例(不推荐)

事件

// nav.js
Component({
created() {
this.triggerEvent('dismiss', {value: '123'})
}
})

// app.wxml
<nav bind:dismiss="handleDismiss" />

Page({
handleDismiss(e) {
console.log('[params]:', e.detail.value)
//=>> '123'
}
})

Behaviors

与vue的mixins类似

image-20221010230053627

每个behaviors的属性、数据、声明周期、方法与组件混合在一起

9 npm包

小程序npm包限制:

  • 不能有node内置库
  • 不能有浏览器内置对象
  • 不能有c++插件

10 Vant定制主题

在app.wxss中定义css变量

page {
--button-danger-background-color: #ff00ff;
}

vant定制主题

11 状态管理

image-20221010231107912

安装npm包后,删除mini...program目录后,再构建npm

mobx-miniprogram-bindings

12 结合TS

12.1 标注事件类型

handleChange(event: WechatMiniprogram.BaseEvent) {
...
},

13 引入字体&字体图标

整体步骤:1.选中资源转base64 2.引入项目中

阿里图标库图标

[2][微信小程序引入iconfont单色图标实例(Font class方式)](https://blog.csdn.net/gyueh/article/details/119786184)

[1][微信小程序 如何使用阿里图标库图标](https://www.jianshu.com/p/fc050c81178d?u_atoken=521de186-37a6-413e-8b52-3c5484d02d84&u_asession=01FEluoIwKboo8VvOZGze1kj5ma77WFQv5I33ykFng2hLRyHTEEOMz-_L-92qDx1DfX0KNBwm7Lovlpxjd_P_q4JsKWYrT3W_NKPr8w6oU7K-H6W7LH7doRG9z0ivWRS0sp0TkEaMSMNP4umocQhrjq2BkFo3NEHBv0PZUm6pbxQU&u_asig=05aKhHCfLIMI7ffgmH0YvZE2USqv1Sn4JZGG0IMNNnO-cb3LPeT7YYBhuhMvNf26Q8MOGrkXrTH-yLKxBHS-iWwWqmsSfYSjxDWxii7Mtm5HIS4i82BxCueL_nvlfPKHtXpvY552sSaMXOfZ1btMCdZn8ZGmsHZUZjeBB4XGdPQOb9JS7q8ZD7Xtz2Ly-b0kmuyAKRFSVJkkdwVUnyHAIJzeAwINxmWA7Yb6URKHGHhvPdRlQcX0Zs_4cSAQ8uJjJxG3pf5vm7LeeSuJ_913VgsO3h9VXwMyh6PgyDIVSG1W_FhbXBHMoVG3KRnCFfQnf7IA_x6eH4B6QCtHBvxE9JXyWednXnsvWhPpqE55CbVudqZ_foCzUujbraz9KO74SImWspDxyAEEo4kbsryBKb9Q&u_aref=U3fYqLPy5UtZTrjHMlIJh9YUPgw%3D)

[3] 微信小程序 引入字体图标 字体 使用base64解决字体请求问题

14 小程序表单验证

we-validator可以结合vant组件进行表单验证+提示

[1][we-validator](https://github.com/ChanceYu/we-validator)

15 一些实用组件和实用功能实现方式

15.0 第三方实用组件和功能集合

15.1 原生小程序实现tooltips提示信息

<view class="my-tooltip-root" bindtap="clickTips">
<view class="tips {{ isNeedTips ? 'visible' : 'hidden' }} {{ placement }}" style="width: {{ tooltipWidth + 'rpx' }}; left: {{-(tooltipWidth / 2) + 'rpx'}}">
<slot name="tips" />
</view>
<slot />
</view>
@import '../../styles/index.scss';

.my-tooltip-root {
position: relative;

.tips {
position: absolute;
background-color: var(--bg1);
top: 22px;
display: flex;
padding: 6px 10px;
border-radius: 8px;
-webkit-border-radius: 8px;
-moz-border-radius: 8px;
-ms-border-radius: 8px;
-o-border-radius: 8px;
}
.tips::after {
content: '';
position: absolute;
top: -4px;
left: 50%;
display: block;
width: 0;
height: 0;
border-radius: 4px;
border: 8px solid transparent;
border-right-color: var(--bg1);
border-bottom-color: var(--bg1);
transform: translateX(-50%) scaleX(0.8) rotate(225deg);
-webkit-transform: translateX(-50%) scaleX(0.8) rotate(225deg);
-moz-transform: translateX(-50%) scaleX(0.8) rotate(225deg);
-ms-transform: translateX(-50%) scaleX(0.8) rotate(225deg);
-o-transform: translateX(-50%) scaleX(0.8) rotate(225deg);
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
-ms-border-radius: 4px;
-o-border-radius: 4px;
}
.visible {
display: block;
}
.hidden {
display: none;
}
}
const screenWidth = wx.getSystemInfoSync().windowWidth // 获取设备信息(宽度)

// page
Component({
options: {
addGlobalClass: true,
multipleSlots: true,
},
externalClasses: ['my-class'],
properties: {
placement: {
type: String,
value: 'top', // right bottom left
},
maxWidth: {
type: Number,
value: 354,
},
tipsWidth: {
type: Number,
value: 200,
},
duration: {
type: Number,
value: 2000,
},
},
data: {
tooltipWidth: 100,
isNeedTips: false,
},
lifetimes: {
attached() {
setTimeout(() => {
this.getTooltipWidth()
this.judgePosition()
}, 0)
},
},
methods: {
getTooltipWidth() {
const contentRPXWidth = this.data.tipsWidth // 自定义的一个tips宽度
const conetntPXWidth = (contentRPXWidth / 750) * screenWidth // 根据屏幕适配的宽度
this.createSelectorQuery()
.select('.my-tooltip-root')
.boundingClientRect((res) => {
const { width } = res

const { maxWidth } = this.data

let tooltipWidth = 0
// 获取自定义元素的宽度 与 屏幕适配宽度做比较
// 最终参考使用一个合适的宽度作为 tips 的宽度
if (conetntPXWidth < width) {
tooltipWidth = contentRPXWidth
} else {
tooltipWidth = contentRPXWidth < maxWidth ? contentRPXWidth : maxWidth
}

this.setData({
tooltipWidth,
})
})
.exec()
},
// 判断tips是否在某个位置上有足够的空间放置
judgePosition() {
// 同理通过 wx.createSelectorQuery().in(this).select('').boundingClientRect去获取自定义元素的位置元素信息以及tips的位置元素信息去做处理
// 在这里只列举了其中一种情况的例子进行判断来设置该tips的实际放置位置
// 以下情况可能出现的场景是:自定义元素放置的位置过于偏左 导致上下的tips没办法放置 因此设置tips到右边 别的情况同理
const tipWidth = this.data.tooltipWidth
const targetLeft = 0
const targetWidth = 50

if (this.data.placement === 'top' || this.data.placement === 'bottom') {
const targetLeftCenter = targetLeft + targetWidth / 2 // 点击元素的中间位置坐标
if (targetLeftCenter < tipWidth / 2) {
this.setData({
placement: 'right',
})
}
}
},
clickTips() {
const { duration } = this.data
this.setData({ isNeedTips: true }) // 显示tips

let timer: number | null = setTimeout(() => {
this.setData({
isNeedTips: false, // 在duration毫秒后隐藏tips
})
wx.nextTick(() => {
if (timer) {
clearTimeout(timer) // 清理定时器
timer = null
}
})
}, duration)

this.triggerEvent('click') // 抛出一个点击事件
},
},
})

[1][原生小程序实现tooltips提示信息](https://juejin.cn/post/7012301682977538079)

15.2 小程序自定义相机

<!-- scan.wxml -->
<view class="root-scan scan-view">
<view class="scan-border">
<camera wx:if="{{showCamera}}" class="{{!showCamera ?'scan-camera__camera-hidden': 'scan-camera'}}" mode="scanCode" bind:error="handleCameraError" bind:scancode="handleScanCode" frame-size="large">
<view class="scan-animation row" animation="{{animation}}">
<view class="scan-animation_view" />
</view>
</camera>
<view class="wrap-content__cover">
<slot name="bottom" />
</view>
</view>
<!-- 弹出层 -->
<van-popup round position="{{position}}" show="{{ show }}" close-on-click-overlay="{{false}}" closeable bind:close="handleClose">
<slot name="popup" />
</van-popup>
</view>

自定义相机优缺点:

  • 优点
    • 可以在相机上自定义样式
  • 缺点
    • 卡顿,性能以及流畅度没有 wx.scanCode 好

[1][wx.scanCode(Object object)](https://developers.weixin.qq.com/miniprogram/dev/api/device/scan/wx.scanCode.html)

15.3 微信小程序蓝牙对接热敏打印机

[1][微信小程序蓝牙对接热敏打印机](https://blog.csdn.net/qq_37970097/article/details/119148707)

[2][miniprogram-bluetoothprinter](https://github.com/benioZhang/miniprogram-bluetoothprinter)

[3][微信小程序 蓝牙热敏打印 标签打印机](https://gitee.com/techer_li/wechat_print)

15.4 微信小程序隐藏scroll-view的滚动条

::-webkit-scrollbar {
display:none;
width:0;
height:0;
color:transparent;
}

[1] https://blog.csdn.net/m0_46156566/article/details/120696699

[2][微信小程序如何隐藏scroll-view滚动条](https://developers.weixin.qq.com/community/develop/doc/00006473cf08f8c29da606b2d56c00)

15.5 如何让 scroll-view 填充剩余空间

TODO

[1][scroll-view](https://developers.weixin.qq.com/miniprogram/dev/component/scroll-view.html)

15.6 微信小程序上传图片

import { UPLOAD_URL } from '../env/index'
import { HttpResponseBaseSync } from './api'

type Options = {
type: 'sign' | 'perfect' | 'fail' | 'other'
success?: (data: HttpResponseBaseSync<UploadRes[]>) => void
fail?: (err: any) => void
}

type UploadRes = {
destination: string
}

function upload(file: any, { success, fail, type }: Options) {
const token = wx.getStorageSync('token')

const url = (UPLOAD_URL as any)[type]

// 当设置 mutiple 为 true 时, file 为数组格式,否则为对象格式
wx.uploadFile({
url,
filePath: file.url,
header: {
Authorization: `Bearer ${token}`,
},
name: 'file',
success(res) {
const data: HttpResponseBaseSync<UploadRes[]> = JSON.parse(res.data)
success && success(data)
},
fail,
})
}

export { upload }

[1][wx.uploadFile(Object object)](https://developers.weixin.qq.com/miniprogram/dev/api/network/upload/wx.uploadFile.html)

16 格式化

16.1 Prettier格式化微信小程序

几点注意:

  • 用vscode时,要注意代码是prettier格式化还是其他工具在格式化,否则会导致 prettier 命令行和vscode自动保存代码格式不一致的问题
  • prettier无法格式wxml、wxss,需要配置,但wxml有自己的格式化插件 XML Language Server

prettier配置示例:

module.exports = {
singleQuote: true,
tabWidth: 2,
useTabs: false,
semi: false,
trailingComma: 'all',
printWidth: 120,
endOfLine: 'auto',

// 识别wxss wxml
overrides: [
{
files: ['*.wxss', '*.acss'],
options: {
parser: 'css',
},
},
{
files: ['*.wxml', '*.axml'],
options: {
parser: 'html',
},
},
{
files: ['*.wxs', '*.sjs'],
options: {
parser: 'babel',
},
},
],
}

eslint配置示例:

module.exports = {
root: true,
env: {
es6: true,
browser: true,
node: true,
},
extends: ['eslint:recommended'],
parser: '@babel/eslint-parser',
parserOptions: {
ecmaVersion: 2018,
sourceType: 'module',
requireConfigFile: false,
ecmaFeatures: {
// lambda表达式
arrowFunctions: true,
// 解构赋值
destructuring: true,
// class
classes: true,
},
},
globals: {
wx: true,
App: true,
Page: true,
getCurrentPages: true,
getApp: true,
Component: true,
requirePlugin: true,
requireMiniProgram: true,
},
rules: {
'no-unused-vars': 'warn',
'no-useless-escape': 'warn',
},
}

命令:

{
"lint": "eslint \"miniprogram/{pages,components,templates,styles,utils,test,env}/**/*.{js,ts}\" --fix",
"format": "prettier --config ./.prettierrc.js --write \"miniprogram/{pages,components,templates,styles,utils,test,env}/**/*.{js,ts,wxss}\"",
"format:check": "prettier --config ./.prettierrc.js --check \"miniprogram/{pages,components,templates,styles,utils,test,env}/**/*.{js,ts,wxss}\""
}

[1][微信小程序集成 ESLint + Preitter 插件](https://juejin.cn/post/7051440695139106847)