这一次彻底学会微信小程序开发

852 阅读16分钟

首先是个人反思,反思一下我为什么这两年写的博客屈指可数。转眼间毕业已经四年多了,依稀记得刚毕业的前两年,我一有时间就会写写博客记录学的新技术或者是工作中遇到的问题。自从跳槽之后,我很少写博客了,更多的在自己的笔记记录。这一次写博客也是源于自己输出的一次部门内部培训,把自己以前开发小程序的经验拿出来分享,趁这个机会也在博客记录一下。

1.开发工具

1.1.安装

image.png

1.2.了解编译器

image.png

  • 菜单栏

包括项目操作、文件操作、编译器设置等功能

  • 工具栏

包括模拟器编译器调试器打开收起、项目编译真机调试、代码版本管理等功能

  • 模拟器

模拟在真机上显示效果

  • 目录栏

包括文件目录管理、文件以及文件夹创建、搜索

  • 编辑区

编码区域

  • 调试器

Wxml panel:Wxml panel 用于帮助开发者开发 wxml 转化后的界面。在这里可以看到真实的页面结构以及结构对应的 wxss 属性、组件数据,同时可以通过修改对应 wxss 属性,在模拟器中实时看到修改的情况(仅为实时预览,无法保存到文件)。通过调试模块左上角的选择器,还可以快速定位页面中组件对应的 wxml 代码。

Console panel :开发者可以在此输入和调试代码;小程序的错误输出,会显示在此处;在控制台中可以输入以下命令

Sources panel: 用于显示当前项目的脚本文件,同浏览器开发不同,微信小程序框架会对脚本文件进行编译的工作,所以在 Sources panel 中开发者看到的文件是经过处理之后的脚本文件,开发者的代码都会被包裹在 define 函数中,并且对于 Page 代码,在尾部会有 require 的主动调用。

AppData panel: 用于显示当前项目运行时小程序 AppData 具体数据,实时地反映项目数据情况,可以在此处编辑数据,并及时地反馈到界面上。

Network Panel:用于观察和显示 request 和 socket 的请求情况

Performance Panel:开发者可以使用「模拟器」中的「Performance,分析小程序逻辑层的 JS 执行情况。

Memory Panel:开发者可以使用「模拟器」中的「memory」面板,获取小程序逻辑层的 JS 堆内存快照,分析内存分布情况,排查内存泄漏问题。

AppData Panel:用于显示当前项目运行时小程序 AppData 具体数据,实时地反映项目数据情况,可以在此处编辑数据,并及时地反馈到界面上。

Storage panel: 用于显示当前项目使用 wx.setStorage 或者 wx.setStorageSync 后的数据存储情况。可以直接在 Storage panel 上对数据进行删除(按 delete 键)、新增、修改

Sensor panel :有两大功能:开发者可以在这里选择模拟地理位置;开发可以在这里模拟移动设备表现,用于调试重力感应 API

Mock Panel:用于观察和显示 request 和 socket 的请求情况

Audits Panel:体验评分,需要用户自行操作页面触发交互得到体验评分

Vulnerbility Panel:接口安全扫描

2.起步

2.1.小程序注册

地址:mp.weixin.qq.com/

image.png

image.png

image.png

微信公众平台可以对小程序进行管理,包括小程序信息管理(基础信息、版本信息、版本发布、开发人员管理)、数统计(访问量、收入信息)、开发文档查看

2.2.开发文档

image.png

3.上手

3.1.工程创建

image.png 如果没有创建小程序,可以点击测试号随机生成测试AppID,也可以使用小程序生成的AppID

  • APP ID image.png
3.2.页面构成及配置
3.2.1.目录结构
assets //静态文件 
components // 组件 
miniprogram_npm // npm依赖 
pages // 页面文件夹 
    index // index页面 
        index.wxml 
        index.wxss 
        index.js 
        index.json 
