TOC

小程序开发

    原生小程序开发最主要包含三个技术,第一个为WXML,它类似HTML,WXML是小程序的文档语言,内部有各种各样的元素,这些元素在WXML中称之为组件,甚至有的组件和原生HTML的元素是一摸一样的,比如text、button、input等;
    第二个为WXSS,和CSS大体上差不多,只不过内部做了很多的优化,比如某些选择器在WXSS中不支持,同时,也对某些技术进行了增强,但是基本上都是一致的;
    第三个为JavaScript+WXS,在小程序内我们可以直接使用原生JavaScript语言,同时在这个基础上,还增加了WXS语言,它本质也是JavaScript代码,但是它对JavaScript进行了进一步的封装;

目录结构

    当我们使用微信开发者工具创建出一个项目之后,默认会生成一个基本的小程序的项目结构,如下示例;
[cce@doorta ~]# tree /usr/local/Project/wechat 
├── app.js
├── app.json
├── app.wxss
├── pages
│   ├── index
│   │   ├── index.js
│   │   ├── index.json
│   │   ├── index.wxml
│   │   └── index.wxss
│   └── logs
│       ├── logs.js
│       ├── logs.json
│       ├── logs.wxml
│       └── logs.wxss
├── project.config.json
├── project.private.config.json
├── sitemap.json
└── utils
    └── util.js
pages
    pages目录主要是用来存放页面的一个目录,该目录下面每一个子目录都是一个页面,每个页面下面总共有四类文件,即后缀为wxml、wxss、js和json;
    后缀为wxml为小程序内容结构文件,类似html的超文本语言,后缀为wxss为样式文件,类似css,后缀为js为逻辑文件,后缀为json为页面配置文件;
utils
    该目录非常简单,其主要的作用就是用来存放封装工具模块的一个目录,该目录下面,我们可以存放实现特定功能的功能函数以模块的方式放在utils目录下;
other
    除了上述两个目录之外,还有非常多的文件,这些文件都在项目根目录下,它们的主要作用就是为了对整个项目进行设置的文件,如下;
app.js:小程序逻辑文件,如创建App对象,设置App的生命周期等;
app.json:App的全局配置文件;
app.wxss:App的全局样式文件;
project.config.json:项目配置文件;
project.private.config.json:和project.config.json一样,主要用来配置当前小程序项目,但是该配置的优先级更高;
sitemap.json:用于配置小程序及其页面是否允许被微信索引;

小程序配置

    在默认情况下,我们使用微信开发工具创建一个小程序框架,会有很多的配置文件,如app.js、app.json、project.config.json和project.private.config.json;
    此外,小程序的很多开发需求是被归档在配置文件当中的,也就是说,在小程序当中想要开发一个页面,不一定需要我们自己去手动开发这个页面,我们可以直接通过配置,将页面直接创建出来,这样非常有利于我们的高效开发;
项目配置文件
    在小程序中,project.config.json作为整个小程序项目的配置文件,该文件的主要作用就是声明当前项目的一些配置信息,比如项目名称、描述、版本、依赖的基础库版本甚至AppID等信息,具体的配置信息可查看微信小程序开发文档中的工具栏的设置项;
    其实,我们也很少去直接修改project.config.json中的配置,一般来讲,我们都会去修改project.private.config.json中的配置,它们两者的作用是一样的,但是project.private.config.json的优先级较高,只不过为了更加方便多人开发,所以微信在后面加入了project.private.config.json的配置,在project.private.config.json配置文件中,我们可以加入一些个人的设置,然后将这个配置文件加入.gitignore文件,进行忽略提交,从而解决版本管理的冲突文件;
站点映射文件
    在一个小程序框架中,有一个sitemap.json的配置文件,微信开放了小程序内的内容搜索功能,所以开发者可以通过sitemap.json配置文件,或者管理后台页面收录开关来配置小程序的页面是否允许微信索引,当开发者允许微信索引时,微信会通过爬虫的形式,为小程序的页面内容建立索引,当用户的搜索词条触发该索引时,小程序的页面将可能展示在搜索结果中,有点类似SEO优化;
    所以,如果我们希望将小程序的内容能够被微信的搜索框搜索到,我们可以直接在sitemap.json配置文件中进行相应的配置,默认情况下,所有的页面都可以被搜索,具体的配置信息,请查看微信官方文档;
