在 Flutter 使用 Redux 來共享狀態和管理單一資料
React 生態裡廣為人知的 Redux 狀態管理,其實在 Flutter 中也能適用,它能很好的處理單一資料和狀態共享,在一定程度上對於分割專案之間複雜的業務有一定的積極作用,可閱讀可維護也能做的很不錯。對於使用過 React 的前端開發來說 Redux 的概念肯定熟記於心了,不過我還是要簡單說一些東西,只有這樣我們才能更好的進入下一個環節。
Redux 主要由三個部分組成:Store,Action,Reducer
Action 用於定義資料變化的行為(至少在語義上我們應該定義明確的行為)
Reducer 用於根據 Action 來產生新的狀態
Store 用於儲存和管理 state
這個專案的 Redux 例子使用瞭如下兩個 package:
https://
github。com/brianegan/fl
utter_redux
https://
github。com/brianegan/fl
utter_redux_dev_tools
讓我們先來看一看具體的效果圖:
根據效果來分析我們的 Store 至少是一個數組,數組裡面是一個物件,這個物件至少有兩個屬性分別是 name 和 icon,那麼我們應該先來定義全域性的 state 和 這個物件。
// 全域性 state
class
AppState
{
List
<
AVList
>
data
;
AppState
(
this
。
data
);
}
import
‘package:flutter/material。dart’
;
// 具體使用的物件
class
AVList
{
final
String
name
;
final
IconData
icon
;
AVList
(
this
。
name
,
this
。
icon
);
AVList
。
fromJSON
(
Map
<
String
,
dynamic
>
json
)
:
name
=
json
[
‘name’
],
icon
=
json
[
‘icon’
];
}
你能看見它們分別做了兩件事情,往ListView中新增一個Item,將最後一個Item從ListView中刪除,那麼接下來我們要定義它們的Action和Reducer。
// Action
import
‘package:my_flutter_app/flow/listModel。dart’
;
List
<
AVList
>
addItem
(
List
<
AVList
>
avLists
,
action
){
avLists
。
add
(
action
。
avLists
[
0
]);
return
avLists
;
}
List
<
AVList
>
removeItem
(
List
<
AVList
>
avLists
,
action
){
avLists
。
removeLast
();
return
avLists
;
}
import
‘package:redux/redux。dart’
;
import
‘package:my_flutter_app/flow/listModel。dart’
;
import
‘package:my_flutter_app/flow/listActions。dart’
;
final
ListReducer
=
combineReducers
<
List
<
AVList
>>
([
TypedReducer
<
List
<
AVList
>
,
AddAVListAction
>
(
addItem
),
TypedReducer
<
List
<
AVList
>
,
RemoveAVListAction
>
(
removeItem
)
]);
class
AddAVListAction
{
final
List
<
AVList
>
avLists
;
AddAVListAction
(
this
。
avLists
);
}
class
RemoveAVListAction
{}
我們可以使用 combineReducers 來註冊你的 Action,並且使用 TypedReducer 來對映你的 Action。
現在,我們可以在
main。dart
中定義你全域性的 Store 和 Reducer :
AppState
appReducer
(
AppState
state
,
action
)
{
return
new
AppState
(
ListReducer
(
state
。
data
,
action
)
);
}
final
store
=
new
Store
<
AppState
>
(
appReducer
,
initialState:
new
AppState
([
new
AVList
(
“android”
,
Icons
。
android
)])
);
之前我們定義的資料結構中是一個List,其中物件的型別是AVList,因為我們可以在初始化的時候給它一個
預設值
。
接下來我們可以來完善 Widget 這一層,在這一層中基本上我們需要做:
Widget 繫結 Store 中的 state
Widget 觸發某個 Action
Reducer 根據某個 Action 觸發更新 state
更新 Store 中 state 繫結的 Widget
在這裡我們會使用到幾個 Widget 和一個 Dispatch 來完成上述的步驟,第一步我們要使用 StoreProvider 它會將繫結的 Store 傳遞給它的所有子 Widget ,其次我們需要使用 StoreConnector 它會將更新後的資料 callback 給你,最後我們會使用 dispatch 來執行某些 Action ,完成某些 state 的操作。
完整的例子:
import
‘package:flutter/material。dart’
;
import
‘package:redux/redux。dart’
;
import
‘package:flutter_redux/flutter_redux。dart’
;
import
‘package:my_flutter_app/flow/listModel。dart’
;
import
‘package:my_flutter_app/flow/listReducer。dart’
;
class
AppState
{
List
<
AVList
>
data
;
AppState
(
this
。
data
);
}
AppState
appReducer
(
AppState
state
,
action
)
{
return
new
AppState
(
ListReducer
(
state
。
data
,
action
)
);
}
class
AVReduxList
extends
StatelessWidget
{
final
Store
<
AppState
>
store
;
AVReduxList
({
Key
key
,
this
。
store
})
:
super
(
key:
key
);
@
override
Widget
build
(
BuildContext
context
)
{
return
new
StoreProvider
<
AppState
>
(
store:
store
,
child:
new
MaterialApp
(
home:
new
Scaffold
(
appBar:
new
AppBar
(
title:
new
Text
(
‘AVReduxList’
),
),
body:
new
Column
(
children:
<
Widget
>
[
new
StoreConnector
<
AppState
,
List
<
AVList
>>
(
converter:
(
store
)
=>
store
。
state
。
data
,
builder:
(
BuildContext
context
,
data
){
return
new
Container
(
height:
500。0
,
child:
ListView
。
builder
(
itemCount:
data
。
length
,
itemBuilder:
(
BuildContext
context
,
int
position
){
return
new
Padding
(
padding:
EdgeInsets
。
all
(
10。0
),
child:
new
Row
(
children:
<
Widget
>
[
new
Text
(
data
[
position
]。
name
),
new
Icon
(
data
[
position
]。
icon
,
color:
Colors
。
blue
)
],
),
);
},
),
);
},
),
new
Row
(
crossAxisAlignment:
CrossAxisAlignment
。
center
,
children:
<
Widget
>
[
new
RaisedButton
(
color:
Colors
。
blue
,
child:
new
Text
(
‘更新’
,
style:
new
TextStyle
(
color:
Colors
。
white
),
),
onPressed:
(){
store
。
dispatch
(
new
AddAVListAction
(
[
new
AVList
(
“android”
,
Icons
。
android
)]
));
},
),
new
RaisedButton
(
color:
Colors
。
blue
,
child:
new
Text
(
‘刪除最後一項’
,
style:
new
TextStyle
(
color:
Colors
。
white
),
),
onPressed:
(){
store
。
dispatch
(
new
RemoveAVListAction
()
);
},
)
],
)
],
),
),
)
);
}
}
Redux Dev Tools
這是一個類似 Redux Time Travel 的 UI 小工具,在開發階段我們可以使用這個工具來追溯你的操作,因此我們需要重新定義一個
入口檔案
main_dev。dart:
import
‘package:flutter/material。dart’
;
import
‘package:flutter_redux_dev_tools/flutter_redux_dev_tools。dart’
;
import
‘package:redux_dev_tools/redux_dev_tools。dart’
;
import
‘package:my_flutter_app/AVReduxList。dart’
;
import
‘package:my_flutter_app/flow/listModel。dart’
;
void
main
(){
final
store
=
new
DevToolsStore
<
AppState
>
(
appReducer
,
initialState:
new
AppState
([
new
AVList
(
“android”
,
Icons
。
android
)])
);
runApp
(
new
ReduxDevToolsContainer
(
store:
store
,
child:
new
AVReduxList
(
store:
store
,
devDrawerBuilder:
(
BuildContext
context
){
return
new
Drawer
(
child:
new
Padding
(
padding:
new
EdgeInsets
。
only
(
top:
24。0
),
child:
new
ReduxDevTools
(
store
),
),
);
},
),
));
}
在這裡我們需要使用 DevToolsStore 來定義你的全域性 Store ,另外我們還需要對原來的 AVReduxList進行一些改造,增加一個 devDrawerBuilder 屬性來控制 DevTools 的繪製。
// AVReduxList。dart
class
AVReduxList
extends
StatelessWidget
{
final
Store
<
AppState
>
store
;
final
WidgetBuilder
devDrawerBuilder
;
AVReduxList
({
Key
key
,
this
。
store
,
this
。
devDrawerBuilder
})
:
super
(
key:
key
);
@
override
Widget
build
(
BuildContext
context
)
{
return
new
StoreProvider
<
AppState
>
(
store:
store
,
child:
new
MaterialApp
(
home:
new
Scaffold
(
endDrawer:
devDrawerBuilder
!=
null
?
devDrawerBuilder
(
context
)
:
null
,
。。。
)
)
)
}
}
最後,我們在 VSCode 中重新新增一個新的
啟動項
:
{
“name”
:
“Flutter_Redux_DevTools”
,
“type”
:
“dart”
,
“request”
:
“launch”
,
“program”
:
“lib/main_dev。dart”
},
效果圖: