数据分析平台-实践系列三

数据分析平台-前端实践(三)

本章讲解项目目录结构的搭建,路由模式选择和代码分割

目录结构

目前前端项目结构的划分如图

2018-06-18 22:22:17屏幕截图

由于项目并非十分庞大,因此按照功能划分。(涉及Redux的文章会后期写)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
| -- api  # 后台接口
| -- components # 公共组件
| -- commonA
| -- index.js
| -- index.less
| -- config # 项目配置项
| -- constant.js # 项目常量
| -- font # 字体图标文件
| -- redux
| -- store.js
| -- pageA # 与containers对应
| -- action-type.js
| -- actionA.js
| -- actionB.js
| -- reducer.js
| -- pageB
| -- action-type.js
| -- actionA.js
| -- actionB.js
| -- reducer.js
| -- router # 项目一级路由
| -- utils # 项目工具函数
| -- containers
| -- pageA
| -- components # 展示组件
| -- A1
| -- index.js
| -- index.less
| -- index.js
| -- index.less
| -- Xcontainer.js # 容器组件
| -- Ycontainer.js
| -- pageB
| -- components # 展示组件
| -- A1
| -- index.js
| -- index.less
| -- index.js
| -- index.less
| -- Xcontainer.js # 容器组件
| -- Ycontainer.js
| -- index.js # 入口文件
| -- index.less

目前开发保证几个原则:

  1. 页面开发。目前项目页面并不算多,仅有接近二十个页面。在containers文件夹中前期区分好容器组件及展示组件。当写到一定程度,拆分出项目的公共组件,放入components文件夹中。
  2. 状态管理。在Redux文件夹中,直接对应了每个container。在store.js中使用combineReducers整合所有页面的reducer。其后每个页面引用其的action就可以了,对于所有的数据处理,全在action.js中进行。
  3. 路由管理。由于使用的为React Router 4,其不推荐集中路由,组件在哪里匹配就在哪里渲染。因此只定义了一个项目一级路由。

然而由于前期未对项目的复杂度进行合理估计。这套目录结构随着业务发展,已经渐渐有些不够合理。后期项目对于目录结构需要进行重构,不以功能划分,需混合,以业务划分。

建立modules目录,按照业务指定其的containercomponentrouterservice等。且遵从就近依赖,将actionreducer置于对应的container中。

路由模式

页面为单页面应用,因此所有路由是由前端控制。路由使用的是React Router,有两种路由可以选择:hashHistory以及browserHistory

hashHistory是根据#后的路径进行处理,而browserHistory则是我们同常情况下的页面访问路径。如使用hashHistory,路径为www.abc.com/#/helloWorldbrowserHistorywww.abc.com/helloWorld

一般下,两种模式都可以,但是由于个人十分介意多了个#符号,因此选用browserHistory。使用browserHistory需要后端进行配合,因为如果后台没有进行正确配置,如果直接访问www.abc.com/helloWorld会像后台发起请求,返回404。

配置例子在Vue Router中有几个例子。而该项目,后台在开发环境下使用Tomcat,需要修改web.xml文件。

1
2
3
4
5
6
...
<error-page>
<error-code>404</error-code>
<location>/index.html</location>
</error-page>
...

代码分割

代码分割是webpack最引人注目的功能之一,它可以让我们实现代码的按需加载。其最多的使用情况是基于路由的分割。使用import()语法可实现动态加载js,实现代码按需加载。import()返回的为Promise,我们定义一个高阶组件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
function asyncComponent(importComponent) {
class AsyncComponent extends Component {
constructor(props) {
super(props);

this.state = {
component: null
};
}

async componentDidMount() {
const { default: component } = await importComponent(); // 当引入组件时才动态加载js

this.setState({component});
}

render() {
const C = this.state.component;

return C ? <C {...this.props} /> : null;
}
}

return AsyncComponent;
}

实现按需加载

1
2
3
4
5
6
7
8
const Hello = asyncComponent(() => import('../Hello'));

...
<Switch>
<Route path="/hello" component={Hello} />
...
</Switch>
...

当访问/hello时,打开network,可看到动态加载了一个javascript文件。若对其原理感兴趣,可看这篇文章,其最根本的原理就是动态添加script标签。