最近没什么事情,再学一个技术栈吧!

在中国,小程序这么火,虽然很不情愿(一家独大,还对开发者很不友好),但感觉是有必要学一下的。

建项目

学习一个技术栈,从第一个项目开始!ψ(`∇´)ψ

我打算用 Taro + React + NutUI 来写一个塔罗牌占卜的小程序。

在此之前,有另一个同学在用这个技术栈写小程序,还问了我一些离奇的问题,比如,NutUI这个库往Taro的项目里装就一直报错,这个坑我就用官方模板避过去了。

相比于H5开发,小程序开发是真的浑身难受。

一天写完的代码,我真的写了好久好久(忘了多久了)( ̄▽ ̄)"。

看起来东西多,用起来都没有。

我的代码错了,我的代码报错。

我的代码对了,他的代码报错。

他的代码对了,我的代码无效。

编译器不报错,小程序就报错。

小程序不报错,编译器刷警告。

整整齐齐( •̀ ω •́ )✧

拿DOM元素

小程序里面拿DOM元素是异步的,好好的函数都得加上await

这还不完,拿元素还会失败,不管是useEffect里面还是Taro的钩子useReady里面拿元素都会失败,而且还不支持ref(支持,但是ref拿过来的东西啥都干不了)。

好好的useEffect,非得写成这个样子才能拿到元素:

useEffect(()=>{
  Taro.nextTick(()=>{
    Taro.createSelectorQuery()
      .select(query)
      .XXXX((res) => {
       // code //
      })
      .exec()
  })
})

而且,要拿节点滚动状态、节点包围盒大小、上下文对象、节点对象,这些都是分开的,上面的XXXX要换成具体方法。

这样写必须得用ID

写死ID吧,对React来说不优雅,万一组件同时存在两个呢?

生成ID吧,麻烦。

所以我选择···写死ID(因为我写代码的时候忘记了之前写了一个生成ID的Hook)

无限滚动

无限滚动是一个很常见的效果,一个列表往下滚动会一直往下加载。

在此之前我在H5里面也用过。

但是,我在小程序里面用组件库的这个组件,它有一个问题,能无限滚动的前提是必须有滚动条,如果一开始列表是空的,它不会自动加载,必须手动加载,而且必须手动加载满屏幕,出现滚动条,无限滚动组件才有效。

手动加载也没关系,但请问我要加载几页才能填满屏幕?

为了能够知道滚动条已经出来了,我还要获取组件的高度和它内容的高度。

而内容高度属于滚动部分的数据,组件高度属于包围盒,我要调用两次这两个异步函数。

好在最后解决了这个问题,解决方法应该还算优雅。

虚拟列表

这东西的概念我以前是有的,只是不知道它叫虚拟列表,是用来优化的,就是,看不到的东西DOM结构也是空的,用个占位符占着。

而我刚好有这个需求,我想在列表项目上显示一个图标,而这个图标是是根据数据用DOM元素拼的(简单粗暴,但效果很好,还好维护( •̀ ω •́ )✧),这个列表项目肯定性能是不好的。

组件库的虚拟列表是支持列表项目不同高度的,但是小程序上似乎不支持。

使用动态高度会疯狂刷错误,说找不到获取包围框的函数。肯定啊,小程序包围框要单独异步获取的`(*>﹏<*)′

去看Github,也没人提出这个Issue(我猜他们赶项目,写出来就完了,都不用这玩意儿)

现在代码写完了,而我对这玩意儿的高度还是个迷,不知道是只能定死的还是我用的不对。

3D

即使是在Web中,这个我也没怎么用过,但我总感觉小程序里面的有BUG,不知道是不是Web就这样。

开了背面剔除,如果一个元素直接背对摄像机,那么不会被渲染,但是如果这个元素是因为父元素选择而背对相机的,那么它依然会被渲染。

我不能理解···

没有fetch

我写React Native都有,写小程序没有了?

小程序提供了一个回调式的网络请求函数,我把它封装成了fetch,然后赋值给了window.fetch。

export function fetchTaro(
    input: string | URL,
    init?: RequestInit,
): Promise<Response> {
    if (init?.headers && init.headers instanceof Array) {
        init.headers = Object.fromEntries(init.headers)
    }
    const url = typeof input === 'string' ? input : input.href
    const headers = (init?.headers && init.headers instanceof Array) ? Object.fromEntries(init.headers) : init?.headers

    return new Promise<Response>((resolve, reject) => {
        Taro.request({
            url,
            data: init?.body,
            header: headers,
            method: init?.method as any,
            dataType: 'other',

            success: (res) => {
                resolve(new Response(res.data, {
                    status: res.statusCode,
                    statusText: res.errMsg,
                    headers: res.header,
                }))
            },
            fail: (err) => {
                reject(new Error(err.errMsg))
            }
        })
    })
}

当然Response类也是我实现的。

class Response {

    constructor(
        private body: string,
        _: ResponseInit,
    ) {}
    async arrayBuffer(): Promise<ArrayBuffer> {
        throw new Error("方法未实现")
    }
    async blob(): Promise<Blob> {
        throw new Error("方法未实现")
    }
    async formData(): Promise<FormData> {
        throw new Error("方法未实现")
    }
    async json(): Promise<any> {
        return JSON.parse(this.body)
    }
    async text(): Promise<string> {
        return this.body
    }
    clone(): Response {
        throw new Error("方法未实现")
    }
}

这实现了个啥?这不啥也没写嘛——如写


还有各种布局上的、样式上的,总之各种不舒服。

还好我提前买了域名,申请了SSL证书,网络上没出什么问题。( ̄▽ ̄)"

玄学算法

咱写的是占卜的工具,玄学这地方还要下点功夫。

占卜基于共时性,共时性的前提是随机,但是计算机做不到真随机。

但是小程序是跑在手机上的,手机上面有加速度计之类的传感器。

生成一组牌经过以下步骤:

  • 生成种子

  • 随机打乱

  • 均匀取牌

后面取出的牌取决于一开始生成的种子,后面两步只是增加计算复杂性,让人无法直接通过种子看出结果。

生成种子是采样一段时间的传感器,获得一组种子,后续需要随机数时每次使用不同的种子进行计算,算完更新种子。

发布小程序

发布小程序真麻烦啊

又是备案又是人脸识别的。

但走到最后一步让我交30块审核费,还不保过。

我也不打算盈利,市面上也有很多类似的小程序,就不麻烦了吧。因为穷


总结

到此,虽然东西写的不怎么样,虽然也没发布出去,但也算把流程走一遍了。

以后我说我会开发小程序,有过独立开发小程序的经验,不过分吧?(心虚)

顺带一提,我还给小程序写了服务器呢( ̄︶ ̄)↗ 

我能想到的,最大的成功就是无愧于自己的心。