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

4.3 模块接口

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

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

  

  4.3 模块接口

  “在最理想的情况下,我们应该通过增加代码就能增加系统的功能,而不是通过对现有代码的修改来增加功能。”

  ——Robert C.Martin

  著名软件架构师Robert C.Martin这句话阐述了模块化软件的要求。当然,如果要达到这种“最理想的情况”还是有点难度,但是这个目标值得我们去努力奋斗,而React和Redux这个辅助工具可以帮助我们在这个目标上迈进一大步。

  不同功能模块之间的依赖关系应该简单而且清晰,也就是所谓的保持模块之间低耦合性;一个模块应该把自己的功能封装得很好,让外界不要太依赖与自己内部的结构,这样不会因为内部的变化而影响外部模块的功能,这就是所谓高内聚性。

  React组件本身应该具有低耦合性和高内聚性的特点,不过,在Redux的游乐场中,React组件扮演的就是一个视图的角色,还有reducer、actions这些角色参与这个游戏。对于整个Redux应用而言,整体由模块构成,但是模块不再是React组件,而是由React组件加上相关reducer和actions构成的一个小整体。

  以我们将要实现实现的Todo应用为例,功能模块就是todoList和filter,这两个功能模块分别用各自的React组件、reducer和action定义。

  可以预期每个模块之间会有依赖关系,比如filter模块想要使用todoList的action构造函数和视图,那么我们希望对方如何导入呢?一种写法是像下面的代码这样:

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

  import container as TodoList from '../todoList/views/container';

  这种写法当然能够完成功能,但是却非常不合理,因为这让filter模块依赖于todoList模块的内部结构,而且直接伸手到todoList内部去导入想要的部分。

  虽然我们在上面的例子中,todoList和filter中的文件名几乎一样,但是这毕竟是模块内部的事情,不应该硬性要求,更不应该假设所有的模块都应该按照这样的文件名命名。在我们的例子中,存储视图代码文件的目录叫做views,但是有的开发者习惯把这个功能的目录叫做components;我们把包含容器组件的文件名叫做container.js,根据开发者个人习惯也可能叫做TodoList,这些都没有必要而且也不应该有硬性规定。

  现在既然我们把一个目录看做一个模块,那么我们要做的是明确这个模块对外的接口,而这个接口应该实现把内部封装起来。

  请注意我们的todoList和filter模块目录下,都有一个index.js文件,这个文件就是我们的模块接口。

  比如,在todoList/index.js中,代码如下:

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

  import reducer from './reducer.js';

  import view from './views/container.js';

  export {actions, reducer, view}

  如果filter中的组件想要使用todoList中的功能,应该导入todoList这个目录,因为导入一个目录的时候,默认导入的就是这个目录下的index.js文件,index.js文件中导出的内容,就是这个模块想要公开出来的接口。

  注意

  虽然每个模块目录下都会有一个actionTypes.js文件定义action类型,但是通常不会把actionTypes中内容作为模块的接口之一导出,因为action类型只有两个部分依赖,一个是reducer,一个是action构造函数,而且只有当前模块的reducer和action构造函数才会直接使用action类型。模块之外,不会关心这个模块的action类型,如果模块之外要使用这个模块的动作,也只需要直接使用action构造函数就行。

  下面就是对应的导入todoList的代码:

  import {actions, reducer, view as TodoList} from '../todoList';

  当我们想要修改todoList的内部代码结构,比如把views目录改名为components目录,或者把container.js改名为TodoListView.js或者任何一个我们觉得更加有意义的名字,所要做的只是修改todoList目录下的index.js内容,而这个文件export出来的内容不会有任何改变,也就是说对导入todoList的代码不用任何改变。这就是我们确定模块边界想要达到的目的。

  还有一种导出模块接口的方式,是不以命名式export的方式导出模块接口,而是以export default的方式默认导出,就像这样的代码:

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

  import reducer from './reducer.js';

  import view from './views/container.js';

  export default {actions, reducer, view}

  如果像上面导出,那么导入时的代码会有一点区别,因为ES6语法中,export default和export两种导出方式的导入方式也会不同,代码如下:

  import TodoListComponent from './actions.js';

  const reducer = TodoListComponent.reducer;

  const actions = TodoListComponent.actions;

  const TodoList = TodlistComponent.view;

  无论使用哪种导出方式,都请在整个应用中只用一种模块导出方式,保持一致,避免混乱。

  在本书中,全部使用export的方式,因为从上面的代码看得出来,如果使用export default的方式,在导入的时候不可避免要用多行代码才能得到actions、reducer和view,而用导入命名式export只用一行就可以搞定,相对而言要更加简洁。

  读者可能也注意到了,上面接口代码中导入actions的语句和导入view和reducer不一样,代码如下:

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

  我们预期actions.js中是按照命名式export,原因和上面陈述的一样,actions.js可能会导出很多action构造函数,命名式导出是为了导入actions方便。对于view和reducer,一个功能模块绝对只有一个根视图模块,一个功能模块也只应该有一个导出的reducer,所以它们两个在各自代码文件中是以默认方式导出的。 深入浅出React和Redux

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