index.d.ts 
utils // 工具方法 
page.js // 小程序逻辑 
page.json // 小程序公共配置 
app.wxss // 小程序公共样式表
3.2.2.配置文件
  • 全局配置page.json
{ 
    "entryPagePath": "pages/index/index",// 小程序默认启动首页 
    "pages": [// 用于描述当前小程序所有页面路径,新建的页面会自动添加到数组中 
        "pages/index/index", 
        "pages/logs/logs" 
     ], 
    "tabBar": {// 底部 tab 栏的表现 
        "selectedColor": "#35c8fb", 
        "color": "#7f8389", 
        "list": [// 最少 2 个、最多 5 个 tab 
            { 
                "pagePath": "pages/index/index", 
                "text": "发现音乐", 
                "iconPath": "images/music/cm2_btm_icn_discovery.png", 
                "selectedIconPath": "images/music/cm2_btm_icn_discovery_prs.png" 
            }, 
            { 
                "pagePath": "pages/me/index", 
                "text": "我的音乐", 
                "iconPath": "images/music/cm2_btm_icn_music.png", 
                "selectedIconPath": "images/music/cm2_btm_icn_music_prs.png" 
             } 
         ]} 
        "window": {// 定义小程序所有页面的顶部背景颜色,文字颜色定义等 
            "backgroundTextStyle": "light", 
            "navigationBarBackgroundColor": "#fff", 
            "navigationBarTitleText": "Weixin", 
            "navigationBarTextStyle": "black" 
        }, 
        "style": "v2", 
        "subpackages": [// 分包加载 
            { 
                "root": "pages/indexes/",// 分包根目录 
                "name": "index",// 分包别名,分包预下载时可以使用 
                "pages": ["indexes", "base/index", "custom/index"], // 分包页面路径,相对于分包根目录 
                "independent": false// 分包是否是独立分包打包原则 
            }],
        "usingComponents": {// 全局自定义组件配置 
            "t-demo": "./components/demo-block/index", 
            "t-button": "tdesign-miniprogram/button/button", 
            "t-icon": "tdesign-miniprogram/icon/icon" 
        }, 
        "sitemapLocation": "sitemap.json",// sitemap.json 文件用来配置小程序及其页面是否允许被微信索引。 
        ... 
}

详细配置可以参考小程序全局配置

  • 页面配置,以index为例-index.json
{ 
    "navigationBarBackgroundColor": "我的云盘",// 导航栏背景颜色,如 #000000 
    "navigationBarTextStyle": "black",// 导航栏标题、状态栏颜色,仅支持 black/white 
    "navigationBarTitleText": "我的云盘",// 导航栏标题文字内容 
    "backgroundColor": "#ffffff",// 窗口的背景色 
    "enablePullDownRefresh":true,// 是否开启当前页面下拉刷新。 
    "onReachBottomDistance": 50,// 页面上拉触底事件触发时距页面底部距离,单位为px。 
    "navigationStyle": "default",// 导航栏样式,仅支持以下值:default 默认样式custom 自定义导航栏,只保留右上角胶囊按钮。 
    "homeButton": false,// 在非首页、非页面栈最底层页面或非tabbar内页面中的导航栏展示home键 
    "usingComponents": {// 页面自定义组件配置 
        "t-toast": "tdesign-miniprogram/toast/toast", 
        "list": "./list", 
        "status": "./status", 
        "grid": "./grid", 
        "align": "./align" 
    }
}
3.3.语法
3.3.1.WXML语法
  • 内置组件
名称功能
view视图容器
text文本
scroll-view可滚动视图区域
swiper滑块视图容器
swiper-item仅可放置在swiper组件中,宽高自动设置为100%
button按钮
input输入框
checkbox多选框
checkbox-group多选框组
radio单选框
radio-group单选框组
picker从底部弹起的滚动选择器
image图片。支持 JPG、PNG、SVG、WEBP、GIF 等格式
video视频
canvas画布
map地图
  • 数据绑定

WXML 中的动态数据均来自对应 Page 的 data。

<view class="test1 {{flag ? 'test2' : ''}}" style="width: 100px; {{height: flag ? '20px' : '30px'}}"> 
    {{ message }} 
</view> 

Page({ 
    data: { 
        message: 'Hello MINA!' 
    } 
})
  • 列表渲染
<view wx:for="{{list}}" wx:for-item="item" wx:for-index="idx" wx:key="id"> 
    {{item.id}}: {{item.name}}----{{idx}} 
</view> 

Page({ 
    data: { 
        array: [{ message: 'foo', }, { message: 'bar' }] 
    } 
})

使用 wx:for-item 可以指定数组当前元素的变量名 使用 wx:for-index 可以指定数组当前下标的变量名

  • 条件渲染
<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> 

<text hidden="{{visible}}">12321312</text>

wx:if与hidden类似于v-if与v-show

  • 模板template

WXML提供模板(template),可以在模板中定义代码片段,然后在不同的地方调用; 调用模板的时候,起作用的只有wxml和wxss文件,模板中的JS文件是不起作用的。模板中的逻辑都要在调用的文件中处理。应用场景:骨架屏

template的声明

