闲鱼专家详解:Flutter React编程范式实践
首先是业务逻辑和界面分离,界面是无状态(Stateless)的,我们也正在尝试自动化的方法直接生成界面代码,所以Widget中是不会有业务逻辑代码的。当我们把一个能描述当前界面的数据(State)交给View层时,界面就应该能正常展示。用户和界面交互会产生Action,Action代表了用户交互的意图,Action可以携带信息(比如用户使用输入留言,Action中就应该携带用户留言的内容信息)。Action会输入给Store,Store会通过注册的Interrupters对Action做前期拦截处理,可以通过Interrupter截拦Action,也可以把一个Action重新改写成另外的Action。Store然后收集相应绑定的Reducers对Action做一次reduce操作,产生新的State,并通知界面刷新。 通常我们在创建Store的时候就组册好Reducer和Interrupter: Store<PublishState> buildPublishStore(String itemId) {//设置状态初始值 PublishState initState = new PublishState(); initState.itemId = itemId; initState.isLoading = true;//创建Reducer和对应Action的绑定 var reducerBinder = ActionBinder.reducerBinder<PublishState>() ..bind(PublishAction.DETAIL_LOAD_COMPLETED, _loadCompletedReducer) ..bind(PublishAction.DELETE_IMAGE, _delImageReducer) ..bind(PublishAction.ADD_IMAGE, _addImageReducer);//创建Interrupter和对应Action的绑定 var interrupterBinder = ActionBinder.interrupterBinder<PublishState>() ..bind(PublishAction.LOAD_DETAIL, _loadDataInterrupter) ..bind(PublishAction.ADD_IMAGE, UploadInterruper.imageUploadInterrupter); //创建Store return new CommonStore<PublishState>( name: 'Publish', initValue: initState, reducer: reducerBinder, interrupter: interrupterBinder); } Reducer中就是处理用户交互时产生的Action的逻辑代码,接收3个参数,一个是执行上下文,一个要处理的Action,一个是当前的State,处理结束后必须返回新的State。函数式理想的Reducer应该是一个无副作用的纯函数,显然我们不应该在Reducer中去访问或者改变全局域的变量,但有时候我们会对前面的计算结果有依赖,这时可以将一些运行时数据寄存在ReduceContext中。Reducer中不应该有异步逻辑,因为Store做Reduce操作是同步的,产生新State后会立即通知界面刷新,而异步产生对State的更新并不会触发刷新。 PublishState _delImageReducer(ReduceContext<PublishState> ctx, Action action, PublishState state) {int index = action.args.deleteId; state.imageUplads.removeAt(index);return state; } Interrupter形式上和Reducer类似,不同的是里面可以做异步的逻辑处理,比如网络请求就应该放在Interrupter中实现。 *为什么会有Interrupter呢?换一个角度,我们可以把整个Store看成一个函数,输入是Action,输出的是State。函数会有副作用,有时我们输入参数并不一定得会相应有输出,比如日志函数( void log(String) ),我们输入String只会在标准输出上打印一个字符串,log函数不会有返回值。同样,对Store来说,也不是所有的Action都要去改变State,用户有时候触发Action只要想让手机震动下而已,并不会触发界面更新。所以,Interrupter就是Store用来处理副作用的。 ///截拦一个网络请求的Action,并在执行请求网络后发出新Action bool _onMtopReq(InterrupterContext<S> ctx, Action action) { NetService.requestLight( api: action.args.api, version: action.args.ver,params: action.args.params, success: (data) { ctx.store.dispatch(Action.obtain(Common.MTOP_RESPONSE) ..args.mtopResult = 'success' ..args.data = data); }, failed: (code, msg) { ctx.store.dispatch(Action.obtain(Common.MTOP_RESPONSE) ..args.mtopResult = 'failed' ..args.code = code ..args.msg = msg); });return true; } 通常我们会让一个界面根部的InheritedWidget来持有Store,这样界面上的任何Widget都能方便的访问到Store,并和Store建立联系。这种做法可以参考redux_demo,再此不详细展开。 最后简单的说说Store的实现,Store能够接收Action,然后执行reduce,最后向widget提供数据源。Widget可以基于提供的数据源建立数据流,响应数据变更来刷新界面。这其中最核心的就是Dart的Stream。 ...... //创建分发数据的Stream _changeController = new StreamController.broadcast(sync: false);//创建接收Action的Stream _dispatchController = new StreamController.broadcast(sync: false);//设置响应Action的函数 _dispatchController.stream.listen((action) { _handleAction(action); }); ...... //向Store中分发Action void dispatch(Action action) { _dispatchController.add(action); } //Store向外提供的数据源 Stream<State> get onChange => _changeController.stream; Store中最核心的对Action进行reduce操作: //收集该Action绑定的Reducer final List<ReduceContext<State>> reducers = _reducers.values .where((ctx) => ctx._handleWhats.any((what) => what == action.what)) .toList(); //执行reduce Box<Action, State> box = new Box<Action, State>(action, _state); box = reducers.fold(box, (box, reducer) { box.state = reducer._onReduce(box.action, box.state);return box; }); //触发更新 _state = box.state; _changeController.add(_state); Widget基于Store暴露的数据源建立数据流: store.onChange//将Store中的数据转换成Widget需要的数据 .map((state) => widget.converter(state)) //比较前一次数据,如果想等则不用更新界面 .where((value) => (value != latestValue)) //更新界面 .listen((value){ ... setState() ... }) 组件化的扩展 (编辑:晋中站长网) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |