React项目规范

目录

一、前言  
二、项目初始化
三、运行项目
四、项目命令
五、在IDE中调试项目
六、项目支持的特性
七、根目录结构
八、项目结构的核心思想
九、src目录结构
十、组件结构规范
十一、新概念的定义
十二、reducer的拆分
十三、旧版本ReactRouter的拆分
十四、忠告

内容


一、前言

本项目是用 React 的脚手架 create-react-app 创建的,但是 create-react-app 创建的项目的 webpack 配置文件是放在项目依赖的 react-scripts 工具包里,即:存在于 node_modules 目录中;所以 create-react-app 创建的项目并不适合需要高度自定义打包配置的项目;然而,又因为脚手架生成的项目无论在工具配置还是项目管理上都是较权威的和较成熟的;所以,为了使用 create-react-app 脚手架生成的项目,并且实现项目配置高度可定制,我便把 create-react-app 创建的项目依赖的配置文件都导出到项目目录中;

备注: 本项目的Git地址是:https://gitee.com/guobinyong/react-project

二、项目初始化

在 把本项目从远程仓库克隆下来后,首次启动项目前,应该做如下操作:

  1. cd 命令转到项目的根目录;
  2. 安装依赖包: npm install

注意:

  • 项目只需初始化一次,以后不必再执行初始化;

三、运行项目

  1. cd 命令转到项目的根目录;
  2. 启动项目:npm start

四、项目命令

在项目中,可以使用如下命令:

npm start

在开发模式下运行项目;

备注:

  • 项目查看地址:http://localhost:3000
  • 代码的更改会触发页面自动刷新;
  • lint错误会输出到控制台中;

npm test

以交互的方式启动测试;

npm run build

编译并打包应用程序,并输出到 build/ 目录下;

备注:

  • 在生产模式下会捆绑React,并且会优化构建以获得最佳性能;

五、在IDE中调试项目

由于本项目已经配置了SourceMap,所以可以实现在IDE中(如:WebStorm、VSCode等)调试代码;具体配置方法,请参考WebStorm和VSCode中调试代码的配置教程

六、项目支持的特性

1. JavaScript语言特性

本项目支持的JavaScript语言特性是最新的JavaScript标准的超集,除了ES6语法功能外,还支持以下特性:

注意:
本项目仅支持以下 ES6 polyfills :

七、根目录结构

项目的根级目录及说明如下所示:

.
├── build/      : 存放项目被webpack处理后生成的文件;
├── config/     : 存放的是项目的配置文件;
├── node_modules/   : 存放 npm 安装的工具包 或 模块;
├── public/     : 静态资源,该目录下的文件不会被webpack处理,它们会被拷贝到 build/ 文件夹下;
├── scripts/    : 与项目的构建、打包 或 服务 相关的脚本;
└── src/        : 项目的源代码及资源;

注意事项:

  • webpack 只能编译 src/ 目录下的代码;如果在 src/ 下导入了 src/ 目录之外的文件(除了npm包),则 webpack 会报错;这样做的目的是为了减少由于路径书写错导而导致的错误;如果一定要导入 src/ 外的 非npm包 文件,则可以在 /config/paths.js 文件中的 allowedFilesOutOfSrc 属性中添加需要包含的 src外部非npm包文件 的路径;
  • public 虽然目录可以添加任何资源,如:图片、代码 等等,但是不建议把这些资源添加到 public 目录中;public 目录适合存放以下内容:
    • 与 webpack 不兼容的库;
  • public/ 目录的资源不会被webpack处理,它们会被拷贝到 build/ 文件夹下;
  • 要引用 public/ 目录下的资源,需要使用一个特殊的变量 PUBLIC_URL 来表示 public 目录;在HTML中用 %PUBLIC_URL% 引用 PUBLIC_URL 变量,在JavaScript中用 process.env.PUBLIC_URL 引用 PUBLIC_URL 变量;在执行 npm run build 时,PUBLIC_URL 变量会被替换成正确的绝对路径;
    示例:
    在文件 public/index.html 可以这样引用 public/favicon.ico:
    <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
    

八、项目结构的核心思想

因为代码的相关性主要与业务功能有关,而与文件类型的关系不大,所以,为了便于 编写、查阅、理解 代码,项目结构遵循以下核心宗指(宗指属于思想):

  • 以业务功能为单位组织项目结构;
  • 以低耦合度为目标划分模块职责和逻辑;

优点:

  • 业务功能模块的相关代码都集中在一块,方便移动和删除;
  • 实现了关注点分离,方便开发、调试、维护、编写、查阅、理解代码;

九、src目录结构

根据项目结构的核心思想,src的目录结构将以业务功能划分,具体如下 :

src/
├── app/    : 存放项目业务代码;
├── common/     : 存放项目共用的资源,如:常用的图片、图标、共用的组件、共用的样式、常量文件等等;
│   ├── assets/     : 存放项目共用的代码以外的资源,如:图片、图标、视频 等;
│   ├── component/      : 存放项目共用的组件,如:封装的导航条、选项卡等等;  备注:这里的存放的组件应该都是展示组件;
│   ├── constant.less       : 存放Less的常量;
│   └── constant.js     : 存放js的常量;
├── index.jsx       : webpack的入口文件;
└── registerServiceWorker.js

注意:

  • 项目的业务代码应该从 src/app/ 目录开始;
  • src/common/ 的子目录中,在深度上的层级结构应该是尽量扁平的,不应该有很深的层级结构;如果 src/common/ 中的目录树 与 src/app/ 中的目录树在深度上有十分相似的层级结构,则就表示你应该重新考虑 src/common/ 中的资源是否是真的需要被共享的资源,被共享的资源的目录层级结构应该是尽可能扁平的;对于不必共享的资源,应该放在 src/app/ 下相应的目录结构中;

十、组件结构规范

  • 根据组件的分离思想,把组件分为:容器组件 和 展示组件;
  • 组件的 主js模块 和 主样式模块 以组件的名字为文件名;
  • 对于经常需要导出的东西,需要按照统一的格式规范命名导出的名字,目前已有如下导出格式规范:
    • <组件名> : 组件自身; 类型:组件类型;
    • <组件名>Reducer :组件的reducer; 类型:函数;
    • <组件名>Router :组件的路由; 类型:Route元素 或者 Route类型的数组; 注意: 仅当使用ReactRouter4之前的版本时需要该导出项;

1. 容器组件结构规范

  • 以容器组件为单位,为每个容器组件创建以组件名为名的独立目录,容器组件目录的层级结构 要与 容器组件 的组件层级结构对应;
  • 容器组件目录下有以下几个目录:
    container/      : 容器组件的独立目录;
    ├── assets/       : 存容器组件所特有的除代码以外的资源;
    ├── component/      : 存容器组件所特有的展示组件;
    ├── Container.css   : 容器组件的样式模块;
    ├── Container.jsx   : 容器组件的JS模块;
    :
    :
    ├── subContainer1/   : 子容器组件1的独立目录;
    ├── subContainer2/   : 子容器组件2的独立目录;
    :
    :
    └── subContainerN/   : 子容器组件N的独立目录;
    
  • 容器组件的js模块中定义以下内容:
    • 容器组件类;
    • ActionCreator;
    • Reducer;
    • connect;
  • 如果需要,容器组件的 主js模块 需要导出以下标识符(导出时,可以用ES6的as操作符转为以下的标识符):
    • <组件名> : 容器组件自身; 类型:组件类型;
    • <组件名>Reducer :组件的reducer; 类型:函数;
  • 容器组件所有特有的展示组件需要放在该容器组件的子目录 component 中;

2. 展示组件结构规范

  • 展示组件的js模块包含展示组件的定义,并且需要导出(如果有的话)以下内容:
    • <组件名> : 组件自身; 类型:组件类型;
  • 如果展示组件的逻辑或结构比较复杂,则展示组件也可以有自己独立的目录;
  • 如果展示组件有自己的独立目录,则展示组件的目录结构应该如下:
    performer/          : 展示组件的独立目录;
    ├── assets/         : 存展示组件所特有的除代码以外的资源;
    ├── Performer.jsx   : 展示组件的JS模块;
    ├── Performer.css   : 展示组件的样式模块;
    :
    :
    ├── subPerformer1   : 子展示组件1的模块 或 独立目录;
    ├── subPerformer2   : 子展示组件2的模块 或 独立目录;
    :
    :
    └── subPerformerN   : 子展示组件N的模块 或 独立目录;
    
  • 展示组件的js模块中不应该定义以下内容:
    • ActionCreator;
    • Reducer;
    • connect;

十一、新概念的定义

为了方便描述,我定义了以下概念:

假设: 有 A 和 B 2个模块,且,在A模块中使用了B模块;
则: 称 A模块 为 B模块 的 使用者,B模块 为 A模块 的 提供者