全局配置文件
    全局配置文件就是项目根目录下面的app.json文件,它主要用来配置框架的一些配置信息,比如路由列表、页面窗口、tab栏等,这些配置项非常多,具体的请查看微信官方文档;
    pages配置为一个列表,列表内每一个元素都是一个页面路径,小程序中所有的页面都必须在pages中进行注册,才可以能被使用;
    window配置主要的作用就是用来配置全局的默认窗口展示,内部有非常多的属性,比如导航栏的颜色、导航栏标题的颜色、导航栏标题内容等等;
    tabBar配置,主要是用来配置底部的tab栏的展示,直接通过配置的方式来定义底部的tabbar;
页面配置文件
    项目根目录下的app.json配置文件,主要是用来规定整个项目的配置文件,那么如果我们希望对某个页面进行一些单独的配置,我们就需要用到页面配置文件,在每个页面目录下,都有一个以json结尾的一个文件,该文件就可以用来配置指定页面的一些配置信息,具体的请查看官方文档;

小程序开发初探

    对于小程序模版语言,其实和Vue的模版语言,很相像,小程序内也有插值语法,同样的它也是使用Mustache插值语法来实现的,此外,对于小程序来讲,它也有专属自己的循环、条件判断语法,在这一块的某些地方也和Vue相似,比如每次循环出来元素都建议加上一个key的属性,用来优化动态渲染,如下示例;
// pages/home/home.js
Page({
  data: {
    text: "Mustache插值语法",
    names: ["cce", "cfj", "csw"]
  }
})

<!--pages/home/home.wxml-->
<view style="margin-left: 10px;">
    <text>{{ text }}</text>
    <view wx:for="{{ names }}" wx:key="*this">
        {{item}}
    </view>
</view>

    同样的,在小程序内,也支持事件处理,在元素上添加一个bindtap的属性来指定点击事件发生时需要调用的事件处理函数,比如我们通过一个按钮,来对一个响应式数据进行动态变化,但是,我们需要知道的是,默认情况下,数据并非响应式,如果我们希望将一个数据变成响应式,需要调用this.setData的函数来实现,该函数接受一个参数,该参数为一个对象,在这个对象内,可以指定对数据的操作方法
// pages/home/home.js
Page({
  data: {
    counter: 0
  },
  add() {
    this.setData({
      counter: this.data.counter + 1
    })
  }
})

<!--pages/home/home.wxml-->
<view style="margin-left: 10px;">
    <view>
        <text>{{ counter }}</text>
    </view>
    <button size="mini" type="primary" bindtap="add">按钮</button>
</view>

MVVM架构

    小程序的整体开发思想和Vue的开发思想是非常相似的,只不过是在语法上有一些微小的差异,但是它们的开发思想都是一样的,所以在掌握Vue之后,再来开发小程序,会发现,很多地方都是差不多的,因此,软件开发最重要的还是掌握其开发思想;
    小程序也是采用MVVM的架构,和Vue是一样的,在小程序当中也是有自己的View视图,另外,它也包含自己的逻辑层,即Model,然后View视图层和Model逻辑层就是通过小程序的底层框架,ViewModel层进行结合的,ViewModel层将View视图层和Model层进行了结合;
    在ViewModel层,有两个主要的作用,第一个作用,就是将Model层的数据绑定到View层,第二个作用就是监听View的变化,一旦View层数据发生变化,能够直接将该变化反应到Model层,从而实现数据双向绑定;

双线程模型

    小程序是运行在微信客户端之上的,所以其实它的基于微信的,当我们编写的各种小程序代码文件,如wxml文件、wxss文件或js文件,最终都是交给微信来解析的,所以微信除了基本的聊天、语音、视频等功能之外,它还可以充当一个浏览器的作用;
    当我们在微信中打开一个小程序之后,它会将这个小程序的wxml文件、wxss文件或js文件缓存到本地,然后进行渲染,那么微信为了去渲染这些文件,需要用到一个WebView控件,微信首先会将wxml文件、wxss文件或js文件编译成WebView能够识别的元素,从而用户就能看到对应的页面;
    但是因为JS逻辑中如果存在网络请求,或者类似setTimeout这种阻塞性技术,那么WebView控件就可能会阻塞住整个渲染过程,从而导致界面卡顿;
    那么因为这种问题,所以小程序就采用了双线程模型,第一个线程主要是用来做渲染的,主要是处理DOM树创建、CSS解析、样式计算等等,第二个线程专门用来执行JS代码逻辑,那么当JS逻辑执行完成之后,会通过将结果,通过微信客户端,来进行中转,交给第一个WebView线程来进行渲染,所以微信就采用了双线程模型解决了页面卡顿的问题;

