最近没什么事情,再学一个技术栈吧!
在中国,小程序这么火,虽然很不情愿(一家独大,还对开发者很不友好),但感觉是有必要学一下的。
建项目
学习一个技术栈,从第一个项目开始!ψ(`∇´)ψ
我打算用 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块审核费,还不保过。
我也不打算盈利,市面上也有很多类似的小程序,就不麻烦了吧。因为穷
总结
到此,虽然东西写的不怎么样,虽然也没发布出去,但也算把流程走一遍了。
以后我说我会开发小程序,有过独立开发小程序的经验,不过分吧?(心虚)
顺带一提,我还给小程序写了服务器呢( ̄︶ ̄)↗