让我们从一个故事开始:当我的职业泡沫之外的人问我我在做什么时,我说“我建立网站”。如果他们好奇,我补充说这些网站相当复杂。如果他们继续问,我会尝试用一些例子来详细说明:Facebook、Spotify、Twitter。并不是我为这些公司工作,而是希望给他们一个“我建什么样的网站”的好印象。然而,大多数情况下,谈话不会超出“我建立网站”的范围,我对此没有意见。
如今,一个网站不等于另一个网站。网站的范围从产品营销网站到成熟的社交媒体平台。作为web开发的新手,要了解整个情况并不容易:从HTML和CSS的传统简单网站开始,从web服务器返回,变成一个复杂得多的具有复杂客户端的全栈应用程序-服务器通信和状态管理。
在本演练中,我想向您展示Web开发从一个简单的网站到一个复杂的Web应用程序的演变过程,我们在其中澄清了以下术语:
根据您当前作为Web开发人员的水平,我鼓励您在阅读本指南时随时休息一下,因为它非常广泛并且对于初学者来说可能有点不知所措,尤其是在最后。让我们开始吧...
如果您正在学习Web开发,您很可能会从使用HTML和CSS构建的传统网站开始。没有样式(CSS)且没有任何逻辑(JavaScript)的网站只是HTML。
在我们的网站示例中,通过访问浏览器中的URL从Web服务器向客户端提供服务,浏览器执行HTTPGET方法以从Web服务器读取HTML文件。
由于客户端不一定需要是本地计算机上的浏览器,因此它也可以位于远程位置。但稍后会详细介绍。
Web服务器提供资源(例如HTML、CSS和JavaScript),这些资源是可以通过HTTP传输的格式。当客户端向Web服务器请求资源时,Web服务器通过将资源发送回客户端来完成请求。通常,这些资源只是服务器上的文件。如果将HTML发送到客户端,则客户端(在本例中为浏览器)解释HTML以呈现它。
Web服务器和应用程序服务器都可以归类为服务器。因此,您经常会听到人们谈论服务器时指的是这两者之一。然而,人们通常所说的服务器是指一台物理计算机,它运行在远程某个地方,网络服务器或应用程序服务器正在运行。
您可能还会遇到另外两个术语:部署和托管。我将简要介绍这些术语:部署描述了在服务器上运行网站的行为,托管描述了从该服务器为网站提供服务的持续行为。所以,在自己的电脑上开发网站时,必须要用localhostURL打开,这就说明你是这个网站的本地主机。
当我通过一个URL访问一个网站并围绕这个域(例如mywebsite.com)从一个路径(例如/about)导航到另一个路径(/home)时会发生什么?对于传统网站,客户端会针对每个不同的URL向Web服务器发出新请求。
现代网站由HTML(结构)、CSS(样式)和JavaScript(逻辑)组成。没有CSS,网站就不会闪亮,没有JavaScript,网站就不会有动态交互。通常在使用CSS和JavaScript文件时,它们会链接在一个HTML文件中:
如果浏览器向Web服务器请求URL的HTML,Web服务器会发回HTML文件,其中可能包含链接到其他资源(如CSS或JavaScript文件)的HTML标记。对于每个链接,都会向Web服务器发出另一个请求以检索文件。
然而,至少如果在一个文件中有多个引用,例如链接到CSS和JavaScript文件的初始HTML文件,这些资源将被并行请求和解析,如前一个示例所示,但也说明了下一个。
最终,浏览器将拥有特定URL的所有资源(例如HTML、CSS、JavaScript、PNG、JPG、SVG),并解释HTML及其所有包含的资源以显示所需的结果。它已准备好让您作为用户与其进行交互。
由于用户现在可以创建动态内容,我们需要有一个数据库来存储这些数据。数据库可以与Web服务器位于同一台物理服务器(计算机)上(最有可能出现在Web2.0的早期),也可以位于另一台远程计算机上(最有可能出现在Web开发的现代时代)。
具有面向消费者的网站(静态内容)的Web1.0和具有面向生产者的网站(动态内容)的Web2.0都从服务器返回HTML。用户在浏览器中导航到一个URL并请求它的HTML。但是,对于Web2.0中的动态内容,发送到客户端的HTML不再是具有静态内容的静态HTML文件。相反,它会使用服务器上数据库中的动态内容进行插值:
我们还在处理网站吗?从技术上讲是的,但是通过使用数据库从Web服务器(或应用程序服务器)提供动态内容来超越静态内容的网站也可以称为Web应用程序。不过,这两种类型之间的界限是模糊的。
Web2.0一词及其流行度在2010年左右减弱,因为Web2.0的功能变得无处不在并失去了新鲜感。
在单页应用程序出现之前,浏览器会从Web服务器请求网站的HTML文件和所有链接文件。如果用户碰巧在同一个域(例如mywebsite.com)中从页面(例如/home)导航到页面(例如/about),则每次导航都会向Web服务器发出新的请求。
相比之下,单页应用程序主要将整个应用程序封装在JavaScript中,JavaScript具有关于如何以及在其中使用HTML(和CSS)呈现的内容的所有知识。对于单页应用程序的最基本用法,浏览器只会请求一次HTML文件和一个域的一个链接JavaScript文件。
单页应用程序(此处为React应用程序)请求的HTML只是请求JavaScript应用程序(此处为bundle.js)的中间人,在客户端请求和解析后,将在HTML中呈现(这里id="app"):
从那里开始,React从./bundle.js接管这个小JavaScript:
import*asReactfrom'react';importReactDOMfrom'react-dom';consttitle='HelloReact';ReactDOM.render(
在这个小小的React应用程序中,只有一个名为的变量title显示在HTMLdiv元素中。但是,HTMLdiv元素之间的所有内容都可以替换为使用React组件及其模板语法JSX构建的整个HTML结构。
import*asReactfrom'react';importReactDOMfrom'react-dom';constApp=()=>{const[counter,setCounter]=React.useState(42);return(
这本质上是早期的模板引擎,但只是在客户端而不是服务器上执行,因此这不再是服务器端渲染。
constApp=()=>{const[books,setBooks]=React.useState(['TheRoadtoJavaScript','TheRoadtoReact',]);const[text,setText]=React.useState('');consthandleAdd=()=>{setBooks(books.concat(text));setText('');};return(
- {list.map((item,index)=>(
由于这种从服务器到客户端执行渲染的变化,我们现在将其称为客户端渲染。换句话说:我们不是直接从Web服务器提供预呈现的HTML,而是主要从Web服务器提供JavaScript,它在客户端执行,然后才呈现HTML。通常术语SPA可以与术语客户端呈现的应用程序同义使用。
如果SPA仅从Web服务器请求一次,那么当用户在同一域内从一个页面导航到另一个页面(例如mywebsite.com/about到mywebsite.com/home)而不请求另一个HTML时,它如何工作?
随着传统SPA的使用,我们也从服务器端路由转移到客户端路由。基本SPA最初请求的JavaScript文件封装了网站的所有页面。从一个页面(例如/about)导航到另一个页面(例如/home)不会对网络服务器执行任何请求。相反,客户端路由器(例如React的ReactRouter)接管以从最初请求的JavaScript文件中呈现适当的页面。
简而言之:基本的单页应用程序使用客户端呈现/路由而不是服务器端呈现/路由,同时仅从Web服务器检索整个应用程序一次。它是单个页面,因为整个应用程序只有一个请求,即链接到一个JavaScript文件的单个HTML页面;它封装了所有实际的UI页面并在客户端执行。
可以说,在我们拥有单页应用程序之前,我们一直在使用多页应用程序,因为对于每个页面(例如/about),都会向Web服务器发出一个新请求,以检索所有必要的文件。然而,术语多页面应用程序并不是真正的东西,因为它是单页面应用程序流行之前的默认设置。
一旦应用程序的大小增长,将整个应用程序作为JavaScript文件请求将成为一个劣势。对于更复杂的单页应用程序,代码拆分(在React+ReactRouter中也称为延迟加载)等技术用于仅服务当前页面所需的应用程序的一小部分(例如mywebsite.com/home)。当导航到下一个页面(例如mywebsite.com/about)时,将向Web服务器发出另一个请求以请求该页面的分数。
如果您回顾一下传统网站的工作方式,您会发现它与启用代码拆分的SPA非常相似。对于传统网站,每次用户导航到新路线时,都会加载一个新的HTML文件(带有可选的CSS、JavaScript和其他资产文件)。对于在路由级别进行代码拆分的SPA,每次导航都会导致新请求的JavaScript文件。
我们仍然可以调用这个单页应用程序还是回到多页应用程序?你会看到术语最终是如何变得模糊的……
代码拆分不需要像前面的场景那样在路由级别发生。例如,人们也可以将更大的React组件提取到他们的独立JavaScript包中,这样它就只在实际使用它的页面上加载。
但是,如您所见,这会导致从Web服务器请求的冗余代码。当用户两次导航到代码拆分路由时也会发生同样的情况,因为它也会从Web服务器加载两次。因此,我们希望浏览器缓存(阅读:存储在用户机器上的浏览器缓存中)结果。
为了将React应用程序(或库)捆绑到一个或多个(带有代码拆分)JavaScript文件,另一种称为treeshaking的技术开始发挥作用,它消除了死代码(阅读:未使用的代码),因此它不会打包在最终的包中.从历史上看,JavaScript中使用了以下打包器(从过去到最近):
我们正在进入与SPA同时流行的全栈应用程序范例。全栈应用程序包括客户端(例如SPA)和服务器应用程序。如果公司正在寻找全栈开发人员,他们通常希望有人能够在两端创建客户端-服务器应用程序。有时客户端和服务器共享相同的编程语言(例如客户端上的JavaScript和React,服务器上的JavaScript和Node.js),但它们不是必须的。
无论如何,为什么我们需要全栈应用程序?由于客户端单页应用程序的兴起,对全栈应用程序的需求诞生了。
SPA应用程序(封装在JavaScript文件中)没有任何用户特定数据。这只是页面的逻辑;它们的外观以及它们在用户交互中的行为方式。实际数据并没有嵌入其中,因为它仍然位于数据库中的某个位置,并且不再在服务器上进行插值。这是从服务器端渲染转移到客户端渲染时必须做出的权衡。
客户端呈现的应用程序(SPA)附带一个警告,即从一开始就没有所有数据可供他们使用。他们必须要求一切以填补空白。作为网上冲浪的最终用户,您会以两种方式注意到客户端呈现的应用程序:
除了额外的数据获取往返之外,客户端呈现的应用程序还必须应对状态管理的挑战,因为用户交互和数据需要在客户端的某个地方存储和管理。
RESTAPI连接客户端和服务器应用程序,而无需使用相同的编程语言来实现它们。他们只需要提供一个用于发送和接收HTTP请求和响应的库。REST是一种不受数据格式(过去是XML,但现在是JSON)和编程语言的通信范式。
通过到目前为止讨论的技术,全栈应用程序将客户端和服务器应用程序分离。两者都通过精心挑选的API(例如REST或GraphQL)进行通信。当客户端应用程序在浏览器中呈现Web应用程序所需的一切时,服务器应用程序处理来自客户端的读写数据请求。
我们还没有讨论前端和后端这两个术语,因为我不想预先添加太多信息。前端应用程序可以是用户在浏览器中看到的所有内容(例如网站、Web应用程序、SPA)。因此,您会看到前端开发人员最常使用HTML/CSS或类似React.js的库。相比之下,后端通常是幕后的逻辑:它是从数据库读取和写入数据库的逻辑,与其他应用程序对话的逻辑,通常是提供API的逻辑。
但是,不要在这里将客户端应用程序始终误认为前端,而将服务器应用程序始终误认为后端。这些条款不能轻易交换。前端应用程序通常在浏览器中可见,而后端通常执行不应在浏览器中公开的业务逻辑,并且通常还连接到数据库。
但是,相比之下,客户端和服务器这两个术语是一个角度问题。使用另一个后端应用程序(后端2)的后端应用程序(后端1)成为服务器应用程序(后端2)的客户端应用程序(后端1)。但是,同一个后端应用程序(后端1)仍然是另一个客户端应用程序的服务器,该客户端应用程序是前端应用程序(前端)。
如果有人问您实体在客户端-服务器体系结构中扮演什么角色,您想回答客户端-服务器问题,请始终问自己谁(服务器)为谁(客户端)服务以及谁(客户端)使用谁的(后端)功能?
例如,微服务是一种将一个大后端(也称为单体)拆分为多个较小后端(微服务)的架构。每个较小的后端可能具有一个特定领域的功能,但毕竟它们都服务于一个前端(或多个前端)。但是,一个后端也可以消费另一个后端,而前一个后端成为客户端,而后者成为服务器。
在微服务架构中,每个后端应用程序都可以使用不同的编程语言创建,而所有后端都能够通过API相互通信。他们选择哪种API范式并不重要,无论是RESTAPI还是GraphQLAPI,只要与他们的服务器对话的客户端了解API规范即可。也可能发生一个前端不只与一个后端对话,而是并排与多个后端对话的情况。
如果所有这些还不是让您感到困惑,请尝试了解全栈应用程序的最新发展。随着从传统网站到全栈应用程序的所有发展,您可能已经注意到从X到Y的转变常常使事情变得更加复杂......
当使用位于React之上的流行Next.js框架时,您仍在开发React应用程序。但是,您在Next.js中实现的所有内容都将在服务器端呈现React。在Next.js中,您使用React实现每个页面(例如/about、/home)。当用户从一个页面导航到另一个页面时,只有一小部分服务器端呈现的React被发送到浏览器。它的伟大之处在于:你已经可以请求数据来填充服务器上的空白,用React插入数据,然后将它无间隙地发送到客户端。
这与客户端渲染不同,因为React只接管客户端,并且只有在客户端最初渲染时没有数据时才开始请求数据来填补空白。使用SSRReact,您可以在服务器上插入React中的数据,但也可以选择在应用程序呈现时在客户端上获取数据。客户端渲染和服务器端渲染这两个选项可以混合使用。
传统网站使用来自Web服务器的静态文件在浏览器上呈现。正如我们所了解的,没有应用服务器的参与,也没有服务器端渲染的参与。传统网站的方法非常简单,因为网络服务器只托管您的文件,用户访问浏览器的每个URL都会发出获取必要文件的请求。那么如果我们可以对静态文件使用React呢?
React本身不适用于静态文件。相反,React只是在客户端动态创建应用程序的JavaScript文件。但是,位于React之上的框架Gatsby.js用于为React应用程序生成静态站点。Gatsby使用React应用程序并将其编译为静态HTML和JavaScript文件。然后所有这些文件都可以托管在网络服务器上。如果用户访问URL,静态文件将提供给浏览器。
与服务器端渲染React相比,静态文件不是在用户请求时动态创建的,而是仅在构建时创建一次。对于数据经常变化的动态内容(例如电子商务)来说,这可能是一个缺点,但是,对于内容不经常变化的营销页面或博客,偶尔构建一次网站是完美的解决方案。