手把手入門Fish-Redux開發flutter(下)

手把手入門Fish-Redux開發flutter(上) (中) (下)git

前面兩篇,咱們瞭解了fish-redux並實現了簡單的功能,此次咱們再瞭解fish-redux一些其餘的特色。看一下結果圖:github

1 使用 Component 和 Adapter 作一個列表

1.1 建立列表頁、定義數據

頁面建立的過程跟以前同樣,就省略啦。我建立了名爲 List 的頁面,結果以下 redux

在 app.dart 中加入咱們的這個頁面。修改過的 app.dart 以下markdown

Widget createApp() {
  final AbstractRoutes routes = PageRoutes(
    pages: <String, Page<Object, dynamic>>{
      'entrance_page': EntrancePage(),
      'grid_page': GridPage(),
      'list_page':ListPage(),   //此處加入咱們新建的頁面
    },
  );
//省略...
複製代碼

而後實現從 Grid 頁面跳轉到這個頁面,(頁面跳轉上一篇講過了,這裏不詳細說。就是建立action、在view中dispatch action、effect中接收到action並跳轉頁面)代碼以下 併發

1.2 建立 component

而後咱們在 list 包下面建立一個 Component 做爲列表的每一個 item 。app

第一步 經過插件新建Component

首先建立一個名爲 item 的包,而後在 item 下建立FishReduxTemplate,此次咱們選擇 Component框架

建立結果以下,能夠看到組件中的 component.dart 相似頁面中的 page.dart。 ide

第二步 定義組件數據和ui

咱們給 state 三個字段 type(圖標的形狀),title(標題),content(內容)。修改 /list/item/state.dart 以下post

import 'package:fish_redux/fish_redux.dart';

class ItemState implements Cloneable<ItemState> {

  int type;
  String title;
  String content;

  ItemState({this.type, this.title, this.content});

  @override
  ItemState clone() {
    return ItemState()
    ..type = type
    ..title = title
    ..content = content;
  }
}

ItemState initState(Map<String, dynamic> args) {
  return ItemState();
}
複製代碼

而後咱們來實現 item 的視圖,使用上面 state 的數據,而且根據 type 不一樣顯示不一樣的 icon 圖標(詳見註釋)。/list/item/view.dart 以下ui

import 'package:fish_redux/fish_redux.dart';
import 'package:flutter/material.dart';

import 'action.dart';
import 'state.dart';

Widget buildView(ItemState state, Dispatch dispatch, ViewService viewService) {
  return Container(
    margin: EdgeInsets.fromLTRB(0, 0, 0, 10),
    height: 120.0,
    color: Colors.white,
    child: GestureDetector(
      child: Row(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: <Widget>[
          //左側圖標
          Container(
            padding: const EdgeInsets.only(right: 5.0),
            child: Center(
              child: Icon(
              //不一樣type顯示不一樣icon
                state.type == 1 ? Icons.account_circle : Icons.account_box,
                size: 50.0,
              ),
            ),
          ),
          //右側
          Expanded(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.start,
              crossAxisAlignment: CrossAxisAlignment.start,
              children: <Widget>[
                //標題部分
                Container(
                  height: 30,
                  child: Text(
                    state.title,
                    style: TextStyle(fontSize: 22.0),
                  ),
                ),
                //內容部分
                Text(
                  state.content,
                  style: TextStyle(fontSize: 16.0),
                ),
              ],
            ),
          ),
        ],
      ),
      onTap: () {
        //todo 點擊事件
      },
    ),
  );
}
複製代碼

1.3 關聯組件和頁面

Component告一段落。接着咱們在列表中使用組件。

第一步 建立adapter

首先在 List 的 State 中存儲每個 Item 的 State 數據。/list/state.dart/ 以下

import 'package:fish_demo/list/item/state.dart';
import 'package:fish_redux/fish_redux.dart';

class ListState implements Cloneable<ListState> {
  
  List<ItemState> items;    //保存item的state

  @override
  ListState clone() {
    return ListState()
    ..items = items;
  }
}

ListState initState(Map<String, dynamic> args) {
  return ListState();
}
複製代碼

而後把 list 和 item 關聯,咱們使用 fish-redux 的一個組件叫 adapter。首先在 list 包下用插件建立 FishReduxTemplate,而後選中 DynamicFlowAdapter,並取消其餘的勾選,取名 List,以下

第二步 實現connector

看到默認的 adapter 代碼中爲咱們準備了兩個類:一個 ListAdapter 和一個 _ListConnector。其中 ListAdapter 中咱們配置須要關聯的組件,即把組件添加到 pool。以及列表和組件的數據適配關係,經過實現一個 connector 。完成的 /list/adapter.dart 以下

import 'package:fish_demo/list/state.dart';
import 'package:fish_redux/fish_redux.dart';

import 'item/component.dart';
import 'item/state.dart';

class ListAdapter extends DynamicFlowAdapter<ListState> {
  ListAdapter()
      : super(
          pool: <String, Component<Object>>{
            "MyItem": ItemComponent(),  //引用組件
          },
          connector: _ListConnector(),
        );
}

