Jasper Ji

开口不在舌头上

0%

因为之前看过《启示录:打造用户喜爱的产品》,看到这本时,我当时想着是否要入手,看了下目录似乎跟《启示录》不一样,但当时并入手,后来看一些豆瓣的评价,与前作相比评分不高,主要是集中在章节多,内容不够深入,所以更不想入手了。最近的一些事让意识到产品的重要性,于是在京东的试读上,看了点开头,提到了坎贝尔这个人,这个其实硅谷很有名的被誉为硅谷教练的人物,之前在《乔布斯传》中有,但我没有想到坎贝尔对于谷歌、脸书这样的公司也是影响,这个是后话,总之我入手《启示录2:打造优秀的产品团队》这本书,不到300页的,大部分在上班路上的地铁上看到,最后这点在家看完了。

内容

先说说这个书名吧,《启示录:打造用户喜爱的产品》,原书名是《Empowered: Ordinary People, Extraordinary Products》,如果直译的应该是《赋能:普通人,非凡产品》,但可能是为了凑前作《启示录》的热度,起了这么一个名字。

书中倡导自主型团队,我感觉的这是一种过于理想的团队,或许这应该是创业团队的理想状态。大家志同道合,相互贡献自己的知识,为一个共同的产品愿景努力。有书中的团队,也让想到过去或者现在所在团队的一些问题。

总结

面向老板型团队

几年前我所在的一个公司就是这样现状,老板很有钱,决定超着互联网的方向玩一把,当时正值移动应用兴起的2014年,大家都在做APP,当时的开发团队其实比较全,客户端有iOS、安卓,服务端、前端、测试。我当时负责iOS,安卓的人当时比我们iOS多些。现在回想起来,那会技术团队人员配置最全,设计团队只是按项目配的人并没有设计负责人,所有人向当时的一位类似项目经理的人汇报。现在回想起来,当时是没有产品经理的,如果真的要找一位,那可能就是并不是常来但总是不断提需求的老板吧。后来的一些项目也设置过产品的岗位,但这名产品是项目经理转过来的,并不专业,完全是他们设计他们的,我们拿到了就得开发,而我们觉得这个功能有问题,那会总感觉我们想把产品做好,但总是做不好的怪圈。

这样的现象,我把称为面向老板型团队,当时没有产品经理是一个最大问题,老板自己想着如何开发产品,然后将这种需求直接给开发团队,并没有转换成合理的产品需求,如果老板比较强势的话,即使大家觉得有问题,在一次次沟通无效后,最终在变成了妥协,而后的团队的一些人就转变成面向老板的倾向,不再关心产品本身,最终这样的团队注定成功不了,事实也是这样。

面向业务型团队

后来我去了一家传统公司,当时招技术,本想着技术可以对这个业务有一定的赋能。但慢慢的发现陷入了书里提到的功能性团队的境地,而我更愿意称为面向业务型团队。传统公司的问题可能更严重,尤其是中小型的,这类企业的大都是通过贷款来做业务,所以每一分钱都需要有产出,所以这类公司很难在产品上有预先的投入,所以我们技术这边往往是平时不太忙,一旦有新的业务来了,我们就得立马开发业务相对应的功能,这几乎注定每次都是草草开始,只能先做一些功能让业务先用。后来我渐渐发现除了技术团队的投入不足外,我们最大问题就是没有产品,没有产品愿景,有的只是不停变化的业务。

无法启动问题

安装的教程主要参考官方文档,主要记录下安装遇到的问题,首先因为域名没有下来,使用的是IP,另外端口也是自定义的。

1
2
3
4
5
6
7
8
9
sudo docker run --detach \
--publish 47.xxx.xxx.xx:8929:80 \
--name gitlab \
--restart always \
--volume $GITLAB_HOME/config:/etc/gitlab \
--volume $GITLAB_HOME/logs:/var/log/gitlab \
--volume $GITLAB_HOME/data:/var/opt/gitlab \
--shm-size 256m \
gitlab/gitlab-ce:15.1.2-ce.0

这个命令会提示bind: cannot assign requested address,所以就只改端口。

1
2
3
4
5
6
7
8
9
sudo docker run --detach \
--publish 8929:80 \
--name gitlab \
--restart always \
--volume $GITLAB_HOME/config:/etc/gitlab \
--volume $GITLAB_HOME/logs:/var/log/gitlab \
--volume $GITLAB_HOME/data:/var/opt/gitlab \
--shm-size 256m \
gitlab/gitlab-ce:15.1.2-ce.0

