前段时间折腾魔方的时候,发现微信小程序里居然没有一个好用的魔方计时器,作为一个魔方爱好者(伪)和码农(真),那是不能忍的,于是就只能自己动手了。
熟悉魔方计时器操作的魔友应该很容易就知道 QTimer 怎么用。需要注意的是,QTimer 不鼓励大家随意修改自己的成绩,因此只有在完成的时候,左下角计时 Tab 会出现小红点,此时双击才会出现+2、DNF 和删除的操作。除此以外,QTimer 不允许大家修改和删除已经录入统计的成绩。在完成 5、12、50 和 100 次复原时,右下角统计 Tab 会相应的提醒。
你以为这样就完了?当然没有,我还试了一下微信小程序热更新的可行性。魔方计时器有个很重要的功能就是生成打乱公式,目前这个模块更新频繁,所以我决定基于 Sval 试试热更新这个模块。
首先,我将打乱生成的模块进行打包,exports 暴露出需要用到的函数,打包发布到公用的 CDN 上,Unpkg: QTimer。
于是,在 App 初始化的时候,检查热更新模块版本,然后判断是否需要更新,如果需要则再将热更新模块拉回来,存在 LocalStorage 里。
// utils/patch.js
const version = wx.getStorageSync('__generate_scramble__version')
const code = wx.getStorageSync('__generate_scramble')
if (code) {
// 如果已经缓存,先跑一遍代码
worker.postMessage({ type: 'run', data: code })
}
wx.request({
url: 'https://unpkg.com/qtimer?meta',
success(res) {
const integrity = res.data.integrity
if (integrity && integrity !== version) {
wx.request({
url: 'https://unpkg.com/qtimer',
success(res) {
wx.setStorageSync('__generate_scramble__version', integrity)
wx.setStorageSync('__generate_scramble', res.data)
// 如果有更新,再跑一遍代码,覆盖之前的 exports
worker.postMessage({ type: 'run', data: res.data })
}
})
}
}
})
因为打乱生成是 CPU 密集操作,我把他放在了 worker 里做。那么我们将拉取回来的代码,直接 postMessage 给 worker 去跑。同时,worker 把代码执行的 exports 给暴露出来共主线程调用。
// workers/hotload.js
const interpreter = require('interpreter/index.js')
worker.onMessage(req => {
if (req.type === 'run') {
interpreter.run(req.data) // 跑代码
} else {
const { generateScramble } = interpreter.exports
const scramble = generateScramble(req.type) // 调用热更新模块的方法生成打乱
worker.postMessage({ type: req.type, data: scramble })
}
})
因为小程序本身禁用了eval
、new Function
、setTimeout
等等,所以 interpreter 就是通过 Sval 构造的解释器。
// workers/interpreter/index.js
const Sval = require('sval')
const init = require('init.js')
const interpreter = new Sval()
// 先初始化一下需要用到的方法
// 免得第一次打开 LocalStorage 里面没有缓存
// 同时代码还没有拉回来,等拉取到代码会将初始化覆盖掉
interpreter.run(init)
module.exports = interpreter
最后,愉快地在业务代码里面找 worker 要结果就好了。
// app.js
const worker = wx.createWorker('workers/hotload.js')
App({
// Worker 调用封装
getWorkerResult(req, done) {
worker.postMessage(req)
worker.onMessage(done)
}
})
// pages/timer/index.js
const app = getApp()
Page({
getScramble() {
// 调用 Worker 生成打乱
app.getWorkerResult({ type: '3x3' }, res => {
this.setData({ scramble: res.data })
})
}
})
不过说实话,性能不怎么样,在 xs 上,7 阶打乱生成算法用原生的跑需要 2ms,而用 Sval 跑需要 600ms。对于 QTimer 来说,打乱的生成不影响体验,但是在打乱生成模块稳定以后,还是需要替换成原生实现,打包进小程序里。
目前我只是将解释器跑在逻辑层,理论上应该也能跑在视图层(wxs),但是对视图(wxml)的影响还十分有限,所以热更新只能对业务逻辑进行更新。当然,你甚至可以十分疯狂地将整个 Page 或者整个 App 的逻辑做热更新,只是是否有必要而已。
const Sval = require('sval')
const interpreter = new Sval()
interpreter.import({ Page, wx, app: getApp() })
interprete.run(`
Page({
data: {},
onShow() {},
customMethod() {}
})
`)
这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。
V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。
V2EX is a community of developers, designers and creative people.