实现微信小程序编译和运行环境系列(核心篇三)

本章节就带大家通过微信官方的创建项目部分代码来讲解一下这些对外api如何通过我们自己方式来实现和微信相同的功能操作,我们通过微信开发者工具来自动创建一个默认的小程序项目 一个首页和日志页。

这个项目大家应该都比较熟悉吧,应该第一次接触小程序开始时引入眼前的场景,具体的其他内容我们就不在这里啰嗦了,直接看下它的app.js文件,编辑器打开后可以看到里面写了这些.

 //app.js
App({
  onLaunch: function () {
    // 展示本地存储能力
    var logs = wx.getStorageSync('logs') || []
    logs.unshift(Date.now())
    wx.setStorageSync('logs', logs)

    // 登录
    wx.login({
      success: res => {
        // 发送 res.code 到后台换取 openId, sessionKey, unionId
      }
    })
    // 获取用户信息
    wx.getSetting({
      success: res => {
        if (res.authSetting['scope.userInfo']) {
          // 已经授权,可以直接调用 getUserInfo 获取头像昵称,不会弹框
          wx.getUserInfo({
            success: res => {
            }
          })
        }
      }
    })
  },
  globalData: {
    userInfo: null
  }
})

先给大家把接下来部分要说明的消息事件整理出来让大家可以看的清楚一些,后面的内容主要是围绕这个消息事件和代码来说明

上面这个图就是从微信开发者工具里面打开页面时候出现的事件和消息类型以及有关数据,分析一下可以看出:

1、首先调用了同步api getSystemInfo,我们可以看到在我们的项目代码里面没有存在这个api,但是开发者工具第一步就调用了,所以在项目编译初始化的时候就触发了,至于Receive的数据返回的就是工具界面左上角的一些配置数据,大家可以打开自己的开发者工具看看,一些参数是你可以直接从上面观察到的,如手机型号 屏幕占比 网络类型等,这个同步api是系统自动调用的不用我们做写什么操作,而且它会比较频繁的调用。(这点我也没理解,当我打开控制台的时候他会调用很多次)

  • 在前面的文章中讲到同步的api都是通过走http协议’/apihelper/assdk’链接过来的,里面携带了api名和一些参数,所以我们可以直接拦截’/apihelper/assdk’请求进行处理

实例代码展示

     router.post('/apihelper/assdk', async (ctx, next) => {
    const { api, args } = JSON.parse(ctx.request.body);
    if (api === 'getSystemInfo') {
      ctx.body = {
        'errMsg': 'getSystemInfo:ok',
        'model': 'iPhone 7 Plus',
        'pixelRatio': 3,
        'windowWidth': 414,
        'windowHeight': 624,
        'system': 'iOS 10.0.1',
        'language': 'en',
        'version': '7.0.4',
        'screenWidth': 414,
        'screenHeight': 736,
        'SDKVersion': '2.7.1',
        'brand': 'devtools',
        'fontSizeSetting': 16,
        'benchmarkLevel': 1,
        'batteryLevel': 100,
        'statusBarHeight': 20,
        'safeArea': { 'right': 414, 'bottom': 736, 'left': 0, 'top': 20, 'width': 414, 'height': 716 }
      };
    }
    })

ctx.body的结果集可以直接复制开发者工具中的数据就可以,如果要实际使用的话需要把部分参数改成动态获取的。

2、当代码执行wx.getStorageSync(‘logs’) wx.setStorageSync(‘logs’, logs)的时候(标签二)通过前面的日志描述可以知道它们都是appservice sdk调用的同步的api,就是我们普通使用的Storage它们包装做了一层转换,因为这个页面是初始化的时候调用的所以这里面的数据都是空的。当点击log页面的时候,就可以看到调用了APPSERVICE_PUBLISH事件类型,此类型是view层发给service层的我们看到它的webviewID存在值。

看出data里面的数据就是Storage存储的Date.now()时间, wx.setStorageSync wx.getStorageSync在源码对应的地方。

var r = re.IS_IOS ? "setStorage" : "setStorageSync"
var r = re.IS_IOS ? "getStorage" : "getStorageSync"

看出如果是ios的话就只能调用setStorage getStorage方式。

模拟代码展示:

const storage = new Map();

const getStorage = function (key) {
  return storage.get(key);
};
const setStorage = function (key, value) {
  storage.set(key, value);
};
const removeStorage = function (key) {
  storage.delete(key);
};

module.exports = {
  getStorage,
  setStorage,
  removeStorage
};