IP在gitlab.rb配置中修改

1
external_url 'http://47.xxx.xxx.xx' # 注意不需要加端口

修改完毕后,我就直接执行Docker命令重启了,访问网页直接显示502的界面,然后CPU暴涨,直接卡死,只能控制台重启机器。我以为是机器配置的问题,毕竟是2核4G的,于是我又换了一个低一点的版本13,试了下,发现依旧卡死。最后参考这篇文章docker 搭建gitlab后,出现502的处理方案之一

1
docker exec gitlab gitlab-ctl reconfigure

Dcoker启动后先不要访问网页,等一会CPU降下去后再执行这个命令。

初始化密码问题

之前安装的是13,密码在第一次访问时会显示重置密码的网页,但是安装的15,发现没有这个重置密码的,只有一个登录,原来从14开始初始密码放置在/etc/gitlab/initial_root_password,找到后直接登录后再重置。

安装Gitlab也算有好多次了,之前主要是原生的安装并没有用过Docker,另外吐槽下这玩意,动不动就CPU爆满,真的不是很友好。

参考

Linux初装gitlab初始默认密码

缘起

早些时候有使用React Native开发了第一版的App,后来又用Flutter重新开发了。Flutter的开发和用户体验确实比RN要好,我们的App其实一直没有使用,用的是小程序,功能的频繁变更,如果是App的话,可能用户手机上依旧安装的是老的版本,当然也可以通过版本控制来让用强制更新,从而可以避免老版本的问题。另外就是热更新,RN的最大优势就是热更新吧,如果不考虑热更新我肯定还是Flutter优先,RN的热更新主要以微软的CodePush,刚出来的时候免费的,目前已经变成AppCenter,需要付费了。另外React Native 中文网的Pushy,不过没有用过。后来偶然发现code-push-server这个开源的项目,最近才想到应该试下这个方案。

CodePush Server

这个项目的初衷是因为微软的CodePush在国内太慢了,另外官方的CodePush Server是没有开源的,只有React Native CodePush是开源的。理论上是可以通过React Native CodePush 反推出CodePush Server逻辑的,但是不知道CodePush Server的这个项目是怎么来的,不过能用就好。

不过这个项目在3年前就已经停止更新了,另外React Native CodePush则一直在更新,所以实际会有不少的问题。我用的是Docker搭建的CodePush Server服务,这个很简陋没有什么管理的界面,都是用React Native CodePush命令行来管理的。

依赖

package.json中的依赖:"react-native-code-push": "~5.6.0",一定要使用小版本,因为5.7以上的版本就会报错。

屏蔽自动生成

react-native-code-push插件会自动在/android/app/build/generated/rncli/src/main/java/com/facebook/react/PackageList.java文件中生成CodePush的类型,但是如果使用CodePush Server的话,就需要手动的更改CodePush的Server路径,而不是微软的,需要的项目下生成react-native-config.js,屏蔽自动生成。

1
2
3
4
5
6
7
8
9
module.exports = {
dependencies: {
'react-native-code-push': {
platforms: {
android: null, // disable Android platform, other platforms will still autolink
},
},
},
};
Gradle 配置

需要变更app/build.gradle

1
2
apply from: "../../node_modules/react-native/react.gradle"
apply from: "../../node_modules/react-native-code-push/android/codepush.gradle" // 新增此段
常用命令

热更新是分平台,所以需要创建对应的安卓以及iOS的。

1
code-push release-react [应用名称]-android android -d Production # 打包生产版本

运行程序,注意热更新也是分为测试和生产版本的。

1
react-native run-android --variant=release  # 生成release版本,默认是Debug版本

总结

整个算是跑通了,从业务角度来看,热更新确实很棒。但实际情况就是如果不是大厂,那只能自己搭建CodePush Serser的服务了,不过考虑到这个开源的项目以及很久没人在维护了,所以实际上留下一个巨大坑,后续维护的难度还是有的。

同事的之前是用Mac本开发的,后来换成了小米的本,发现无法用真机和模拟器调试,执行adb devices 命令后直接卡死。网上搜索很多,多是说端口被占用的问题,但是试了好多次,发现端口没有被占用。当时也没有细想,后来冷静下来发现还是得从原理上来寻找问题的根源,于是一大早就把谷歌官网关于adb的文档重新看了下,原来只是使用,并没有深入的去理解原理。adb分为服务端和客户端,端口一般是5037,连接不上的问题实际上网络连接的问题。第一个想到的是防火墙,结果发现不是,后来觉得是不是代理软件的问题,于是就把代理软件关闭了,发现也没有解决问题。后来搜索发现了这篇文章如何解决adb卡死,命令不返回的问题,于是从微软官网下载了Process Explorer,发现果然是代理软件的问题,虽然之前虽然有关闭代理软件,因为adb连接的时候会默认加入代理,因为代理无法连接,所有adb的连接就一直不响应。

其实过去几年因为代理而产生的问题也是不少,但每次的还是会中招。另外发现现在的网络确实很厉害,很多问题只要一搜索大都能解决问题,而面对的这个问题的时候,我也陷入了这样的思维,不停的搜索,完全丧失了自己思考的能力。实际上当一个问题搜索很多遍后,依旧没有解,那么多半是一些只有在自己电脑上遇到的特殊问题。

Process Explorer,这个软件真的非常棒,主要还是免费提供的,直接从微软官网下载,我第一次从其他地方下载,没有发现问题而且显示患有问题,应该是一个老版本,后来从官网下载的就没有问题。因为一直使用Mac对于Win的使用经验还是停留在很多年前,发现要深度玩转Win。Sysinternals 套件真是宝藏库。

《苹果上的缺口:我与史蒂夫·乔布斯的生活回忆录》(The Bite in the Apple: A Memoir of My Life with Steve Jobs),很早之前听说乔布斯第一任,也是乔布斯第一个孩子的母亲,女友克里斯安·布伦南(ChrisannBrennan)写了一本《The Bite in the Apple》的书,我英语太菜,后来发现居然被翻译成中文版了,也是第一时间就购买了,与乔布斯有关的书很多,甚至连乔布斯经常光顾的餐厅的厨师都出书了,此书有点被忽视,人们大都了解成功后的乔布斯,虽然《乔布斯传》也曾描写创建苹果之前的乔布斯的一些事,不过还是有点不够细致,而此书刚好是一种补充。

18年的时候为了开发出海的应用,专门购买了一台二手的LG Nexus 5X,图的就是有谷歌服务,最近又有用到开发,发现一些问题,都是众所周知的原因了,好在都一一解决了,提前是要先安装adb,通过adb来设置。

连接WIFI成功后,提示无法访问互联网

实际上是可以访问网络,当然谷歌是访问不了的,网上搜索了下,主要是替换验证的网址了,以下方法亲测可用。

1
2
3
4
5
6
adb shell settings put global captive_portal_detection_enabled 1
adb shell settings put global captive_portal_mode 1
adb shell settings put global captive_portal_use_https 0
adb shell settings put global captive_portal_server connect.rom.miui.com
adb shell settings put global captive_portal_http_url http://connect.rom.miui.com/generate_204
adb shell settings put global captive_portal_https_url https://connect.rom.miui.com/generate_204

无法使用互联网时间

同样的也是无法谷歌提供的时间服务, 我用的阿里的NTP服务。

1
adb shell settings put global ntp_server ntp.aliyun.com

参考

开发者必备手机nexus 5x 开发环境预备

Android 系统时间不对有遇到的吗?

记事本这个小应用完全是为了学习Rust而产生的应用,今年4月6号创建了这个项目,主要是用业余时间弄下,刚好五一假期,我也就加把劲,算是弄出一个基本功能的东西。本来想写那些遇到的问题,可一旦动笔却不知道如何写起来,问题已经都解决了,新的问题依旧在路上。于是直接把项目上传到Github了,yew-notepad,有兴趣的可以自己拉下来看看,或许有点用。

目前用Rust写前端的资料很少,Yew资料更少,遇到问题基本上是反复的看官方的文档以及例子为主,很多时候总想抛开这些去寻找,但最后发现还是得认真的看文档和例子。另外就是看书吧,程序类的书籍我一般喜欢看纸质书,之前买的《Rust权威指南》,结合项目中遇到的问题期间又翻了翻,蛮有收获的。昨天又去省图借了《Rust程序设计》(Programming Rust),英文书名跟前者很像,出版也早一些,所以《Rust权威指南》为什么不是《Rust 程序设计语言》,估计是害怕读者搞混乱吧。这两本书各有千秋吧,我是拿来互补的。《Programming Rust》貌似已经出了第二版,我估计会考虑买本。

在线书籍

