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语法功能外,还支持以下特性:

  • 指数运算符 (ES2016)
  • Async/await (ES2017)
  • Object Rest/Spread Properties (stage 3 proposal)
  • Dynamic import() (stage 3 proposal)
  • Class Fields and Static Properties (part of stage 3 proposal)
  • JSX and Flow syntax.

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

  • 通过 object-assign 支持 Object.assign()
  • 通过 promise 支持 Promise
  • 通过 whatwg-fetch 支持 fetch()

七、根目录结构

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

.
├── 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:
    
    

八、项目结构的核心思想

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

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

优点:

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

九、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 是将路由规则集中在一个位置,使它们与布局组件分离。以下是早期版本的路由的核心思想:

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

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

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

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

    export let BSubRoutes = [
        ,
        ,
        
    ];
    
  2. 在 使用者 中导入 提供者 的子路由,并配置 使用者 的子路由:

    import {B,BSubRoutes} from './B.jsx';
    
    export let ASubRoutes = [
        {BSubRoutes},
        ,
        ,
        
    ];
    

十四、忠告

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

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

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

  • 模型的非等价映射;

——?!科研者

你可能感兴趣的:(React项目规范)