跨平台

Redux了解

2020-11-14  本文已影响0人  平安喜乐698
目录

  0. 相关概念
  1. 在Flutter中使用Redux

一个用于JavaScript应用的可预测状态管理容器。

0. 相关概念

Redux由Flux演变而来,但受Elm(函数式编程语言)的启发,避开了Flux的复杂性。最初是为了解决React应用组件之间共享状态。

采用单向数据流。这意味着应用中所有的数据都遵循相同的生命周期,这样可以让应用变得更加可预测且容易理解。
组件可以监听状态修改,如果有修改Redux会通知组件,从而使组件重新绘制。

3大原则

  1. 单一数据源
应用中所有的State都以一个对象树的形式储存在一个唯一的store中(不支持多个)。
store职责:
    1. 维持应用的 state;
    2. 提供 getState() 方法获取 state;
    3. 提供 dispatch(action) 方法更新 state;
    4. 通过 subscribe(listener) 注册监听器;
    5. 通过 subscribe(listener) 返回的函数注销监听器。


import { createStore } from 'redux'
import todoApp from './reducers'
// 第二个参数是可选的, 用于设置 state 初始状态。
let store = createStore(todoApp);
  1. State是只读的
不能直接修改State状态对象,惟一改变State的办法是触发Action。
Action是store数据的唯一来源。通过store.dispatch(action)将action传到store。
State可以是基本类型、数组、对象等。
实际开发中,经常会有数据相互引用,在 State 里同时存放 todosById: { id -> todo } 和 todos: array<id> 是比较好的方式。每个数据以 ID 为主键,不同实体或列表间通过 ID 相互引用数据。
Action可以被序列化,用日记记录和储存下来,后期还可以以回放的方式执行。
应该尽量减少在 Action 中传递的数据(利用id)。
Action创建函数只是简单的返回一个Action。

// 返回Action
function addTodo(text) {
  return {
    type: ADD_TODO,
    text
  }
}

可以:
通过dispatch(addTodo(text))来触发Action。

也可以:
const boundAddTodo = text => dispatch(addTodo(text));
boundAddTodo(text);
通过使用指定的 middleware,action 创建函数除了返回 action 对象外还可以返回函数。
当 action 创建函数返回函数时,这个函数会被 Redux Thunk middleware 执行。
这个函数并不需要保持纯净;它还可以带有副作用,包括执行异步 API 请求。
这个函数可以 dispatch action。
这个函数可以有返回值,会被当作 dispatch 方法的返回值传递。
这个函数的结果可以再次被 dispatch。

// thunk action创建函数
// store.dispatch(fetchPosts('reactjs')) 触发Action
export function fetchPosts(subreddit) {
  return function (dispatch) {
    // 触发Action,通知API请求发起了
    dispatch(requestPosts(subreddit))
    return fetch(`http://www.subreddit.com/r/${subreddit}.json`)
      .then(
        response => response.json(),
        // 不要使用 catch,因为会捕获在 dispatch 和渲染中出现的任何错误,导致 'Unexpected batch number' 错误。
         error => console.log('An error occurred.', error)
      )
      .then(json =>
        dispatch(receivePosts(subreddit, json))
      )
  }
}



const loggerMiddleware = createLogger()
const store = createStore(
  rootReducer,
  applyMiddleware(
    thunkMiddleware, // 允许thunk函数
    loggerMiddleware // 打印 action 日志
  )
)
store
  .dispatch(fetchPosts('reactjs'))
  .then(() => console.log(store.getState())
)
  1. 使用纯函数来执行修改
为了描述Action如何改变State树,需要编写Reducers函数。
Reducer是一个接收现有State和Action并返回新State的函数。描述了Action如何把现有State转变成新State(全新的对象,而不是修改的旧对象)。
永远不要在Reducer函数中:
  1. 修改传入的参数。应该去创建一个副本然后修改并返回。
  2. 执行有副作用的操作,如 API 请求和路由跳转
  3. 调用非纯函数,如 Date.now() 或 Math.random()