Rust 程序设计语言,其实就是我买的那本《Rust权威指南》,我觉得在线版的译名更合适。

通过例子学 Rust 中文版

尝试用Rust已经有点时间了,之前主要的问题是卡在IndexedDB的使用上,最后的代码是这样。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
spawn_local(async move {
let (tx, rx) = oneshot::channel::<IdbDatabase>();
let window = web_sys::window().unwrap();
let idb_factory = window.indexed_db().unwrap().unwrap();

let open_request = idb_factory
.open_with_u32(String::from("todo").as_str(), 1)
.unwrap();

let on_upgradeneeded = Closure::once(move |event: &Event| {
let target = event.target().expect("Event should have a target; qed");
let req = target
.dyn_ref::<IdbRequest>()
.expect("Event target is IdbRequest; qed");

let result = req
.result()
.expect("IndexedDB.onsuccess should have a valid result; qed");
assert!(result.is_instance_of::<IdbDatabase>());
let db = IdbDatabase::from(result);
let store:IdbObjectStore = db.create_object_store(&String::from("user")).unwrap();
let _index = store.create_index_with_str(&String::from("name"), &String::from("name")).expect("create_index_with_str error");

});
open_request.set_onupgradeneeded(Some(on_upgradeneeded.as_ref().unchecked_ref()));
on_upgradeneeded.forget();

let on_success = Closure::once(move |event: &Event| {
// Extract database handle from the event
let target = event.target().expect("Event should have a target; qed");
let req = target
.dyn_ref::<IdbRequest>()
.expect("Event target is IdbRequest; qed");

let result = req
.result()
.expect("IndexedDB.onsuccess should have a valid result; qed");
assert!(result.is_instance_of::<IdbDatabase>());

let db = IdbDatabase::from(result);
let _ = tx.send(db);
});
open_request.set_onsuccess(Some(on_success.as_ref().unchecked_ref()));
on_success.forget();

let db = rx.await.unwrap();
let transaction = db.transaction_with_str_and_mode(&String::from("user"), IdbTransactionMode::Readwrite).expect("transaction_with_str error");
let store = transaction.object_store(&String::from("user")).expect("store error");

let name = JsValue::from_str(_content_element.value().as_str());
let add_request = store.add_with_key(&name, &JsValue::from("name")).expect("add error");

let on_add_error = Closure::once(move |event: &Event| {
console::log_1(&String::from("写入数据失败").into());
console::log_1(&event.into());
});
add_request.set_onerror(Some(on_add_error.as_ref().unchecked_ref()));
on_add_error.forget();

let on_add_success = Closure::once(move |event: &Event| {
console::log_1(&String::from("写入数据成功").into());
});
add_request.set_onsuccess(Some(on_add_success.as_ref().unchecked_ref()));
on_add_success.forget();

console::log_1(&String::from("do").into());
});

因为IndexedDB连接成功是个异步事件,db 只能在成功事件中才能拿到,最初因为对IndexedDB的使用不熟悉,这玩意在很早以前看HTML5的时候就看到过,不过一直没有用过。我在成功事件中调用create_object_store,结果给报错了,实际上创建数据库只能在onupgradeneeded事件中处理。

1
2
let myDatabase = MyDatabase::new();
myDatabase.add(String::from("jasper", String::from("name")));

早期的想法是把数据库的操作封装起来,但是连接是异步的,上面的add操作时数据库可能还没有连接成功,db等于为空。可能是之前做iOS的缘故,我一直想着数据库的操作应该是个单例,不过Rust的单例好像不那么简单,一直报错,所以这个想法就先放放了。

早期的时候甚至不知道如何用Rust设置回调,最后发现了这个库kvdb_web,代码也是参考了indexed_db.rs中的方法。这个库并没有演示的例子,所以只是参考了他打开数据库的方法。里面有用到futures::channel,但是实际使用老是报错。后来我忘了Rust写前端,实际上是运行在Wasm虚拟机的环境下,Rust的多线程则是一般的操作系统的环境下了。实际上要在Wasm使用多线程,得用另外一种方式。实际上是用Rust的代码调用JS了,JS是单线程运行的。所以整个思路虽然是用Rust在写,但实际思想的话得跟着JS来走。我想在后面的代码中拿到db,有点类似JS中使用await的方式。最后用了两个库,wasm-bindgen-futures主要使用了spawn_local这个东西,另外还有futures-channel这个库。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
spawn_local(async {
let (tx, rx) = oneshot::channel::<i32>();
// 省略中间代码
let on_success = Closure::once(move |event: &Event| {
// Extract database handle from the event
let target = event.target().expect("Event should have a target; qed");
let req = target
.dyn_ref::<IdbRequest>()
.expect("Event target is IdbRequest; qed");

let result = req
.result()
.expect("IndexedDB.onsuccess should have a valid result; qed");
assert!(result.is_instance_of::<IdbDatabase>());

let db = IdbDatabase::from(result);
let _ = tx.send(db);
});
open_request.set_onsuccess(Some(on_success.as_ref().unchecked_ref()));
on_success.forget();

// 等于这块回一直阻塞,直到拿个值。
let db = rx.await.unwrap();
// 省略剩余代码
})

