内容简介:这是我个人的项目结构,仅供参考。有些与官方的 example 不太一样。例如官方 todoList 的 demo 中是把 connector 与 state 写在一块的,我这里将 connector 单独抽出了一个文件。fish_redux 有一套自己的路由,有三种 路由方式AppRoutes 包含了 State, Reducer, pages && connector,可以将 store 中的数据 分发到各个页面。其组装方式跟 Page 差不多哦。
阅读这遍文章需要具备以下知识:
- 了解 Flutter,最好写过 demo
- 了解 fish_redux 的基本概念(此文基本没有概念性的东西)
一、 fish_redux 构建项目顺序与项目目录
- 项目构建步骤:
- 配置路由
- 使用 Page 构建页面,这里面可以配置 state、effect、reducer 等要素
- 定义全局 state
- 定义 effect 、 middleware 、reducer 用于实现副作用、中间件、结果返回处理等
- 定义 view 用于绘制页面
- 使用 dependencies 声明页面需要用的适配器(adapter)、插槽(slots)
- 用 Connector(继承自 ConnOp),将 store 的数据分配到 component
-
项目目录:
这是我个人的项目结构,仅供参考。有些与官方的 example 不太一样。例如官方 todoList 的 demo 中是把 connector 与 state 写在一块的,我这里将 connector 单独抽出了一个文件。
二、关键 API
Route
fish_redux 有一套自己的路由,有三种 路由方式
- AppRoutes:多个 page 共享一个 store
- PageRoutes: 页面级别的路由,每个页面都有一个 store
- HybridRoutes:可以结合上面两种 route 方式
AppRoutes
AppRoutes 包含了 State, Reducer, pages && connector,可以将 store 中的数据 分发到各个页面。其组装方式跟 Page 差不多哦。
例:
final AbstractRoutes appRoutes = AppRoutes<AppState>( preloadedState: AppState.initState(), pages: { 'homePage': HomePageConnector() + HomePage(), }, reducer: buildReducer() ); return MaterialApp( home: appRoutes.buildPage('homePage', null), onGenerateRoute: (RouteSettings settings) { return MaterialPageRoute<Object>(builder: (BuildContext context) { return appRoutes.buildPage(settings.name, settings.arguments); }); }, ); /// 想要使用 AppRoutes 发起一个 action,需要将它保存起来,使用它的 store /// 像下面这样 appRoutes.store.dispatch(AppActionCreator.someEvent()); 复制代码
- 使用 AppRoutes 创建了一个路由 -> appRoutes
- 使用 preloadedState 属性初始化 state
- 里面定义了 homePage 页面,HomePageConnector 中将某些 state 分配给 homePage。
PageRoutes
PageRoutes 使用起来就比较简单,因为其不需要定义与分配 state,每个页面的 store 在其自身的 Page 里定义。
例:
final AbstractRoutes pageRoutes = PageRoutes( pages: <String, Page<Object, dynamic>>{ 'homePage': HomePage(), }, ); return MaterialApp( home: pageRoutes.buildPage('homePage', null), onGenerateRoute: (RouteSettings settings) { return MaterialPageRoute<Object>(builder: (BuildContext context) { return pageRoutes.buildPage(settings.name, settings.arguments); }); }, ); 复制代码
HybridRoutes
HybridRoutes 接受一个 route 数组,数组里既可以接受 AppRoutes 也可以接受 PageRoutes
例:
final AbstractRoutes hybridRoutes = HybridRoutes(routes: <AbstractRoutes>[ appRoutes, pageRoutes ]); 复制代码
状态管理(redux)
Store
这个store是我自己的叫法,类比于 redux 的 store,定义全局的 state.
1、 定义 store 数据
使用 class 来声明全局 state,在 声明 page 的时候注入
例:
class PageState implements Cloneable <PageState> { String someState; @override PageState clone() { return PageState() ..someState = someState } } 复制代码
- 上面有个 clone 方法,用来获取 state,在 reducer 中会用到用来 merge state。
- 需要注意的是,如果没有特殊场景需要,在 clone 中要把所有的 state 写全,不然在使用 clone 方法进行 merge 数据的时候,会丢掉里面没写上的数据。
- 使用 Connect 给组件分配 state
类比于 redux 的 connect
class HeaderConnector extends ConnOp<PageState, HeaderState> { @override HeaderState get(PageState state) { final HeaderState headerState = HeaderState( someState: state.someState, ); return headerState; } @override void set(PageState state, HeaderState headerState) { state.someState = headerState.someState; } } 复制代码
- 上面例子将 store 中的 someState 分配给了 HeaderState,作为 header 组件的 state。
- 里面的 set、get 方法会在更新 state 的时候自动调用。
- 这里的 HeaderConnector 会在 Page 创建的时候 dependencies 的 slots 中使用,作用是分配 store。
Action
类比于 redux 的 action,当发起 action 的时候,根据 action 名字匹配 reducer
例:
enum HomeAction { setSomeState, } class HomeActionCreator { static Action setSomeState(String someState) { return Action(HomeAction.setSomeState, payload: someState); } } 复制代码
官方推荐的做法是像上面一样用两个类管理 action
- HomeAction 是一个枚举类,作为 action 的集合
- HomeActionCreator 是一个 ActionCreator 类,在这里面可以约束 payload 类型,在 dispatch 的时候掉用 这个类中的函数并传入 playload。
reducer
类比 redux 的 reducer,在这里处理发起的 action,并更新 state
例:
Reducer<PageState> buildHomeReducer() { return asReducer({ HomeAction.setSomeState: setSomeStateHandle, }); } PageState setSomeStateHandle(PageState state, Action action) { final newSomeState = action.payload; return state.clone()..someState = newSomeState; } 复制代码
- buildHomeReducer 中对 action 与 reducer 进行了映射, 当 dispatch 名为 setSomeState 的 action 时,调用下面的 setSomeStateHandle 方法
- 上面的 setSomeStateHandle 使用了 PageState 的 clone 函数复制了一份新的数据,修改之后并 return
- 需要注意的是,与 redux 一样。fish_redux 的 state 也是 immutable 的,并且直接修改不生效,只能进行替换。
Middleware(中间件)
类比 redux 中的 Middleware,在 dispatch 时调用
例:
import 'package:fish_redux/fish_redux.dart'; Middleware<T> logMiddleware1<T>({ String tag = 'redux', String Function(T) monitor, }) { return ({Dispatch dispatch, Get<T> getState}) { return (Dispatch next) { print('1'); return next; }; }; } 复制代码
return next
Component
每一个 Component 都是一个组件,它对视图(view),修改数据(reducer), 非修改数据操作(effect)这三个逻辑进行了剥离。
Component = View + Effect(可选) + Reducer(可选) + Dependencies(可选)
例:
class HeaderComponent extends Component<HeaderState> { HeaderComponent() : super( view: buildView, effect: buildEffect(), reducer: buildReducer(), dependencies: Dependencies<HeaderState>( adapter: SomeAdapter(), slots: <String, Dependent<HeaderState>>{ 'avatar': AvatarConnector() + AvaterComponent() }), ); } 复制代码
- 这是一个 header 组件,里面使用了 view、effect、reducer、dependencies 四个配置项目,下面挨个介绍
dependencies
dependencies 是一个表达组件之间依赖关系的结构。它包含两个属性 adapter 和 slots。
- adapter: 组件依赖的具体适配器(用来构建高性能的 ListView)。
- slots:组件依赖的插槽。
- 上面的例子中,其中 slots 里有个 ‘avatar’,这是说明 HeaderComponent 页依赖 avatar 组件,其组件名叫 AvaterComponent。 AvaterComponent 的 state 在 AvatarConnector 里被分配。
- adapter 和 所依赖的组件 会在
view
中被ViewService.buildComponent
调用使用
adapter
adapter 是对 listView 的封装优化。适用于长列表的渲染。其有三种实现方式。
- DynamicFlowAdapter 接受数组类型的数据驱动
- StaticFlowAdapter 接受map类型的数据驱动
- CustomAdapter 自定义 adapter
1. DynamicFlowAdapter:
class SomeDynamicAdapter extends DynamicFlowAdapter<PageState> { SomeDynamicAdapter() : super( pool: <String, Component<Object>>{ 'task': Task(), }, connector: Connector(), // reducer: buildReducer(), // filter: bindReducerFilter(), // effect: bindEffect(), // higherEffect: bindHeightEffect(), // onError: onError(), ); } class Connector extends ConnOp<PageState, List<ItemBean>> { @override List<ItemBean> get(PageState state) { return state.taskList .map<ItemBean>((TaskState data) => ItemBean('task', data)) .toList(growable: true); } } 复制代码
- 这个文件有两部分,一个是 adapter 主体类,一个是 adapter 的数据源 --
Connect
类 - adapter 这里只用到了 pool 与 connector 属性。其还有 reducer、filter、effect 等其他属性,这里不列举。
- pool 属性用来说明 adapter 所依赖的子组件。这里用到了一个名叫 'task' 的组件,会在下面的 Connector 中用来作为列表的 itemView。
- 使用
ItemBean
方法将data
传进task
组件
2. StaticFlowAdapter
staticFlowAdapter 接收 map 类型的数据
例:
class SomeStaticAdapter extends StaticFlowAdapter<PageState> { SomeStaticAdapter() : super( slots: [ SomeComponent().asDependent(SomeComponenConnectt()), TaskAdapter().asDependent(TaskAdapterConnect()), ], ); } 复制代码
- StaticFlowAdapter 没有 pool 属性,但是有 slots,其他的属性与 DynamicFlowAdapter 相同。
- slots 接受一个数组,里面每一个元素都是与 connect 连接好的 component 或 adapter。
3. CustomFlowAdapter
CustomFlowAdapter 接受的属性与 Component 一样, 唯一的不同是 Adapter 的视图部分返回的是一个 ListAdapter
例:
import 'package:fish_redux/fish_redux.dart'; import 'package:flutter/material.dart'; class CommentAdapter extends Adapter<PageState> { CommentAdapter() : super( adapter: buildCommentAdapter, ); } ListAdapter buildCommentAdapter(PageState state, Dispatch dispatch, ViewService service) { final List builders = Collections.compact([ _buildCommentHeader(state, dispatch, service), ]); if (state.commentList.length > 0) { builders.addAll(_buildCommentList(state, dispatch, service)); } else { builders.addAll(_buildEmpty(state, dispatch, service)); } return ListAdapter( (BuildContext buildContext, int index) => builders[index](buildContext, index), builders.length, ); } 复制代码
commentList.length > 0
view
view 是一个输出 Widget 的上下文无关的函数。其负责视图层的构建,由 state 驱动。
例:
Widget buildView(HeaderState state, Dispatch dispatch, ViewService viewService) { final ListAdapter useAdapter = viewService.buildAdapter(); return FlatButton( child: Container( child: ListView( children: <Widget>[ viewService.buildComponent('avatar'), ListView.builder( itemBuilder: useAdapter.itemBuilder, itemCount: useAdapter.itemCount) ], ), ), onPressed: () { dispatch(HeaderActionCreator.profileOpen(state.profileIsOpen)); }, ); } 复制代码
- 这是一个 view,其内容是一个按钮,点击发起一个 action
- 按钮的 child 是他依赖的 avatar 组件,由 ViewService.buildComponent 调用。
- onPressed 的时候调用 dispatch 事件,在对应的 reducer 中更新 state。
参数:
- state:传进来的 state
- dispatch:发起 action
- viewService:调用 dependencies 所声明依赖的 adapter 或 slots
- viewService.buildAdapter: 调用依赖的 adapter
- viewService.buildComponent(slotsName): 调用依赖的 slots
effect
Effect 是一个处理所有副作用的函数。我把它分为两种,一种是对生命周期的回调,一种是对非处理数据事件的回调。这里面不做任何数据的处理,如果需要处理数据的话就发起一个 action 到 reducer 里处理。
例:
Effect<PageState> buildEffect() { return combineEffects(<Object, Effect<PageState>>{ Lifecycle.initState: _initStateEffect, 'onClick': _onClickEffect, }); } void _initStateEffect(Action action, Context context)async { final response = await fetch( 'GET', 'https://xxx.api.xxx', params: { 'size': 10, 'page': 1, } ); context.dispatch(HomeActionCreator.onSetTaskDataAction(response)); } 复制代码
context.dispatch
Page
用来构建页面,每个页面都有一个 Page 并且有一个 store。在这里初始化 store,配置 Middleware,对 Redux 做 AOP 管理。它继承于 Component
例:
// page, 继承自 Component,PageState 是 store 里定义的 state class HomePage extends Page<PageState, Map<String, dynamic>> { HomePage() : super( view: buildView, initState: initState, effect: buildEffect(), reducer: buildHomeReducer(), dependencies: Dependencies<PageState>( adapter: SomeAdapter(), slots: <String, Dependent<PageState>>{ 'header': HeaderConnector() + HeaderComponent(), } ), middleware: <Middleware<PageState>>[ // 中间件 logMiddleware1(tag: 'HomePage'), logMiddleware2(tag: 'HomePage'), ], ); } 复制代码
- middleware 是一个数组,用来注册中间件,示例中注册了两个中间件,在 dispatch 的时候,他们会按序执行
其他配置
除了上面那些 Component 还有其他几个比较简单或不常用的配置,如:OnError 、HigherEffect 等,如果以后有空了再补充。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 开发者文档深度挖掘:下一代 iPhone 可能更智能
- 可能是除文档外最详细的说明--VueRouter完全指北
- IPFS:区块链“不可能三角”的可能解 | 万向区块链小课堂
- 响应慢也有可能是SQL惹的祸:Limit可能会惹祸上身
- 私有链可能没有未来?
- 你可能需要 SPI 了
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
构建可扩展的Web站点
Cal Henderson / 徐宁 / 电子工业出版社 / 2008 / 58.00元
随着Web 2.0网站的蓬勃发展,如何成功地构建可扩展的Web站点成为网站开发人员必备的技能。本书是Flickr.com的主力开发人员讲解构建可扩展的Web站点的经典之作。本书主要介绍了Web应用程序的概念、体系结构、硬件需求、开发环境的原则及国际化、本地化和Unicode等基本内容,并为解决Web应用程序的数据安全、电子邮件整合、远程服务交互、应用程序优化、扩展、监测和预警、开放API等问题提供......一起来看看 《构建可扩展的Web站点》 这本书的介绍吧!