本文同步在我的博客shymean.com上,歡迎關注前端
最近發現了以前沒寫完的一個Flutter版APP,因而打算重構並繼續開發,本文主要整理在Flutter中使用redux的一些開發經驗。vue
參考react
跟在JS中使用Redux相似,主要分爲下面幾個步驟git
首先實現storegithub
// store/index.dart
import 'package:redux/redux.dart';
class IncrementAction {
final payload;
IncrementAction({this.payload});
}
class AppState {
int count;
AppState({
this.count,
});
static AppState initialState() {
return AppState(
count: 0,
);
}
AppState copyWith({count}) {
return AppState(
count: count ?? this.count,
);
}
}
AppState counterReducer(AppState state, dynamic action) {
switch (action.runtimeType) {
case IncrementAction:
return state.copyWith(count: state.count + action.payload);
}
return state;
}
// 暴露全局store
Store store =
new Store<AppState>(counterReducer, initialState: AppState.initialState());
複製代碼
咱們能夠將store的state
渲染到widget中,並經過dispatch
的方式更新state。當state更新後,會觸發訂閱的onChange
事件從新渲染視圖vuex
// page.dart
import '../../store/index.dart' as BaseStore;
class BaseState extends State<MyPage> {
int _count = 0;
StreamSubscription _listenSub;
@override
void initState() {
super.initState();
print("init state");
// 註冊state變化的回調
_listenSub = BaseStore.store.onChange.listen((newState) {
BaseStore.AppState state = newState;
setState(() {
_count = state.count;
});
});
}
@override
void dispose() {
super.dispose();
_listenSub.cancel();
}
@override
Widget build(BuildContext context) {
Widget btn2 = FloatingActionButton(
onPressed: () {
BaseStore.store.dispatch(BaseStore.IncrementAction(payload: 2));
},
child: Text(
_count.toString(), // 使用state裏面的值
style: Theme.of(context).textTheme.display1,
),
);
}
}
複製代碼
這種方案須要咱們手動訂閱和銷燬store.onChange,以及setState中的一些值更新邏輯,顯得比較繁瑣。在React中咱們使用react-redux
的connect
方法來節省這些步驟,同理,在flutter中咱們可使用flutter_redux
來實現自動訂閱變化的邏輯。redux
首先須要經過StoreProvider
在組件樹根節點注入store網絡
// main.dart
import 'package:flutter_redux/flutter_redux.dart';
import './store/index.dart';
void main() {
runApp(new StoreProvider<AppState>(
store: store,
child: MainApp()));
}
複製代碼
而後須要使用store.state
的地方聲明,經過StoreConnector
將組建和store關聯起來app
// page.dart
import '../../store/index.dart' as BaseStore;
// 在組件中經過StoreConnector輸入組件
Widget btn1 = new StoreConnector<BaseStore.AppState, dynamic>(
// converter相似於mapStateToProps,其返回值會做爲builder方法的第二個參數傳入
converter: (store) {
return store;
},
builder: (context, store) {
return FloatingActionButton(
onPressed: () {
// 觸發action修改state
store.dispatch(BaseStore.IncrementAction(payload: 10));
},
child: new Text(
store.state.count.toString(),
style: Theme.of(context).textTheme.display1,
),
);
},
);
複製代碼
習慣了使用React中的connect
方法來注入store
,因此會以爲在組件中使用StoreConnector
不是很簡潔。所以能夠進一步封裝一個flutter版本的connect
方法異步
typedef MapStateToPropsCallback<S> = void Function(
Store<AppState> store,
);
typedef BaseViewModelBuilder<ViewModel> = Widget Function(
BuildContext context,
ViewModel vm,
Store<AppState> store,
);
Widget connect(
MapStateToPropsCallback mapStateToProps, BaseViewModelBuilder builder) {
return StoreConnector<AppState, dynamic>(
converter: mapStateToProps,
builder: (context, props) {
// 傳入props和store
return builder(context, props, store);
},
);
}
複製代碼
而後就能夠經過connect(mapStateToProps, builder)
的方式來使用組件啦
Widget page = connect((store) {
return store.state.joke.jokes;
}, (context, jokes, store) {
return ListView.builder(
itemCount: jokes.length,
itemBuilder: (BuildContext context, int index) {
var joke = jokes[index];
return JokeItem(joke: joke));
});
});
複製代碼
上面的代碼演示了在flutter中使用redux的基本步驟,然而對於一個大型應用而言,還必須考慮state的拆分、同步異步Action等狀況。
將不一樣類型的state放在一塊兒並非一件很明智的事情,咱們每每會根據業務或邏輯對state進行劃分,而不是在同一個reducer中進行很長的switch
判斷,所以拆分state是一種很常見的開發需求。
從前端的經驗來看
module
配置model
的概念,並提供了app.model
接口所幸的是redux.dart
也提供了combineReducers
的方法,咱們能夠用來實現state的拆分
首先咱們來定義全局的state
import 'package:redux/redux.dart';
import './module/test.dart';
import './module/user.dart';
class AppState {
// 把拆分了TestState和UserState兩個state
TestState test;
UserState user;
AppState({this.test, this.user});
static initialState() {
// 分別調用子state的initialState方法
AppState state = AppState(
test: TestState.initialState(), user: UserState.initialState());
return state;
}
}
// 全局reducer,每次返回一個新的AppState對象
AppState _reducer(AppState state, dynamic action) {
return AppState(
test: testReducer(state.test, action),
user: userReducer(state.user, action));
}
// 暴露全局store
Store store =
new Store<AppState>(_reducer, initialState: AppState.initialState());
複製代碼
接下來咱們看看單個state的封裝
// module/test.dart
import 'package:redux/redux.dart';
class TestState {
int count = 0;
TestState({this.count});
static initialState() {
return TestState(count: 1);
}
}
// 使用combineReducers關聯多個Action的處理方法
Function testReducer = combineReducers<TestState>([
TypedReducer<TestState, IncrementAction>(IncrementAction.handler),
]);
// 每次action都包含與之對應的handler,並返回一個新的State
class IncrementAction {
final int payload;
IncrementAction({this.payload});
// IncrementAction的處理邏輯
static TestState handler(TestState data, IncrementAction action) {
return TestState(count: data.count + action.payload);
}
}
複製代碼
而後在UI組件中調用時使用store.state.test.count
的方式來訪問,爲了訪問更簡單,咱們也能夠封裝注入getters
等快捷屬性訪問方式。
Widget btn1 = new StoreConnector<BaseStore.AppState, dynamic>(
converter: (store) {
// 直接返回store自己
return store;
},
builder: (context, store) {
return FloatingActionButton(
onPressed: () {
print('click');
store.dispatch(TestModule.IncrementAction(payload: 10)); // 觸發具體的Action
},
child: new Text(
store.state.test.count.toString(), // 經過store.state.test.xxx來調用
style: Theme
.of(context)
.textTheme
.display1,
),
);
},
);
複製代碼
強類型的一個好處就是咱們不須要經過字符串或枚舉值的方式來定義ACTION_TYPE
了。
當某個子state須要額外的字段和action,直接在各自模塊內定義和實現便可,這樣就實現了一種將全局state進行拆分的方案。
異步action是業務場景中很是常見的action,在redux中咱們能夠經過redux-thunk
或redux-saga
等插件來實現,一樣地,在flutter中咱們也可使用相似的插件。
import 'package:flutter_redux/flutter_redux.dart';
// 在初始化的時候傳入thunkMiddleware
Store store = new Store<AppState>(_reducer,
initialState: AppState.initialState(), middleware: [thunkMiddleware]);
複製代碼
註冊了thunkMiddleware
以後,就能夠定義函數類型的Action
了
// 定義loginAction
Function loginAction({String username, String password}) {
return (Store store) async {
var response = await loginByPassword();
LoginModel.login res = LoginModel.login.fromJson(response.data);
store.dispatch(LoginSuccessAction(payload: res.data));
};
}
複製代碼
最後在視圖中提交Action
void sumit(){
store.dispatch(loginAction(username: username, password: password));
}
複製代碼
就這樣,咱們將視圖中的網絡請求、數據存儲等異步操做等邏輯都封裝在Action的handler中了。
本文主要整理了在Flutter中使用Redux的一些事項,包括
redux
和flutter_redux
管理全局狀態,並封裝了簡易的connect方法redux_thunk
管理異步Action固然,上面的封裝在實際操做中,仍是沒法避免須要些不少State和Action的模板文件;在實際的業務開發中,還須要進一步研究如何編寫更高質量的flutter代碼。