评论

一张页面引起的项目架构思考(rax+Typescript+hooks)

一张页面引起的项目架构思考(rax+Typescript+hooks)

前言

好的书本分章节、好的代码分模块,那么好的架构该如何定义呢?

咳咳,不要意思,题目起大了~~ 小生之辈,岂敢以架构而论。

不过话说来,很多人都认为前端无非就是 HTML+CSS+JS,一个目录一类文件,有何架构可言。但是我想说。。。。你说的都对!

但是,笔者一直在探索不同的页面架构组织形式,鄙人愚见,好的架构,能够方便拓展和开发以及后期的项目维护。

在笔者刚开始接触前端的时候,就一直在思考怎么样的架构比较舒服易于扩展,且能装 B。React-Full-Dianping-Demo里面就有写到对于react+react-redux+soga的一些列代码组织的思考:react技术栈项目结构探究 (详见 github/Nealyang)

一直还在学习,本文也只是拿来探讨下本次我开发一个页面时,我个人的一些代码组织方式。抛个砖~

望各位大佬不啬赐教。

项目架构

src
├─ action-log
│    ├─ constants.ts
│    └─ index.ts
├─ app.js
├─ app.json
├─ common
│    ├─ animation-utils.ts
│    ├─ business-utils.ts
│    ├─ constants.ts
│    ├─ detail-utils.ts
│    ├─ mtop-utils.ts
│    ├─ net-utils.ts
│    ├─ price-utils.ts
│    ├─ storage-utils.ts
│    ├─ string-utils.ts
│    ├─ time-utils.ts
│    ├─ type.ts
│    ├─ url-utils.ts
│    └─ utils.ts
├─ components
│    ├─ loading-page
│    │    ├─ index.css
│    │    └─ index.tsx
│    └─ pm-bottom
│           ├─ index.css
│           └─ index.tsx
├─ document
│    └─ index.jsx
├─ event
│    └─ EVENTS.ts
├─ modules
│    ├─ bottom-action
│    │    ├─ index.css
│    │    └─ index.tsx
│    └─ page-container
│           ├─ base
│           ├─ decorator
│           ├─ index.tsx
│           └─ libs
└─ pages
       ├─ buyer-identity
       │    ├─ components
       │    ├─ constants
       │    ├─ customized-hooks
       │    ├─ index.tsx
       │    ├─ types
       │    └─ utils

或许上面看起来并不是很直观,截图解释下

大概的看下,脑海中有个大概的位置和每个文件的作用。下面我们再来细品

目录职责

其实划分了这么多的目录,无非就是为了最大可能的复用。其中也包括对于组件状态的抽离、hooks 特性的利用

pages 层以外的公共逻辑

毕竟是MPA应用,所以一切还都是围绕着 pages 展开。

action-log

首先这里的action-log目录就不多说了,因为没有太多可借鉴性。大概就是返回一个 ActionLog对象,来进行一些业务上的埋点、信息收集等逻辑的处理。所以这里如果大家有一些公共的基础类封装,都是可以放这里的。

common

common
├─ animation-utils.ts
├─ business-utils.ts
├─ constants.ts
├─ detail-utils.ts
├─ mtop-utils.ts
├─ net-utils.ts
├─ price-utils.ts
├─ storage-utils.ts
├─ string-utils.ts
├─ time-utils.ts
├─ type.ts
├─ url-utils.ts
└─ utils.ts

由于该项目的比较复杂,业务逻辑相对较多。所以这里我将 utils按照类别,区分出来了以上几种。方面后期开发中的维护和扩展,也便于查找。

除了一些从命名可以区分出来的utils 以外,这里还放了一个 type.tsconstants.ts,用途自如其名。

components

相信框架使用者对于 components 的命名都不为陌生.是的,就是对于一些公共组件的封装,比如我这里放的两个组件loading-page,pm-bottom等公共组件。components 相对来说是比较“小”的概念,划分依据这这个项目中也比较简单,就是是否为“木偶组件”(虽然 hooks 了以后,咱不太适合这么说),

modules

modules
├─ bottom-action
│    ├─ index.css
│    └─ index.tsx
└─ page-container
       ├─ base
       │    ├─ base.tsx
       │    ├─ error.tsx
       │    └─ scrollBase.tsx
       ├─ decorator
       │    └─ withError.tsx
       ├─ index.tsx
       └─ libs
              ├─ displayName.ts
              ├─ navbarTransparent.ts
              ├─ spm.ts
              └─ title.ts

更具有模块的概念,这里最典型的page-contaienr的模块,作用就是每一个页面的通用底层容器,早在之前的文章中其实有介绍到这个容器,如何用 Decorator 装饰你的 Typescript,所以这里就不再赘述了,其实就是一些基础功能的封装。所以也就是解释了event的目录存在。

而这里modulesconponents最大的区别就是,复杂度和内部状态管理。如果内部状态较为复杂,且有很多的交互,那么我们就称之为 module.是的,这里的界限,我们划分较为模糊。

但是当你拿到一份设计稿的时候,估计就能明白我的良苦用心了~

红色框就可以理解为 module,绿色框可以理解为 components

page 的组织

针对单个页面里面的组织,其实都大同小异。(突然发现前端架构没有太多可言)

目录区分的并不是很多,但是也都较为清晰。简单介绍下每个区域的分工,需要展开的,我们在后续展开介绍

  • index.tsx 页面的入口文件,但是本身里面不会编写太多业务逻辑
  • utils 该页面的工具函数,包括接口的请求、数据的 format
  • customized-hooks 自定义hooks,这里有两个,初始化 UI 所需要的数据(边距等),业务请求的数据。
  • constants 页面的常量,包括请求的 apispm 埋点、固定的一些该页面业务数据等
  • components 该页面的组件(注意这里没有 module,因为太多了真的容易混乱),页面的 components,有简单的,也有复杂的。

以上就是一些目录结构和代码组织的交代。其实还是比较简单清晰的。下面介绍下

页面数据流向和管理规则

碎碎叨叨道不到个明明白白

因为是业务代码,所以这里就不会粘贴太多代码了

简单的解释下上面的流程

初始化 UI 的逻辑比较偏于业务,其实没有太多可借鉴的。这里我代码里面的工作也就是适配 iPhone X的一些UI。

重点说下初始化接口数据的过程吧。其实也就是各个页面中的 components 的状态初始化

interface

首先我们需要定义每一个模块的 props,毕竟是因为用的 ts注释即文档。所以我们将每一个 components 的 props 都定义到 type 目录中,毕竟很多时候接口返回的数据,需要我们做一次 format,而这个 format 的目的就是为了 components 更好的使用。换句话说,这些接口,可复用! 那必然定义到外面

注意接口上都要写注释啊!!!!理由如下:

将所有数据处理的方法,全部放到 utils 中(注意数据兜底的处理,这里我所有的数据处理都写好工具函数,并添加充分的单元测试)

真正的做到对 components 而言,开箱即用

因为有 type 的定义和 components 之间的约束,所以无论是componemts 内部的数据使用还是 index.tsx 里面的模块引入时 props 的注入,都有很好的约束

编写时候的提醒

漏写时候的报错

组件通信

由于我们使用了 hooks,且相对隔离的组件划分,原则上,组件通信其实并不是很多。当然,也必然是有的。

其实这方面的约束主要归结于业务的复杂度,如果数据逻辑比较复杂,且通信较多。那么可以考虑使用 useContext 和 useReducer

说下这次需求中涉及到的通信。

原则:组件尽可能值管理自己的状态。

遵循如上原则,最终的业务交互逻辑都是由组件内部管理,涉及到的同级通信则通过父组件操作。而父组件操作的原则就是只拿数据,不做任何业务处理。(尽可能的撇清关系)

约束

  • index尽可能不写业务逻辑
  • UI 初始化和模块数据初始化需自定义 hooks
  • 状态尽可能抽离。component 过于复杂需额外抽离 component 、 utils 和 customized-hooks 等。参照上文
  • component 的 props 需抽离复用
  • 公共 utils 方法编写充分的单元测试
  • 公共 utils 的方法导出需单独导出(bundle 大小),且编写注释(调用时候的提醒)
  • 尽可能定义 interface,并且编写注释.毕竟注释即文档

以上约束后期应该都会编写相应的 Eslint 来进行强约束(咳咳,程序猿基本素养不可靠)

最后看下我正在补充的单元测试,编写单元测试过程中,的确发现了不少工具函数的边缘情况处理的有问题

结束语

按照如上的 page 代码组织后面又写了一个页面,感觉代码的组织和状态的管理还是较为清晰的。后续会编写相应的 cli 来自动生成页面基础架构,比如 pmCli add page or pmCli add com

原创 Nealyang 全栈前端精选

最后,本文只做一个抛转,并非定义一种规范。更多的约束和组织,希望大家多多交流,互相学习。

点赞 4
收藏
评论

1 个评论

  • 『空白』
    『空白』
    2020-05-22

    前排吃瓜

    2020-05-22
    赞同
    回复
登录 后发表内容