class _ListConnector extends ConnOp<ListState, List<ItemBean>> {
  @override
  List<ItemBean> get(ListState state) {
    //判斷ListState裏面的items數據是否爲空
    if (state.items?.isNotEmpty == true) {
      //若不爲空,把item數據轉化成ItemBean的列表
      return state.items
        .map<ItemBean>((ItemState data) => ItemBean('MyItem', data))
        .toList(growable: true);
    }else{
      //若爲空,返回空列表
      return <ItemBean>[];
    }
  }

  @override
  void set(ListState state, List<ItemBean> items) {
    //把ItemBean的變化,修改到item的state的過程
    if (items?.isNotEmpty == true) {
      state.items = List<ItemState>.from(
        items.map<ItemState>((ItemBean bean) => bean.data).toList());
    } else {
      state.items = <ItemState>[];
    }
  }

  @override
  subReducer(reducer) {
    // TODO: implement subReducer
    return super.subReducer(reducer);
  }
}
複製代碼

第三步 把adapter添加到列表頁的依賴中

咱們打開 List 頁面的 page 文件,而後吧adapter添加到頁面的 dependencies 中。以下

import 'package:fish_demo/list/adapter.dart';//注意1
import 'package:fish_redux/fish_redux.dart' hide ListAdapter;//注意1

import 'effect.dart';
import 'reducer.dart';
import 'state.dart';
import 'view.dart';

class ListPage extends Page<ListState, Map<String, dynamic>> {
  ListPage()
      : super(
            initState: initState,
            effect: buildEffect(),
            reducer: buildReducer(),
            view: buildView,
            dependencies: Dependencies<ListState>(
                adapter: NoneConn<ListState>() + ListAdapter(),//注意2
                slots: <String, Dependent<ListState>>{
                }),
            middleware: <Middleware<ListState>>[
            ],);

}
複製代碼

上面的代碼我標註了兩點注意。

注意1:咱們這裏引用的是剛纔自定義的ListAdapter而不是fish-redux自帶的ListAdapter類,這裏要處理一下引用。(早知道就起名字的時候就不叫ListAdapter了。。。)

注意2:這裏使用的"加號"是 fish-redux 重載的操做符。後者是咱們自定義的 adapter ,而因爲咱們在外層已經不須要使用 connector,因此前者傳入一個 NoneConn

第四步 列表ui

最後咱們在 /list/view.dart 獲取到 adapter 並完成列表頁的UI。/list/view.dart 以下

import 'package:fish_redux/fish_redux.dart';
import 'package:flutter/material.dart';
import 'state.dart';

Widget buildView(ListState state, Dispatch dispatch, ViewService viewService) {
  
  ListAdapter adapter = viewService.buildAdapter();     //建立adapter

  return Scaffold(
      appBar: new AppBar(
        title: new Text('列表頁'),
      ),
      body: Container(
        child: ListView.builder(
          itemBuilder: adapter.itemBuilder, //把adapter配置到list
          itemCount: adapter.itemCount,     //
        ),
      ));
}
複製代碼

1.4 配上假數據

最後咱們給 List 頁面配上一些假數據來看看效果。具體過程咱們已經學過了

  1. effect 在頁面初始化時建立假數據
  2. effect 把發送攜帶假數據的 action
  3. reducer 接收 action,經過假數據更新state

你們基本都會了,要注意一點此次 action 攜帶了參數。貼一下代碼

最後運行一下看看效果

2 使用全局 store 更換主題

此次咱們來接觸一下 store。它負責管理全局的狀態,咱們以主題顏色爲例進行演示。

2.1 建立全局state

以前頁面和組件的建立都是經過插件模板,store 此次手動建立。 建立名爲 store 的 package。而後建立一個 state.dart,咱們用它來保存全局狀態。本例中就是保存主題顏色。/store/state.dart 以下

import 'dart:ui';
import 'package:fish_redux/fish_redux.dart';

abstract class GlobalBaseState {
  Color get themeColor;
  set themeColor(Color color);
}

class GlobalState implements GlobalBaseState, Cloneable<GlobalState> {
  @override
  Color themeColor;

  @override
  GlobalState clone() {
    return GlobalState();
  }
}
複製代碼

注意這裏咱們先定義了一個抽象類 GlobalBaseState 包含 themeColor 字段。一下子咱們會讓全部頁面的 state 繼承它。另外上面咱們還寫了一個它的實現類 GlobalState,即全局的 state。

2.2 建立store

咱們再新建一個 store.dart,做爲 App的Store。/store/store.dart 以下

import 'package:fish_redux/fish_redux.dart';
import 'state.dart';

class GlobalStore {
  static Store<GlobalState> _globalStore;

  static Store<GlobalState> get store =>
    _globalStore ??= createStore<GlobalState>(GlobalState(), buildReducer());

}
複製代碼

能夠看到 store 保存了全局 state,而且它也能擁有 reducer 來處理事件。接下來咱們就定義它的 action 和 reducer。

2.3 建立store的action和reducer

建立 action.dart ,定義一個改變主題顏色的事件。/store/action.dart 以下

import 'package:fish_redux/fish_redux.dart';

enum GlobalAction { changeThemeColor }