<template name="listTemp"> 
    <text class="info">这是一个msg模板</text> 
    <view class="list-template"> 
        <text>{{ item.id }}</text> ------------- <text bind:tap="handleTextTap" data-id="{{item.name}}">
        {{ item.name }}
        </text> 
    </view> 
</template>

template的使用

<!-- 引入template --> 
<import src="../../template/list/list"></import> 

<view> 
    <text>使用template</text> 
    <template is="listTemp" wx:for="{{list}}" wx:for-item="item" wx:for-index="idx" wx:key="id" data="{{ item }}" /> 
</view>

template中的逻辑需要写在引用文件中,比如home页面引入listTemp模板,listTemp模板中绑定的handleTextTap就需要写在home.js中,home页面也需要引入listTemp的wxss文件,才能使样式生效。

如何解决逻辑代码重复的问题?

// template.js 
const template = { 
    handleTextTap() { 
        console.log('template 模版 click') 
    } 
} 
    
export default template; // 使用template的地方引入 

import template from '../../template/template' 
Page({ handleTextTap: template.handleTextTap })

template与component的区别:template模板:轻量级,主要是展示,没有配置文件(.json)和业务逻辑文件(.js),所以template模板中的变量引用和业务逻辑事件都需要在【引用模板的页面js】文件中进行定义;

Component组件:有自己的业务逻辑,由4个文件构成,与page类似,但是js文件和json文件与页面不同。

  • 事件绑定
类型触发条件
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 设备,重按时会触发
3.3.2.逻辑语法
  • Page
  1. 直接修改 this.data 而不调用 this.setData 是无法改变页面的状态的,还会造成数据不一致。
  2. 单次设置的数据不能超过1024kB,请尽量避免一次设置过多的数据。
  3. 请不要把 data 中任何一项的 value 设为 undefined ,否则这一项将不被设置并可能遗留一些潜在问题。
  • Component

父子组件生命周期

// 加载 子组件created-->父组件created-->父组件attached-->子组件attached-->子组件ready-->父组件ready 
// 销毁 子组件detached-->父组件detached

获取组件实例

// 父组件 
<view class="parent"> 
    <button bind:tap="handleClick">调用子组件方法</button> 
    <child-component id="child-component" dataList="{{dataList}}"></child-component> 
</view>

Page({ 
    data: {}, 
    handleClick: function () { 
        // 获取子组件实例 
        const child = this.selectComponent('#child-component') 
        child && child.handleTest() 
    } 
})


// 子组件 
methods: { 
    handleTest: function () { 
        console.log("提供给父组件调用的方法") 
    }, 
},

可在父组件里调用this.selectComponent ,获取子组件的实例对象。默认情况下,小程序与插件之间、不同插件之间的组件将无法通过selectComponent 得到组件实例(将返回 null)。如果想让一个组件在上述条件下依然能被 selectComponent 返回,可以自定义其返回结果.

自定义的组件实例获取结果 若需要自定义 selectComponent 返回的数据,可使用内置 behavior: wx://component-export 从基础库版本 2.2.3 开始提供支持。使用该 behavior 时,自定义组件中的 export 定义段将用于指定组件被 selectComponent 调用时的返回值。

// 自定义组件 my-component 内部 
Component({ 
    behaviors: ['wx://component-export'], 
    export() { 
        return { myField: 'myValue' } 
    } 
})

插槽

<!-- 普通插槽 --> 
<view class="wrapper"> 
    <view>这里是组件的内部节点</view> 
    <slot></slot> 
</view> 

<!-- 具名插槽 --> 
<view class="wrapper"> 
    <slot name="before"></slot> 
    <view>这里是组件的内部细节</view> 
    <slot name="after"></slot> 
