React Redux + Typescript 代码以及文件架构解析 - CSDN博客

React-Redux+Typescript, 解析 文件,代码结构

    • 前言
    • 用词
    • 概述
      • 文件结构
        • 例子: 网购平台
          • 数据模型-位置
          • 组件-位置
          • Redux
      • 小结
      • 代码结构(干货):
        • Redux
          • Action & Action Types
          • Reducers
          • Dispatcher
          • Selector
          • 功能(Cart)
            • Container
            • View
      • 小结
    • 链接

前言

这是一篇实用性的文章, 是给react-redux已入门的读者 (推荐懂typescript)。 这篇文章讨论的问题是怎样构架一个大型react-redux项目,通过文件结构以及代码结构来进行探讨。

这篇文章总结的结构,只是作者自己的总结, 并不是完美的也有可能在一些情况下不适用。

如果你想简单粗暴直接看代码去(代码结构) 标题。

用词

一个大型项目: 这里指3-5 个程序员同时开发的项目, 其中包括有人离岗,以及新人加入。

概述

一个大型项目的结构, 需要考虑到两个重要的原则:可被理解性(understandability) 和 可推测性 (inferrability)。

  1. 可被理解性 在代码层面虽然依赖于程序员写代码的能力, 但是也可通过团队的代码模板,和习惯性的标注,所提升。在文件结构层面,则依赖于项目初期构架的文件结构,一个容易理解的代码项目一定需要一个易懂的文件结构。
  2. 可推测行: 这条原则,是基于上一条。 一个简单易懂的项目结构, 其实反面则代表的是只承认自己结构。所以开发者在写新代码的时候, 他清楚的知道文件应该放在那里, 代码的格式应该是么, 因为其他的结构是不被接受的。其他开发者可以轻松的推测实现新的功能的代码在哪里。

所以一个好的 react 项目也应该遵守这两个原则。这篇文章作者会用 typescript 因为其类强型功能本身有助于提高可理解行。

文件结构

任何一个react 项目有三个主要的组成部分为:

  1. Redux
  2. react 组件 (react component)
  3. 数据模型
    注解: 数据模型是整个项目其组件需要处理的,跟最终实现的商业目的其实也是最接近的。

对与产品经理,或者程序员则最重要的是:

  1. 功能
    注解: 因为程序员是被雇来就是为了实现,更改或删除功能的,不管用的什么技术。

所以很自然的,一个项目的文件结构也该已一个功能为中心, 而 react 和r edux 只是实现功能的技术。

例子: 网购平台

假设一个网购平台就需要两个功能:

  1. 购物车,选了要买的东西可以加入车里
  2. 货柜,呈现产品。

React Redux + Typescript 代码以及文件架构解析 - CSDN博客_第1张图片
这里注解:

  • 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有直接关系的代码, 因为其他的代码太基本,也没有可复制性。
略过:

  1. 数据模型(models)
  2. 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(public product: CartProduct, public id: number) {}
}

export type CartProductAction = AddCartProductAction | RemoveCartProductAction;

Redux 包,自含typescript 的类型。
这个文件里,值得注意的就是最后一行的类型联合, 要记得,这是因为,action 相对应的 reducer 是需要知道它的 action 的类型的。

Reducers
//CartProductReducer.ts
export interface CartProductState {
  products: CartProduct[];
}

const initialState: CartProductState = {
  products: [],
};

export function cartProductReducer(state: CartProductState = initialState, action: CartProductAction):CartProductState {

  switch (action.type) {
    case CartProductActionTypes.ADD_PRODUCT: {
      const product = (action as AddCartProductAction).product;
      const products = state.products.concat(product);
      return {
        ...state,
        products,
      };
    }

    case CartProductActionTypes.REMOVE_PRODUCT: {
      const products = [...state.products];
      const id = (action as RemoveCartProductAction).id;
      products.splice(id, 1);
      return {
        ...state,
        products,
      };
    }

    default:
      return state;
  }
}