class GlobalActionCreator {
  static Action onchangeThemeColor() {
    return const Action(GlobalAction.changeThemeColor);
  }
}
複製代碼

建立 reducer.dart,接收事件並修改 GlobalState 的主題色。(這裏咱們讓主題色在藍色和綠色中來回切換)/store/reducer.dart 以下

import 'dart:ui';
import 'package:fish_redux/fish_redux.dart';
import 'package:flutter/material.dart' hide Action;
import 'action.dart';
import 'state.dart';

Reducer<GlobalState> buildReducer() {
  return asReducer(
    <Object, Reducer<GlobalState>>{
      GlobalAction.changeThemeColor: _onchangeThemeColor,
    },
  );
}

GlobalState _onchangeThemeColor(GlobalState state, Action action) {
  final Color color =
  state.themeColor == Colors.green ? Colors.blue : Colors.green;
  return state.clone()..themeColor = color;
}
複製代碼

這樣整個 store 就寫好了。全家福

2.4 繼承GlobalBaseState

咱們讓全部頁面的 state 繼承 GlobalBaseState,並在頁面視圖中吧標題欄的顏色設置爲 themeColor。咱們一共有三個界面(entrance、grid、list)須要修改,以 list 頁面爲例,首先是 /list/state.dart

而後是 /list/view.dart

其餘兩個頁面同理,不一一展現了。

2.5 關聯state

如何把各個頁面的 state 和全局 GlobalState 聯繫起來呢?咱們要在 app.dart 中配置 visitor。在這裏,咱們判斷頁面是否繼承了 GlobalBaseState,而後跟全局 store 創建聯繫,即該頁面的 state 隨全局state更新而更新。修改好的 app.dart以下

Widget createApp() {
  final AbstractRoutes routes = PageRoutes(
    pages: <String, Page<Object, dynamic>>{
      'entrance_page': EntrancePage(),
      'grid_page': GridPage(),
      'list_page': ListPage(),
    },
    visitor: (String path, Page<Object, dynamic> page) {
      /// 知足條件 Page<T> ,T 是 GlobalBaseState 的子類。
      if (page.isTypeof<GlobalBaseState>()) {
        /// 創建 AppStore 驅動 PageStore 的單向數據鏈接
        /// 1. 參數1 AppStore
        /// 2. 參數2 當 AppStore.state 變化時, PageStore.state 該如何變化
        page.connectExtraStore<GlobalState>(
          GlobalStore.store, (Object pagestate, GlobalState appState) {
          final GlobalBaseState p = pagestate;
          if (p.themeColor != appState.themeColor) {
            if (pagestate is Cloneable) {
              final Object copy = pagestate.clone();
              final GlobalBaseState newState = copy;
              newState.themeColor = appState.themeColor;
              return newState;
            }
          }
          return pagestate;
        });
      }
    },
  );
//如下省略...
}
複製代碼

2.6 觸發主題修改

最後我但願經過點擊 List 頁面的 item,來實現主題色的切換。

第一步

首先咱們定義一個 action,/list/item/action.dart 以下

import 'package:fish_redux/fish_redux.dart';

enum ItemAction { action, onThemeChange }

class ItemActionCreator {
  static Action onAction() {
    return const Action(ItemAction.action);
  }

  static Action onThemeChange() {
    return const Action(ItemAction.onThemeChange);
  }
}
複製代碼

第二步

咱們在 item 點擊時,發送這個事件。/list/item/view.dart 以下

Widget buildView(ItemState state, Dispatch dispatch, ViewService viewService) {
  return Container(
    margin: EdgeInsets.fromLTRB(0, 0, 0, 10),
    height: 120.0,
    color: Colors.white,
    child: GestureDetector(
      child: Row(
        //省略...
      ),
      onTap: () {
        dispatch(ItemActionCreator.onThemeChange());
      },
    ),
  );
}
複製代碼

第三步

在 effect 接收事件,併發送咱們以前定義的全局修改主題色事件。/list/item/effect.dart 以下

import 'package:fish_demo/store/action.dart';
import 'package:fish_demo/store/store.dart';
import 'package:fish_redux/fish_redux.dart';
import 'action.dart';
import 'state.dart';

Effect<ItemState> buildEffect() {
  return combineEffects(<Object, Effect<ItemState>>{
    ItemAction.action: _onAction,
    ItemAction.onThemeChange: _onThemeChange,
  });
}

void _onAction(Action action, Context<ItemState> ctx) {
}

void _onThemeChange(Action action, Context<ItemState> ctx) {
  GlobalStore.store.dispatch(GlobalActionCreator.onchangeThemeColor());
}
複製代碼

最後運行看下效果: 一步步進入列表頁,屢次點擊item,主題色隨之變化。返回上級頁面,主題色也被改變。

總結

至此,咱們瞭解了 fish-redux 的一些基本的使用。因爲客戶端同窗對 redux 類框架接觸很少,因此在使用時要轉變思路、多看文檔、多思考。

項目源碼 github.com/Jegaming/Fi…

🤗若是個人內容對您有幫助,歡迎點贊、評論、轉發、收藏。

相關文章
相關標籤/搜索