</view> 
<!-- 配置支持多个插槽 --> 
Component({ 
    options: { multipleSlots: true // 在组件定义时的选项中启用多slot支持 }, 
    properties: { /* ... */ }, 
    methods: { /* ... */ } 
})
  • behaviors
// behavior.js 
export default Behavior({ 
    data: { 
        behaviorData: { test: '1\`2312312' } 
    }, 
    ready: function () { 
        this.behaviorMethod() 
    }, 
    methods: { 
        behaviorMethod: function () { 
            console.log("behaviorMethod") 
        } 
    } 
})

behaviors 是用于组件间代码共享的特性,类似于vue中的 “mixins”。

同名字段的覆盖和组合规则

组件和它引用的 behavior 中可以包含同名的字段,对这些字段的处理方法如下:

  • 如果有同名的属性 (properties) 或方法 (methods):
    1. 若组件本身有这个属性或方法,则组件的属性或方法会覆盖 behavior 
    2. 若组件本身无这个属性或方法,则在组件的 behaviors 字段中定义靠后的 behavior 的属性或方法会覆盖靠前的同名属性或方法;
    3. 在 2 的基础上,若存在嵌套引用 behavior 的情况,则规则为:引用者 behavior 覆盖 被引用的 behavior 中的同名属性或方法。
  • 如果有同名的数据字段 (data):
    • 若同名的数据字段都是对象类型,会进行对象合并;
    • 其余情况会进行数据覆盖,覆盖规则为: 引用者 behavior > 被引用的 behavior 、 靠后的 behavior > 靠前的 behavior。(优先级高的覆盖优先级低的,最大的为优先级最高)
  • 生命周期函数和 observers 不会相互覆盖,而是在对应触发时机被逐个调用:
    • 对于不同的生命周期函数之间,遵循组件生命周期函数的执行顺序;
    • 对于同种生命周期函数和同字段 observers ,遵循如下规则:
      • behavior 优先于组件执行;
      • 被引用的 behavior 优先于 引用者 behavior 执行;
      • 靠前的 behavior 优先于 靠后的 behavior 执行;
    • 如果同一个 behavior 被一个组件多次引用,它定义的生命周期函数和 observers 不会重复执行。
3.3.3.WXS

WXS(WeiXin Script)是内联在 WXML 中的脚本段。通过 WXS 可以在模版中内联少量处理脚本,丰富模板的数据预处理能力。另外, WXS 还可以用来编写简单的 WXS 事件响应函数

模块

每一个 .wxs 文件和  标签都是一个单独的模块。 每个模块都有自己独立的作用域。即在一个模块里面定义的变量与函数,默认为私有的,对其他模块不可见。 一个模块要想对外暴露其内部的私有变量与函数,只能通过 module.exports 实现。

wxs语法 .wxs 文件不支持 ES6,只能使用蹩脚的 ES5 写法

3.3.4.API
  • 请求

wx.request({ url: 'example.php', //仅为示例,并非真实的接口地址 data: { x: '', y: '' }, header: { 'content-type': 'application/json' // 默认值 }, success (res) { console.log(res.data) } })

  • 扫码
// 允许从相机和相册扫码 
wx.scanCode({ 
    success (res) { 
        console.log(res) 
    } 
})
  • 支付

发起微信支付。调用前需在小程序微信公众平台 -功能-微信支付入口申请接入微信支付。

wx.requestPayment({ 
    timeStamp: '', 
    nonceStr: '', 
    package: '', 
    signType: 'MD5', 
    paySign: '', 
    success (res) { }, 
    fail (res) { } 
})
  • 登录

调用接口获取登录凭证(code)。通过凭证进而换取用户登录态信息,包括用户在当前小程序的唯一标识(openid)、微信开放平台账号下的唯一标识(unionid,若当前小程序已绑定到微信开放平台账号)及本次登录的会话密钥(session_key)等。用户数据的加解密通讯需要依赖会话密钥完成。

wx.login({ 
    success (res) { 
        if (res.code) { 
            //发起网络请求 
            wx.request({ 
                url: '<https://example.com/onLogin>', 
                data: { code: res.code } 
            }) 
        } else { 
            console.log('登录失败!' + res.errMsg) } 
        } 
})
  • 分享

onShareAppMessage(Object object)监听用户点击页面内转发按钮(button 组件 open-type="share")或右上角菜单“转发”按钮的行为,并自定义转发内容。此事件处理函数需要 return 一个 Object,用于自定义转发内容

<button open-type="share" type="primary"> 分享给好友 </button> 
/*
 *用户点击右上角分享或者点击分享按钮 
 */ 
onShareAppMessage: function (res) { 
// 如果该参数存在,则以 resolve 结果为准,如果三秒内不 resolve,分享会使用上面传入的默认参数 
    // const promise = new Promise(resolve => { 
        // setTimeout(() => { 
            // resolve({ 
                // title: '测试标题1231' 
            // }) 
        // }, 2000) 
    // }) 
    return { 
        title: '测试标题', 
        path: '/page/user?id=123', 
        imageUrl:'../../assets/images/login/login-bg.jpg', 
        // promise 
    } 
},

4.拓展

4.1.使用npm安装依赖

补充:为什么需要构建?官方阐述的原理:

首先 node_modules 目录不会参与编译、上传和打包中,所以小程序想要使用 npm 包必须走一遍 构建 npm 的过程,在每一份 miniprogramRoot 内开发者声明的 package.json 的最外层的 node_modules 的同级目录下会生成一个 miniprogram_npm 目录,里面会存放构建打包后的 npm 包,也就是小程序真正使用的 npm 包。

  • npm初始化
npm init -y
  • 安装vant
npm i @vant/weapp -S --production
  • 将 app.json 中的 “style”: “v2” 去除,小程序的新版基础组件强行加上了许多样式,难以覆盖,不关闭将造成部分组件样式混乱。
  • 修改project.config.json/setting
"packNpmManually": true, 
"packNpmRelationList": 
    [{ 
        "packageJsonPath": "./package.json", 
        "miniprogramNpmDistDir": "./" 
    }]
  • 打开微信开发者工具,点击 工具 -> 构建 npm,并勾选 使用 npm 模块 选项,可见官方文档 快速上手 的 步骤四。新版的微信开发者工具中,详情 -> 本地设置中没有【使用 npm 模块】选项,则不用理会, 如果有则需要勾选。这时候就会生成nimiprogram_npm文件夹
  • 只需要在 app.json 或 你需要使用 vant 的页面中的 json 文件进行组件的注册即可使用了这里涉及到注册组件的两种方式
"usingComponents": { 
    "my-list": "/components/my-list/my-list", 
    "vant-button": "@vant/weapp/button/index" 
}, // 使用 

<view> 
    <vant-button type="primary">测试vant</vant-button>
</view>
4.2.预编译语言(less、scss)支持
  1. 在vscode上先安装Easy-Scss插件
  2. 打开微信开发者工具,找到 设置>扩展设置>打开编辑器扩展面板>从已解包扩展文件夹安装

image.png 3. 找到微信开发者工具 编辑>编辑器扩展目录,找到easy-sass,打开修改Easy-Scss package.json配置

image.png

  1. 重启微信开发者工具
4.3.域名配置

image.png

每个微信小程序需要事先设置通讯域名,小程序只可以跟指定的域名进行网络通信。

  • 域名只支持 https wx.requestwx.uploadFilewx.downloadFile wss wx.connectSocket
  • 域名不能使用 IP 地址(小程序的局域网 
  • 对于 https 
  • 对于 wss 
  • 域名必须经过 ICP 备案;
  • 出于安全考虑,api.weixin.qq.com 不能被配置为服务器域名,相关API也不能在小程序内调用。  getAccessToken  access_token
  • 不支持配置父域名,使用子域名。

image.png

注意:小程序很多配置都是限制次数的,每个月只有为数不多的机会修改配置,所以每次修改都需慎重!!!

4.4.分包

按需注入和用时注入

4.5.1.分包的目的

某些情况下,开发者需要将小程序划分成不同的子包,在构建时打包成不同的分包,用户在使用时按需进行加载。

在构建小程序分包项目时,构建会输出一个或多个分包。每个使用分包小程序必定含有一个主包。所谓的主包,即放置默认启动页面/TabBar 页面,以及一些所有分包都需用到公共资源/JS 脚本;而分包则是根据开发者的配置进行划分。

在小程序启动时,默认会下载主包并启动主包内页面,当用户进入分包内某个页面时,客户端会把对应分包下载下来,下载完成后再进行展示。

目前小程序分包大小有以下限制:

  • 整个小程序所有分包大小不超过 20M(开通虚拟支付后的小游戏不超过30M)
  • 单个分包/主包大小不能超过 2M

对小程序进行分包,可以优化小程序首次启动的下载时间,以及在多团队共同开发时可以更好的解耦协作。

4.5.2.如何实现分包
"subpackages": [{ 
    "root": "package-login", 
    "pages": [ "pages/login/login" ] 
    }, 
    { "root": "package-search", "name": "search", "pages": [ "pages/search/search" ] } 
]

打包原则

  • 声明 subpackages 后,将按 subpackages 配置路径进行打包,subpackages 配置路径外的目录将被打包到主包中
  • 主包也可以有自己的 pages,即最外层的 pages 字段。
  • subpackage 的根目录不能是另外一个 subpackage 内的子目录
  • tabBar 页面必须在主包内

引用原则

  • packageA 无法 require packageB JS 文件,但可以 require 主包、packageA 内的 JS 文件;使用 分包异步化 时不受此条限制 
  • packageA 无法 import packageB 的 template,但可以 require 主包、packageA 内的 template
  • packageA 无法使用 packageB 的资源,但可以使用主包、packageA 内的资源