App实例

    对于小程序来讲,当用户打开微信当那一刻微信本身首先会注册一个小程序的App,这个App就是根据小程序框架内有一个app.js的文件来注册的,该文件内必须存在一个App函数,在该函数内可以绑定对应的生命周期函数,然后作为开发者,我们可以根据不同的生命周期函数来完成不同的功能,比如判断小程序的进入场景,是通过分享进入的还是通过搜索进入的等等;
    甚至在正式进入小程序之前,还可以加入用户登陆的逻辑或网络请求的一系列的操作,那么由于App实例在全局只有一个,所以App实例其实是一个单例对象,因此我们也可以将一些共享的数据放在这个APP函数内,然后在别的地方通过getApp()函数,来拿到这个App实例,继而取出对应的数据;
    那么在这个App实例内,一共有七个生命周期函数,此外,作为开发者,我们还可以加入一些自定义的一些数据,如下;
生命周期函数
类型
必填
描述
onLaunch
function
监听小程序初始化,只触发一次;
onShow
function
监听小程序启动或从后台切换到前台;
onHide
function
监听小程序从前台切换后台;
onError
function
监听小程序发生脚本错误或API调用报错时触发,也可以使用wx.onError绑定监听;
onPageNotFound
function
小程序要打开的页面不存在时触发,也可以使用wx.onPageNotFound绑定监听;
onUnhandledRejection
function
小程序有未处理的Promise拒绝时触发,也可以使用wx.onUnhandledRejection绑定监听;
onThemeChange
function
系统切换主题时触发(黑天/白天),也可以使用wx.onThemeChange绑定监听;
其他
any
开发者可以添加任意的函数或数据变量到Object参数中,可以直接使用this可以访问;
onLaunch生命周期
    onLaunch生命周期函数为初始化生命周期函数,即onLaunch钩子函数,该钩子函数会在用户首次进入小程序时触发,所以,它只会触发一次,同时,该函数默认接受一个参数,小程序在回调该钩子函数时,会传入一个对象,该对象内包含,访问路径、参数、场景等信息,如下示例;
// app.js
App({
  onLaunch(options) {
    console.log("监听小程序初始化,参数为:", options)
  }
})
# 首次进入小程序之后的输出信息为
监听小程序初始化,参数为:
{path: "pages/home/home", query: {…}, scene: 1001, shareTicket: undefined, referrerInfo: {…}, …}
onShow生命周期
    onShow生命周期函数,和onLaunch生命周期函数类似,但是onLaunch生命周期函数只会执行一次,而onShow生命周期函数会执行多次,在进入小程序时,会调用一次,当微信从后台切换到前台也会调用一次,同样的,该生命周期函数也接受一个参数,该参数和onLaunch生命周期函数的值是一样的,如下;
// app.js
App({
  onShow(options) {
    console.log("监听小程序初始化,参数为:", options)
  }
})
# 首次进入小程序之后的输出信息为
监听小程序初始化,参数为:
{path: "pages/home/home", query: {…}, scene: 1001, shareTicket: undefined, referrerInfo: {…}, …}
# 从后台切换到前台的输出信息为:
监听小程序初始化,参数为:
{path: "pages/home/home", query: {…}, scene: 1011, shareTicket: undefined, referrerInfo: {…}}
场景值
    对于onLaunch、onShow及onHide三个回调函数的参数对象内默认有一个属性,即scene,该属性为一个串数字,该数字为场景值,即从何处进入微信,不同的场景下打开小程序都有不同的场景值,非常之多,具体的请查看官方文档;

小程序场景值

全局数据共享
    在App函数内,除了定义生命周期函数,我们还可以将一些一成不变的数据放在App函数内,但是这里需要关注一个关键词,即一成不变,因为它不是响应式的,我们无法动态去修改它;
    那么将这些自定义数据放在App函数内之后,我们可以在别的地方使用getApp这个全局函数对象来直接获取,该函数返回一个对象,这个对象就是我们在定义App时传入的这个对象,如下示例;
