【AICon】 如何构建高效的 RAG 系统?RAG 技术在实际应用中遇到的挑战及应对策略?>>> 了解详情
写点什么

Flutter 状态 State 的 5 种应对方法

  • 2020-11-29
  • 本文字数:11532 字

    阅读完需:约 38 分钟

Flutter状态State的5种应对方法

本文最初发布于 poetryincode.dev 网站,经原作者授权由 InfoQ 中文站翻译并分享。


点击这里查看本文中示例的代码。


不管你是刚开始了解 Flutter,还是已经接触 Flutter 有了一段时间,你都可能已经知道有很多方法可以处理应用程序状态。我可以肯定,每个月都会冒出来一些新的途径。因为没有太多可以直接对比的例子,所以想要了解这些方法之间的差异和各自的权衡可能会很困难。


我认为,学习如何使用一个库的最佳方法是用它来构建项目。对比两个库的最佳方法是用它们来执行相同的任务:构建相同的功能,从而更好地了解彼此的权衡所在。


在本文中,我将使用同样的应用程序作为基础,用我很喜欢的几位作者制作的 5 个库,使用相同的模式来实现共享状态。这些库有的很流行,有的罕见,有的很新,有的相对老旧。


  1. Riverpod(带有 StateNotifier)

  2. Bloc

  3. flutter_command

  4. MobX

  5. binder


我会尽力找出各个库之间的差异,并对每种方法做一个总结。为了演示各个库的 API,我们将实现一个笔记应用,它会显示一个输入字段以创建新的笔记,还会显示已创建笔记的列表。你可以在这里观看演示效果。


在开始之前我想澄清一下,就算我可能会批评其中的某些库,但它们仍然值得大家使用。我发现所有这些库都可以为你完成大部分工作,而且我的观点会有一些主观偏见。选择它们本来也是个人喜好的结果。同样,本文没有介绍的库也并不是不好。我的观点也不是真理,而且就算我的看法是正确的,我也没那么勤奋来选出所有好用的库来。另外,这毕竟是一篇博客文章,不是什么大部头。


准备工作


如果你决定跟我一起研究,请创建一个新的Flutter应用来测试这几种方法:flutter create state_examples
复制代码


要运行这个应用的时候,请在项目根目录中执行 run 命令。


flutter run
复制代码


我们将在示例中重用一些类,因此接下来会定义它们。


// A simple helper function to allow us to immutably add to lists.extension ImmutableList<T> on List<T> {  List<T> concat(T item) => List<T>.from(<T>[...this, item]);}
复制代码


// A simple widget for displaying individual notes.class Note extends StatelessWidget {  final String text;  const Note({Key key, this.text}) : super(key: key);  @override  Widget build(BuildContext context) {    return Padding(      padding: const EdgeInsets.symmetric(vertical: 8.0),      child: Text('Note: $text'),    );  }}
复制代码


// You can add equatable as a dependency to your pubspec.yaml// https://pub.dev/packages/equatable/installclass NotesState extends Equatable {  final List<String> notes;  final String input;  NotesState(    this.notes,    this.input,  );  @override  List<Object> get props => [notes, input];  @override  bool get stringify => true;  NotesState copyWith({    List<String> notes,    String input,  }) {    return NotesState(      notes ?? this.notes,      input ?? this.input,    );  }  NotesState.initial()      : notes = [],        input = '';}
复制代码


我们还希望重构 main.dart 文件,以便轻松换掉页面。


