Redux了解

作者: 平安喜乐698 | 来源:发表于2020-11-14 19:18 被阅读0次
    目录
    
      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学习官网

    相关文章

      网友评论

        本文标题:Redux了解

        本文链接:https://www.haomeiwen.com/subject/edxfvktx.html