Redux 由 Facebook 2015年提出,是基于 Flux 理念实现的一个响应式框架的状态管理方式,最早应用于 React 中,而后 React Native 、 Flutter 等多个框架也同样可以使用。
学习 Redux 之前,请了解 Redux 所能做到的事情:
单一状态来源:整个应用程序的状态存储在单个的对象树中。
状态只读:更改状态的唯一方法是发出一个 action(一个描述发生什么事件的对象)。
使用纯函数更改状态:通过 reducers,状态知道如何通过操作进行改变,reducers便是一个纯函数。
[图片上传失败...(image-ef403a-1550631610955)]
如果你用过 React ,相信你对上面的概念已经很清晰了,没使用过?也 OK,下面慢慢道来....
整体的思路:所有状态存放在 store 中,Redux 将 store 内所有状态放入对应的组件中呈现在 Ui 上,用户与 UI 交互(如点击)发起一个 action(一个描述行为的对象),store 可判别 action类型并作用相应的 reducer(操作改变状态的纯函数),reducer 完成相应改变后将数据放到全局的 store 中,实现改变。
[图片上传失败...(image-fdc2e1-1550631610955)]
思路简单,描述起来蛮复杂,其实它的目的就是实现代码作用域的分离。
那么如何让将以上描述应用于实际的应用程序中呢?
redux 实现计数器应用
其中的实现与 ScopedModel 很相似,抓住两个要点,如何在 UI 中呈现以及如何 发起 action 改变状态。
我们首先定义 一个全局状态 AppState:
@immutableclass AppState { final counter; AppState(this.counter); }
里面有一个 counter 变量用于计数。
Action
如前面所述,State 的变化,会导致 UI 的变化。但是,用户接触不到 State,只能接触到 UI。所以,State 的变化必须是 UI 导致的。Action 就是 UI 发出的通知,表示 State 应该要发生变化了。
我们这里有一个促使 counter 增加的 action:
enum Actions { Increment }
reducer
Store 收到 Action 以后,必须给出一个新的 State,这样 View 才会发生变化。这种 State 的计算过程就叫做 Reducer。
Reducer 是一个函数,它接受 Action 和当前 State 作为参数,返回一个新的 State。
AppState reducer(AppState prev, action) { if (action == Actions.Increment) { return new AppState(prev.counter + 1); } return prev; }
reducer 中判断了 action 时 Increment 这一类型,将一个新的 AppState 返回到 store。
在 UI 中呈现(StoreConnector)
首先初始化状态:
final store = new Store(reducer, initialState: new AppState(0));
然后显示状态:
new StoreConnector( converter: (store) => store.state.counter, builder: (context, counter) => new Text( '$counter', style: Theme.of(context).textTheme.display1, ), )
通过 converter 属性拿到 state 中的 counter 属性。
然后传入 builder 应用在组件中。
发起 action 改变状态
new StoreConnector( converter: (store) { return () => store.dispatch(Actions.Increment); }, builder: (context, callback) => new FloatingActionButton( onPressed: callback, tooltip: 'Increment', child: new Icon(Icons.add), ), ),
同样是使用 StoreConnector,然而这次通过 converter 拿到的是一个 store.dispatch(Actions.Increment)
,store.dispatch()
是 View 发出 Action 的唯一方法。而后在组件中调用该回调方法即可。
完整代码
完整代码被放在一个文件中:样例代码
Redux 应用在 ShoppingCart:样例代码
[图片上传失败...(image-f51297-1550631610955)]
[图片上传失败...(image-c2f10-1550631610955)]
将 Redux 用于更复杂的应用中
在复杂的应用中我们可以将应用程序中的 reducer 根据业务类型分离,例如用户信息、产品信息等不同业务的操作分离到单独的模块,
[图片上传失败...(image-1c8383-1550631610955)]
而后将其合并:
[图片上传失败...(image-254edb-1550631610955)]
实现上看的分离后,又可只将相应业务的数据放到相应业务根 widget(其子组件的所有数据和操作来自该根组件)
我们将需要的数据及操作到一个对象中,传递到组件中:
class DeviceFragment extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( body: new StoreConnector<AppState, _ViewModel>( converter: _ViewModel.fromStore, builder: (context, vm) { return DeviceList( devices: vm.devices, onStateChanged: vm.onStateChanged, onRemove: vm.onRemove, onUndoRemove: vm.onUndoRemove, ); }), ); } }class _ViewModel { final List<Device> devices; // final bool loading; final Function(Device) onStateChanged; final Function(Device) onRemove; final Function(Device) onUndoRemove; _ViewModel({ @required this.devices, // @required this.loading, @required this.onStateChanged, @required this.onRemove, @required this.onUndoRemove, }); static _ViewModel fromStore(Store<AppState> store) { return _ViewModel( devices: store.state.devices, // loading: store.state.isLoading, onStateChanged: (device) { store.dispatch(editItem(device.copyWith(state: !device.state))); }, onRemove: (device) { store.dispatch(deleteDevice(device)); }, onUndoRemove: (device) { store.dispatch(AddDeviceAction(device)); }, ); } }
这样使得产生模块化管理的思想!(以上实例来自正在开发的一个真实项目,目前还未开源。)
同时,在实际的应用程序中,你一定需要在程序启动初期来加载来自云端或者数据本地的数据,你可以在入口的页面中接受一个加载数据的函数,放入 initState 函数中。
作者:Meandni
链接:https://www.jianshu.com/p/c3cc0216759b