尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
Flutter状态管理实战:从setState到Provider的原理与选型
聊到 Flutter状态管理永远是绕不开的话题。我刚开始用 Flutter 写项目的时候觉得 setState 挺好用的页面刷新说干就干完全理解不了为什么社区里为了一个状态管理方案能争得那么厉害。直到某一次我们自己做一个模拟项目涉及登录态、购物车、用户信息、消息角标这些数据散落在不同页面里我才真正被各种传参、手动刷新、状态不同步的问题折腾到怀疑人生也才真正明白“状态管理”到底是来解决什么问题的。这篇文章就把我学习 Flutter 状态管理的完整过程整理出来从 setState 的局限性讲起到用 Provider 跑通一个真实例子再手拆一遍 Provider 的原理最后对比几个主流方案并列出实际项目里最容易踩的坑。适合刚入门 Flutter、或者已经能写几个页面但总感觉代码越来越乱的同学。你能按照这篇文章的思路一步步走踩坑成本会小很多。1. 为什么 setState 撑不了大场面状态管理到底在解决什么问题1.1 setState 本身并没有错先把话说清楚setState 不是坏东西它是 Flutter 里最基础、最原生的状态更新手段。一个页面内部的局部状态比如一个开关的开和关、一个表单输入框的内容、一个临时展开的面板用 setState 处理是完全正确且推荐的做法。Flutter 官方的很多示例代码里也是直接用 setState 的因为它简单、直观、没有任何额外概念。你点一个按钮然后调用 setState(() { _count; })框架就知道这个 State 需要重建然后重新执行 build 方法。这套机制本身没有任何问题它适合的是“局部于某个组件”的状态。官方文档其实也强调过如果状态是某个组件私有的优先考虑用 StatefulWidget setState。很多人一上来就全局状态管理结果把简简单单的一个输入框搞得整个页面都在重建反而得不偿失。1.2 当数据需要跨页面共享时麻烦才真正开始一个计数器 Demo 永远不会让你感受到状态管理的必要性但真实项目很快就会出现这样的场景你在商品列表页点了一个“加入购物车”首页右上角的购物车角标要立刻 1购物车页面要显示出新加入的商品条目结算页面要重新计算总金额。这四个页面如果只用 setState你要怎么联动最简单的想法是把状态“提升”到共同的父级组件然后通过构造函数一层层传下去。可如果页面层级很深比如首页里有 TabTab 里又有嵌套导航每个中间组件都要透传数据和回调函数代码很快就变成“ prop drilling ”的重灾区。更痛苦的是每次页面切换目标页面被压栈、原页面状态还保留着但如果某个页面被销毁重建了它的 State 也跟着没了。购物车数据如果只存在某一个页面的 State 里你从商品页跳到首页再回来数据直接归零。这时候你需要的是“全局共享”的数据购物车就算放在根级组件里任何页面都能读任何页面都能改数据变化时所有依赖它的界面自动得到通知并刷新。基础 setState 给不了你这个能力于是状态管理方案登场了。1.3 状态管理的本质就三件事很多新手一看状态管理觉得特别玄乎其实剥开外层看所有状态管理方案干的事情永远只有三件存储状态把共享数据放在一个任何组件都能访问到的地方读取状态组件能够从那个地方拿到自己需要的数据通知变更数据变了所有依赖它的组件自动得到通知并重建。把这三件事想清楚了后面选哪个框架其实都只是实现方式不同。你可以想象家里每个房间的温度显示不能每个房间自己烧锅炉对吧那样你根本没法统一控制。你需要一个中央供能系统配合每个房间的传感器中央系统状态变了传感器通知显示面板刷新。Flutter 的状态管理就是这套中央供能系统和传感器。2. 先用 Provider 跑通一个完整购物车我推荐的第一个入手方案2.1 为什么我让新人从 Provider 开始Flutter 生态里的状态管理方案多到让人眼花我之所以把 Provider 作为第一个推荐理由其实很务实。第一学习成本低。它核心概念就三个模型类、Provider 组件、context.watch/read。你不需要先理解函数式编程、不需要掌握事件流模型半天就能上手。第二基于 Flutter 原生机制。Provider 本质上是 Flutter 自带的 InheritedWidget 的封装也就是说它没有发明一套全新的运行机制你学到的原理将来迁移到别的方案也通用。第三官方文档在介绍状态管理时给的示例就是用 Provider 这套思路写的社区里案例、第三方封装、周边库都很丰富遇到问题搜得到答案。第四绝大多数中小型 App 的业务复杂度用它完全够。一个电商类的应用购物车、登录态、用户信息、消息中心Provider 都能轻松覆盖。2.2 一个能跑的购物车例子先说场景商品列表页面可以添加商品到购物车购物车页面展示商品清单和总价首页顶部有一个角标显示购物车总件数。数据在三个页面之间共享。第一步写一个模型类。这个类继承 ChangeNotifier里面有数据、有业务方法方法执行完调用 notifyListeners() 通知所有监听者class CartItem { CartItem({required this.name, required this.price, this.count 1}); final String name; final double price; int count; } class CartModel extends ChangeNotifier { final ListCartItem _items []; ListCartItem get items List.unmodifiable(_items); int get totalCount _items.fold(0, (sum, item) sum item.count); double get totalPrice _items.fold(0, (sum, item) sum item.price * item.count); void add(CartItem item) { final index _items.indexWhere((e) e.name item.name); if (index 0) { _items[index].count item.count; } else { _items.add(item); } notifyListeners(); } void remove(String name) { _items.removeWhere((e) e.name name); notifyListeners(); } }第二步在组件树顶层注入这个模型。用 ChangeNotifierProvider 包裹根组件这样整个应用里的任何页面都能通过 Provider 拿到同一个 CartModel 实例void main() { runApp( ChangeNotifierProvider( create: (_) CartModel(), child: const MyApp(), ), ); }第三步在各个页面读取数据。商品列表页里只需要触发“添加购物车”这个动作不需要监听数据变化所以用 context.readclass ProductPage extends StatelessWidget { const ProductPage({super.key}); static const products [ (name: 苹果, price: 5.0), (name: 香蕉, price: 3.5), (name: 牛奶, price: 12.0), ]; override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text(商品列表)), body: ListView( children: [ for (final product in products) ListTile( title: Text(product.name), subtitle: Text(${product.price} 元), trailing: IconButton( icon: const Icon(Icons.add_shopping_cart), onPressed: () { final cart context.readCartModel(); cart.add(CartItem(name: product.name, price: product.price)); }, ), ), ], ), ); } }购物车页需要随着数据变化实时刷新所以用 context.watch。数据一变当前页面自动重建UI 自然就更新了class CartPage extends StatelessWidget { const CartPage({super.key}); override Widget build(BuildContext context) { final cart context.watchCartModel(); return Scaffold( appBar: AppBar(title: const Text(购物车)), body: Column( children: [ Expanded( child: ListView.builder( itemCount: cart.items.length, itemBuilder: (context, index) { final item cart.items[index]; return ListTile( title: Text(item.name), subtitle: Text(${item.count} 件 x ${item.price} 元), trailing: IconButton( icon: const Icon(Icons.close), onPressed: () cart.remove(item.name), ), ); }, ), ), Padding( padding: const EdgeInsets.all(16), child: Text(合计${cart.totalPrice} 元), ), ], ), ); } }首页角标同理在角标组件里用 watch 拿到 totalCount 即可。整个购物车逻辑就从“层层传参”变成了“谁需要谁来读”代码清爽了不止一点。2.3 read、watch、select 的取舍新手最容易搞混的就是 read 和 watch。记住这个判断方法你只是要“读一次”比如按钮点击、提交表单这种一次性动作用 read你需要“跟着变”也就是这个组件要实时响应数据变化用 watch。watch 用多了也会有问题。如果一个大页面里多个位置都 watch 同一个模型哪怕你只是改了一个字段整个页面都会重建。比如购物车页里有一个商品列表和你只想关注总价结果列表一刷新整页重建稍有性能损失。更好的做法是缩小监听范围。把一个组件拆成多个小部件每个小部件只 watch 自己关心的数据或者用 context.select 只监听某个字段。例如只监听 totalPricefinal totalPrice context.selectCartModel, double((cart) cart.totalPrice);这样只有 totalPrice 变化时才触发重建items 变化时这个组件不受影响。Provider 能覆盖绝大部分业务场景学习阶段先把手上的项目跑通再来琢磨这些优化细节完全不迟。3. 拆开 Provider 看原理一个迷你版的 Provider 只需要几十行3.1 InheritedWidget 是 Flutter 原生就有的共享通道很多人学 Provider 是直接用但心里始终觉得不踏实总怕背后是什么黑魔法。我可以负责任地告诉你Provider 没多少魔法它建立在 Flutter 框架自带的 InheritedWidget 机制之上。InheritedWidget 是 Flutter 里一种特殊的 Widget它的作用是把数据放在组件树的“高处”然后任何后代组件都可以顺着组件树向上查找并获取这份数据。Flutter 自带的 Theme、MediaQuery 本质都是 InheritedWidget。当你通过 MediaQuery.of(context).size 获取屏幕尺寸时背后就是用这套查找机制。要理解 Provider 的原理你只需要明白一件事InheritedWidget 可以通过context.dependOnInheritedWidgetOfExactTypeT()查找最近的祖先数据并且一旦这个 InheritedWidget 的数据变化所有依赖过它的组件会重新 build。这个“依赖登记”机制是框架原生的不是第三方发明的。3.2 ChangeNotifier 只是一个简单的订阅发布模型ChangeNotifier 是 Flutter 自带的一个类它内部维护了一个监听器列表。你调 addListener 把回调方法加进去之后调 notifyListeners()它就会把列表里的回调挨个执行一遍。就这么简单。你可以把它理解成一个公告栏你贴了一张“购物车有变化”的通知所有贴了便签纸来关注的人都会收到消息。至于收到消息之后做什么是组件自己的事。所以 CartModel 继承 ChangeNotifier 的含义是这个模型“有变化时能说话”而组件关心的是“它说话了我要刷新界面”。Provider 要做的事情就是把这个“说话”和“刷新”连接起来。3.3 把两者拼起来迷你 Provider 的完整代码我来写一个简化版 Provider总共也就三四十行class MiniProviderT extends InheritedWidget { const MiniProvider({ super.key, required this.value, required super.child, }); final T value; static T of(BuildContext context) { final widget context.dependOnInheritedWidgetOfExactTypeMiniProviderT(); assert(widget ! null, 组件树上层找不到对应的 MiniProviderT); return widget!.value; } override bool updateShouldNotify(MiniProviderT oldWidget) { return value ! oldWidget.value; } }这个 MiniProvider 的作用是把某个类型的实例注入组件树然后任何后代组件调用MiniProvider.ofCartModel(context)都能拿到它。注意这里的dependOnInheritedWidgetOfExactType是关键它不光是“取数据”还把这个组件登记成了这个 InheritedWidget 的依赖。以后 MiniProvider 的数据变化了Flutter 会通知所有依赖者重建。但是只有注入还不够你还要有“监听刷新”。真正的 Provider 内部是把 MiniProvider 和一个 ListenableBuilder 组合在一起MiniProvider 存放模型数据ListenableBuilder 监听模型的通知收到通知后触发一个局部刷新。展开来看就是这么一套组合class MiniChangeNotifierProviderT extends ChangeNotifier extends StatefulWidget { const MiniChangeNotifierProvider({super.key, required this.create, required this.child}); final T Function(BuildContext context) create; final Widget child; override StateMiniChangeNotifierProviderT createState() _MiniChangeNotifierProviderStateT(); } class _MiniChangeNotifierProviderStateT extends ChangeNotifier extends StateMiniChangeNotifierProviderT { late T _value; override void initState() { super.initState(); _value widget.create(context); } override void dispose() { _value.dispose(); super.dispose(); } override Widget build(BuildContext context) { return ListenableBuilder( listenable: _value, builder: (context, child) { return MiniProviderT( value: _value, child: child, ); }, ); } }整套流程就是提供数据、监听变化、变化时通过 InheritedWidget 通知依赖者重建。真的没有黑魔法。理解了这一点你以后不管换什么状态管理方案都会有底气。3.4 明白了原理之后很多“坑”就自然理解了为什么大家都说不要在 build 方法里创建 Provider因为每次 build 都会 new 一个新的模型实例InheritedWidget 的 updateShouldNotify 一对比发现实例变了于是下面所有依赖者全部重建状态也丢了。用上面的代码反推一眼就能发现这个问题。为什么 Provider 会帮你自动调用 dispose因为 ChangeNotifierProvider 本身是一个 StatefulWidget它在 dispose 阶段会销毁自己创建的模型实例。这就是“谁创建谁负责”的原则。如果你自己手动 new 了一个 ChangeNotifier又没用 Provider 管理那它的生命周期就要你自己维护忘记释放就会泄漏。原理这东西现在花半天搞懂后面能帮你省下大把排查问题的时间。4. 主流方案横评Provider、Riverpod、Bloc、GetX 各自适合谁4.1 五个维度的对照表先看一张总表然后我逐个展开讲方案依赖 BuildContext错误暴露学习曲线样板代码典型使用场景Provider是运行期为主平缓少中小型项目、快速开发Riverpod否分析器能提前发现问题中等中中大型项目、需要脱离 UI 测试Bloc是运行期配合工具偏高多复杂业务流程、团队协作GetX混合运行期为主平缓极少个人项目、快速原型4.2 Riverpod 和 Provider 的关系同门但思路不同Riverpod 是同一个作者对 Provider 的下一代重构可以理解成“Provider 思路的重新设计版”。它最大的变化是不再依赖 Flutter 的 BuildContextProvider 成为纯 Dart 层的概念。这带来的直接好处是脱离 Widget 树也能测试业务逻辑同一个 Provider 可以在任何地方通过“引用”访问不用在组件树里层层注入。Riverpod 在开发体验上有不少提升比如分析器能帮你发现很多 Provider 使用中的错误不用等运行到那一行才崩溃。代码写法也更能适应大型项目。它的代价是概念变多Provider、StateProvider、FutureProvider、Notifier、AsyncNotifier……理解门槛比 Provider 高一截。如果项目从一开始就有脱离 UI 测试业务逻辑的需求可以直接从 Riverpod 开始如果还处在学习期先用 Provider 把原理吃透再迁 Riverpod 会非常平滑因为心智模型是同源的。4.3 Bloc 的强约束和 GetX 的高自由两种极端的平衡点Bloc 是“事件驱动 单向数据流”。所有界面操作都有一个事件发出去然后业务逻辑响应这个事件抛出一个新状态界面根据状态重建。这种强流程约束非常适合多人协作的团队项目也因为状态变化是线性的配合它的调试插件你可以清晰地看到每次事件触发了哪些状态变化排查问题非常舒服。代价就是样板代码确实多。一个最简单的功能你也要写 Event 类、State 类、Bloc 类、界面上的 BlocProvider、BlocBuilder……如果项目本身就简单这套重量级流程会让你感觉杀鸡用牛刀。GetX 走的是另一个极端极简、极快。一个 controller 装状态、装业务逻辑界面里用 Obx 包裹要刷新的部分用 Get.put 注册依赖、Get.to 跳转路由全屏都是全局函数代码量特别少。但 GetX 有比较明显的隐患侵入性强代码里到处是 Get.xxx一旦项目规模变大约束不够的松耦合会让代码风格变得不可控后面重构和维护的成本偏高。我的看法是GetX 适合小工具和个人快速原型如果你想走得更远不建议一开始就扎进 GetX 的舒适区。4.4 我实际项目里的选型经验很多人问我到底选哪个我给的建议不是看哪个最强而是看你的限制条件。如果你是刚入门的个人学习者先 Provider。不要在这个阶段浪费时间纠结不管别人吹得多天花乱坠先把状态管理的“三件事”亲身经历一遍这比什么框架都重要。如果项目需要把核心业务逻辑放到纯 Dart 层做单元测试组件树耦合会让测试很痛苦那就直接 Riverpod。层级越复杂Riverpod 不依赖组件树的优势体现得越明显。如果团队超过三个人在同一个仓库里协作Bloc 的强流程约束能带来统一性。虽然写起来麻烦但至少别人看不懂代码的时候还能根据事件驱动流程去理逻辑。GetX 我真心建议用来做个人练习原型它的快速体验会让早期开发很有成就感但它不是一个能让你安心把项目做深做大的依赖。选型没有绝对答案。我也见过用 Provider 撑起一个大型项目的团队人家状态模型划分得足够清晰照样维护得很好。所以与其天天纠结换框架不如把状态模型画清楚。模型清晰了从 Provider 切到 Riverpod 其实半天就能干完。5. 实际项目里最容易踩的五个坑从监听失效到 context 滥用5.1 坑一异步回调里用 BuildContext这是新手最容易遇到也最经典的问题。你发起一个网络请求等结果回来之后想用 context 做点什么结果页面早被用户切走销毁了直接报错。// 错误写法 onPressed: () async { final data await fetchData(); Navigator.push(context, AppPage(data: data)); // 可能崩溃 } // 正确写法 onPressed: () async { final data await fetchData(); if (!context.mounted) return; Navigator.push(context, AppPage(data: data)); }新版 Flutter 的分析器里有一个 use_build_context_synchronously 规则会在你写成第一种写法时给出警告直接提示你异步之后用 context 要谨慎。如果你的代码里还有context.readCartModel()之类的调用同样要小心异步上下文。新版 Flutter 的分析器里有一个 use_build_context_synchronously 规则会在你写成第一种写法时给出警告直接提示你异步之后用 context 要谨慎。如果你的代码里还有context.readCartModel()之类的调用同样要小心异步上下文。养成在 await 之后先判断 mounted 的习惯就好也千万别在 initState 里直接去 read 一个还没准备好的异步状态那个时机很微妙很多问题都是在 initState 里强上 watch 才引出来的。5.2 坑二在 build 方法里 new Provider我有段时间写代码图省事直接在 build 方法里创建一个 ChangeNotifierProvider包住局部逻辑。当时没注意到问题直到某个页面数据怎么都不对像抽风一样偶尔丢失。问题的根源就是 Provider 实例不唯一。build 方法每次执行都会创建新的 Provider 实例也就是新的模型实例旧实例里的数据还没用上就被丢弃了。依赖这个 Provider 的下层组件收到 updateShouldNotify 返回 true全部跟着重建数据全部初始化基本等于状态归零。正确做法很简单把 Provider 放在合理的顶层要么在 main 方法里要么在某个页面的顶层字段里确保不会跟着 build 反复创建。如果你确实需要一个仅在某个流程中有效的局部 Provider就把它放到那一个页面的根组件位置而不是塞进列表项的 build 里。5.3 坑三手动创建 ChangeNotifier 却忘了销毁ChangeNotifier 是需要释放的资源。官方封装好的 ChangeNotifierProvider 之所以好用是因为它内部自动帮你调用 dispose不需要你操心。但如果你在一个 StatefulWidget 里自己手动 new 了一个 CartModel又手动 addListener那责任就在你这边了class MyPage extends StatefulWidget { const MyPage({super.key}); override StateMyPage createState() _MyPageState(); } class _MyPageState extends StateMyPage { late final CartModel _cart CartModel(); override void dispose() { _cart.dispose(); super.dispose(); } override Widget build(BuildContext context) { return ListenableBuilder( listenable: _cart, builder: (context, child) { return Text(${_cart.totalCount}); }, ); } }注意我这里是手动 new CartModel然后在 dispose 里释放。如果你把 CartModel 塞进组件树然后又不做任何清理这个模型就会一直挂在内存里监听起来越多内存泄漏就越明显。社区里通常的约定是“谁创建谁负责销毁”Provider 负责了创建Provider 就负责销毁你自己 new 的你就得自己清理。5.4 坑四把不该全局的状态塞进全局 Provider不少新手接触状态管理之后会走向另一个极端什么东西都往里塞。输入框的临时内容、某个页面里临时展开的面板、一个还没提交的表单草稿全放进全局 CartModel。典型的反效果就是你每敲一个字母所有 watch 了 CartModel 的组件全部重建一遍。本来一个 TextField 的输入是局部的现在变成了全局广播。性能被折腾得很难看代码也看不出哪些才是真正需要共享的状态。正确思路是按“使用范围”划分。只有真正会被多个不相关页面读取或修改的业务数据才有资格成为全局共享状态比如登录信息、购物车、消息中心。一个页面内部的 UI 临时状态比如输入内容、开关状态、Tab 切换直接用局部的 setState 就够了。这个坑其实就是状态管理刚上手时最容易犯的过度设计。5.5 坑五watch 范围过大导致页面无谓重建watch 是把双刃剑。它好用但如果你在页面的根 build 里写一个大型的 watch比如Widget build(BuildContext context) { final cart context.watchCartModel(); return Column( children: [ // 这里有一个复杂的列表 // 这里有一个跟购物车无关的静态区域 // 这里有一个频繁变化的动画区域 ], ); }那么只要购物车里的任何一条数据发生变化这个页面整块重建。列表项其实只跟购物车商品相关无关区域也跟着闪一下重新布局。界面简单的时候无所谓但页面复杂到一定程度这种重建就会明显丢帧。解法有两个方向一是用 context.select 只监听你真正关心的字段二是把组件拆小。比如单独拆一个 CartTotalText 组件只让它 watch totalPrice列表组件单独用独立的模型子集。这种拆解本身也是组件化设计的一种练习久了之后你会觉得它比单纯写大页面舒服多了。回顾我自己的学习路径最大的体会是不要一上来就迷信某个框架先搞清楚状态管理解决的“三件事”再用 Provider 这种成本最低的方案把项目跑起来踩过几次坑之后你自然能形成自己的判断。我自己现在的习惯是setState 管局部、Provider 管共享、页面里尽量用 Consumer 或 select 包最小块、需要脱离 UI 做单元测试的项目再评估 Riverpod 或 Bloc。另外不管用哪个方案先把状态模型画清楚比选框架更重要。模型的边界合理了你会发现换实现真的一点都不痛苦。
RELATED

