评论

小程序入门初探

本文将自己入门小程序时学习到的相关知识做一个梳理,希望可以给大家带来帮助。

小程序简介

小程序是一个不需要下载安装就可使用的应用,它实现了应用触手可及的梦想,用户扫一扫或搜一下即可打开应用。目前小程序已经应用于电商、视频、教育、音乐、新闻资讯等众多领域中,而它与App和公众号有什么区别呢?下面将做一个简单的比较。

小程序与App的区别

  1. 小程序无需安装,用户通过微信扫描二维码、搜索、分享等途径即可打开小程序,而APP则需要提前安装,相对来说小程序的推广成本较低;
  2. App的开发经常需要适配各种主流机型,并且需要开发安卓和IOS两套,开发成本较高,而小程序的适配较简单,开发周期相对较短;
  3. 小程序可以实现的功能受到微信平台开放能力的限制,而App对手机资源的利用更加淋漓尽致,可以满足更多的功能场景和更流畅的交互体验,但小程序的交互体验也可以接近原生App

小程序与公众号的区别

  1. 小程序依赖微信提供的运行环境,而公众号基于H5,尽管小程序不是原生App,但可以借助微信封装的一系列接口能力去实现更丰富的功能;
  2. 公众号服务于营销与信息传递,而小程序面向产品与服务,因此小程序在对营销功能(如消息推送)进行了严格控制;
  3. 在交互体验上公众号操作延时相对较大,而小程序体验接近原生App,比公众号更流畅。

整体对比

运行环境 功能性 便捷性 交互体验 开发成本 推广难度 消息推送
公众号 H5 简单 无需安装 一般 支持
小程序 微信 轻应用 无需安装 接近原生App 受限
App 原生系统 丰富 需要安装 最流畅 支持

小程序的架构设计

小程序的技术选型

小程序在进行技术选型时考虑到以下3种页面渲染的方式:

  1. 纯客户端原生技术来渲染,意味着小程序要与微信代码一起编包和发版本,开发节奏无法像Web那样快;
  2. 纯 Web 技术来渲染,因为UI渲染和JS脚本执行都在同一线程,容易导致业务逻辑和UI渲染抢占资源,从而在一些复杂的页面上面临性能问题;
  3. 介于客户端原生技术与 Web 技术之间,互相结合各自特点(又可称为Hybrid),可像Web一样支持在线快速更新,在另一方面也比Web更接近原生体验。

因此,小程序最终选择了 Hybrid的渲染方式,界面主要由成熟的 Web 技术渲染,辅之以大量的接口提供丰富的客户端原生能力,同时通过双线程模型分离界面渲染和逻辑处理,提高渲染性能和管控安全。

双线程模型

小程序的架构主要可分为3层:

  1. 逻辑层(AppService)
  2. 视图层(View)
  3. 系统层(Native)

逻辑层

  1. JsCore线程中运行,主要进行数据请求和业务逻辑处理;
  2. 通过JS引擎(如JavaScriptCoreX5 JSCore)提供一个沙箱环境来执行JS,与浏览器相比开发者无法直接操作DOMBOM,从而阻止开发者使用一些浏览器暴露的接口(如跳转页面、动态执行脚本),提高管控和安全性;

视图层

  1. WebView线程中执行界面渲染相关的任务,并且小程序中存在多个界面,故渲染层也存在多个WebView线程;
  2. 通过Virtual DOM减少渲染开销,提高局部更新数据和重渲染的效率,让页面更流畅;
  3. 视图层中描述语言使用的WXMLWXSS与Web开发中htmlcss类似,降低开发者的学习成本。

系统层

  1. 通过 JSBridge构建了jsnative之间的通信,以便上层可以间接调用客户端原生底层接口;
  2. 提供网络请求、数据缓存、本地文件、媒体等基础能力;
  3. 为逻辑层与视图层的通信做中转,逻辑层发送网络请求也经由Native转发。

在双线程模型下把界面渲染和逻辑处理分离和并行处理,可以加速渲染速度,避免单线程模型下,js 运算时间过长而导致UI 出现卡顿, 并且采用数据驱动的方式,开发者无法直接操作 DOM,加强管控和安全。但在另一方面双线程模型也意味着逻辑层与渲染层之间的通信、各层与客户端原生交互都有一定的延时。

双线程下的渲染过程

由微信官方的图可以看出,在渲染层把WXML转化成对应的JS对象模拟虚拟DOM树,在逻辑层更新数据的时候,通过小程序基础库提供的setData方法把需要更新的数据经过Native层转发传递到视图渲染层,再对比两棵虚拟DOM树的前后差异,因为虚拟DOM和实际的WXML节点一一对应的关系,最后把差异应用到原来的DOM树,只会渲染被更新的部分。

小程序的启动加载和生命周期

小程序启动有两种情况:

  1. 冷启动:用户首次打开或小程序被微信主动销毁后再次打开的情况,此时小程序需要重新加载启动;
  2. 热启动:用户在打开过小程序后的一定时间内再次打开,此时小程序在后台还未被销毁,只需将后台态的小程序切换到前台。

在冷启动的时候如果小程序发现有新版本,则会异步下载新版本的代码包,并同时用客户端本地的包进行启动,这意味着当前打开的依然不是最新版本,需要等下一次冷启动才会应用上新版本。 但考虑到可能存在需要用户马上应用新版本的情况,开发者可以通过wx.getUpdateManagerAPI 来实现。

  1. 小程序启动后首次加载页面,会触发页面的onLoad,每个页面只有在首次加载才会触发一次;
  2. 页面显示的时候触发onShow,如果是页面首次渲染完成则会接着触发onReady
  3. 小程序被切换到后台,页面被隐藏,触发onHide,下次从后台切换到前台,会再触发onShow
  4. 页面被回收销毁时触发onUnload