futures-channel这个库的用法跟Rust自带那个有点类似,不过只用这个是可以工作的。后面的问题基本上是IndexedDB的用法问题,最后终于可以把数据成功插入了,虽然再次插入数据时会有写入失败的问题,不过这个已经是后面的事了。

当我费了很多周折,成功写入时,也就是写这篇文章时候,突然间发现可以直接用下面的方式写,如果只是要写入数据的话。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
let window = web_sys::window().unwrap();
let idb_factory = window.indexed_db().unwrap().unwrap();

let open_request = idb_factory
.open_with_u32(String::from("todo").as_str(), 1)
.unwrap();

let on_upgradeneeded = Closure::once(move |event: &Event| {
let target = event.target().expect("Event should have a target; qed");
let req = target
.dyn_ref::<IdbRequest>()
.expect("Event target is IdbRequest; qed");

let result = req
.result()
.expect("IndexedDB.onsuccess should have a valid result; qed");
assert!(result.is_instance_of::<IdbDatabase>());
let db = IdbDatabase::from(result);
let store: IdbObjectStore = db.create_object_store(&String::from("user")).unwrap();
let _index = store
.create_index_with_str(&String::from("name"), &String::from("name"))
.expect("create_index_with_str error");
});
open_request.set_onupgradeneeded(Some(on_upgradeneeded.as_ref().unchecked_ref()));
on_upgradeneeded.forget();

let on_success = Closure::once(move |event: &Event| {
// Extract database handle from the event
let target = event.target().expect("Event should have a target; qed");
let req = target
.dyn_ref::<IdbRequest>()
.expect("Event target is IdbRequest; qed");

let result = req
.result()
.expect("IndexedDB.onsuccess should have a valid result; qed");
assert!(result.is_instance_of::<IdbDatabase>());

let db = IdbDatabase::from(result);
let transaction = db
.transaction_with_str_and_mode(&String::from("user"), IdbTransactionMode::Readwrite)
.expect("transaction_with_str error");
let store = transaction
.object_store(&String::from("user"))
.expect("store error");

let name = JsValue::from_str(_content_element.value().as_str());
let add_request = store
.add_with_key(&name, &JsValue::from("name"))
.expect("add error");

let on_add_error = Closure::once(move |event: &Event| {
console::log_1(&String::from("写入数据失败").into());
console::log_1(&event.into());
});
add_request.set_onerror(Some(on_add_error.as_ref().unchecked_ref()));
on_add_error.forget();

let on_add_success = Closure::once(move |event: &Event| {
console::log_1(&String::from("写入数据成功").into());
});
add_request.set_onsuccess(Some(on_add_success.as_ref().unchecked_ref()));
on_add_success.forget();
});
open_request.set_onsuccess(Some(on_success.as_ref().unchecked_ref()));
on_success.forget();

总结

至此算是把IndexedDB的问题解决了,如果想把操作封装起来,那么可以用前面的方式。只是简单的操作的话,后面的代码也是可以的。到目前为止,基本上还没有写网页的东西,Yew的东西,也就只是搭了个架子时有用到,并没有深入的使用。后面得需要好好的深入下,Yew这个框架跟传统的还不太一样,用trunk serve启动后,实际会有一个WebSocket在前后端之间通讯。

再谈谈Rust学习的东西,实际上《Rust权威指南》这本书我只是看了个大概,而目前的实验项目中并没有使用Rust去写很多逻辑的代码,更多的是调用。用Rust写前端这个方式,起步会比一般的难点,主要还是牵涉的东西不只是Rust的问题,还有Wasm以及JS相关的东西,最重要的还是要明白他的运行环境是浏览器中Wasm虚拟机。不过后续如果有大量成熟的库,可以解决这些基础问题的话,只写网页的话,难度会小些。