相关推荐

HTML+CSS 实战:仿QQ音乐静态网页布局完整拆解

HTML+CSS 实战:仿QQ音乐静态网页布局完整拆解

简介:一份基于纯HTML与CSS实现的仿QQ音乐静态网页项目,面向前端初学者、在校学生以及需要练习页面布局的开发者,适合作为课程设计或个人作品集的参考。项目完整还原了QQ音乐首页的视觉风格与常见功能模块,如导航栏、轮播图、歌单卡…

📅 2026/10/18 8:27:24
小熊猫C++免安装版:便携式C++开发环境与编译器配置指南

小熊猫C++免安装版:便携式C++开发环境与编译器配置指南

简介:小熊猫C是Dev-C的升级版本,由开发者在原版停止更新后基于教学需求继续维护,定位为轻量级开源C/C集成开发环境,免安装、解压即用,非常适合初学C/C的编程爱好者与课堂教学场景。相比VSCode、Visual Studio&#xff…

📅 2026/10/18 8:27:24
Themida WinLicense脱壳实战:V1.8与V2.X工具链选择与避坑指南

Themida WinLicense脱壳实战:V1.8与V2.X工具链选择与避坑指南

简介:本资源是面向逆向分析工程师、安全研究人员及软件保护技术学习者的Themida与WinLicense 1.8.x–2.x版本专用脱壳工具集,聚焦于解决主流商业壳在新版环境下的动态脱壳与静态分析难题。压缩包共301个文件,总计32.77MB,涵盖核心…