遇到未知的Action时,一定要返回旧的State。
Reducer合成:随着应用不断变大,应该把根级的 Reducer 拆成多个小的 Reducer,分别独立地操作 State 树的不同部分。将拆分后的 reducer 放到不同的文件中, 以保持其独立性并用于专门处理不同的数据域。


方式1
function todoApp(state = {}, action) {
  return {
    todos: todos(state.todos, action),
    visibilityFilter: visibilityFilter(state.visibilityFilter, action)
  };
}
方式2(等价)
const todoApp = combineReducers({
  visibilityFilter,
  todos
})
combineReducers只是生成一个函数,这个函数来调用一系列 reducer,每个 reducer 根据它们的 key 来筛选出 state 中的一部分数据并处理,然后这个生成的函数再将所有 reducer 的结果合并成一个大的对象。

好处

  1. 可预测性(一致性)(便于后期维护)
无论是在任何环境(Web端、服务端、原生端),应用的行为都保持一致。
  1. 中心化
因为对状态的修改集中到了一个地方,从而使得完成诸如撤销/重做、持久化等操作变得容易,
  1. 可调试性
使用Redux DevTools,可以追踪到应用状态什么时候、什么地方、为什么,以及怎样发生了改变。
  1. 灵活性
可以同任意的UI层搭配工作。

import { createStore } from 'redux';

// reducer
function counter(state = 0, action) {
  switch (action.type) {
  case 'INCREMENT':
    return state + 1;
  case 'DECREMENT':
    return state - 1;
  default:
    return state;
  }
}

// 创建store
let store = createStore(counter);
// 可以手动订阅更新,也可以将事件绑定到视图层。
store.subscribe(() =>
  console.log(store.getState())
);

// 触发action更新state
// 触发action后store会把现有state和action传给reducer函数,最后将新state保存在store。
// 约定action 内必须使用一个字符串类型的 type 字段来表示将要执行的动作
store.dispatch({ type: 'INCREMENT' }); // 1
store.dispatch({ type: 'INCREMENT' }); // 2
store.dispatch({ type: 'DECREMENT' }); // 1

1. 在Flutter中使用Redux

只使用Redux来管理全局性或需要持久化的状态,局部性和不需要持久化的状态直接存放在有状态组件的State对象中。
不可变对象如果需要支持修改,可以实现copyWith方法。

第一步:添加依赖库

1. redux库
使用Redux

2. redux_logging库
打印Action日志

3. redux_thunk库
支持异步操作

4. flutter_redux库
StoreConnector组件来获取状态并监听状态修改。StoreProvider直接获得store对象。

5. redux_persist库
持久化状态

6. redux_persist_flutter 

第二步:创建相关目录

1. reducers目录
reducers.dart文件(统一处理)
各个子模块reducer.dart

2. actions目录
各个子模块action.dart

3. models目录
state子目录(state.dart、各个子模块state.dart)

创建state


class AppState{
  final String version;
  // 各个子模块state
  final AccountState account;
  final PostState post;
  final PublishState publish;
  final UserState user;

  AppState({
    String version,
    AccountState account,
    PostState post,
    PublishState publish,
    UserState user,
  }):
        this.version=version??ProjectConfig.packageInfo.version,
        this.account=account??AccountState(),
        this.post=post??PostState(),
        this.publish=publish??PublishState(),
        this.user=user??UserState();

  AppState copyWith({
    String version,
    AccountState account,
    PostState post,
    PublishState publish,
    UserState user,
  })=>
      AppState(
        version: version??this.version,
        account: account??this.account,
        post: post??this.post,
        publish: publish??this.publish,
        user: user??this.user,
      );

  factory AppState.fromJson(Map<String,dynamic> json)=>_$AppStateFromJson(json);
  Map<String,dynamic> toJson()=>_$AppStateToJson(this);
}