void main() {  runApp(App());}class App extends StatelessWidget {  @override  Widget build(BuildContext context) {    return ProviderScope(      child: MaterialApp(        title: 'Flutter Demo',        theme: ThemeData(          primarySwatch: Colors.blue,          visualDensity: VisualDensity.adaptivePlatformDensity,        ),        // home: SomePage(), //This is where our page will go,      ),    );  }}
复制代码


Riverpod


Riverpod文档


Riverpod 是 Remi Rousselet 创建的一个相当新的库,也是他对自己另一个库provider遇到的一些最常见问题的直接回应,其中很重要的一个问题是当开发人员无法提供依赖项时会引发 ProviderNotFound 异常。Riverpod 解决了这个问题,还有更简单的 API,我觉得这是它的两个最大优势。


与其他选项相比,我的看法是如果你只使用 StateNotifier,那么用起来就没那么头疼,因为你要处理的是一个简单的 API,这个 API 实现了 MVU 模式,还有一大堆文档支持。如果你正在使用完整的 API,我觉得你会遇到的麻烦会比其他选项平均来说高很多。。


Riverpod 实践


要开始使用 Riverpod,请将这个库安装为依赖项:


dependencies:  # ...other dependencies  flutter_riverpod: ^0.12.1  riverpod: ^0.12.1
复制代码


接下来,将 ProviderScope 添加到应用的根/入口点:


void main() {  // For widgets to be able to read providers, we need to wrap the entire  // application in a "ProviderScope" widget.  // This is where the state of our providers will be stored.  runApp(ProviderScope(child: App()));}
复制代码


然后,要创建和更改状态,我们将创建一个 NotesController,它扩展一个 StateNotifier 和一个 StateNotifierProvider,来存储对 NotesController 的引用,并将其提供给我们的应用程序和设置一些初始状态:


// We create a "provider", which will store a reference to NotesController.final notesProvider = StateNotifierProvider((ref) => NotesController());class NotesController extends StateNotifier<NotesState> {  NotesController() : super(NotesState.initial());  void addNote() {    var notes = state.notes.concat(state.input);    state = state.copyWith(notes: notes, input: '');  }  void updateInput(String input) => state = state.copyWith(input: input);}
复制代码


现在,我们可以在 BuildContext 和 Consumer 上使用 read 扩展方法来访问应用中任何位置的 NotesController。read 扩展使我们能够执行突变函数,而 Consumer 小部件使我们能够订阅状态更改。这是在 Riverpod 中实现的 Notes UI:


class RiverpodPage extends StatefulWidget {  const RiverpodPage({Key key}) : super(key: key);  @override  _RiverpodPageState createState() => _RiverpodPageState();}class _RiverpodPageState extends State<RiverpodPage> {  TextEditingController _controller;  @override  void initState() {    super.initState();    _controller = TextEditingController();  }  @override  void dispose() {    _controller?.dispose();    super.dispose();  }  @override  Widget build(BuildContext context) {    return Scaffold(      appBar: AppBar(title: Text('My notes app')),      body: Padding(        padding: const EdgeInsets.all(8.0),        child: Column(          children: [            FlatButton(                onPressed: () {                  // Get a reference of the NotesController                   // and add a note.                  context.read(notesProvider).addNote();                  _controller.clear();                },                child: Text('Create Note')),            TextField(              controller: _controller,              // Get a reference of the NotesController               // and update the input value.              onChanged: (value) =>                  context.read(notesProvider).updateInput(value),                decoration: InputDecoration.collapsed(hintText: 'Add a note'),            ),            Divider(),            Expanded(              child: Consumer(                builder: (context, watch, child) {                                    // Subscribe to the NotesController's state =                  var state = watch(notesProvider.state);                  return ListView.builder(                    itemBuilder: (context, index) =>                        Note(text: state.notes[index]),                    itemCount: state.notes.length,                  );                },              ),            )          ],        ),      ),    );  }}
复制代码


最后,我们将反注释 home: SomePage(),并使用上面定义的小部件换掉 SomePage。


评价


这个示例可能会让大家觉得 Riverpod 用起来平平无奇,但这是设计使然。Riverpod 是一个功能非常强大的库,提供了状态管理和依赖项注入。本文介绍的其他选项并不是都有这样的能力。但是强大的力量意味着更大的……算了不说了。这里的权衡是,这种能力造就了一个相当大的 API,这可能会令人生畏。Riverpod 有丰富的文档支持,但是你可能会发现自己要在文档里翻来翻去,具体取决于要学习的内容。不要因为这个就放弃使用它,因为大多数用例只需使用 StateNotifier 就能解决了。


Riverpod 非常顺滑,也许有点难以入门,但它解决了 provider 这个库的主要问题。我目前没在使用它,但是如果我开始了一个项目,并且很熟悉 provider,需要在两者之间做出选择,那么我会毫不犹豫地选择 Riverpod。


Bloc


Bloc文档


Bloc 库是这个列表中最古老的一员,足足有两年的历史!它最初是由 Felix Angelov 创建的,是一种基于事件驱动的 Bloc 模式,最近它经过了重构,以同时支持事件驱动和函数驱动的状态更新。这听起来似乎是微不足道的变化,但是作为在多个框架中用过事件驱动模式的程序员,我可以肯定这种改变是很有价值的。我已经见识过了事件驱动的大型应用程序中会冒出来多少样板和技术债。能够选择何时使用这种模式是非常重要的。


Bloc 有两种类型的状态对象:Blocs 是事件驱动的,继承自 Cubits,后者是函数驱动的。两者都继承自 Stream,并使开发人员免受其某些复杂性的影响。实质上,它们的区别就这些了。由于 Blocs 是 Cubits 的扩展,因此生态系统的其余部分(BlocProvider、BlocListener、BlocBuilder 等)都可以被两者共享。


Bloc 也是这个列表上所有库中文档做得最好的榜样。它的整个 API 都在 bloclibrary.dev 上提供了文档支持,以及你可能要解决的每一个重要问题的示例,从使用 RESTful/GraphQL/Firebase API 到处理导航,无所不包。他们的文档写得太好了,怎么夸都不为过。


与其他选项相比,我想说的是这个库用起来要轻松一些,因为你要处理的是一个简单的 API,这个 API 实现了 MVU 模式,有一大堆文档支持。


Bloc 实践


要开始使用 Bloc,请将这个库安装为依赖项:


dependencies:  # ...other dependencies  bloc: ^6.1.0  flutter_bloc: ^6.0.6
复制代码


接下来,要创建和更改状态,我们将使用来自 Bloc 的 Cubit,使用一些初始状态初始化我们的类,并创建几个突变器(mutator)函数:


class NotesCubit extends Cubit<NotesState> {  NotesCubit(): super(NotesState.initial());    void addNote() {    emit(state.copyWith(notes: state.notes.concat(state.input), input: ''));  }  void updateInput(String input) => emit(state.copyWith(input: input));}
复制代码


我们将使用 BlocProvider 类在整个应用中共享我们的 NotesCubit。为了保持一致,我们将在小部件树中 App 的上方添加 BlocProvider。


void main() {  // For widgets to be able to read providers, we need to wrap the entire  // application in a "BlocProvider" widget.  // This is where the state of our NotesCubit will be stored.  runApp(BlocProvider<NotesCubit>(create: (_) => NotesCubit(), child: App()));}
复制代码


现在,我们可以在 BuildContext 和 BlocBuilder 上使用 bloc 扩展方法来访问应用中的 NotesCubit。这是在 Bloc 中实现的 Notes UI:


class BlocPage extends StatefulWidget {  const BlocPage({Key key}) : super(key: key);  @override  _BlocPageState createState() => _BlocPageState();}class _BlocPageState extends State<BlocPage> {  TextEditingController _controller;  @override  void initState() {    super.initState();    _controller = TextEditingController();  }  @override  void dispose() {    _controller?.dispose();    super.dispose();  }  @override  Widget build(BuildContext context) {    return Scaffold(      appBar: AppBar(title: Text('My notes app: Bloc')),      body: Padding(        padding: const EdgeInsets.all(8.0),        child: Column(          children: [            FlatButton(                onPressed: () {                  // Get a reference of the NotesCubit                   // and add a note.                  context.bloc<NotesCubit>().addNote();                  _controller.clear();                },                child: Text('Create Note')),            TextField(              controller: _controller,              // Get a reference of the NotesCubit               // and update the input value.              onChanged: (value) =>                  context.bloc<NotesCubit>().updateInput(value),              decoration: InputDecoration.collapsed(hintText: 'Add a note'),            ),            Divider(),            // Subscribe to the NotesCubit's state             BlocBuilder<NotesCubit, NotesState>(              builder: (context, state) => Expanded(                child: ListView.builder(                  itemBuilder: (context, index) =>                      Note(text: state.notes[index]),                  itemCount: state.notes.length,                ),              ),            )          ],        ),      ),    );  }}
复制代码


同样,我们将反注释 home: SomePage()(如果尚未反注释),并用上面定义的小部件换掉 SomePage。


评价


实际上,我经手的几个项目已经从 provider 迁移到了 Bloc。这不是说 Riverpod 就不行,因为用 Bloc 重构要比 Riverpod 简单得多。此外,在使用 Firebase 的情况下,由于 Firebase 和 Bloc 都依赖 Streams,因此我们重构起来会很简单。


目前来说,我发现自己使用 Bloc 的效率最高,因为它没那么复杂,而且文档很丰富。再次强调,我能充分理解 API 是非常值得夸奖的优点。


我不会说未来五年内我都会继续使用 Bloc,但是对我来说,这绝对是现在的合适之选。


flutter_command


flutter_command文档


flutter_command 实际上是 Thomas Burkhart 对 rx_command 库的重新构想。两者之间的主要区别在于,前者基于 ValueNotifier 和 ValueListenableBuilder 构建,而后者基于 Streams 构建。两者都是从.Net 的 ReactiveCommands 派生的,因此如果你熟悉这个模式,也许你会发现自己很容易就能上手这个库。


与其他选项相比,设置大致上没什么区别,而如果你从未使用过 ReactiveCommands,我觉得它用起来会更难一些。


flutter_command 实践


要开始使用 flutter_command,请将这个库安装为依赖项:


dependencies:  # ...other dependencies  flutter_command: ^0.9.3
复制代码


接下来,要创建和改变状态,我们将创建一个 NotesViewModel 类来定义我们的状态,以及来自 flutter_command 的 Command(s),用于突变这个状态:


class NotesViewModel {  NotesState state = NotesState.initial();  Command<String, String> inputChangedCommand;  Command<String, NotesState> updateNotesCommand;  NotesViewModel() {    inputChangedCommand = Command.createSync((x) => x, '');    updateNotesCommand = Command.createSync((input) {      state = state.copyWith(notes: state.notes.concat(input));      inputChangedCommand.execute('');      return state;    }, NotesState.initial());  }}
复制代码


现在,我们可以使用创建的 NotesViewModel 订阅状态(使用 ValueListenableBuilder),并执行 inputChangedCommand 和 updateNotesCommand 来突变状态。这是在 flutter_command 中实现的 Notes UI:


class FlutterCommandPage extends StatefulWidget {  const FlutterCommandPage({Key key}) : super(key: key);  @override  _FlutterCommandPageState createState() => _FlutterCommandPageState();}class _FlutterCommandPageState extends State<FlutterCommandPage> {  final _notesViewModel = NotesViewModel();  TextEditingController _controller;  @override  void initState() {    super.initState();    _controller = TextEditingController();  }  @override  void dispose() {    _controller?.dispose();    super.dispose();  }  @override  Widget build(BuildContext context) {    return Scaffold(      appBar: AppBar(title: Text('My notes app')),      body: Padding(        padding: const EdgeInsets.all(8.0),        child: Column(          children: [            FlatButton(                onPressed: () {                                    // Execute updateNotesCommand                  // to add a note.                  _notesViewModel.updateNotesCommand                      .execute(_notesViewModel.inputChangedCommand.value);                  _controller.clear();                },                child: Text('Create Note')),            TextField(              controller: _controller,                       // Execute inputChangedCommand              // to update the input value.              onChanged: _notesViewModel.inputChangedCommand,              decoration: InputDecoration.collapsed(hintText: 'Add a note'),            ),            Divider(),                // Use ValueListenableBuilder            // to subscribe to state.            ValueListenableBuilder<NotesState>(              valueListenable: _notesViewModel.updateNotesCommand,              builder: (context, state, _) => Expanded(                child: ListView.builder(                  itemBuilder: (context, index) =>                      Note(text: state.notes[index]),                  itemCount: state.notes.length,                ),              ),            )          ],        ),      ),    );  }}
复制代码


我们将反注释 home: SomePage()(如果尚未操作),并使用上面定义的小部件换掉 SomePage。


评价


我对这个库的感受很复杂。对于熟悉其概念的人来说,它可能是一个完美的选择。对于像我这样来自 React/Angular 社区的人来说,它并不会给人浑然天成的感受。文档来说,应付这个示例是足够了。


我可能不会使用 flutter_command,但是我觉得这个库在别人手中的效果和列表中的其他库相比并不会落下风。我肯定会向已经熟悉这些模式的人,或想尝试一些异国风情的人们推荐它。


MobX


MobX文档


MobX.dart 已经出来一段时间——快两年了!只要你有一定 React 经验,肯定会立刻知道 MobX.dart 是 MobX.js 的一个端口。概念是相同的,只是实践中有所区别:你需要使用代码生成来复现运行时在后台通过动态语言(如 JavaScript)执行的神奇功能。


与其他选项相比,我的看法是如果你从未用过可观察对象(observable),那么这个库设置起来要比其他选项复杂一些,并且用起来会更难一点。


MobX 实践


要开始使用 MobX,请将这个库安装为依赖项:


dependencies:  # ...other dependencies  mobx: ^1.2.1+4  flutter_mobx: ^1.1.0+2dev_dependencies:  build_runner: ^1.10.4  mobx_codegen: ^1.1.1+3
复制代码


接下来,要创建和更改状态,我们将使用来自 mobx 的一个 Store,使用一些初始状态初始化我们的类,并创建几个突变器函数:


part 'notes.g.dart';class Notes = NotesBase with _$Notes;abstract class NotesBase with Store {  @observable  String input = '';  @observable  ObservableList<String> notes = ObservableList();  @action  void updateInput(String val) {    input = val;  }  @action  void addNote() {    notes.add(input);    input = '';  }}
复制代码


你可能会马上注意到到处都是红色的花体。这是因为我们尚未生成 store 所依赖的文件。我们运行以下命令来做到这一点。


flutter packages pub run build_runner build
复制代码


然后,我们可以使用 flutter_mobx 中的 Observer 和 store 中定义的动作来引用和更新状态。这是在 mobx 中实现的 Notes UI:


class MobxPage extends StatefulWidget {  const MobxPage({Key key}) : super(key: key);  @override  _MobxPageState createState() => _MobxPageState();}class _MobxPageState extends State<MobxPage> {  var _notesStore = Notes();  TextEditingController _controller;  @override  void initState() {    super.initState();    _controller = TextEditingController();  }  @override  void dispose() {    _controller?.dispose();    super.dispose();  }  @override  Widget build(BuildContext context) {    return Scaffold(      appBar: AppBar(title: Text('My notes app')),      body: Padding(        padding: const EdgeInsets.all(8.0),        child: Column(          children: [            FlatButton(                onPressed: () {                          // Execute addNote action                  // to add a note.                  _notesStore.addNote();                  _controller.clear();                },                child: Text('Create Note')),            TextField(              controller: _controller,                // Execute updateInput action              // to update the input value.              onChanged: _notesStore.updateInput,              decoration: InputDecoration.collapsed(hintText: 'Add a note'),            ),            Divider(),                        // Use Observer to subscribe            // to updates to the NotesStore.            Observer(              builder: (_) => Expanded(                child: ListView.builder(                  itemBuilder: (context, index) =>                      Note(text: _notesStore.notes[index]),                  itemCount: _notesStore.notes.length,                ),              ),            )          ],        ),      ),    );  }}
复制代码


同样,我们将反注释 home: SomePage()(如果尚未操作),并用上面定义的小部件换掉 SomePage。


评价


尽管我从未在 JavaScript 中使用过 mobx,但由于它们的本质是相同的,因此对我来说它的概念很浅显易懂。它简化了很多事情这一点我很喜欢,虽然我发现装饰器+构建生成用起来比较奇妙。我个人还认为,依赖状态管理层的构建生成可能会很烦人。在 UI 层之后,我发现这是自己更改最多的层。


mobx 最大的优势就是它的易用性和完善的文档。它在这份列表中仅次于 Bloc,因为你应该能从 mobx.netlify.app 了解所需的一切。


binder


binder文档


刚诞生不久的 binder 是由 Romain Rastel 创造的。它受到了 Recoil 和 Riverpod 的深刻影响,并试图创建一个易于学习且 API 表面积较小的库。


与其他选项相比,我觉得这个库的上手难度要低很多,因为你要处理的是非常小的 API,而这个 API 还有着丰富的文档支持。


binder 实践


要开始使用 binder,请将这个库安装为依赖项:


dependencies:  # ...other dependencies  binder: ^0.1.5
复制代码


接下来,将 BinderScope 添加到应用的根/入口点:


void main() {  // For widgets to be able to read app state, we need to wrap the entire  // application in a "BinderScope" widget.  runApp(BinderScope(child: App()));}
复制代码


为了创建一些状态,我们将使用一个来自 binder 的 StateRef,并设置一些初始状态:


// We create a "state ref", which will store a reference to NotesState.final notesRef = StateRef(NotesState.initial());
复制代码


接下来,要更改状态,我们将创建一个 Logic 类和一个来自 binder 的 LogicRef:


// We create a "logic ref", which will store a reference to NotesViewLogic.final notesViewLogicRef = LogicRef((scope) => NotesViewLogic(scope));class NotesViewLogic with Logic {  const NotesViewLogic(this.scope);  @override  final Scope scope;  void addNote() {    var state = read(notesRef);    write(notesRef,        state.copyWith(notes: state.notes.concat(state.input), input: ''));  }  void updateInput(String input) =>      write(notesRef, read(notesRef).copyWith(input: input));}
复制代码


现在,我们可以使用 BuildContext 上的 read、use 和 watch 扩展方法来访问应用程序中任何位置的 StateRef 和 LogicRef。这是在 binder 中实现的 Notes UI:


class BinderPage extends StatefulWidget {  const BinderPage({Key key}) : super(key: key);  @override  _BinderPageState createState() => _BinderPageState();}class _BinderPageState extends State<BinderPage> {  TextEditingController _controller;  @override  void initState() {    super.initState();    _controller = TextEditingController();  }  @override  void dispose() {    _controller?.dispose();    super.dispose();  }  @override  Widget build(BuildContext context) {    // Subscribe to NotesState    final state = context.watch(notesRef);    return Scaffold(      appBar: AppBar(title: Text('My notes app')),      body: Padding(        padding: const EdgeInsets.all(8.0),        child: Column(          children: [            FlatButton(                onPressed: () {                  // Get a reference of NotesViewLogic                   // and add a note.                  context.use(notesViewLogicRef).addNote();                  _controller.clear();                },                child: Text('Create Note')),            TextField(              controller: _controller,              // Get a reference of NotesViewLogic               // and update the input value.              onChanged: (value) =>                  context.use(notesViewLogicRef).updateInput(value),              decoration: InputDecoration.collapsed(hintText: 'Add a note'),            ),            Divider(),            Expanded(              child: ListView.builder(                itemBuilder: (context, index) => Note(text: state.notes[index]),                itemCount: state.notes.length,              ),            )          ],        ),      ),    );  }}
复制代码


同样,我们将反注释 home: SomePage()(如果尚未操作),并用上面定义的小部件换掉 SomePage。


评价


binder 是很新的库,在本文发表前一周在刚刚发布。因此,指望这个库与其他库具有相同级别的文档是不公平的。就它的当前状态来说,文档已经很够用了。另外,由于 binder 从 provider 和 Riverpod 借鉴了很多东西,以尽可能接近 Recoil,所以我很容易就可以上手并用起来这个库。


我对这个库寄予很高的期望,并期待看到更多示例来凸显其优点。如果你没什么选择可用,那么它通过减小 API 表面积可以提供与 Riverpod 相似的优势。


总结


公平起见,我想重申一下,我的偏好是很主观的,因为它们是基于我个人的经验。另外,这是一个基础示例,其中不包含副作用/异步性或测试,因此没有充分利用这些库的功能。


希望本文能帮助大家基本了解如何使用这些库完成相同的任务,并看明白它们之间的异同。尽管我有自己的偏好,但我敢肯定,你使用这个列表中的任何选项都可以做得很好。


作者介绍


我叫 Ryan Edge,是 Flutter 项目的谷歌开发人员专家、Superformula 的软件工程师和半专业的开源贡献者。


原文链接:https://poetryincode.dev/flutter-state-5-ways


2020-11-29 23:591520

评论

发布
暂无评论
发现更多内容

如何使用 Checkmk 监控 SSL TLS 证书?

Ethereal

私有云与公有云,哪种云模型最适合企业的需求

Ethereal

全新 OceanBase 社区版开发者中心 ODC 核心功能解读

OceanBase 数据库

oceanbase OceanBase 开源 OceanBase 社区版

Linux性能优化—内存实战篇

Linux服务器开发

性能优化 内存管理 Linux服务器开发 Linux内核 内核源码

一个 测试岗 面了 30 多人,不能再真实了...

六十七点五

软件测试 面试题 自动化测试 经验总结 测试工程师

2022 年值得关注的 9 个最新 Java 趋势

Ethereal

Web 键盘输入法应用开发指南(9)—— 标准与实现

天择

JavaScript 键盘 输入法 3月月更

2022年济南正规等保测评公司名单(排名不分先后)

行云管家

等保 等保测评 等保2.0 济南

OceanBase 存储引擎详解

OceanBase 数据库

Map-Reduce 思想在 ABAP 编程中的一个实际应用案例

Jerry Wang

mapreduce abap CRM系统 企业级应用 3月月更

抓到Netty一个隐藏很深的内存泄露Bug | 详解Recycler对象池的精妙设计与实现

bin的技术小屋

中间件 池化技术 java netty 内存池

数字化时代,如何做好用户体验与应用性能管理

云智慧AIOps社区

监控宝 监控工具 自动化运维 数字化经济

【三级等保】三级等保服务费用一年大概要多少?一年需要测评一次嘛?

行云管家

网络安全 等保 等级保护 三级等保

两小时,掌握四个数字化工具!

明道云

【直播回顾】OpenHarmony知识赋能第四期第二课——GPIO驱动开发

OpenHarmony开发者

OpenHarmony GPIO 驱动开发

一站式运维管理工具平台 OCP 到底有多好用,看这篇文章就够了!

OceanBase 数据库

企业帮助中心的搭建步骤

小炮

帮助中心

零基础学编程?从这本豆瓣评分9.2的入门级神作开始

图灵社区

Python 零基础

恒源云(Gpushare)_【存储优化】/hy-tmp可以扩/缩容啦

恒源云

云计算 存储 tmp

电科申泰加入龙蜥社区并成为理事单位,共创基础软硬件生态新未来

OpenAnolis小助手

开源 理事单位 申威 软硬件

数据对接 - 大屏云极简使用手册

shulinwu

可视化 数据可视化 大屏可视化 数据可视化控件 大屏

ZEGO 自研客户端配置管理系统 —— 云控

ZEGO即构

后台开发 客户端配置 音视频架构

并发异步编程之争:协程(asyncio)到底需不需要加锁?(线程/协程安全/挂起/主动切换)Python3

刘悦的技术博客

多线程 协程 Python3 协程原理

国内首届DataOps+MLOps meetup回顾

星策开源社区

人工智能 机器学习 DevOps Meetup MLOps

测试开发【Mock平台】01开篇:平台设计和整体规划

MegaQi

测试开发 测试平台开发教程 测试干货

Meetup预告| AIOps指标相关算法体系分享

云智慧AIOps社区

机器学习 大数据 算法 AIOPS 智能运维

OceanBase 在线体验环境,现已上线!

OceanBase 数据库

在 Manjaro 上安装 Chrome

信号量

chrome Linux

HSC推出「万物生长计划」 赋能虎符交易所HOO新应用场景

区块链前沿News

Hoo 虎符交易所 虎符智能链

鸿蒙开发必备书籍【收藏】

坚果

鸿蒙 3月月更

资产管理系统开发解决方案

低代码小观

企业管理 资产管理 CRM系统 企业管理软件

Flutter状态State的5种应对方法_编程语言_Ryan Edge_InfoQ精选文章