September 30, 2023

React Native 使用报告

最近邀请我参加一个七牛云的校园编程竞赛,我觉得自己能学到些什么,所以就半同意下来了。

昨天晚上三个人简要谈了谈之后,说是要决定一下选什么框架好,目前就两个方向 —— React NativeFlutter,前者是我只听说过但没实际了解的东西,后者则是自去年 11 月份就在学的东西。

于是昨天配置好 RN 的基础环境后,今天就看教程去了,但是嘛,越看就越觉得 RN 不好用,我来具体分析一下。

语言不行

首先,RN 使用 JS/TS 语言来编写程序,而前者 JS 是我认为最令人难受的语言(PHP 好像也挺垃圾的),它的变量类型可以说是一塌糊涂,动态类型 Python 也有,但 Python 我就用来写点小东西,要我用它开发程序 …… 我也不是没写过,WordCloud 就是用 Python 写的,体验也是非常糟糕。除此之外我还非常讨厌它有 ===== 的区别,隐式转换是坏文明。

后者 TS 我就直白的说,我看不懂!Python 里加入类型检查的方法是在变量后面冒号然后一个类型,而且重要的是,即便没给全部的变量都加上类型,程序仍然能跑起来,相当于只是给个 lint 提示一下,但 TS 要写类型就得全部写完,类型还不直观,之前学 React 的时候想转 TS 都转不了,后面对 React 就没多大兴趣了。

再来聊聊 Flutter 使用的语言 Dart,这是我用的最喜欢的一门语言。

Most Used Languages

首先,他非常像 Java,而我曾经写过一段时间的 Java 程序,所以很快就上手了。而且相比 Java,它有空安全检查,也就是一个变量只有在其类型后面接上个问号时才能赋值为 null。相比之下,Java 就没有这样的要求,就容易抛出空指针错误。它的语法比 Java 简约,new 关键字不再是必要的。它还有动态类型,所以动态类型的好处 Dart 也有。

其次,我非常喜欢它的 extension 方法,可以在已有的一个类(如 DateTime)里,注入自己需要的一些便捷方法,这是非常好的,pub.dev 上还有一个包专门收录了许多的 extension,极大方便了开发。

框架不行

包管理

ReactReact Native 都会搞出一个极大的 node_modules 文件夹,这是非常屎的一个设计,我就不多吐槽了,npmyarnpnpm 都是为了解决这个烂玩意的,见 此博客

RN 一个令我难受的点是,其 core 核心组件太少了,像是 Navigation 导航都要引入一个包,听说之前它是在 core 里的,之后被分离出去了 …… 嗯 ……

再来聊聊 Flutter 的包管理,它没有什么外置的包管理器,要么一句 flutter pub add <包名> 解决,要么自己在 pubspec.yaml 文件里添加一行 <包名>: <版本号> 就行,甚至版本号都不填,留个冒号在那里,Flutter 自己会获取最新且兼容的包,一手包办的感觉太爽了。

Flutter 的包是放在哪的呢?在 %LOCALAPPDATA%\Pub\Cache\hosted 下,它按源的不同,分多个文件夹,一般来说是 pub.dev 文件夹和 pub.flutter-io.cn 文件夹,进 pub.dev 文件夹能看到很多包的不同版本的文件夹,就统计下来连 2 GB 都没有。

PS > "{0} MB" -f [math]::round((Get-ChildItem -Path "$env:LOCALAPPDATA\Pub\" -Recurse | Measure-Object -Property Length -Sum).Sum / 1MB, 2)
1301.09 MB

高下立判。

组件设计

ReactRN 的类组件和 Flutter 的组件非常相似,但没有明显区分自己管理状态和无状态的组件,换言之,都是继承自 Component,都有一个 state,只是看你用不用就是了。

相似的点在于,前者的 constructor 相当于后者的 initState,前者的 render 相当于后者的 build,生命周期非常相似。

Flutter 是分了 StatelessWidgetStatefulWidget 的,前者只会要重写一个 build 方法,后者则是可重写整个生命周期。

组件的修改

这是我很不喜欢的一点,所有的,对于一个组件的修改,都要在一个远离组件的一个键值对里配置,这非常割裂。

其实我在学 HTMLCSS 的时候就有这样的感觉,样式为什么要放到另一个文件里?紧靠着不是更方便吗?没办法,网页这样搞是历史原因,React 也是搞网页的所以能理解,但 RN 的话 …… 当我知道它不是在各平台的 WebView 里跑 React 时我就不理解了。

而且写的配置,对 key 是没有 lint 提示的,我鼠标移到上面,它不会提示我这个组件有哪些可以修改的属性,加重了记忆负担。value 更是离谱,绝大多数都是字符串,都是同一个类型,要是打错了怎么办,要是没有代码补全怎么办?

相比之下,Flutter 的组件的配置要更加舒服,在其构造函数里填入配置的属性即可,而且 lint 会显示构造函数的参数和参数类型,还有 dartDoc 显示示例。除此之外,全局的主题配置也是可以的,像是 MaterialApp 就有 theme 属性,给其子组件树应用上。

组件在多平台下的表现

RN 说是一套代码跑多个平台,但我觉得它的表现不尽人意,多平台的表现差距太大。就拿圆形加载器组件举例,大小属性值怎么可以只在安卓有效?

这是受到原生组件的限制导致的,iOS 没有大小属性值 ……

Flutter,实际上也有多平台适配,部分已多平台适配的组件如 AlertDialog 是有个 adaptive 方法的,而且不会有“受原生组件限制”这一说法。

Navigation 导航

我人都麻了,不知道是不是我会错意了,所有进入路由的组件都要受改造,还要在 Navigation 根组件里登记命名路由?

Flutter 哪来那么多事,在 MaterialApphome 属性填初始路由(也就是 "/" 的命名路由),再用 Navigator.push 方法压入未命名的路由,或者和 RN 类似地在 MaterialApp 填命名路由,然后用 Navigator.pushNamed 方法压入命名路由。

灵活性比 RN 强太多了。

我了解不够

这里是我自己的一些问题,以上都是刚接触一天所体验到的,可能了解上面的一些问题也早都有了解决方案。