如果想模拟全局的存储简单的可以直接使用map进行操作,然后在调用的时候获取对应的api进行获取和设置操作,完善的话存储可以采用浏览器的LocalStorage,SessionStorage或者一些npm包进行处理。

3、我们比较常用的一个api通过wx.login换取code可以看出(标签三)中,是通过逻辑层service自发自收的,发起一个请求通过service逻辑层处理后在回调回去,通过微信api源码可以看到调用的地方

这一块要想要弄的很好不是很容易,涉及到用户体系就要和服务端彻底打通,好在登录授权这块微信也是采用OAuth2协议实现的

如果有朋友对OAuth2还不是很了解明白的话,建议看下这个文档比市面上90%的相关内容讲的更彻底通透。

4、当在小程序中打开页面时触发了 onAppRoute 事件,通过日志看出发送了APPSERVICE_ON_EVENT事件,path表示当前页面,openType表示操作类型,openType如果不是很明显的话我们试着点击一下,从日志页面返回首页的后退操作。

看到"openType":"navigateBack"这个应该比较好理解解释了。

到这里我们会有一些不一样的地方了,因为这里的操作的是事件处理不是简单的api处理就ok了,所有前端页面操作的控制器都是一个整体,首先我们要先搭建这个载体容器存放各个部分。

模拟代码实例:

构建一个总管理处理的信息

constructor(wxConfig = {}, socketPort) {
    super();
    this.wxConfig = wxConfig;
    this.systemManager = new SystemManager(this, wxConfig);
    this.navigatorManager = new NavigatorManager(this, wxConfig);
    this.pageManager = new PageManager(wxConfig, socketPort);
    this.tabbarManager = new TabbarManager(this, wxConfig);
    this._render();
    this._launch();
    window.socketClient.setEmitter(this);
  }

然后render页面信息,结合node后台服务渲染前端展示

    this.domElement = document.createElement('div');
    this.domElement.id = 'container';
    this.domElement.style = ` height: ${global.simulator.height}px; width: ${global.simulator.width}px;`
    // system
    this.domElement.appendChild(this.systemManager.domElement);
    // navigator
    this.domElement.appendChild(this.navigatorManager.domElement);
    // pages
    this.domElement.appendChild(this.pageManager.domElement);
    // tabbar
    this.domElement.appendChild(this.tabbarManager.domElement);

后面就是监听各个事件控制和各种业务处理,核心还是要按照开发者工具的消息顺序和内容来实现。

例如上面我们提到的navigateBack我们自己这边实现按照平常的业务写法就ok。

  navigateBack (path, query) {
    let currentWebview = this.domElement.children.item(0);
    let currentIndex = currentWebview.style['z-index'];
    this.domElement.removeChild(currentWebview);

    let targetPage = null;
    for (let i = 0; i < this.domElement.children.length; i++) {
      let webview = this.domElement.children.item(i);
      if (webview.style['z-index'] === `${currentIndex - 1}`) {

        let viewId = +webview.getAttribute('data-view-id');
        let path = webview.getAttribute('data-view-path');
        let query = JSON.parse(webview.getAttribute('data-view-query'));
        window.socketClient.send(WebsocketMessage.onAppRoute(viewId, path, query, 'navigateBack'));
        window.socketClient.send(WebsocketMessage.onAppRouteDone(viewId, path, query, 'navigateBack'));
        targetPage = path;
        break;
      }
    }
    return targetPage;
  }

重要的还是socketClient.send消息的正确传递才可以和基础库正确的交互

其他的很多对外的api实现方式都是大同小异,主要是在接收到消息后怎么处理设计

我们知道了核心的流程,下面要做的就是模仿设计,模仿它的消息格式和返回结构,设计自己的各系统模块的关联

对于小游戏而言大致是一样的,主要有几个点不同:

  1. 小游戏是通过根目录下的 game.json 来对小游戏进行全局配置,决定相关界面渲染和属性设置等;
  2. 在小游戏的运行环境里面不存在 BOM 和 DOM API,只有 wx API对它们进行了包装,所以无法直接使用;
  3. 小游戏的运行层只有一层在view里面跑;
  4. 小游戏的大部分api主要都是对文件系统和网络的处理。

上面的一些总结主要是根据一些api的实现来描述扩展了一些,看完后希望大家对此有所了解,后面我打算从全局来讲下怎么从代码设计方面来设计整个浏览器运行环境实现方案。

  • 发表于:
  • 本文为 InfoQ 中文站特供稿件
  • 首发地址https://www.infoq.cn/article/APYrPLrkzF3M6x64hz9S
  • 如有侵权,请联系 yunjia_community@tencent.com 删除。

扫码关注云+社区

领取腾讯云代金券