// app.js
App({
  userInfo: {
    username: "cce"
  }
})
// pages/home/home.js
Page({
  onLoad() {
    const app = getApp()
    console.log(app.userInfo) 
  }
})
# 输出结果
{username: "cce"}
    如果我们希望将这个数据在WXML中使用,那么我们就需要将这个数据放在页面的Page函数传入对象的data对象内,此时,我们可以使用sedData来像data对象来动态的新增数据,如下示例;
// pages/home/home.js
Page({
  onLoad() {
    const app = getApp()
    let username = app.userInfo.username
    this.setData({
      username // 对象简写形式
    })
  },
  data: {}
})

<!--pages/home/home.wxml-->
<view style="margin-left: 10px;">
    <text>{{ username }}</text>
</view>

本地数据存储
    onLanch生命周期函数,在实际应用场景下,一般用来进行用户登陆,也就是说,如果当用户首次进入小程序一般都会在onLaunch生命周期函数中进行登陆,因为一旦在onLaunch里面登陆成功之后,在访问其他页面时,都是一个登陆的状态,且onLanch生命周期函数也非常符合登陆的逻辑,每次进入小程序只会调用一次;
    但是这里有一个问题,就是如何实现状态的保存问题,当用户第一次进入小程序进行了登陆,虽然在登陆之后,我们将这个数据保存在了全局对象当中,但是这个数据始终都是存在内存当中,所以每次关闭小程序,这个数据就丢失了;
    所以为了解决这个问题,我们其实也可以将这个用户登陆的状态保存在本地存储当中,微信小程序也是支持类似浏览器的LocalStorage的,那么我们将这个状态保存在本地存储中后,就可以实现无需二次登陆的功能;
    那么想要使用微信小程序的本地存储,就需要用到小程序的一个API,即使wx.setStorageSync和wx.getStorageSync,前者存,后者取,如下示例;
// app.js
App({
  userInfo: {},
  onLaunch() {
    if (!wx.getStorageSync('token') && !wx.getStorageSync('username')) { // 如果本地存储没数据,那么通过网络请求拿到切同时存储到全局共享对象和本地存储中
      const token = "asdsadsafdsdasdd";
      const username = "cce";
      wx.setStorageSync('token', token);
      wx.setStorageSync('username', username);
      this.userInfo.token = token;
      this.userInfo.username = username;
    } else { // 如果全局共享对象中没有数据,但是本地存储有数据,那么就将本地存储的数据加载到全局共享对象当中
      this.userInfo.token = wx.getStorageSync('token');
      this.userInfo.username = wx.getStorageSync('username');
    }
  }
})

Page实例

    在小程序内,每一个页面都会有一个js结尾的文件,该文件内一般都有一个Page函数,该Page函数就是向App实例来注册一个页面,那么在注册页面的时候,和App实例一样,只不过App实例它针对的是全局性的,而Page实例只针对于当前页面;
    那么在使用Page函数注册页面时,和App实例一样,我们可以绑定一些初始化数据,这些数据可以在本页面进行数据共享,同时,我们还可以绑定一些生命周期,当当前页面在某种特殊的场景下,小程序框架会自动调用这些生命周期函数,从而实现不同的功能,此外,我们还可以在Page实例内注册一些事件处理函数,当当前页面发生某些事件的时候,可以绑定对应事件处理函数;
data对象
    在Page实例当中,有一个data对象,它和Vue的createApp实例内的data对象很相似,它们都是用来初始化当前页面数据的,换而言之,这个里面的数据可以直接被当前页面的WXML文件使用Mustache插值语法所引用,同时,在data对象内的数据,我们也可以直接在Page实例当中的事件处理函数、生命周期函数直接使用this.data直接引用,如下示例;
// pages/page/page.js
Page({
  data: {
    name: "cce"
  }
})
<!--pages/page/page.wxml-->
<text>{{ name }}</text>

普通事件处理函数
    在Page实例当中,我们也可以定义一些普通的事件处理函数,那么当我们将这些事件处理函数,和页面当中的事件进行绑定时,会自动调用当前页面的Page实例内对应的事件处理函数,这一点,同样的,和Vue非常相像,如下;