创建 store对象

  Store<AppState> getStore(){
    if(_store==null){
      final List<Middleware<AppState>> wms=[];  // 中间件列表
      if(ProjectConfig.isLogAction){  // 需要打印Action日志
        // 添加 日志打印中间件
        wms.add(LoggingMiddleware<AppState>(logger: getLogger('action'),level: Level.FINE));
      }
      wms.addAll([
        thunkMiddleware,    // 添加 异步操作中间件
        getPersistor().createMiddleware(),  // 添加 持久化状态中间件
      ]);

      //
      _store=Store<AppState>(
        appReducer, // Action处理器
        initialState: AppState(), // 初始状态
        middleware: wms,  // 中间件列表
      );
    }
    return _store;
  }

在应用的上下文添加store对象

  Widget build(BuildContext context) {
    return StoreProvider<AppState>( 
      store: store,
      child: MaterialApp(
        title: ProjectConfig.packageInfo.appName,
        theme: ProjectTheme.theme,
        routes: {
          '/':(context)=>BootstrapPage(),
          '/login':(context)=>LoginPage(),
          '/register':(context)=>RegisterPage(),
          '/tab':(context)=>TabPage(),
        },
      ),
    );
  }

绑定stroe到widget

class HomePage extends StatelessWidget{
  static final _bodyKey=GlobalKey<_BodyState>();

  HomePage({
    Key key,
  }):super(key:key);

  @override
  Widget build(BuildContext context) {
    return StoreConnector<AppState,_ViewModel>(
      converter: (store)=>_ViewModel( // store发生变化,会重新执行converter,builder。
        postsFollowing:store.state.post.postsFollowing.map<PostEntity>((v) => store.state.post.posts[v.toString()]).toList(),
      ),
      builder: (context,vm)=>Scaffold(
        appBar: AppBar(
          title: Text('首页'),
        ),
        body: _Body(
          key:_bodyKey,
          store:StoreProvider.of<AppState>(context),
          vm:vm,
        ),
        bottomNavigationBar: ProjectTabBar(tabIndex:0),
      ),
    );
  }
}

定义action

class ResetStateAction{
}

中间件
class AccountInfoAction{
  final UserEntity user;

  AccountInfoAction({
    @required this.user,
  });
}
ThunkAction<AppState> accountRegisterAction({
  @required RegisterForm form,
  void Function(UserEntity) onSuccessed,
  void Function(NoticeEntity) onFailed,
})=>(Store<AppState> store)async{
  final projectService=await ProjectFactory().getProjectService();
  final response=await projectService.post(
    '/account/register',
    data: form.toJson(),
  );
  if(response.code==ProjectApiResponse.codeOK){
    final user=UserEntity.fromJson(response.data['user']);
    if(onSuccessed!=null)onSuccessed(user);
  }else{
    if(onFailed!=null)onFailed(NoticeEntity(message: response.message));
  }
};

reducer

AppState appReducer(AppState state,action){
  if(action is ResetStateAction){ // 清零(应用置为初始状态)
    return AppState();
  }else{
    return state.copyWith(
      account: accountReducer(state.account,action),
      publish: publishReducer(state.publish,action),
      post: postReducer(state.post,action),
      user: userReducer(state.user,action),
    );
  }
}


account如下:
final accountReducer=combineReducers<AccountState>([
  TypedReducer<AccountState,AccountInfoAction>(_accountInfo),
]);
// 更新账户信息
AccountState _accountInfo(AccountState state,AccountInfoAction action){
  return state.copyWith(
      user: action.user,
  );
}

触发action

widget.store.dispatch(ResetStateAction())


StoreProvider.of<AppState>(context).dispatch(accountRegisterAction(
  onSuccessed:(UserEntity user){
    setState(() {
      _isLoading=false;
    });
    Navigator.of(context).pop(true);
  },
  onFailed:(NoticeEntity notice){
    setState(() {
      _isLoading=false;
    });
    ScaffoldMessenger.of(context).showSnackBar(SnackBar(
      content: Text(notice.message),
      duration: notice.duration,
    ));
  },
  form:_form,
));
}

Redux学习官网

上一篇 下一篇

猜你喜欢

热点阅读