小程序开发Demo

下面通过一个简单的demo为大家展示下小程序开发界面和代码目录结构。

1. 官方开发者工具界面

官方提供的微信开发者工具让我们可以很方便地完成小程序的 API 和页面的开发调试、代码查看和编辑、预览和发布等功能。

2. 目录结构

小程序的目录主要包含一个描述整体程序的app和多个描述各自页面的page

  1. 小程序主体
    1. app.js:小程序逻辑,例如小程序的启动监听处理
    2. app.json:公共配置,例如页面文件的路径、窗口表现、设置网络超时等
    3. app.wxss:公共样式表
  2. 小程序页面
    1. {page}.wxml:页面结构,如index.wxml
    2. {page}.json:页面配置,独立定义每个页面的一些属性,为可选文件
    3. {page}.wxss:页面样式表
    4. {page}.js:页面逻辑,例如网络请求、页面事件处理等
  3. 公共库和组件
    1. utils目录:存放自己写的库
    2. lib目录:存放外部引入的库
    3. components:自定义的组件
  4. cloudfunctions目录:如果有使用小程序云进行开发,则可在此存放自己的云函数

3. 页面描述例子

  1. wxml 例子

    <view class="container">
      <view class="userinfo">
        <button open-type="getUserInfo" bindgetuserinfo="onGetUserInfo" class="userinfo-avatar" style="background-image: url({{avatarUrl}})"></button>
        <text open-type="getUserInfo" bindgetuserinfo="onGetUserInfo" class="userinfo-nickname">您好,{{userInfo.nickName}}</text>
      </view>
      <view class="uploader">
        <view class="uploader-text" bindtap="gotoStatistics">
          <text>统计打卡记录</text>
        </view>
      </view>
      ...
    </view>
    
  2. wxss 例子

    .userinfo, .uploader, .tunnel {
      margin-top: 40rpx;
      height: 140rpx;
      width: 100%;
      background: #fff;
      border: 1px solid rgba(0, 0, 0, 0.1);
      border-left: none;
      border-right: none;
      display: flex;
      flex-direction: row;
      align-items: center;
      transition: all 300ms ease;
    }
    ...
    

可以看出在构建页面时候类似写前端页面,可以让初学者很快地学习和上手。开发者通过wxml来完成列表&条件渲染、模版、事件绑定等操作,小程序框架也为开发者提供一系列基础组件以便快速组合开发:

绑定事件的时候可以在wxml文件中通过bind + 事件类型catch+事件类型来定义,后面跟上在{page}.js同名的处理函数(例如bindtap="gotoStatistics"),其中bind的方式绑定事件不会阻止事件冒泡,catch的方式绑定会阻止事件冒泡。

4. 代码入口

  1. 每新建一个页面都需要在app.json中配置页面的路径,如

    {
      "pages": [
        "pages/index/index",
        "pages/countRes/countRes",
        ...
      ],
      ...
    }
    
  2. 小程序的注册,在app.js中进行小程序注册、生命周期回调的处理

    App({
      onLaunch: function(options) {
        //监听小程序初始化
      },
      onShow: function(options) {
        //监听小程序显示
      },
      onHide: function() {
        //监听小程序隐藏
      },
      onError: function(msg) {
        //监听错误
      },
      //全局数据对象
      globalData: {...}
    })
    
  3. 页面的注册

    Page({
      //与页面相关的数据
      data: {
        avatarUrl: './user-unlogin.png',
        userInfo: {},
        requestResult: '',
        needInit: true,
        ismask: 'none'
      },
      
      onLoad: function(options) {
        //页面加载
        ...
        //发请求
        wx.request({
      		url: 'abc.php', 
      		data: {...},
      		header: {'content-type': 'application/json'},
      		success(res) {
        		//更新数据
                this.setData({
                    avatarUrl: res.userInfo.avatarUrl,
                    userInfo: res.userInfo,
                    needInit: false,
                    ismask: 'none'
                })
      		}
    	})
        ...
      },
      onReady: function() {
        //页面初次渲染完成
      },
      onShow: function() {
        //页面显示
      },
      onHide: function() {
        //页面隐藏
      },
      onUnload: function() {
        //页面卸载
      },
          
      //自定义事件处理函数
      gotoStatistics: function() {
        if (this.checkIsNeedInit()) {
          return
        }
        wx.navigateTo({
          url: '../statistics/statistics'
        })
      },    
      ...
    })
    

    {page}.js中进行页面的生命周期回调、数据更新和事件处理等操作,例如监听页面加载完成时请求用户信息,通过setData更新于页面绑定的数据,监听按钮点击等,其中涉及到网络请求、媒体、文件、缓存、界面等相关操作都有小程序提供API来支持:

    例如Demo中存在选择图片并上传的场景:

    //上传图片打卡
    doUploadPic: function() {
        // 选择图片
        wx.chooseImage({
            count: 1,
            sizeType: ['compressed'],
            sourceType: ['album', 'camera'],
            success: function(res) {
                const filePath = res.tempFilePaths[0]
                // 上传图片
                const cloudPath = filePathPre + filePath.match(/\.[^.]+?$/)[0]
                wx.cloud.uploadFile({
                    cloudPath,
                    filePath,
                    success: res => {
                        console.log('[上传文件] 成功:', res)
                        ...
                    },
                    fail: e => {
                        console.error('[上传文件] 失败:', e)
                    },
                    complete: () => {}
                })
            },
            fail: e => {
                console.error(e)
            }
        })
    }
    

相关参考资料

点赞 0
收藏
评论
登录 后发表内容