虽然项目整体进展不大,也走了一些弯路,不过却也让我思考到了更多的东西。现在感觉Rust是未来全栈程序员必备的语言,这门语言是非常考验你是不是一个合格的程序员,因为他既有很底层的东西,比如内存概念,也有很现代的语法,但前提是你需要都懂,对于那些不是科班出身又没有好好学习计算机相关的东西人来说,这个确实有难度。

Python平时主要是处理数据用,在Mac上使用很方便,最近想把一个程序让下面人用,他们是Window环境,Python他们并不熟悉,所以直接把源文件丢过去,他们又得折腾一番。我就想到打包成exe,其实很早就有这样的想法,但一直没有做过。用的是PyInstaller这个库,还想着这么简单,结果一打包,发现并不是Window下的exe,原来是我理解的错误,以为可以直接打包成Window上执行的exe了,实际上PyInstaller只是在不同平台上将python打包成对应平台的应用了。难道我又得找台Windows,环境弄好,再打包一个?真麻烦,想着可以交叉编译吗?查了下PyInstaller以前好像可以,后来就去掉了。之前用过点Go语言,就是可以交叉编译的,一时间我尽有想用Go重写那个功能的想法,但是一对比还是Python方便。最后找了一个通过docker进行打包的方案docker-pyinstaller

直接这个命令就可以打包了,具体可以参考文档。

1
docker run -v "$(pwd):/src/" cdrx/pyinstaller-windows

但是有个问题,打包出来的应用一执行,提示一个依赖的库,没有找到。不对啊,文档中明明说把依赖写在requirements.txt文件里,我也写了的。

文档中的这段话,看着没有其他的操作,也没有相关的示例代码。

If the src folder has a requirements.txt file, the packages will be installed into the environment before PyInstaller runs.

折腾了好几个小时,最后找到了一篇日文的文章,看完后豁然开朗,原来是写法的问题,我对docker的一些东西不够熟吧。

1
2
3
4
5
docker run --rm -v "$(pwd):/src/" cdrx/pyinstaller-windows -c \
"pip install -r requirements.txt && \
pyinstaller main.py --onedir --onefile --clean && \
mv dist/main.exe main.exe && \
rm -rf __pycache__/ build/ dist/ main.spec"

最后打包后的exe就没有依赖缺失的问题了。不过打包成exe后,我最终还是改了下python的代码,比如os.path.dirname(__file__)是无法使用了,另外打开文件时提示UnicodeDecodeError: 'gbk' codec can't decode byte的错误,需要加encoding='utf-8'

参考

Docker環境のPyInstallerでキレイにExe化する

最近在看WebAssembly的东西,觉得Rust写基于WebAssembly的Web应用好像是不错的选择。另外Rust这门语言,很早就听说的了,后来也买了《Rust权威指南》这本书,只是简单的翻了翻,并没有做过什么项目。于是就起了个备忘录的简单项目。

Yew

有点类似React,用起来还是比较顺手了。目前版本迭代还是相当的快,我一开始用的0.19.0的版本,最后因为一个库的关系,直接选择了最新的版本,版本之间还时存在兼容问题。我打包后有2M,相比传统的还是比较大了,不过可能是是没有优化的结果。在代码里面写Html,主要的问题是格式化的问题,vscode里面一格式化代码后,html的代码就乱了。

问题

Yew本身提供的文档还可以,有问题看看文档也能解决。主要的问题是wey_sys的一些库的使用,比如我想使用IndexedDB这个东西,其实JS版的文档很好看懂,Rust版的就有点不知所措,主要还是我对Rust了解不够,再加上这些库并没有特别全的文档,所以折腾半天。比如设置IndexedDB回调的问题,参数显示js_sys::Function,我想着那就构造个Function出来,结果翻遍文档没有看到如何构造的例子,最后解决方案是闭包转Function的方式,一开始也是报错,提示闭包已久被销毁了,后来发现官方的例子有点问题,最后修复了。

另外因为要在几个页面间共享一些数据,想着写个单例吧,发现不那么简单,找了个lazy_static这样库,提示*mut u8` cannot be shared between threads safely这样的错误,最终还是停了下来。

总结

Rust的学习曲线比较陡,主要是很多语法不同于传统的C体系,所以感觉原来的经验无法施展,而且沿着经验的思路反而会比较绕,发现还是得按Rust的方式解决问题。