十二、reducer的拆分

在 redux 中,每个应用程序一般只有一个store,而reducer只会用在创建store的地方,这使得reducer的逻辑集中在一个位置,这一点与我们的项目结构不太吻合!不过好在 redux 支持拆分 reducer;为了遵循我们项目的核心思想,可以使用以下规范实现对 reducer 的拆分:

  1. 在 提供者 中导出 提供者的reducer:

    //B的reducer
    function BReducer(state = bDefaultState, action) {
        const {type, payload} = action;
        let newState = null;
    
        switch (type) {
            case bType1:{
                //执行相关逻辑
    
                newState = {...state,...payload};
                break;
            }
            case bType1:{
                //执行相关逻辑
    
                newState = {...state,...payload};
                break;
            }
            default: newState = state;
        }
    
        return newState;
    }
    
    //导出B的reducer
    export {BReducer};
    
  2. 在 使用者 中导入 提供者 的reducer,并组合 使用者 的所有子reducer:

    import {combineReducers} from 'redux';
    import {BReducer} from './B'
    
    //用A的所有 子reducer 生成A的 reducer
    let AReducer = combineReducers({
        a1:a1Reducer,
        a2:a2Reducer,
        B:BReducer
    });
    
    
    //A的子reducer
    function a1Reducer(state = a1DefaultState, action) {
        const {type, payload} = action;
        let newState = null;
    
        switch (type) {
            case a1Type1:{
                //执行相关逻辑
    
                newState = {...state,...payload};
                break;
            }
            case a1Type1:{
                //执行相关逻辑
    
                newState = {...state,...payload};
                break;
            }
            default: newState = state;
        }
    
        return newState;
    }
    
    //A的子reducer
    function a2Reducer(state = a2DefaultState, action) {
        const {type, payload} = action;
        let newState = null;
    
        switch (type) {
            case a2Type1:{
                //执行相关逻辑
    
                newState = {...state,...payload};
                break;
            }
            case a2Type1:{
                //执行相关逻辑
    
                newState = {...state,...payload};
                break;
            }
            default: newState = state;
        }
    
        return newState;
    }
    
    //导出A的reducer
    export {AReducer};
    

十三、旧版本ReactRouter的拆分

早期版本 ReactRouter 是将路由规则集中在一个位置,使它们与布局组件分离。以下是早期版本的路由的核心思想:

  • 路由集中在一个地方;
  • 布局和页面嵌套是通过 <Route> 组件的嵌套而来的;
  • 布局和页面组件是完全纯粹的,它们是路由的一部分;

从ReactRouter4(以下称为:react-router-dom)开始,就不再主张集中式路由了,而是主张路由分散在各个组件;react-router-dom 的这一思想与我们的项目结构的思想不谋而合,所以,在该项目结构中 react-router-dom 能够很好地发挥其特性;

如果要在该项目结构中使用ReactRouter4之前的路由,则可以通过以下方案对ReactRouter进行拆分:

  1. 在 提供者 中导出 提供者的子路由:

    export let BSubRoutes = [
        <Route path={bSubPath1} component={BSubComponent1} />,
        <Route path={bSubPath2} component={BSubComponent2} />,
        <Route path={bSubPath3} component={BSubComponentN} />
    ];
    
  2. 在 使用者 中导入 提供者 的子路由,并配置 使用者 的子路由:

    import {B,BSubRoutes} from './B.jsx';
    
    export let ASubRoutes = [
        <Route path={bPath} component={B}>{BSubRoutes}</Route>,
        <Route path={aSubPath1} component={ASubComponent1} />,
        <Route path={aSubPath2} component={ASubComponent2} />,
        <Route path={aSubPath3} component={ASubComponentN} />
    ];
    

十四、忠告

事物的逻辑模型(包含程序)的bug的本质原因是以下几点:

  • 非本质逻辑;
  • 非本质的决策依据;

如果用一条原因来等价以上2点原因,那便是:

  • 模型的非等价映射;

——?!科研者

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 156,069评论 4 358
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,212评论 1 287
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 105,912评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,424评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,741评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,194评论 1 206
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,553评论 2 307
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,289评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,923评论 1 237
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,251评论 2 240
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,775评论 1 255
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,144评论 2 249
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,698评论 3 228
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,936评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,658评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,214评论 2 267
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,159评论 2 258

推荐阅读更多精彩内容