fish_redux : https://github.com/alibaba/fish-redux
官方文档太low,建议直接看这里 https://blog.csdn.net/weixin_34163553/article/details/91380928
网上有很多关于fish_redux的相关博客,那为什么我还要重复制造轮子?原因有两:
以下为补充部分,具体的请看完上面的博客,再回来继续阅读
store
和redux里的store类似,用于全局数据共享,如userinfo,token,Theme等,以下代码不做这一部分的演示
state
按官方文档来说,一个Page对应一个state,页面初始化的时候同时初始化相应的PageState,页面销毁的时候会同时释放相应的PageState
page
通常指一个页面,继承自Page,拥有initstate方法,注意只有page才拥有,而component是没有的
component
组件,一个Page 可以由多个component组合而成,用于页面的拆分
reducer
凡是redux类型框架,他们遵守的都是state不可变原则,只有通过reducer去返回新的一个state去刷新试图
effect
fish_redux 新增加的一个概念,用于处理reducer之外的副作用,实际项目中网络请求,业务逻辑也是写在这里的
Route
路由,有AppRoutes,PageRoutes,HybridRoutes
example : https://github.com/ikimiler/fish_redux_example
通过项目实战你可以深入了解到connec,adapter,component,page究竟怎么使用,以及有什么区别
项目里有mvp和fish_redux两种架构模式,mvp请自行阅读,重点介绍fish_redx
//运行fish_redux架构示例
void main() => runApp(createApp());
//运行mvp架构示例
// void main() => runApp(MainPage());
class MainPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: "demo",
theme: ThemeData(
primaryColor: Colors.white,
),
home: MVPPage(),
);
}
}
App.dart
/// 创建应用的根 Widget
/// 1. 创建一个简单的路由,并注册页面
/// 2. 对所需的页面进行和 AppStore 的连接 todo
/// 3. 对所需的页面进行 AOP 的增强 todo
Widget createApp() {
return MaterialApp(
title: 'demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primaryColor: Colors.white,
),
//配置默认页面为OnePage
home: RouteConfig.ROUTES.buildPage(RouteConfig.ONE_PAGE_PATH, null),
onGenerateRoute: (RouteSettings settings) {
return MaterialPageRoute
Page 可以使用fish-redux-template快速生成page,每个page/component都对应6个文件,action,reducer,page/component,effect,state(可有可无,看自己需求),view
这里重点讲component_demo_page,也是体现Page和Component的不同之处
在实际开发中,我们为了代码复用可能一个Page页面由多个Component组装而成,请看最上边图二,它分别由headerComponent,bodyComponent,footerComponent组装而成,代码目录结构为下图
class ComponentDemoPage extends Page> {
ComponentDemoPage()
: super(
//只有page才会有initState方法,普通的component并没有
initState: initState,
effect: buildEffect(),
reducer: buildReducer(),
//渲染视图
view: buildView,
dependencies: Dependencies(
//这里可以映射adapter
adapter: null,
slots: >{
//slots这里是一个映射作用以及数据page的state如何和component的state的进行关联,需要用到connect,connect这个概念要深刻理解,它起到一个数据如何关联的作用,牢记。
//一个页面可以由多个component自由组装,component也可以由其他页面进行复用
//这里分别映射三个component,其中headercomponent的state 共用当前页面的state,所以采用 NoneConn 连接方式
//bodycomponent的state用body component本身的state,所以需要connect 从ComponentDemoState映射到BodyState
//footer和header同理,共用当前页面的state
//注意,component可以共用,比如这是a页面,那么在b页面也可以使用这些component,只要分别提供对应的connec就可以了,
"header": NoneConn() + HeaderComponent(),
"body": BodyComponentConnec() + BodyComponent(),
"footer": NoneConn() + FooterComponent(),
}),
middleware: >[],
);
}
Widget buildView(
ComponentDemoState state, Dispatch dispatch, ViewService viewService) {
return Scaffold(
appBar: AppBar(
title: Text("component demo"),
),
body: Container(
child: ListView(
children: [
//这里直接取映射好的component,也就是page里配置的slots
viewService.buildComponent("header"),
viewService.buildComponent("body"),
viewService.buildComponent("footer"),
Container(
color: Colors.grey,
padding: EdgeInsets.symmetric(vertical: 20),
alignment: Alignment.center,
child: Column(
children: [
Text("ComponentDemoState.header : ${state.header}"),
Text("ComponentDemoState.body : ${state.body}"),
Text("ComponentDemoState.footer : ${state.footer}"),
],
),
)
],
),
),
);
}
bodyComponent.dart
class BodyComponent extends Component {
BodyComponent()
: super(
effect: buildEffect(),
reducer: buildReducer(),
//渲染具体的视图
view: buildView,
dependencies: Dependencies(
adapter: null, slots: >{}),
);
}
//由于bodycomponent用的是自己本身的state,所以需要一个connec来提供如何映射
class BodyComponentConnec extends ConnOp {
//如何从BodyState 映射到 ComponentDemoState
//bodystate是自己本身的state
//ComponentDemoState是页面的state
@override
void set(ComponentDemoState state, BodyState subState) {
// super.set(state, subState);
state.header = subState.up;
state.body = subState.mid;
state.footer = subState.down;
}
//如何从ComponentDemoState 映射到 BodyState
//bodystate是自己本身的state
//ComponentDemoState是页面的state
@override
BodyState get(ComponentDemoState state) {
// return super.get(state);
return BodyState()
..up = state.header
..mid = state.body
..down = state.footer;
}
}
ListviewDemoPage Adapter示例
class ListviewDemoPage extends Page> {
ListviewDemoPage()
: super(
initState: initState,
effect: buildEffect(),
reducer: buildReducer(),
//渲染视图
view: buildView,
dependencies: Dependencies(
//这里映射adapter,由于我们在ListviewDemoState数据源实现了MutableSource接口
//所以这里可以使用NoneConn连接器,如果数据源没有实现相关接口,那么请按照component的形式,去实现ConnOp,提供具体的数据映射关系
adapter: NoneConn() + ListviewAdapter(),
slots: >{
//同时也可以映射components
//由于这里就是一个列表,所以不需要其他的component
}),
middleware: >[],
);
}
Widget buildView(
ListviewDemoState state, Dispatch dispatch, ViewService viewService) {
return Scaffold(
appBar: AppBar(
title: Text("adapter demo"),
),
body: ListView.builder(
itemBuilder: viewService.buildAdapter().itemBuilder,
itemCount: viewService.buildAdapter().itemCount,
),
);
}
//page对应的state,实现了MutableSource接口
class ListviewDemoState extends MutableSource
implements Cloneable {
List datas = List.generate(300, (value) => "generate $value");
@override
ListviewDemoState clone() {
return ListviewDemoState();
}
//getItemData
@override
Object getItemData(int index) {
return ItemState()..name = datas[index];
}
//itemCount
@override
int get itemCount => datas.length;
//getItemType,这里可以根据index 获取不同的type,和android原生的recyclerview一样
@override
String getItemType(int index) {
return index % 2 == 0 ? "item" : "line";
}
//setItemData
@override
void setItemData(int index, Object data) {
datas[index] = data;
}
}
ListviewAdapter.dart
class ListviewAdapter extends SourceFlowAdapter {
ListviewAdapter()
: super(
pool: >{
//这里同样映射component
//左侧key 对应数据源的getItemType 方法
"item": ItemComponent(),
"line": LineComponent(),
},
reducer: buildReducer(),
);
}
ItemComponent.dart
class ItemComponent extends Component {
ItemComponent()
: super(
effect: buildEffect(),
reducer: buildReducer(),
//渲染视图
view: buildView,
dependencies: Dependencies(
adapter: null,
slots: >{
}),);
}
Widget buildView(ItemState state, Dispatch dispatch, ViewService viewService) {
return Container(
height: 50,
color: Colors.orangeAccent,
child: Text("item ---- ${state.name}"),
alignment: Alignment.center,
);
}
LineComponent.dart
class LineComponent extends Component {
LineComponent()
: super(
effect: buildEffect(),
reducer: buildReducer(),
//渲染视图
view: buildView,
dependencies: Dependencies(
adapter: null, slots: >{}),
);
}
Widget buildView(ItemState state, Dispatch dispatch, ViewService viewService) {
return Container(
height: 1,
color: Colors.green,
);
}
如何进行页面跳转传参数?
//点击按钮发送一个相应的action
RaisedButton(
onPressed: () {
//action也可以携带需要的参数
dispatch(OneActionCreator.gotoStretchDemoPage());
},
child: Text("跨页面进行state通讯以及传参示例"),
),
//页面跳转属于副作用,所以一般是在effect里面处理的
void gotoStretchDemoPage(Action action, Context ctx) {
//使用Navigator进行跳转,arguments传递相关的参数,
//如果action里有传入参数,也可以从action中获取相关的参数
Navigator.of(ctx.context).pushNamed(RouteConfig.TWO_PAGE_PATH,
arguments: {"params": "我是上个页面带过来的参数"});
}
//前面说过每个page页面都有一个initState函数,在对应的state里面
TwoState initState(Map args) {
//所以上面传入的参数,从args里就可以去到,然后赋值给相应的state,view在拿到对应的state,去初始化视图
return TwoState()..text = args["params"];
}
跨页面或跨component如何通讯?
Effect buildEffect() {
return combineEffects(>{
ThreeAction.action: _onAction,
ThreeAction.sendBroadcast: _onSendBroadcast,
Lifecycle.initState:_onInitState,//页面的state初始化的action
Lifecycle.dispose:_onDispose,//页面销毁了
});
}
void _onAction(Action action, Context ctx) {}
//state初始化后的action,一般用来进入页面拉取相关数据操作,这里可以执行异步任务
void _onInitState(Action action, Context ctx) {
}
//页面销毁的action,用来清楚一个数据
void _onDispose(Action action, Context ctx) {
}
void _onSendBroadcast(Action action, Context ctx) {
//跨页面/跨component通讯,可以使用这个发送广播
//然后再想通讯的页面注册相应的effect,就可以了
ctx.broadcast(ThreeActionCreator.onReceiveBroadcast());
//下边这个暂时没有尝试出来,看字面意思是通知所有的effect,但自己验证的时候,并不行 todo
// ctx.broadcastEffect(ThreeActionCreator.onReceiveBroadcast());
}