React-Redux+Typescript, 解析 文件,代码结构
前言
这是一篇实用性的文章, 是给react-redux已入门的读者 (推荐懂typescript)。 这篇文章讨论的问题是怎样构架一个大型react-redux项目,通过文件结构以及代码结构来进行探讨。
这篇文章总结的结构,只是作者自己的总结, 并不是完美的也有可能在一些情况下不适用。
如果你想简单粗暴直接看代码去(代码结构) 标题。
用词
一个大型项目: 这里指3-5 个程序员同时开发的项目, 其中包括有人离岗,以及新人加入。
概述
一个大型项目的结构, 需要考虑到两个重要的原则:可被理解性(understandability) 和 可推测性 (inferrability)。
- 可被理解性 在代码层面虽然依赖于程序员写代码的能力, 但是也可通过团队的代码模板,和习惯性的标注,所提升。在文件结构层面,则依赖于项目初期构架的文件结构,一个容易理解的代码项目一定需要一个易懂的文件结构。
- 可推测行: 这条原则,是基于上一条。 一个简单易懂的项目结构, 其实反面则代表的是只承认自己结构。所以开发者在写新代码的时候, 他清楚的知道文件应该放在那里, 代码的格式应该是么, 因为其他的结构是不被接受的。其他开发者可以轻松的推测实现新的功能的代码在哪里。
所以一个好的 react 项目也应该遵守这两个原则。这篇文章作者会用 typescript 因为其类强型功能本身有助于提高可理解行。
文件结构
任何一个react 项目有三个主要的组成部分为:
- Redux
- react 组件 (react component)
- 数据模型
注解: 数据模型是整个项目其组件需要处理的,跟最终实现的商业目的其实也是最接近的。
对与产品经理,或者程序员则最重要的是:
- 功能
注解: 因为程序员是被雇来就是为了实现,更改或删除功能的,不管用的什么技术。
所以很自然的,一个项目的文件结构也该已一个功能为中心, 而 react 和r edux 只是实现功能的技术。
例子: 网购平台
假设一个网购平台就需要两个功能:
- 购物车,选了要买的东西可以加入车里
- 货柜,呈现产品。
这里注解:
- Cart: 购物车
- Shelf: 货架
- dux: Redux, 你也可以命名 store …
- models: 数据模型
数据模型-位置
放在 common 里面是因为,很多情况下一个组件需要的数据模型是从另一个数据模型转换过来的,多个组件都可能用到相同的数据模型, 所以没有把它们分开放到不同的文件夹里面。
组件-位置
这个位置也是很好理解, 因为想让开发者能更快的了解到一个功能所有的代码实现,所以分开放到不同的文件夹里。
Redux
Redux 包含了原组件有:
- Action Type
- Action
- Reducer
- Dispatcher
- Selector
注解:Dispatcher 的意义为了通过定义简单的函数调用,返还一个action。Selector 是从redux store 里面读取数据的。 我把ActionType 和 Action 放到了一个文件里面。
我看过有很多项目, 把所有的action type 放到一个文件夹, 所有的 reducer 放到一个文件夹里, 把dispatcher 和 action混到一个文件放在一个文件夹里。
我自己是极力不推荐这种结构, 除非你的项目小,一旦项目大,十多个或者几十个功能, 三个文件夹里面满满的文件,读代码极大降低效率, 还会遗漏很多东西。
小结
这种文件结构,能提高项目可理解性,因为它把功能和实现功能的代码紧密的联系起来。一个功能如果有bug,开发团队能快速的找到哪几组代码有可能产生了这个bug。 新的程序员也能很好的去理解一块代码的责任。对于可推测性, 一个功能改动,删除,或增加所碰触到的文件,都能被整个团队很好的推测,因为这个结构是不改动的。
代码结构(干货):
代码结构,主要讲跟redux有直接关系的代码, 因为其他的代码太基本,也没有可复制性。
略过:
- 数据模型(models)
- React 组件(component *.tsx)
Redux
Action & Action Types
//CartProductActions.ts
export enum CartProductActionTypes {
ADD_PRODUCT = "[CART_PRODUCT] Add",
REMOVE_PRODUCT = "[CART_PRODUCT] Remove",
}
export class AddCartProductAction implements Action {
public readonly type: CartProductActionTypes = CartProductActionTypes.ADD_PRODUCT;
constructor(public product: CartProduct) {
}
}
export class RemoveCartProductAction implements Action {
public readonly type: CartProductActionTypes = CartProductActionTypes.REMOVE_PRODUCT;
constructor