首页 男生 其他 深入浅出React和Redux

11.3.3 动态更新Store的reducer和状态

深入浅出React和Redux 程墨 10097 2021-04-06 02:29

  您可以在百度里搜索“深入浅出React和Redux 艾草文学(www.321553.xyz)”查找最新章节!

  

  11.3.3 动态更新Store的reducer和状态

  在第4章中,我们学习过将应用分解为若干功能模块,每个功能模块除了包含React组件,还可以有自己的reducer和被这个reducer修改的Store上的状态。

  当实现动态加载分片之后,功能模块会被webpack分配到不同的分片文件中,包含在功能模块中的reducer代码也会被分配到不同的分片文件。这样,应用的bundle.js文件中就没有这些reducer函数的定义,每个应用都有一个唯一的Redux Store,当应用启动创建Store时,并不知道这个应用中所有的reducer函数如何定义。所以,当切换到某个页面的时候,除了要加载对应的React组件,还要加载对应的reducer,否则功能模块无法正常工作。

  功能模块依赖于Store上的状态,所以当页面切换时,除了要更新reducer,Store上的状态树也可能需要做对应改变,才能支持新加载的功能组件。

  感谢Redux的结构,让我们把应用逻辑分散在视图和reducer,而这两者并不负责状态存储,状态存储在一个全局状态树上,才使得这种动态加载变得容易。

  我们在第8章中介绍过Store Enhancer,并且创造reset增强器,可以在store上添加一个名为reset的函数,这个函数可以替换当前store上的状态和reducer,现在我们就要把reset增强器应用到我们的例子中去。

  为了演示更新reducer和状态的功能,我们在代码中增加一个具有reducer的功能模块,在第3章中我们实现过一个计数器功能,在这里我们稍微修改,以第4章功能模块的定义方式放在src/components/Counter目录下。

  在src/components/Counter/index.js文件中定义功能模块的接口,代码如下:

  import * as actions from './actions.js';

  import reducer from './reducer.js';

  import view, {stateKey} from './view.js';

  export {actions, reducer, view, stateKey};

  之前我们在功能模块中只导出actions、reducer和view,现在多了一个stateKey,代表的是这个功能模块需要占据的Redux全局状态树的子树字段名,具体值是字符串coutner,在view.js中定义,因为视图中的mapStateToProps函数往往需要直接访问这个stateKey值。

  在src/components/Counter/actionTypes.js文件中定义action类型对象,代码如下:

  export const INCREMENT = 'counter/increment';

  export const DECREMENT = 'counter/decrement';

  在src/components/Counter/acitons.js文件中定义action构造函数,代码如下:

  import * as ActionTypes from './actionTypes.js';

  export const increment = () => ({

  type: ActionTypes.INCREMENT

  });

  export const decrement = () => ({

  type: ActionTypes.DECREMENT

  });

  在src/componets/Counter/reducer.js中定义reducer,代码如下:

  import {INCREMENT, DECREMENT} from './actionTypes.js';

  export default (state = {}, action) => {

  switch (action.type) {

  case INCREMENT:

  return state + 1;

  case DECREMENT:

  return state - 1;

  default:

  return state

  }

  }

  最后,在src/components/Counter/view.js中定义视图,对于Counter无状态组件的定义,代码如下:

  function Counter({onIncrement, onDecrement, value}) {

  return (

  +

  -

  Count: {value}

  );

  }

  这个Counter组件会直接从Store的counter字段读取当前的计数值,所以需要定义对应的mapStateToProps和mapDispatchToProps函数,代码如下:

  export const stateKey = 'counter';

  const mapStateToProps = (state) => ({

  value: state[stateKey] || 0

  })

  const mapDispatchToProps = (dispatch) => bindActionCreators({

  onIncrement: increment,

  onDecrement: decrement

  }, dispatch);

  export default connect(mapStateToProps, mapDispatchToProps)(Counter);

  可以看到mapStateToProps函数需要直接访问stateKey,所以stateKey虽然在index.js文件中导出,但通常要在视图文件中定义值。

  在src/components/目录下定义的都是功能模块,我们将功能模块和页面严格区分开,为了展示出Counter模块,我们先在src/pages/CounterPage.js文件中增加对应的页面定义,代码如下:

  import {view as Counter} from '../components/Counter';

  const CounterPage = () => {

  return (

  Counter

  

  );

  };

  export default CounterPage;

  页面中只显示了一个caption为any的计数器,除此之外和其他页面的定义没有什么两样。

  最后,我们在src/Routes.js中增加对CounterPage的Route规则,代码如下:

  const getCounterPage = (nextState, callback) => {

  require.ensure([], function(require) {

  callback(null, require('./pages/CounterPage.js').default);

  }, 'counter');

  };

  ...

  

  

  

  最后,在src/componets/TopMenu/index.js顶栏中增加对counter路径的链接,在任何一个页面都可以通过顶栏的链接切换到Counter页面,代码如下:

  Counter

  现在,我们可以启动应用,在浏览器中可以看到顶栏上新添加了一个Counter链接,可以点击这个链接,显示出计数器页面,如图11-7所示。

  图11-7 增加的Counter页面效果

  不过,在计数器页面中点击“+”按钮和“-”按钮,计数并不会变化。这很正常,因为上面的代码只处理了视图,却没有处理reducer,reducer不会自动把自己添加到Redux中。所以,要想具有完整的计数器功能,我们还要继续加工代码。

  在src/pages/CounterPage.js文件中虽然导入了Counter功能组件,但是只使用了view,却没有使用reducer。然而,在CounterPage.js中直接操作Redux Store似乎超出了它的职责范围。所以我们只是让CounterPage.js导出内容中增加reducer,由使用它的模块来操作Redux就好,进行这个操作的模块还需要更新Redux的状态树,所以CounterPage.js还需要提供页面的初始状态initialState,以及需要初始状态挂靠的状态树字段名stateKey。

  最终的src/pages/CounterPage.js是这样:

  import {view as Counter, stateKey, reducer} from '../components/Counter';

  const page = () => {

  return (

  Counter

  

  );

  };

  const initialState = 100;

  export {page, reducer, initialState, stateKey};

  为了让初始计数值有别于默认的0,我们Counter组件的初始计数为100。

  在src/Store.js中,我们引入了第8章中定义的名为reset的Store Enhancer,这样创造出来的store上有一个函数reset,可以更新reducer和状态,代码如下:

  import resetEnhancer from './enhancer/reset.js';

  const originalReducers = {

  routing: routerReducer

  }

  const reducer = combineReducers(originalReducers);

  const storeEnhancers = compose(

  resetEnhancer,

  applyMiddleware(...middlewares),

  (win && win.devToolsExtension) ? win.devToolsExtension() : (f) => f,

  );

  const initialState = {};

  const store = createStore(reducer, initialState, storeEnhancers);

  store._reducers = originalReducers;

  export default store;

  在上面的例子中,store上增加了一个_reducers字段,这是因为无论如何更改Redux的reducer,都应该包含应用启动时的reducer,所以我们把最初的reducer存储下来,在之后每次store的reset函数被调用时,都会用combineReducers来将启动时的reducer包含进来。

  最后,我们在src/Routes.js文件中要更新一下getCounterPage函数的实现,代码如下:

  import {combineReducers} from 'redux';

  const getCounterPage = (nextState, callback) => {

  require.ensure([], function(require) {

  const {page, reducer, stateKey, initialState} = require('./pages/CounterPage.js');

  const state = store.getState();

  store.reset(combineReducers({

  ...store._reducers,

  counter: reducer

  }), {

  ...state,

  [stateKey]: initialState

  });

  callback(null, page);

  }, 'counter');

  };

  在新的getCounterPage函数中,从CounterPage.js文件中不仅获得了代表视图的page,还有reducer,以及代表页面组件初始状态的initialState和状态树字段名stateKey。

  在调用callback通知Router装载页面完成之前,要完成更新Redux的reducer和状态树的操作。因为reset增强器的帮助,现在store上有一个reset函数,第一个参数是新的reducer,第二个参数是新的状态。我们不能破坏应用初始化时的规约逻辑,也不想丢弃状态树上现有的状态,所以使用扩展操作符增量添加了新的reducer和状态,然后调用reset函数。

  现在,在浏览器中重新切换到Counter页面时,计数器初始值是100,说明我们的初始状态设置成功,点击“+”和“-”按钮能够引起计数数值的变化,说明reducer也更新成功,我们实现了完整的动态加载功能模块。 深入浅出React和Redux

目录
设置
手机
书架
书页
评论