📅 2026/10/18 8:22:23
MORE NEWS

更多资讯

📰

AI编程工具范式收敛:TRAE双端合并背后的协议对齐

1. 这不是功能叠加,而是AI编程工具的范式收敛“TRAE 合并双端之后,AI 编程工具正在走向同一个方向”——这句话初看像一句行业观察,细想却是一把解剖刀,精准切开了过去三年AI原生开发工具演进的底层逻辑。我从2021年就开始跟踪这类…

📰

YOLO边缘端部署实战:C#上位机与工业缺陷检测系统架构解析

1. 项目整体思路:为什么把YOLO放到边缘端跑1.1 场景痛点:传统上位机方案卡在哪去年年中接到一条产线的视觉检测改造需求,我当时最开始的想法是沿用过去的套路:工业相机拍照,图像传到工控机的上位机软件里,上…

📰

HTML5响应式简历模板:单文件交付与绿色语义设计

简介:这是一款专为求职者打造的绿色主题HTML5响应式个人简历模板,面向前端初学者与正在准备求职作品集的开发者,解决在线简历缺乏专业性、适配性差及技术展示力不足的问题。资源包共50个文件,包含20张高清图片(用于头像…

📰

从模糊标题到技术落地:以rea为例的项目拆解与选型方法论

1. 从“rea”这个标题说起:一个被低估的万能缩写第一次看到“rea”这个标题的时候,我脑子里蹦出来的第一反应是——这到底是个啥?是某个工具的缩写?某个项目的代号?还是某个技术栈的简称?说实话&#xff0c…

📰

前后端分离餐厅管理系统实战:Flask+Vue+PyCharm全栈开发与踩坑解析

前后端分离做餐厅后台管理系统,这个名字一看就是典型的实战型全栈项目。标题里Flask、Vue、PyCharm、Django挤在一起,信息量不小,大概也说明这个项目经历了一段技术选型的折腾路。作为一个做过不少类似管理系统的开发者,我今天就围…

📰

从召回、排序到重排:X平台For You推荐算法全链路解析

聊算法的人很难不注意到X平台那套“For You”信息流。它是目前社交平台里少有的把推荐系统全链路公开讨论过的案例,从候选生成、排序到重排都值得拆一拆。不管你是做推荐系统的新人,还是已经在信息流业务里摸爬滚打多年,把这条链路的每个环节…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