Fish Redux-フラッター用の新しいReduxライブラリ

2018年の終わりに、Googleはオープンソースコミュニティの助けを借りて、クロスプラットフォームモバイル開発フレームワークFlutterの最初の安定バージョンをリリースすることで、モバイル開発者に素晴らしい贈り物を作りました。







ただし、1ページのHello Worldsよりわずかに大きい大規模なアプリケーションを開発する場合、開発者は不確実性に直面する可能性があります。 アプリケーションの書き方 このフレームワークは非常に新しいものであり、オープンソースの良い例の基盤はまだ十分ではありません。これに基づいて、さまざまなパターンを使用することの長所と短所を理解し、この特定のケースで使用すべきものとそうでないものを理解することが可能になります。







FlutterがReactおよびReact Nativeとある程度の類似性を持っているという事実により、状況は保存されています。つまり、後者のプログラミング経験から学ぶことができます。 おそらくこれが原因で、 Flutter FluxFlutter HooksMobXなどのライブラリ、およびいくつかのRedux実装が同時に登場しました。 長い間、最も人気のあったバージョンはFlutter Reduxと呼ばれるBrian Eganでした。







ただし、数か月前に、Alibabaという名前で公開されたFish Reduxライブラリで最初のコミットが確認されました。 短期間でライブラリは非常に人気を博し、すでに星の数の点でブライアンの実装よりも先に1日目であり、2日目にはその2倍先でした。







その人気にもかかわらず、フィッシュにはドキュメントに関する問題があります。ドキュメントの大部分は、いくつかの短い例を含む既存のクラスの説明です。 さらに悪いことに、一部のドキュメントは中国語でのみ利用可能です。 別の難しさもあります。英語圏の問題はほとんどないため、他の開発者の経験に依存することは非常に難しく、最初のプレビューバージョンのみがリリースされるため、非常に重要です。







では、Fish'aバージョンのBrianとの大きな違いは何ですか? Flutter Reduxは状態管理フレームワークです。 Fishは、状態管理の基盤としてReduxを中心としたアプリケーションフレームワークです。 つまり Fishはさらにいくつかのタスクを解決し、 state management



限定されません。







Fish Reduxの主要な機能の1つは、通常のReduxがそのような機会をまったく提供せず、開発者がすべてを独自に実装することを余儀なくする場合、それらの間の依存関係を直接表現することにより、複数の減速機をより大きなものに統合することです。 しかし、このリデューサーが何であるか、そしてFish Redux自体についても取り上げて、後でこれに戻りましょう。







レデューサー、エフェクト、コンポーネントのビューの関係



画像







Fish Reduxのすべての基盤はコンポーネントです。 これは、Effect、Reducer、Viewの3つの部分で構成されるオブジェクトです。 ビューのみ、つまり EffectとReducerはオプションであり、コンポーネントはそれらなしで動作できます。 コンポーネントには現在の状態もあります。





都道府県



たとえば、クリッカーを取ります。 彼の状態にはフィールドが1つだけあるとします。カウントは、クリックの完全な数を示します。







 class ClickerState implements Cloneable<ClickerState> { int count = 0; @override ClickerState clone() { return ClickerState() ..count = count; } }
      
      





状態は不変、不変でなければなりません。 Cloneableインターフェースを実装することにより、状態耐性を簡単に維持できます。 将来、新しい状態を作成する必要がある場合、単純にclone()



メソッドを使用できます。







減速機



レデューサーの本質は、新しい状態を返すことで何らかのアクションに応答することです。 減速機は副作用を起こしてはなりません。







