ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Flutter双端开发全流程实战:从技术选型到上架

Flutter双端开发全流程实战:从技术选型到上架 手里只有一份产品需求既要在 App Store 上架又要在各大应用商店铺开团队却只有两三个客户端同学——这是最近两年我被问得最多的一类情况。我自己的解法很直接上 Flutter把 UI、业务逻辑、数据层全部收拢到一份 Dart 代码里平台相关的部分用官方插件或者方法通道补齐。Flutter 双端开发这件事真正省下的不是写两遍界面的那点时间而是需求评审、联调、回归测试、发版节奏这四条线上反复对齐的沟通成本。同一份逻辑iOS 和 Android 跑出来几乎一致测试同学只需要写一套用例产品验收也只需要过一遍流程这才是它最值钱的地方。这篇文章面向的是准备用 Flutter 承接正式商业项目的人可能是刚从原生转过来的开发可能是小团队里身兼数职的技术负责人也可能是接过一个半成品 Flutter 工程、被一堆 Gradle 和 Pod 报错卡住的同学。我会按真实项目的推进顺序来讲——从技术选型、环境搭建、网络与数据层设计到双端差异处理、打包上架再到上线之后的性能与稳定性看护。这里面有我踩过的坑、有被审核打回的原因、也有反复调优后沉淀下来的参数和配置。不追求面面俱到只讲那些真正会卡住你的环节。1. 技术选型与整体架构一套代码究竟覆盖到哪一层先把预期对齐这是所有后续决策的前提。很多人对一套代码搞定双端的理解停留在界面写一次实际上 Flutter 能覆盖的范围比这大得多但也确实有边界。搞清楚哪些能共用、哪些必须分平台写后面架构怎么做、目录怎么分、插件怎么选答案就自然出来了。1.1 可复用边界哪些代码能共用哪些必须分平台我一般把整个 App 拆成五层来看判断标准是这一层是否依赖系统能力。层次是否可共用说明UI 与交互完全共用Widget 渲染由 Skia/Impeller 自绘双端像素级一致业务逻辑完全共用状态管理、表单校验、路由跳转全部 Dart 实现数据模型与序列化完全共用JSON 解析、数据库实体、DTO 转换网络与缓存基本共用Dio、本地数据库都是纯 Dart 或跨端插件系统能力需要分平台推送、支付、权限、后台任务、文件选择、分享前四层占到项目代码量的八成以上这也是 Flutter 能省下大量人力的根本原因。第五层看着杂但实际写起来通常只占几百行而且绝大多数场景都有成熟插件兜底真正需要自己写平台通道的情况并不多。我做过的几个项目里需要手写 MethodChannel 的场景无非这么几类对接了只在某一端有的 SDK比如某个安卓渠道的联运登录、需要读取特殊的设备标识、需要对系统相册做定制化的批量处理。这些代码的共性是要么只在一端存在要么两端 API 差异太大没有统一抽象。遇到这种情况我的习惯是先在一端写通再抽一个统一接口让另一端的实现保持同样的方法签名调用方完全无感。2. 项目骨架怎么搭目录分层与环境配置从零开始的项目目录结构定错了后面改起来很痛苦。我头两年吃过这个亏——所有代码堆在 lib 下面按功能散着放做到中期发现同一份订单数据在三处被重复解析改一个字段要动十几个文件。后来统一换成按职责分层 按业务分模块的混合结构扩展性和可读性都稳了很多。2.1 目录分层方案lib/ main.dart 入口只负责初始化与 runApp app/ app.dart 根 Widget、主题、多语言 router.dart 路由表集中管理 core/ network/ Dio 实例、拦截器、统一响应体 storage/ 本地数据库、偏好设置 utils/ 格式化、校验、扩展方法 constants/ 常量、环境变量 features/ order/ data/ 数据源、DTO、Repository 实现 domain/ 实体与用例 presentation/ 页面、组件、状态管理 profile/ ... shared/ widgets/ 跨模块复用的组件 extensions/这么分的好处很直接新同学进来找代码基本不用问人。要找订单列表页去 features/order/presentation要找订单接口解析去 features/order/data。跨模块复用的东西一律进 shared谁都不许在 features 之间互相 import 对方的内部文件这条规则我用 lint 规则强制卡住时间长了团队就形成习惯了。环境变量这块我推荐用--dart-define而不是把配置写死在代码里。构建命令里带上环境标识代码里用String.fromEnvironment读取一份代码同时适配开发、测试、预发、生产四套后端地址不需要在提交前手动改配置。踩过的坑是很多人把环境判断写成kReleaseMode结果预发包也是 release 构建直接连到了生产环境这种事故排查起来非常费时。# 构建时注入环境 flutter build apk --dart-defineAPP_ENVstaging \ --dart-defineAPI_BASEhttps://api-staging.example.com// 读取时给默认值避免漏传导致崩溃 const appEnv String.fromEnvironment(APP_ENV, defaultValue: dev); const apiBase String.fromEnvironment(API_BASE, defaultValue: https://api-dev.example.com);注意--dart-define的值会被编译进产物绝对不要往里放密钥、私钥这类敏感信息。有同学图省事把签名口令塞进去等于把钥匙贴在门上。2.2 依赖版本锁定策略Flutter 项目最容易出的问题不是业务代码而是依赖。我见过一个项目因为某个插件没有锁版本某天作者发了个 breaking change第二天 CI 直接全线红掉。做法很简单pubspec.yaml里主干依赖用精确版本号而不是^范围同时把pubspec.lock提交进版本库。升级依赖单独开分支跑完回归再合。dependencies: flutter: sdk: flutter dio: 5.7.0 drift: 2.20.0 provider: 6.1.2 path_provider: 2.1.4另外Flutter SDK 本身也要锁。团队里每个人的 Flutter 版本不一致会出现我这能跑你那报错的经典问题。我的做法是项目根目录放一个.fvmrc或者在 README 里写死推荐版本CI 上固定同一个版本减少一半以上的无效排查。Dart SDK 版本约束写在environment段里让不合规的版本在pub get阶段就报错而不是编译到一半才炸。3. 开发环境搭建安装顺序与高频报错逐条拆解环境这一关说实话是新手劝退率最高的地方。Flutter 本身安装不复杂麻烦的是它串起了 Android SDK、JDK、Gradle、Xcode、CocoaPods 这一整套工具链任何一环版本不对都会报出一堆让人看不懂的错。我总结的顺序是先装最低层的再往上叠这样出问题时排查范围可控。3.1 三件套的安装顺序Windows 或 macOS 上我的推荐顺序是先装 JDK 17Android 工具链现在普遍用 17装 8 或 11 会出现 Gradle 版本不兼容的提示。再装 Android Studio装的时候勾上 Android SDK、SDK Platform-Tools、SDK Command-line Tools。然后解压 Flutter SDK 到不含空格和中文的路径把flutter/bin加进 PATH。最后跑flutter doctor -v按它给出的缺项一项项补。flutter doctor -v这个命令我建议养成习惯环境出任何问题先跑一遍它会明确告诉你哪一项缺失、装在哪。比在论坛里翻半天靠谱得多。macOS 上还多一层Xcode 和 CocoaPods。Xcode 装完第一件事是跑sudo xcode-select --switch指到正确的路径然后xcodebuild -runFirstLaunch把组件装全。CocoaPods 我推荐用官方安装方式而不是某些一键脚本因为一键脚本装的版本往往和 Xcode 版本对不上后面 pod install 会莫名报错。# 检查环境 flutter doctor -v # 查看当前使用的 Flutter 版本与渠道 flutter --version flutter channel # 清理构建缓存改完配置后必做 flutter clean flutter pub get3.2 高频报错速查表下面这张表是我这几年记录下来的基本覆盖了新手会撞到的大部分墙。每一条我都写过对应的处理方式可以直接照着试。报错关键词触发场景处理方向unable to find suitable Visual Studio toolchainWindows 上编译含原生代码的插件或构建桌面端安装 Visual Studio 2022 并勾选使用 C 的桌面开发工作负载You are applying Flutters main Gradle plugin imperatively在 app 级 build.gradle 里用 apply plugin 方式引入 Flutter 插件改到 settings.gradle 的 plugins 块里声明Could not resolve com.android.tools.build:gradleGradle 版本与 AGP 不匹配对齐 gradle-wrapper 与 AGP 版本Execution failed for task :app:processDebugManifest依赖里存在重复的 application 节点检查依赖中的 manifest 合并冲突CocoaPods could not find compatible versionsiOS 依赖版本冲突更新本地 pod 仓库索引后重新 installLost connection to device真机调试时 USB 连接不稳换数据线、改用无线调试、或重启 adb 服务构建卡在 Running Gradle task首次构建需要下载依赖配置国内镜像加速耐心等待unable to find suitable Visual Studio toolchain这个报错特别容易误导人很多人看到 Visual Studio 就以为是 VS Code 没装好。实际上它指的是微软那套带 C 编译器的完整 IDE跟写 Dart 的 VS Code 完全是两码事。装的时候注意勾选工作负载只装默认的 .NET 部分是不行的。至于 Gradle 插件声明方式的报错本质是 Flutter 换了一套插件引入机制。老教程里写的是// 老写法新版本会告警甚至报错 apply plugin: com.android.application apply from: $flutterRoot/packages/flutter_tools/gradle/flutter.gradle新写法应该把插件放进 settings.gradle// settings.gradle plugins { id dev.flutter.flutter-plugin-loader version 1.0.0 id com.android.application version 8.6.0 apply false id org.jetbrains.kotlin.android version 2.0.20 apply false }// app/build.gradle plugins { id com.android.application id kotlin-android id dev.flutter.flutter-gradle-plugin }改完记得flutter clean否则旧的构建缓存还会带着老配置跑。这个坑我踩过两次第一次排查了整整一个下午后来就养成了改 Gradle 配置必清缓存的习惯。3.3 编辑器怎么选VS Code 与 Android Studio 的分工这两个我都在用不是二选一的关系。日常写 Dart 代码我基本全在 VS Code启动快、插件轻、快捷键顺手装好 Flutter 和 Dart 两个扩展就能跑起来。涉及 Android 原生部分——改 Manifest、调 Gradle、看 Logcat、用 Layout Inspector——我会切回 Android Studio这些功能在 VS Code 里体验差很多。Android Studio 还有个细节值得说它自带中文语言包在 Plugins 里搜一下就能装对刚接触的同学有一定帮助。不过我要提醒一句Android Studio 的索引在有大型工程时挺吃内存机器内存低于 16G 的话建议调高gradle.properties里的堆内存参数否则一边跑模拟器一边写代码会明显卡顿。VS Code 这边则要注意工作区设置关闭不必要的文件监听能显著降低 CPU 占用。4. 网络层与数据层双端共用的核心底座界面写得再漂亮底座不稳整个项目都会崩。网络层和数据层是 Flutter 项目里最能体现架构水平的部分也是双端共用率最高的部分值得花时间设计好。我的原则是网络层统一出口数据层统一入口业务层永远不直接碰底层实现。4.1 Dio 封装与拦截器设计网络库我基本只用 Dio原因是拦截器机制足够灵活取消请求、超时配置、上传下载进度这些都支持得比较完整。封装的目标是让业务方调用时只关心我要什么数据不关心 token 怎么带、错误怎么转、loading 怎么关。class ApiClient { late final Dio _dio; ApiClient() { _dio Dio(BaseOptions( baseUrl: apiBase, connectTimeout: const Duration(seconds: 10), receiveTimeout: const Duration(seconds: 20), headers: {Content-Type: application/json}, )); _dio.interceptors.addAll([ _authInterceptor(), _logInterceptor(), _errorInterceptor(), ]); } Interceptor _authInterceptor() InterceptorsWrapper( onRequest: (options, handler) async { final token await TokenStore.read(); if (token ! null) { options.headers[Authorization] Bearer $token; } handler.next(options); }, onError: (err, handler) async { if (err.response?.statusCode 401) { // 刷新 token 后重试注意加锁避免并发重复刷新 final ok await TokenStore.refresh(); if (ok) { final retry await _dio.fetch(err.requestOptions); return handler.resolve(retry); } } handler.next(err); }, ); Interceptor _errorInterceptor() InterceptorsWrapper( onError: (err, handler) { final mapped ApiException.fromDio(err); handler.reject(DioException( requestOptions: err.requestOptions, error: mapped, )); }, ); }这段代码里有三个地方是我反复调过才定下来的。第一超时时间不能设太短移动网络下 5 秒经常不够10 秒连接超时是个比较平衡的值。第二token 刷新必须加锁否则页面同时发五个请求五个都收到 401会触发五次刷新服务端那边可能直接把这个账号拉黑。第三错误要统一转成自定义异常类型让 UI 层只需要处理ApiException不用去认识DioException那一堆字段。接口联调阶段我习惯在本机起一个抓包工具把测试机的网络代理指过去核对实际发出去的请求头、请求体和服务端返回。这一步能省下大量我传的参数是对的的扯皮时间。要注意的是自签证书在 Android 高版本上默认不被信任需要在 manifest 里配置调试用的网络安全规则只在 debug 构建里开启release 包千万不要带。4.2 本地数据库选型与后端同步Flutter 里做本地持久化我不建议直接裸写 sqflite手写 SQL 维护成本太高。可选的方案大概三类方案特点适用场景sqflite官方同源纯 SQL控制力最强表结构简单、查询高度定制Drift基于 sqflite 的类型安全 ORM编译期校验表多、关系复杂、需要迁移管理IsarNoSQL 风格读写极快以对象为中心、查询模式灵活我目前项目里用得最多的是 Drift因为它把 SQL 的类型安全做到了编译期字段改名、类型改错在写代码时就会报错不用等到跑起来才发现。迁移也有明确的版本管理机制用户从老版本升级上来不会丢数据。本地库和后端同步这块是真正的难点我踩过的坑基本都集中在这里。先说结论我采用的是本地为主、增量同步、版本号仲裁的策略每条记录带一个updatedAt服务端时间戳和一个本地是否待同步的标记App 启动和网络恢复时拉取增量数据用时间戳比对决定覆盖方向。Futurevoid syncOrders() async { final lastSync await SyncMeta.read(order); final remote await api.fetchOrdersSince(lastSync); await db.transaction(() async { for (final dto in remote.items) { final local await db.orderDao.findById(dto.id); if (local null) { await db.orderDao.insert(dto.toEntity()); } else if (dto.updatedAt.isAfter(local.updatedAt)) { await db.orderDao.update(dto.toEntity()); } // 本地更新更晚则不覆盖等待下次推送 } await SyncMeta.write(order, remote.serverTime); }); }这里有几个容易忽略的点。时间戳一定要用服务端返回的时间不能信任客户端本地时间用户改一下手机时间整个同步逻辑就乱了。另外增量拉取要有分页上限服务端接口必须支持按时间游标分页否则首次全量同步遇到几万条数据直接把内存打满。还有冲突处理我的策略是服务端优先因为大部分业务场景下服务端是权威数据源本地未推送的修改会先在下次推送时尝试失败再弹提示给用户避免静默丢数据。4.3 Isolate 与内存优化数据量大到一定程度主线程一定会卡。Dart 是单线程事件循环模型所有 UI 渲染和业务逻辑都跑在 main isolate 上一个耗时的 JSON 解析就能让列表滑动掉帧。我的处理原则很简单任何超过 16 毫秒的操作都往外挪。compute是最省事的方案适合一次性的重计算比如解析一个几千条记录的响应体// 把大 JSON 解析丢到后台 isolate final orders await compute(_parseOrders, response.data); ListOrder _parseOrders(String raw) { final list jsonDecode(raw) as List; return list.map((e) Order.fromJson(e)).toList(); }如果是要长期驻留的后台任务比如持续监听推送、跑定时同步那就得自己起一个长生命周期 isolate通过 SendPort/ReceivePort 通信。要注意 isolate 之间不共享内存传过去的大对象会被复制传几百 KB 的字符串还行传几 MB 的二进制就会明显变慢。真需要传大块数据用 TransferableTypedData 能做到零拷贝。内存优化方面我总结下来最有效的三件事一是列表必须用ListView.builder而不是一次性 build 所有 item长列表还要配cacheExtent控制预渲染范围二是图片一定要限制解码尺寸cacheWidth和cacheHeight设成实际显示尺寸一张 4000 像素的原图直接解码能占几十 MB 内存三是页面销毁时记得取消订阅和释放控制器Dio 的 CancelToken、StreamSubscription、AnimationController 都是常见的泄漏点。用 DevTools 的 Memory 面板跑一遍看曲线能不能稳在一个水平线上会不会持续爬升一眼就能看出有没有泄漏。5. 双端差异处理那些一套代码覆盖不到的地方做到这一步业务基本能跑了但离能上架还有距离。剩下的工作量不大却最费神因为每一条都是平台特有的出的问题也千奇百怪。5.1 权限、分享与文件路径的差异权限是双端差异最明显的地方。Android 从某个版本开始把存储权限拆成了细分类型读媒体文件、写媒体文件、访问所有文件是三套不同的权限申请方式也不一样。iOS 这边则是必须在 Info.plist 里预先声明用途描述否则一调用就闪退而且审核时会核对描述文字和你实际用途是否匹配写得太含糊会被打回。能力Android 侧iOS 侧相册读取按媒体类型细分权限可在设置中单独撤销Info.plist 声明用途系统弹窗只能弹一次文件选择通过系统文件选择器返回 content:// URI通过文档选择器返回安全作用域 URL分享Intent 转发可指定目标应用系统分享面板需提供弹出锚点坐标后台任务受厂商省电策略影响较大后台执行时间受限需申请特定类型文件分享这块有个细节值得展开。Android 新版本禁止直接分享file://路径给其他应用会抛 FileUriExposedException必须通过 FileProvider 生成content://形式的 URI 并配置可访问路径。iOS 则涉及安全作用域书签直接保存文件路径到下次启动再读是读不到的需要先调用访问授权。这两个差异如果不处理表现就是分享按钮点了没反应或者保存的文件找不到了非常难排查。系统分享在 Flutter 里我一般用 share_plus双端都能调起原生面板。iOS 上有个小坑iPad 上分享面板是 popover 形式必须给一个锚点矩形不给的话在 iPhone 上正常、在 iPad 上直接崩溃。这种只在特定机型上才出现的问题测试的时候一定要覆盖到。5.2 真机调试与签名配置模拟器能跑通不代表真机能跑通这是铁律。iOS 真机调试需要开发者账号、设备 UDID 加入描述文件、Xcode 里配置签名团队这套流程第一次走会花不少时间。我建议的做法是先在 Xcode 里打开ios/Runner.xcworkspace把签名配置好确认能跑起来再回到 Flutter 侧跑flutter run。直接在 Flutter 里跑而不配 Xcode报的错会非常模糊。Android 真机相对省事打开开发者选项和 USB 调试就能连。有些机型还需要额外打开USB 安装权限否则 adb 装不上包。无线调试现在也成熟了同一局域网下配对一次就能摆脱数据线对调试体感提升很大。设备列表用flutter devices看连不上的时候先跑adb devices确认底层能不能识别能识别但 Flutter 看不到通常是 IDE 里的设备缓存问题重启 IDE 就好。6. 打包上架全流程从构建产物到商店过审打包这一步我愿意把它称为全流程里最容易被低估的环节。代码写得再好包传不上去、审核被打回项目就是没交付。我把两端分开讲最后再统一说审核。6.1 Android 侧签名、混淆与构建Android 打包前必须做的一件事是生成正式签名。千万别用 debug 签名去传包很多商店会直接拒绝。生成后把 keystore 文件放在项目外口令写在本地配置文件里并加入 gitignoreCI 上通过环境变量注入。# 生成签名文件 keytool -genkey -v -keystore release.jks \ -keyalg RSA -keysize 2048 -validity 10000 \ -alias release # 构建 AAB推荐商店按设备下发对应的资源包 flutter build appbundle --release \ --dart-defineAPP_ENVprod # 构建 APK需要自行分发时用 flutter build apk --release --split-per-abi--split-per-abi这个参数值得单独说。它会按 CPU 架构分别产出安装包用户下载到的包体积能小将近一半。代价是你需要上传多个包到分发平台或者只上传主流的那个。我一般的做法是商店渠道用 AAB 交给商店自己拆分自有渠道用 split-per-abi 单独分发。混淆和压缩也要开。Flutter 的 Dart 代码在 release 构建下本身就会做 AOT 编译和符号剥离Android 侧的 Java/Kotlin 部分需要在 Gradle 里打开 minifyEnabled 和 shrinkResources并且写好 keep 规则。有个坑是很多插件依赖反射混淆后运行时报找不到类表现是功能正常但某个页面一点就崩。上线前的回归测试一定要覆盖到所有接了原生 SDK 的功能点。6.2 iOS 侧证书、描述文件与分发iOS 的签名体系相对繁琐但理清之后就那几样东西证书证明你是谁描述文件说明你能把 App 装到哪些设备上App ID 标识这是哪个应用。三者匹配上签名才能通过。现在 Xcode 的自动签名已经能处理大部分场景我建议中小团队直接用自动签名省去手动导证书的麻烦。只有涉及多团队协作、或者要用特定的企业分发方式时才需要手动管理。打包流程是先在 Xcode 里 Archive然后通过 Organizer 分发到 App Store Connect接着走 TestFlight 内测通过之后再提交正式审核。TestFlight 这一步我强烈建议不要跳过。它能让真实用户在真实网络环境下跑一遍暴露出来的问题往往比内部测试多得多。测试期我一般留三到五天观察崩溃率和用户反馈再决定是否提交正式审核。提交前记得填好隐私清单说明收集了哪些数据、用途是什么这块填错是近期被打回的高频原因。6.3 常见审核打回原因与应对下面这几条是我实际遇到过或者身边同行反馈最多的打回原因具体表现处理方式权限用途描述不清晰相册权限描述写成需要此权限明确写清楚用于什么功能比如用于上传头像功能不完整或存在占位内容页面里有敬请期待字样提审前清理所有未完成页面引导外部支付界面出现站外支付入口按平台规则调整虚拟商品走内购崩溃审核员操作时闪退提审前用 TestFlight 版本做一轮真机全流程回归隐私政策缺失没有可访问的隐私政策链接在应用内和商店后台都提供有效链接有个经验值得分享审核被拒时回复要礼貌且具体逐条说明你做了什么修改不要情绪化。绝大多数情况下沟通清楚就能过。如果确实是理解偏差说明设计意图并附上截图比反复提交同一版本有效得多。7. 上线之后性能看护与稳定性包传上去只是开始真正的考验在用户手里。我在两个项目上见过上线首日崩溃率飙升的情况原因分别是某个机型的内存溢出和第三方 SDK 的初始化顺序问题都是本地测试环境复现不出来的。7.1 启动速度、包体积、内存三项硬指标我给自己定的目标是冷启动在三秒内包体积控制在五十 MB 以内内存峰值不超过两百 MB。这三个指标不是拍脑袋定的而是根据用户留存数据反推的——启动每慢一秒次日留存会有明显下滑。启动优化最有效的三件事一是把非必要的初始化延后到首页渲染之后比如统计 SDK、推送注册不要都堆在 main 函数里二是首屏避免读本地数据库大表用缓存的轻量数据先把框架渲染出来三是减少启动时的网络请求把必须的请求做并发并设置短超时不要串行等。包体积优化主要靠资源瘦身和依赖清理。图片按需提供多倍图而不是一张超大的图字体文件只保留实际用到的字符集发布前跑一遍依赖分析把没用的包删掉。我之前清理过一个项目光是一个没用到的大体积地图 SDK 就占了几十 MB。7.2 崩溃监控与灰度发布崩溃监控我是必接的没有监控等于闭着眼睛开车。选型上要确认它支持 Flutter 侧的异常捕获光接原生那套只能抓到平台异常Dart 层的错误是看不到的。接入后记得配置符号表上传否则堆栈全是乱码等于白接。void main() { FlutterError.onError (details) { FlutterError.presentError(details); CrashReporter.recordFlutterError(details); }; PlatformDispatcher.instance.onError (error, stack) { CrashReporter.recordError(error, stack); return true; }; runZonedGuarded(() runApp(const MyApp()), (error, stack) { CrashReporter.recordError(error, stack); }); }灰度发布是降低风险的另一种手段。Android 侧可以通过自有渠道先给小批量用户或者用商店提供的分阶段发布功能。iOS 侧可以先用 TestFlight 扩大测试范围。我的习惯是上线后头两天盯紧崩溃率和核心接口成功率一旦异常立刻停量排查不要抱着再看看的心态早停一小时能少很多一星差评。最后说点个人的。做 Flutter 这几年最大的体会是它确实能让小团队做出双端体验一致的产品但它不是银弹。平台差异、原生能力、性能边界这些事情该学的还是得学只是学的方式从精通两个平台变成了知道边界在哪、知道怎么调原生。我现在的习惯是每个项目都留一份踩坑文档把环境问题、插件坑、审核反馈都记下来下个项目开工前先翻一遍能省掉至少三成的重复劳动。另外面试里常被问的 isolate 通信、状态管理选型、渲染原理这些问题本质上都是在考察你对这套代码到底怎么跑起来的理解把这些工程项目做扎实了答案自然就有了。
返回列表