Reducer 是吃两个参数的, 一个是 state, 一个是 action。 typescript,需要state 里所有成员被定义, 还有 action 的类别(联合类别)。这个好处很大, 想想javascript, 程序员可以省事不把 state 写全,或者后来修改reducer 往state 里面添加成员。 当另一个程序员想要知道 state 里面有什么的时候很不方便,写selector 的时候也困难。

还要注意 switch 里面, 要把 action 强行转换,然后typescript 就会提示你这个action里面有什么。

Dispatcher
//CartProductDispatcher.ts
export const addCartProduct = (product: Product, quantity: number) => {
  const cartProduct: CartProduct = {
    ...product,
    itemQuantity: quantity,
  };
  return new AddCartProductAction(cartProduct);
};

//thunk example
export const removeCartProduct = (cartProduct: CartProduct, id: number) => (dispatch: Dispatch) => {
  dispatch(new RemoveCartProductAction(cartProduct, id));
};

Dispatcher 的意义在于, 程序员不需要从新码这些逻辑, DRY。Dispatcher 是为了到时候的 mapDispatchToProps.

Selector
//CartProductSelector.ts
import { createSelector } from "reselect";
import { AppState } from "../rootReducer";
import { CartProductState } from "./CartProductReducer";

const getCartState = (state: AppState) => state.cartProducState;

export const getCartProducts = createSelector(
  getCartState,
  (cartState: CartProductState) => cartState.products,
);

Selector, 这里用了reselect 这个包写selector 必备。Selector 代码为了到时候的 mapStateToProps。

功能(Cart)

写完上面一堆的逻辑后才能进入正式的功能开发。 这里我也只会提及怎么把 component(组件) 跟 redux 联到一起。

Container

把一个 功能分成,view 和 container 也是自己的一个喜好, 没有强力的推荐,不过至少阅读起来这样让代码责任分的更清楚。

//CartContainer.ts
import { connect } from "react-redux";
import { CartProduct } from "../../common/models/CartProduct";
import { Product } from "../../common/models/Product";
import { addCartProduct, removeCartProduct } from "../../dux/CartProduct/CartProductDispatcher";
import { getCartProducts } from "../../dux/CartProduct/CartProductSelector";
import { AppState } from "../../dux/rootReducer";
import FloatCart from "./FloatCart";

type addCartProductDispatchProp = (product: Product, quantity: number) => void;
type removeCartProductDispatchProp = (cartProduct: CartProduct, id: number) => void;

interface DispatchProps {
  addCartProduct: addCartProductDispatchProp;
  removeCartProduct: removeCartProductDispatchProp;
}

interface StateProps {
  cartProducts: CartProduct[];
}

export type CartProps = StateProps & DispatchProps;

const mapDispatchToProps = (dispatch): DispatchProps => ({
  addCartProduct: (product: Product, quantity: number) => dispatch(addCartProduct(product, quantity)),
  removeCartProduct: (product: CartProduct, quantity: number) => dispatch(removeCartProduct(product, quantity)),
});

const mapStateToProps = (state: AppState): StateProps => ({
  cartProducts: getCartProducts(state),
});

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

Container, 负责把写的react 组件和 redux 连接起来。可以看到这里有很多类别声明, 但只有一个导出的类别。 有了这个 CartProps, 程序员就可以利用 typescript 的类别提示了。 在 react 组件(component)里面程序员就可以直接查看一个dispatch 函数需要的参数, 或者有哪些参数。

View
//Cart.ts
import { CartProps } from "./FloatCartContainer";
...
interface State {
  isOpen: boolean;
}

class FloatCart extends React.Component<CartProps, State> {
...

小结

写react redux,很大一部分是可重复性很强的。 从数据模组,到 redux 里 action, reducer, dispatcher 和 selector, 还有连接的部分 container。 这些代码的构架都是可以被样板化的, 这样就会在团队内产生共识, 增加代码的可理解性和可推测行。 因为改动和新增都不会脱离原来的样板。

链接

可以去看一下github,希望你看的时候我已经把代码放上去了。

你可能感兴趣的:(结构)