<!--pages/page/page.wxml-->
<button size="mini" type="primary" bindtap="clickEvent">触发事件</button>
// pages/page/page.js
Page({
  clickEvent(event) {
    console.log(event)
  }
})

内置事件处理函数
    在小程序中,每一个Page实例内,都有众多的内置事件处理函数,当某些特定的事件发生之后,小程序会默认的去调用对应的事件处理函数,比如常见的页面刷新事件、上拉触底事件、下拉触底事件、页面滚动事件等等;
    如下,这就是Page实例当中内置的所有事件处理函数,此外,这些事件处理函数其实有很多参数可以定义,具体的使用方法还请查看官方文档,不在此做过多的搬运;
事件处理函数
类型
描述
onPullDownRefresh
function
监听用户下拉刷新事件;
onReachBottom
function
监听用户上拉触底事件;
onPageScroll
function
监听用户滑动页面事件;
onAddToFavorites
function
监听用户点击右上角菜单”收藏“按钮的行为,并自定义收藏内容;
onShareAppMessage
function
监听用户点击页面内转发按钮(button 组件 open-type=”share”)或右上角菜单“转发”按钮的行为,并自定义转发内容;
onShareTimeline
function
监听右上角菜单“分享到朋友圈”按钮的行为,并自定义分享内容;
onResize
function
监听小程序屏幕旋转时触发的事件;
onTabItemTap
function
点击tab时触发的事件;
onSaveExitState
function
每当小程序可能被销毁之前,页面回调函数 onSaveExitState 会被调用,可以进行退出状态的保存;
onPullDownRefresh
    onPullDownRefresh为监听用户刷新页面事件,使用该事件,需要在app.json的window选项中或页面配置中开启enablePullDownRefresh,可以通过wx.startPullDownRefresh触发下拉刷新,调用后触发下拉刷新动画,效果与用户手动下拉刷新一致,当处理完数据刷新后,wx.stopPullDownRefresh可以停止当前页面的下拉刷新;
Page({
  onPullDownRefresh() {
    console.log("用户刷新页面")
  }
})
生命周期钩子函数
    在Page实例当中,小程序也提供了针对页面的一些生命周期函数,当页面流转到指定的阶段时,会自动调用对应的生命周期函数,那么页面的生命周期函数,一共有五个,它们的执行顺序就是排列顺序,如下示例;
生命周期函数
类型
描述
onLoad
function
页面加载时触发,一个页面只会调用一次,可以在onLoad的参数中获取打开当前页面路径中的参数;
onShow
function
页面显示/切入前台时触发;
onReady
function
页面初次渲染完成时触发,一个页面只会调用一次,代表页面已经准备妥当,可以和视图层进行交互;
onHide
function
页面隐藏/切入后台时触发, 如wx.navigateTo或底部tab切换到其他页面,小程序切入后台等;
onUnload
function
页面卸载时触发,如wx.redirectTo或wx.navigateBack到其他页面时;
生命周期流程
    这些生命周期函数的使用,都是非常简单的,我们只需要搞懂它具体在什么时候调用即可,首先,我们必须确定一点,小程序采用双线程模型,对于WXML和WXSS会在一个独立的线程中执行,而以JS和JSON结尾的文件会在第二个线程运行;
    当第一个线程渲染WXML和WXSS时,首先会进行初始化,然后它会等待第二个线程的onLoad生命周期函数执行,当onLoad生命周期函数执行完成之后,会调用onReady生命周期函数函数,随后,第二个线程会将数据发送到第一个线程,第一个线程会正式进行页面渲染,此致,小程序的第一次渲染执行完成;
    那么当,第一次渲染执行完成之后,我们通过某种手段调用了this.setData函数修改了data中数据,且这个数据在WXML中使用Mustache插值语法引用,那么当第二个线程修改完成data对象中的数据之后,会调用onReady生命周期函数,调用完成之后,会通知第一个线程重新渲染页面,次为第二次渲染;
    那么当,我们的小程序被切换到后台时,第二个线程会首先调用onHide生命周期函数,那么当页面从后台切换到前台时,会首先调用onShow事件周期函数,随后将页面切换到前台;
    如果,当页面通过wx.redirectTo或者wx.navigateBack时,会首先调用onUnload事件周期函数,如下图;

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注