什么是框架?
假设你在做一个小型 Web 应用:账户系统、动态流、设置页面。在你自己的想法能出现在屏幕上之前,你需要一堆代码:把 URL 转换成页面的代码、和数据库通信的代码、保护密码安全的代码,还有在数据变化时重新渲染画面的代码。这些都不是你这个应用独有的创意,每个应用都需要它们,而且从一个项目到另一个项目,这部分代码基本都是重复的。
这些底层管道你当然可以自己写,事实上过去很多年人们就是这么干的。但这样做很慢,各种细微的 bug 也往往藏在这里,而且每个团队最后都会造出一套大同小异、又都有点小毛病的同类机制。框架就是大家积累出来的答案:把这些共用的底层管道写一次,经过成千上万个项目的打磨、变得足够可靠,再打包成一套结构,告诉你自己的代码该往哪里放。你负责写出让项目与众不同的那部分,框架负责处理每个项目都要重复的那部分。
框架不是 Web 开发的专利。凡是有程序被开发出来的地方,都会有框架的身影,这一章讲的正是"框架"这个概念本身,跟你用什么语言、做什么领域没有关系。
什么是框架(顺带说说什么是库)
"框架"这个词经常被随便乱用,所以我们直接从代码层面看一个真正管用的区分:
// 库:你的代码说了算,需要的时候才去调用库。
const label = dateLibrary.format(order.createdAt, "MMM D");
// 框架:框架说了算,它会调用你插进去的代码。
export default function OrdersPage() {
return listOfOrders();
}第一行是你在掌控全局:你的程序自己运行,只是在需要的时候借用一个工具来完成某件事。第二行则完全不同。你自己永远不会去调用 OrdersPage,你只是把它写好,放在框架要求的位置上,框架会在合适的时机——也就是访客打开订单页面的那一刻——去调用它。
这种反转有个专门的名字:控制反转(inversion of control)。用库的时候,你的代码掌控着程序的运行流程,需要时借用一些辅助工具;用框架的时候,框架掌控着运行流程,你的代码只是填进框架给你留出的空位。一句话概括:库是你去调用的,框架是主动来调用你的。
同样的模式出现在编程的各个角落。Django 接收 Web 请求,然后调用你写的视图函数。Flutter 运行应用本身,然后询问你的 widget 该画些什么。Unity 运行游戏循环,每一帧都会调用你的脚本。pytest 找到你写的测试函数,然后帮你把它们跑一遍。领域各不相同,思路却是同一个:框架掌控着整台引擎,你负责提供它预留位置要用到的零件。
如果想找个画面来理解:库就像一个放在你身边的工具箱,你搭建东西的过程中,需要用到哪个工具就伸手去拿。框架更像是一栋楼的框架结构,你到场的时候它已经立在那里了。墙体、线路、管道都已经各就各位,你的工作是把自己的东西填进那些让这栋楼真正属于你的房间里。两者都能替你省事,区别在于谁来决定整个建筑的形状。
框架能带来什么,又要付出什么代价
支持使用框架的理由是很实在的。那些原本可能要花你几周时间才能搞定的问题,早就被别人解决好了,而且是被那些最早撞上各种棘手边界情况的人解决好的:密码处理、表单验证、路由、渲染。你的项目会有一套结构,新来的同事几分钟就能看明白,因为它和这个框架下的其他所有项目结构都一样。你还会继承一整个生态:插件、教程、已经有人问过并解答过的问题,以及一大批已经熟悉这套东西的可招募人才。
代价同样是实实在在的,值得同样直白地正视。框架是一个你无法掌控的庞大依赖,它有自己的 bug、自己的节奏、自己的一套看法。在你的第一个页面渲染出来之前,会有一段学习曲线要爬,而且你学到的一部分知识只是关于这个框架的,不是关于编程本身的。你的代码会被框架的形状所塑造,你待得越久,之后想脱身就越难。而且框架是会变的:大版本会不断推出,设计模式会被重新思考,跟上这些变化是一项持续存在的工作量,这在纯手写代码的情况下是不需要的。
这两份清单谁也压不倒谁。取舍完全取决于具体项目,所以对任何框架真正该问的问题是"它能不能让这个项目变得更简单"。一个框架能不能站得住脚,就看它能不能让你的项目变简单。如果能,就大方地用它。如果不能,接下来两章讲的正是如何尽早看出这一点。
接下来往哪儿走
弄清楚这个概念之后,自然而然会想问:市面上到底都有哪些框架?框架的种类会带你逛一圈主要的框架家族,从 Web 到游戏再到测试,涵盖每个领域里最热门的选择。之后,选择和学习框架会讲一些实用的东西:怎么挑一个框架、怎么学一个框架,以及什么时候你其实根本不需要框架。另外,如果上面那些 JavaScript 例子让你觉得陌生,JavaScript 课程会讲这门语言本身,在学任何基于它的框架之前,先打好这个基础才是正确的第一步。

