其他
透过现象看本质: 常见的前端架构风格和案例
作者:_sx_ https://juejin.im/post/5d7ffad551882545ff173083#comment
所谓软件架构风格,是指描述某个特定应用领域中系统组织方式的惯用模式。架构风格定义一个词汇表和一组约束,词汇表中包含一些组件及连接器,约束则指出系统如何将构建和连接器组合起来。软件架构风格反映了领域中众多系统所共有的结构和语义特性,并指导如何将系统中的各个模块和子系统有机的结合为一个完整的系统
分层风格 Virtual DOM Taro 管道和过滤器 中间件(Middleware) 事件驱动 MV* 家喻户晓的MVC Redux 复制风格 微内核架构 微前端 组件化架构 其他 扩展阅读
分层风格
没有什么问题是分层解决不了,如果解决不了, 就再加一层 —— 鲁迅 不不,原话是: Any problem in computer science can be solved by anther layer of indirection.
是隔离业务复杂度与技术复杂度的利器. 典型的例子是网络协议, 越高层越面向人类,越底层越面向机器。一层一层往上,很多技术的细节都被隐藏了,比如我们使用 HTTP
时,不需要考虑TCP
层的握手和包传输细节,TCP
层不需要关心IP
层的寻址和路由。分离关注点和复用。减少跨越多层的耦合, 当一层变动时不会影响到其他层。例如我们前端项目建议拆分逻辑层和视图层,一方面可以降低逻辑和视图之间的耦合,当视图层元素变动时可以尽量减少对逻辑层的影响;另外一个好处是, 当逻辑抽取出去后,可以被不同平台的视图复用。
Virtual DOM
JQuery
很火:view=f(state)
, 这对生产力的解放是有很大推动作用的; 另外有了VirtualDOM这一层抽象层,使得多平台渲染成为可能。Taro
管道和过滤器
*unix
Shell命令,Unix的哲学就是“只做一件事,把它做好”,所以我们常用的Unix命令功能都非常单一,但是Unix Shell还有一件法宝就是管道,通过管道我们可以将命令通过标准输入输出
串联起来实现复杂的功能:curl "http://en.wikipedia.org/wiki/Pipeline_(Unix)" | \
sed 's/[^a-zA-Z ]/ /g' | \
tr 'A-Z ' 'a-z\n' | \
grep '[a-z]' | \
sort -u | \
comm -23 - /usr/share/dict/words | \
less
ReactiveX
, 例如RxJS. 很多教程将Rx比喻成河流,这个河流的开头就是一个事件源,这个事件源按照一定的频率发布事件。Rx真正强大的其实是它的操作符,有了这些操作符,你可以对这条河流做一切可以做的事情,例如分流、节流、建大坝、转换、统计、合并、产生河流的河流……import { throttleTime, map, scan } from'rxjs/operators';
fromEvent(document, 'click')
.pipe(
throttleTime(1000),
map(event => event.clientX),
scan((count, clientX) => count + clientX, 0)
)
.subscribe(count =>console.log(count));
...
module: {
rules: [{
test: /\.scss$/,
use: [{
loader: "style-loader"// 将 JS 字符串生成为 style 节点
}, {
loader: "css-loader"// 将 CSS 转化成 CommonJS 模块
}, {
loader: "sass-loader"// 将 Sass 编译成 CSS
}]
}]
}
};
中间件(Middleware)
中间件没有显式的输入输出。这些中间件之间通常通过集中式的上下文对象来共享状态 有一个循环的过程。管道中,数据处理完毕后交给下游了,后面就不管了。而中间件还有一个回归的过程,当下游处理完毕后会进行回溯,所以有机会干预下游的处理结果。
日志:记录开始时间 ⏸ 计算响应时间,输出请求日志 认证:验证用户是否登录 授权:验证用户是否有执行该操作的权限 缓存:是否有缓存结果,有的话就直接返回 ⏸ 当下游响应完成后,再判断一下响应是否可以被缓存 执行:执行实际的请求处理 ⏸ 响应
const app = new Koa();
// logger
app.use(async (ctx, next) => {
await next();
const rt = ctx.response.get('X-Response-Time');
console.log(`${ctx.method}${ctx.url} - ${rt}`);
});
// x-response-time
app.use(async (ctx, next) => {
const start = Date.now();
await next();
const ms = Date.now() - start;
ctx.set('X-Response-Time', `${ms}ms`);
});
// response
app.use(async ctx => {
ctx.body = 'Hello World';
});
app.listen(3000);
事件驱动
发布-订阅
风格, 对于前端开发来说是再熟悉不过的概念了. 它定义了一种一对多的依赖关系, 在事件驱动系统风格中,组件不直接调用另一个组件,而是触发或广播一个或多个事件。系统中的其他组件在一个或多个事件中注册。当一个事件被触发,系统会自动通知在这个事件中注册的所有组件.发布-订阅
的例子,比如微信公众号信息订阅,当新增一个订阅者的时候,发布者并不需要作出任何调整,同样发布者调整的时候也不会影响到订阅者,只要协议没有变化。我们可以发现,发布者和订阅者之间其实是一种弱化的动态的关联关系。需要注意的是:事件驱动和异步是不能划等号的。异步 !== 事件驱动,事件驱动 !== 异步
响应式编程: 响应式编程本质上也是事件驱动的,下面是前端领域比较流行的两种响应式模式: 函数响应式(Functional Reactive Programming)
, 典型代表RxJS透明的函数响应式编程(Transparently applying Functional Reactive Programming - TFRP)
, 典型代表Vue、Mobx消息总线:指接收、发送消息的软件系统。消息基于一组已知的格式,以便系统无需知道实际接收者就能互相通信
MV*
MV*
架构风格应用也非常广泛。我觉MV*本质上也是一种分层架构,一样强调职责分离。其中最为经典的是MVC架构风格,除此之外还有各种衍生风格,例如MVP
、MVVM
、MVI(Model View Intent)
. 还有有点关联Flux
或者Redux
模式。家喻户晓的MVC
视图层(View) 呈现数据给用户 控制器(Controller) 模型和视图之间的纽带,起到不同层的组织作用: 处理事件并作出响应。一般事件有用户的行为(比如用户点击、客户端请求),模型层的变更 控制程序的流程。根据请求选择适当的模型进行处理,然后选择适当的视图进行渲染,最后呈现给用户 模型(Model) 封装与应用程序的业务逻辑相关的数据以及对数据的处理方法, 通常它需要和数据持久化层进行通信
<div ng-controller="TodoListController as todoList">
<span>{{todoList.remaining()}} of {{todoList.todos.length}} remaining</span>
[ <a href="" ng-click="todoList.archive()">archive</a> ]
<ul class="unstyled">
<li ng-repeat="todo in todoList.todos">
<label class="checkbox">
<input type="checkbox" ng-model="todo.done">
<span class="done-{{todo.done}}">{{todo.text}}</span>
</label>
</li>
</ul>
<form ng-submit="todoList.addTodo()">
<input type="text" ng-model="todoList.todoText" size="30"
placeholder="add new todo here">
<input class="btn-primary" type="submit" value="add">
</form>
</div>
.controller('TodoListController', function() {
var todoList = this;
todoList.todos = [
{text:'learn AngularJS', done:true},
{text:'build an AngularJS app', done:false}];
todoList.addTodo = function() {
todoList.todos.push({text:todoList.todoText, done:false});
todoList.todoText = '';
};
todoList.remaining = function() {
var count = 0;
angular.forEach(todoList.todos, function(todo) {
count += todo.done ? 0 : 1;
});
return count;
};
todoList.archive = function() {
var oldTodos = todoList.todos;
todoList.todos = [];
angular.forEach(oldTodos, function(todo) {
if (!todo.done) todoList.todos.push(todo);
});
};
});
Redux
单一的数据源. 单向的数据流.
类Redux
的框架,例如Vuex
、ngrx,在架构思想层次是一致的:复制风格
cluster
模块,它可以根据CPU数创建多个Worker进程,这些Worker进程可以共享一个服务器端口,对外提供同质的服务, Master进程会根据一定的策略将资源分配给Worker:const http = require('http');
const numCPUs = require('os').cpus().length;
if (cluster.isMaster) {
console.log(`Master ${process.pid} is running`);
// Fork workers.for (let i = 0; i < numCPUs; i++) {
cluster.fork();
}
cluster.on('exit', (worker, code, signal) => {
console.log(`worker ${worker.process.pid} died`);
});
} else {
// Workers可以共享任意的TCP连接 // 比如共享HTTP服务器
http.createServer((req, res) => {
res.writeHead(200);
res.end('hello world\n');
}).listen(8000);
console.log(`Worker ${process.pid} started`);
}
Worker
的概念,但是一般都只推荐在CPU密集型的场景使用它们,因为浏览器或者NodeJS内置的异步操作已经非常高效。实际上前端应用CPU密集型场景并不多,或者目前阶段不是特别实用。除此之外你还要权衡进程间通信的效率、Worker管理复杂度、异常处理等事情。ServerLess
微内核架构
Webpack
、Babel
、PostCSS
以及ESLint
, 这些应用需要应对复杂的定制需求,而且这些需求时刻在变,只有微内核架构才能保证灵活和可扩展性。模块对象图
, 对于模块代码具体编译工作、模块的打包、优化、分析、聚合统统都是基于外部插件完成的.Sean Larkin有个演讲: Everything is a plugin! Mastering webpack from the inside out
微前端
单体前端
分解成更小、更简单的模块,这些模块可以被独立的团队进行开发、测试和部署,最后再组合成一个大型的整体。组件化架构
组件化就是基于可复用目的,将一个大的软件系统按照分离关注点的形式,拆分成多个独立的组件,主要目的就是减少耦合
.按照Vue官网的说法:
组件系统是 Vue 的另一个重要概念,因为它是一种抽象,允许我们使用小型、独立和通常可复用的组件构建大型应用。仔细想想,几乎任意类型的应用界面都可以抽象为一个组件树
:其他
扩展阅读
了解这些模式面向对象风格: 将应用或系统任务分割为单独、可复用、可自给的对象,每个对象都包含数据、以及对象相关的行为 C/S 客户端/服务器风格 面向服务架构(SOA): 指那些利用契约和消息将功能暴露为服务、消费功能服务的应用 N层/三层: 和分层架构差不多,侧重物理层. 例如C/S风格就是一个典型的N层架构 点对点风格
热门推荐: