特性 | Flutter (AutomaticKeepAliveClientMixin) | Vue (keep-alive) |
---|---|---|
缓存粒度 | 页面级别,每个 StatefulWidget 独立缓存 | 组件级别 |
实现方式 | 使用 Mixin 来告知框架保留 State | 指令 管理缓存 |
生命周期管理 | 页面的生命周期完全由开发者控制 | 提供 activated 和 deactivated 钩子 |
使用场景 | TabBarView 等需要频繁切换的场景 | 动态组件切换和性能优化场景 |
性能消耗 | 较低,State 的持久性直接由框架控制 | 缓存的虚拟 DOM 占用更多内存 |
GestureDetector
会直接 拦截手势事件,这意味着它会优先处理用户的手势,而不是将手势事件传递给其他组件。GestureDetector
拦截,那么 PageView
的滑动逻辑就不会被触发。PageView
自己处理了手势事件时,会优先触发它的翻页操作,而不是把手势传递到 WebView。
GestureDetector
的拦截特性加上 PageView
的内部手势监听机制,导致 WebView
的手势被“抢占”,无法生效。Listener
的特点:
Listener
不拦截手势事件,而是监听原始的手势事件(如 PointerDown
、 PointerMove
、 PointerUp
)。PageView
和 WebView
都有机会处理事件。结合 PageView
的方案:
PageView
的默认切换事件(通过 physics: NeverScrollableScrollPhysics()),确保 PageView
不会“抢占”手势。Listener
来监听用户的滑动方向(水平还是垂直),根据滑动方向手动触发 PageView
翻页或将事件交给 WebView。最终的改造逻辑是:
PageView
的默认滑动切换行为。Listener
来捕获手势事件,而不是 GestureDetector
,从而避免拦截。deltaX
大于 deltaY
),手动触发 PageView
的翻页。deltaY
大于 deltaX
),允许 WebView
自然处理滑动事件。PageView
和 WebView
因手势冲突而影响用户体验。GestureDetector
对手势事件的强拦截,确保 WebView
可以正常响应垂直滑动。Listener
的方式更灵活,可以轻松拓展为处理其他类型的手势(如双指缩放等)。WebView
内部缩放),可以进一步细化方向判断逻辑。Listener
是一个较低级的组件,建议在监听逻辑中做更多的优化,避免误触。
自定义动画:当手动切换 PageView
时,可以加入一些切换动画效果(例如使用 PageController 的 animateToPage 方法)。GestureDetector
会直接进行拦截手势事件 而在PageView
默认的切换事件时 PageView
的控件会把手势事件拦截处理 从而导致webview
的手势无法触发
要解决PageView
+ WebView
目前使用方案是 禁用默认的Pageview
切换事件 使用 Listener
对整体进行监听手势事件 而不是进行拦截 webview
的滑动和pageview
的切换逻辑全部改用手写 这样就可以实现功能
在 GetX 中,响应式系统 是通过 Rx 类型(如 RxInt、RxMap 等)来实现数据的自动更新的,但其工作机制与具体的 数据引用 和 响应式对象的通知规则 有紧密的关系。
在 GetX 中,响应式系统的核心原理是通过 观察者模式 监听数据变化。当你修改了 Rx 包裹的数据时,GetX 会自动通知使用了这个数据的 UI 更新。
特殊情况 RxMap
questionDetails['123'] = QuestionDetail(...); // 触发响应式更新
questionDetails['123']!.likeCount.value += 1;
questionDetails.refresh(); // 强制触发响应式更新
使用 RxList 的操作方法触发更新:
最佳实践
class QuestionDetail {
final RxInt likeCount;
final RxInt likeFlag;
QuestionDetail({required this.likeCount, required this.likeFlag});
}
对于对象数组或 RxMap,使用手动 refresh():
修改对象内部属性后,调用 refresh() 触发整体更新。
封装方法修改数据并调用 refresh(),避免在多个地方重复手动刷新。
await GetStorage.init("question_details_box");
final GetStorage box = GetStorage("user_box");
指标 | permanent: true | GetStorage | SharedPreferences |
---|---|---|---|
速度 | ⭐⭐⭐⭐(最快,内存操作 | ⭐⭐⭐(本地缓存+文件) | ⭐⭐(文件存储,速度较慢) |
持久化能力 | 无,数据重启后丢失 | 有,应用重启数据不丢失 | 有,应用重启数据不丢失 |
存储容量 | 受限于内存 | 适合存储小数据(KB 级) | 适合存储小数据(KB 级) |
适合数据类型 | 任何数据结构 | 键值对(支持简单和序列化数据 | 键值对(支持简单和序列化数据 |
生命周期 | 应用运行期间 | 持久化到文件,随时可读取 | 持久化到文件,随时可读取 |
依赖性 | 依赖 GetX | 依赖 GetX | 依赖平台和第三方插件 |
内存占用 | 高,数据在内存中 | 中,依赖文件缓存 | 中,依赖文件缓存 |
TextField
聚焦时弹出键盘,会导致Expanded
和SmartRefresher
布局错乱,错乱表现是
- 键盘弹出导致布局变化 flutter布局会重新计算
- SmartRefresher 的滚动监听受到键盘干扰
- 输入框可能引发多次状态刷新
- 输入框与列表在同一页面竞争焦点 原因是弹出键盘时flutter布局会重新计算 (尤其是 MediaQuery.of(context).viewInsets.bottom 的值发生变化),直接影响 SmartRefresher 的触底机制(底部位置计算发生偏移)
TextField
和 Expanded
+SmartRefresher
拆分开页面,单独用一个SearchResult
结果页去单独展示结果列表,避免这两个组件在一个页面展示,是最安全的解决方案。controller
设计,可以把controller
初始化的时机放在 TextField
,SearchResult
专注于结果展示和触发下拉刷新,上拉加载更多。onChange
事件的value
记录下来,而不是直接对value
进行搜索,保证有输入框时,不渲染SmartRefresher
onSearch
事件前手动关闭键盘,这样最大程度上保证布局不会被键盘弹出所影响。_isloading
进行标记(默认是true
,请求完成且客户端处理完成后变成false
),在build
函数进行判断,若_isLoading
为true时,展示一个空容器(空容器SizedBox.shrink()
基本不占用任何渲染资源;也可以使用骨架屏,但就不需要进度条弹框,两种方案选一种),待客户端处理接口完成后,展示真正需要渲染的Widget
组件。Home
前就开始使用预加载,不管是静态图还是网络图片,都最大程度上保证加载速度。TabBar+TabBarView
;反之使用PageView
,列表组件统一用SmartRefresher
;Getx
的Controller
进行维护(不需要getxStorage
),如果是分类那么用RxMap
(满足列表响应式),对每个分类的数据列表进行处理。每个Map
是由分类的唯一标识 + State
维护,State
包括list
(需要渲染的数据源)、isLoading
(是否在加载中的标记)、isFinished
(当前分类是否加载完毕的标记)、currentPage
(当前页)、pageTotal
(总页数或总数量,最好要求服务端返回)。refresh
刷新和loadMore
加载,区别:loadMore
专注于加载更多,触发时在isLoading
或isFinished
为true
时都需要return
,避免触发过于频繁,之后发起网络请求前让isLoading
为true
,请求完成后isLoading
为false
;refresh
专注于刷新,执行前先把list
请空、currentPage
改为0,isFinished
改为false,之前再发起网络请求;每次网络请求发起后,都去把对应State
的list
补充上,判断当前页是否是最后一页,如果不是让当前页++,如果是让isFinished
变成true
AutomaticKeepAliveClientMixin
缓存页面状态,初始化时先去加载当前的State
,然后渲染当前的State
的list
,遇到isLoading
那么展示加载中的动画,同时处理_refreshController
控制器,对加载完成、加载完毕,执行对应的方法,展示完全定制的Header
和Footer
。content
参数对外暴露,可传入Widget
, 将需要统一的风格全部统一flutter
原生和通过Getx
衍生出的弹框,现主要讨论flutter
原生。loading
分为圆形、和进度条形有不同的使用场景。进度条用OverlayEntry
搭建(有蒙层,展示时不允许点击其他区域),用于初进应用、初进某个页面时进行展示,短时间内不会再出现;圆形的Loading
用CupertinoActivityIndicator
搭建(也可以写Lottie
动画),主要是Widget
组件,是页面展示的一部分,主要用在加载列表、刷新页面时,可以点击其他任一区域。ConFirm
弹框也进行封装,Title
Content
ConfirmText
CancalText
等都是可配置,按钮的点击回调,弹框的整体样式都可定制。Toast
轻提示也进行统一封装,是否展示Icon
Message
position
duration
都对外进行暴露RippleButton
,统一对应用所有的可点击Widget
进行包裹,并对外暴露点击、双击、长按、按下事件token
已经过期,用户仍请求了某些请求后,服务端返回状态码为401,该接口的数据未正常返回。provider
或getx
做一个刷新的标记,将其注入到build
函数中,将这个标记的值变为true
时,重新请求一次接口,注意一定要把接口单独写成一个函数。html
,使用evaluateJavascript
渲染接口返回的js
代码,同时通过 InAppWebView
提供的回调监听 JS 执行结果url
WebView
和Flutter
应用通信,可以使用 WebView 提供的 JS Bridge(如 JavascriptChannel)实现通信。// JS 发送消息给 Flutter
window.flutter_inappwebview.callHandler('methodName', arg1, arg2);
// Flutter 调用 JS
controller.evaluateJavascript(source: "yourFunction()");
Http
Http
类中做出封装,避免在业务代码中写Content-Length
那么在客户端的进度条展示,如果服务端没有该字段,客户端可以用Steam
自行模拟。Future.wait
同时触发多个接口请求,也可以监听每个请求的状态try-catch
或 runZonedGuarded
)防止接口失败导致整体崩溃。Flutter
是单向数据流,不像RN
Uniapp
,所以有些重新需要手动去实现一个数据响应式;场景是父子组件如何数据同步、祖先组件和后代组件的数据同步。比较难实现的是复杂的数据类型,如通过RxMap
维护的长列表,里面的List
中的某个对象里的views
字段在子组件修改了,父组件没有更新。controller
中分别都暴露出一个updateViews
的方法,需要父子组件的controller
中有一个独特的唯一标识如typeId
,进行父子组件内修改views
后同步到另一方。getx
,独特的标记controller
,通过修改该标记的值,触发某方法。PageView
依赖的数据源、搜索历史、动态添加的tabbar
、用户信息、用户的独特配置(主题、语言)等用GetxStoage
进行缓存,从而提高用户的体验。GetxStoage
只能存储键值对,大小应当是kb
,所以需要对存储的数据进行json
化,如果是可修改、可删除、可添加等,还需要对RxList
进行转义处理。sqlLite
暂时未用到config
——配置主题、语言——渲染第一帧等flutter
框架初始化 。mainStart to Flutter-Initialized: 133ms
gpu
等进行监测Logger
,提供四个等级的日志,在输出时添加Tag
当前时间
日志等级
以及对四个等级进行颜色区分 。(Info
warn
error
debug
)Error
等级日志添加stackTrace
,提供上下文和详细调用路径。Error
等级日志,全部过滤,并将Error
等级日志全部强制上传到服务端。GetxStoage
进行缓存,累计到一定条数(20)再进行上报服务端,同时这个方法也提供强制上报的逻辑,用来排查线上用户遇到问题,即使处理。比较指标 | vue2/3(uniapp) | react(react-native) | flutter |
---|---|---|---|
架构模式 | 单向数据流 | 单向数据流 | 单向数据流 |
响应式原理 | vue2通过Object.defineProperty 实现,拦截每一个对象,通过观察者模式,监听每个属性。vue3通过proxy 监听所有的对象,深层次监听,无论是子改父还是父改子都会驱动UI更新 |
没有内建属性监听机制,依赖于状态更新,一般是父子组件通过自定义事件的传递修改状态来驱动UI更新。修改对象时,需要改变对象的引用,而不仅仅是内部属性。setState(()=>{...data,k="key"}) |
通过Widget 树构建UI,状态的变化触发 UI 重建,和react类似,不能仅修改对象的内部属性,要修改对象的引用,才会驱动UI更新。 |
状态管理工具 | vuex pinia |
redux jotai |
getx |
组件通信 | 自定义事件、自定义属性; | props传递; | 自定义事件传递; |
架构设计 | 通过vuex 或pinia |
通过redux jotai |
使用getx 建议每个数据模型的controller ,统一数据控制层,所有用到该数据的页面都用同一套数据来统一管理,使用Obx Rx 实现响应式更新 |
react
/ flutter
在 React 中,UI 是由状态驱动的。无论状态如何发生变化,只要调用 useState
的 setter 方法(函数组件),React 会:
具体流程:
setState
方法。setState
方法更新父组件的状态。在 Flutter 中,UI 由 Widget 树构建,状态变化通过 StatefulWidget
中的 setState
方法触发。和 React 类似,Flutter 也使用“状态驱动 UI”的思想,但它和 React 的根本区别在于:
setState
必须明确告知框架:某些状态已发生变化,重建 Widget 树。Widget.build()
方法重建树。setState
或类似的方法后,Flutter 才会重新执行 build
。如果在 Flutter 中,父组件将某个方法(如 setState
)传递给子组件,并在子组件中调用该方法,这种做法本质上只是调用了一个方法,而不会自动触发 UI 的更新,除非你显式地调用父组件的 setState
。
这是因为:
setState
来触发重建。vue
/ flutter
Rx
系列)在使用时表现得像 Vue 的双向绑定,你可以直接修改数据,UI 就会自动更新。用GetX 实现 的 Vue 系列响应式
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。