対応するアクションを受信すると、数だけカウントを増やす単純なレデューサーを作成します(それより少し低い)。







 ClickerState clickerReducer(ClickerState state, Action action) { //        Action,      . if (action.type == Actions.increase) { // ..       ,   ,       Count. return state.clone() ..count = state.count + action.payload; //        /payload/ count. // payload   . } // if (action.type == ...) { ... } //        . return state; }
      
      





また、このレデューサーは次の形式で記述できます。







 Reducer<ClickerState> buildClickerReducer() { asReducer({ Actions.increase: (state, action) => state.clone() ..count = state.count + action.payload, //Actions.anotherAction: ... }); }
      
      





アクション



アクション-2つのフィールドを含むFishReduxライブラリのクラス:

Object type



-アクションタイプ、通常はenumオブジェクト

dynamic payload



-アクションパラメータ、オプション。







例:







 enum Actions { increase } //     class ActionsCreate { //      static Action increase(int value) => Action(Actions.increase, payload: value); }
      
      





表示する



ロジックの準備が完了し、結果を表示するために残ります。 Viewは、現在の状態、ディスパッチ、ViewServiceをパラメーターとして受け取り、ウィジェットを返す関数です。







ディスパッチ関数は、アクションを送信するために必要です。アクションは、前に説明した作成です。

ViewServiceには(標準のflutterライブラリからの)現在のBuildContextが含まれ、依存関係を作成するためのメソッドを提供しますが、それらについては後で説明します。







例:







 Widget clickerView(ClickerState state, Dispatch dispatch, ViewService viewService) { return RaisedButton( child: Text(state.count.toString()), onPressed: () => dispatch(ActionsCreate.increase(1)) //         ); }
      
      





成分



これらすべてからコンポーネントを組み立てます。







 class ClickerComponent extends Component<ClickerState> { ClickerComponent() : super( reducer: clickerReducer, view: clickerView, ); }
      
      





ご覧のとおり、この例ではエフェクトは使用されていません。 必要ありません。 効果は、すべての副作用を実行する必要がある関数です。 しかし、効果なしではできないケースを考えてみましょう。 たとえば、これはrandom.orgからの乱数によるカウントの増加です。







エフェクト実装例
 import 'package:http/http.dart' as http; //   http   Effect<ClickerState> clickerEffect() { return combineEffects({ Actions.increaseRandomly: increaseRandomly, }); } Future<void> increaseRandomly(Action action, Context<ClickerState> context) async { final response = await http.read('https://www.random.org/integers/?num=1&min=1&max=10&col=1&base=10&format=plain'); //   random.org.      1  10. final value = int.parse(response); context.dispatch(ActionsCreate.increase(value)); } //   increaseRandomly enum Actions { increase, /* new */ increaseRandomly } class ActionsCreate { static Action increase(int value) => Action(Actions.increase, payload: value); static Action increaseRandomly() => const Action(Actions.increaseRandomly); // new } //  ,        . Widget clickerView(ClickerState state, Dispatch dispatch, ViewService viewService) { return Column( mainAxisSize: MainAxisSize.min, children: [ RaisedButton( //   child: Text(state.count.toString()), onPressed: () => dispatch(ActionsCreate.increase(1)) ), RaisedButton( //  child: const Text('Increase randomly'), onPressed: () => dispatch(ActionsCreate.increaseRandomly()) ), ] ); } //     class ClickerComponent extends Component<ClickerState> { ClickerComponent() : super( reducer: clickerReducer, view: clickerView, effect: clickerEffect() ); }
      
      





ページ



Page <T、P>というコンポーネントの拡張機能があります。 このページには、2つの追加フィールドが含まれています。

T initState(P params)



-初期状態を返す関数。 ページが作成されるときに呼び出されます。

List<Middleware<T>> middleware



- List<Middleware<T>> middleware



のリスト-レデューサーの前に呼び出される関数。

また、1つの方法:

Widget buildPage(P params)



-ページを動作中のウィジェットに収集します。







アプリケーションのメインページを作成しましょう。







 class MainPage extends Page<void, void> { MainPage(): super( initState: (dynamic param) {}, view: (state, dispatch, viewService) => Container(), ); }
      
      





ページはコンポーネントを拡張します。つまり、レデューサー、エフェクト、および通常のコンポーネントが持つその他すべてを含めることができます。







この例では、状態もレデューサーも効果もない空白ページが作成されました。 これは後で修正します。







これらはすべてわずかに異なる形式であり、Brian EganのFlutter ReduxおよびReduxの他の実装でも同様です。 新しいライブラリの主な機能である依存関係に移りましょう。







依存関係



Fish Reduxでは、コンポーネント間の依存関係を明示的に定義する必要があります。 コンポーネントでサブコンポーネントを使用する場合は、これらの2つのコンポーネントを記述するだけでなく、状態を別の状態に変換するコネクタを作成する必要があります。 MainPageページにClickerComponentを埋め込むとします。







最初に、ページに状態を追加する必要があります。







 class MainState implements Cloneable<MainState> { ClickerState clicker; @override MainState clone() { return MainState() ..clicker = clicker; } static MainState initState(dynamic params) { return MainState() ..clicker = ClickerState(); } }
      
      





これでConnectorを書くことができます:







 class ClickerConnector extends ConnOp<MainState, ClickerState> { @override ClickerState get(MainState state) => state.clicker; //        . @override void set(MainState state, ClickerState subState) => state.clicker = subState; }
      
      





それだけです コンポーネントを追加する準備がすべて整いました:







 class MainPage extends Page<MainState, void> { MainPage(): super( initState: MainState.initState, dependencies: Dependencies( slots: { 'clicker': ClickerComponent().asDependent(ClickerConnector()), //    // 'clicker': ClickerComponent() + ClickerConnector(), }, ), view: (state, dispatch, viewService) { //   clicker-. final clickerWidget = viewService.buildComponent('clicker'); return Scaffold( body: Column( mainAxisAlignment: MainAxisAlignment.center, mainAxisSize: MainAxisSize.max, children: [ Center( child: clickerWidget, //   ) ], ) ); }, ); }
      
      





したがって、次のコードをmain.dart



追加することにより、完全に機能するアプリケーションを構築できます。







 void main() => runApp(MyApp()); class MyApp extends StatefulWidget { @override _MyAppState createState() => _MyAppState(); } class _MyAppState extends State<MyApp> { @override Widget build(BuildContext context) => MaterialApp(home: MainPage().buildPage(null)); }
      
      





すべてのファイル区切りコードは、こちらから入手できます 。 Flutterの開発経験が豊富です。








All Articles