一、React+Redux 项目结构
二、Redux 演变过程
- 混乱结构 -> 组件化结构
- 插件引用 -> 模块化工程
- MVC -> MVVM
- 零散状态管理 -> 全局状态管理
- 双向数据流 -> 单向数据流
前言
Web 前端近十年的技术革新和巨变可谓是让人膛目结舌。其外在表现是很多团队和公司提出了一个又一个新兴技术和解决方案,各家争鸣,百花齐放。而其内在,则是技术专家们在实践中,对原有问题的不断改进,是互联网蓬勃发展过程中的必然经历。
接下来的时间,我们主要将探讨,一些技术结构形成演变的过程,及其出现的必然性。
1. 混乱结构 -> 组件化结构
在 组件化结构
问世之前,Web 项目的开发模式通常是简单粗暴的将问题一锅炖,最显著的表现就是 View
部分结构的胡乱拼凑。我们姑且称其为混乱时代。
示例中很明显的可以看到,有数个页面都需要填充一个新闻列表。在混乱时代,我们会很粗暴的将重复代码
Ctrl + C
,在需要的另一个页面中 Ctrl + V
。这样的复制粘贴,在初步构建页面应用时显得非常“快捷、方便”,但是一旦我们发现这个新闻列表无法完全适用于其他页面而需要调整改动,或者所有页面的新闻列表都需要变更时(通常几乎所有项目都会需要这样的调整和维护更新),工作量就变得无法想像。假设你有 20 个页面都拥有这样的新闻列表,那么就需要变更 20 个页面。在混乱时代,这样的应用维护问题也是被人们所诟病的。因此出现了三种解决方案:frame框架、动态网页和动态渲染插件。
1.1 frame框架
frame框架主要包含两种使用方式:frameset结构 和 iframe内嵌结构。由于缺陷太多因此 HTML5
已不再支持使用,因此我们仅进行简单介绍。
frameset是一个框架集,主要用于规划组成当前主页面的子页面构成,实际会镶嵌多个子窗口在主页面部分,比如我们有这样的一个 index.html
页面文件:
<html>
<frameset rows="25%,*">
<frame src="/example/html/frame_a.html">
<frameset cols="30%,*">
<frame src="/example/html/frame_b.html">
<frame src="/example/html/frame_c.html">
</frameset>
</frameset>
</html>
它的呈现结果是:
即便是现在,也许我们还能在一些古老的项目中看到以frameset分层的页面结构😂。
而iframe,则是一个内联(行内)框架,其作用与 frameset 相似,也是将其他的页面窗口引入至一个主窗口中:
<html>
<body>
<iframe src="/example/html/frame_a.html"></iframe>
<p>这里是主页面窗口!</p>
<iframe src="/example/html/frame_b.html"></iframe>
</body>
</html>
它的呈现效果是:
二者最显著的区别就是,frameset 作为 frame 的父结构,规范 frame 的呈现方式,frame 不可脱离 frameset 独立使用。而 iframe,则可以以独立的标签方式嵌入到任何地方。其相同点是,都用于嵌入其他页面窗口。
利用 frame 框架,我们可以将上述问题这样设计,新闻列表写在一个独立的页面,由需要的页面进行内嵌引用:
解决了我们的维护问题,可以一处开发,各处镶嵌。但使用 frame 过程中,却会引发其他的问题。主要的缺陷有:
- 样式/脚本需要额外链入,增加请求频次;
- 搜索引擎难以抓取内容,获取搜索排名困难;
- 链接导航困难,脚本中需要触发父级页面(或更高层级)的页面跳转;
- ...
1.2 动态网页
动态网页通常是指一些网页模版,通过编译之后会生成静态的 html
页面。常见的如 php 、jsp、asp、cgi 等。
动态网页主要的作用是通过模板实现数据动态化,通常由服务器端进行构建。同时,动态网页技术往往提供了页面嵌套方案,拿 jsp
举例:
<!-- index.jsp -->
<!-- jsp指令:静态包含 -->
<%@ include file="a.jsp"%>
<!-- jsp动作:动态包含 -->
<jsp:include page="b.jsp"/>
@ include
指令用于将希望包含的页面在编译之后,与主页面生成一个页面,它发生在 Servlet
生成静态页面的过程中,index.jsp
和 a.jsp
将合并成一个 Servlet
然后产生静态页面。而 <jsp:include />
动作标签的文件嵌入,则发生在请求过程中,index.jsp
和 b.jsp
将分别代表两个不同的 Servlet
然后进行页面嵌套。后者往往用于嵌入改动较多的动态页面内容,可以进行参数传递;而前者则往往用于直接嵌入静态文件,无法直接传递参数。但无论使用哪种方案,都可以解决我们的新闻列表可维护性的问题:
动态网页技术很大程度上解决了项目维护性的问题,通过访问者或访问方式的不同,生成不同的呈现视图。动态网页技术的发展和应用历史悠久,在客户需求逐渐从需求供给到体验至上的今天,动态网页的缺陷也愈发明显。主要表现在:
- 访问速度低于静态网页,请求需要通过服务器端才能产生视图内容;
- 对搜索引擎不友好,搜索引擎抓取数据困难;
- 视图表现耦合度高,第三方编程语言侵入性高;
- ......
1.3 UI渲染方案
时间的车轮依旧在向前滚动,技术的改进也依旧在迈步。许多团队认为,视图呈现应当与数据计算剥离,Web前后端分工应当更加明确。我们在专注于处理一件事时,才会表现的更加高效精准,技术升级也可以分头进行的更加深入彻底。
随着 Ajax
异步加载技术的成熟应用,Web视图层逐步开始脱离动态网页技术,所有计算好的数据都当作是数据模型(Model),服务器端的工作仅仅作为提供数据模型(Model)而不用考虑视图表现,Web前端的工作则是负责处理这些数据模型(Model),将其渲染成相应视图。在这个过程中,Web前端涌现了大量的UI渲染插件和解决方案,以便于从另一个层面解决视图可复用性问题。
在这个阶段,主要的解决思路是这样的:由UI插件定义视图表现所需的 html
标签结构 和 css
样式结构,通过插件对外暴露的调用方式,以参数的形式接收数据(通常数据格式必须符合UI插件所制定的数据规范),数据通常是一个 js
对象(通常由ajax获取的数据进行 JSON
反序列化)。代表产品有 jQuery-ui、Bootstrap、ECharts、Layui等等,零散的视图渲染插件更是数不胜数。应用过程大致变成了这个样子:
1.4 组件化应用
组件化应用
的构思源于UI渲染插件,是基于该方案的扩展实践,二者的关系是点和面的关系。如果我们将小规模的渲染插件当作是通过数据构建某一个视图功能,那么组件化框架的作用便是将全站的所有内容统一当作是零散的视图功能,通过设计组装进行全站拼接。优势在于,全站整体视图模型都可以复用,并且不限于一个项目。代表产品有React、Vue、Angular。
组件化应用
通常用于在一个单页面中拼装复杂的页面结构。以 React
为例,我们会逐层级的将子组件于网页模版 index.html
进行合并:
<!-- index.html 主页模版 -->
<body>
<div id="root"></div>
</body>
顶层组件引用:
// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
ReactDOM.render(
<App />,
document.getElementById('root'));
组件的设计是一个由上自下的树级结构,越向下深入,可拼接的子组件可能越丰富:
// App.js
import React, { PureComponent } from 'react';
import './App.css';
import Topbar from './components/Topbar';
import MainPanel from './components/MainPanel';
class App extends PureComponent{
render(){
return (
<div className="App">
<header className="App-header">
<Topbar items={this.props.items} />
</header>
<MainPanel />
</div>
);
}
}
组件化UI结构如图所示:
组件化的UI工程具有如下优势:
- 高内聚性:每一个组件都具备完整的局部功能实现。
- 低耦合度:组件之间是互相独立的,通过状态或参数进行交互。在团队协作的过程,也不会产生过多的冲突和影响。
- 高可复用性:在理想状态下,每一个组件相当于是一个纯函数,渲染结果单一,可在应用多处进行复用。