我真的需要 SPA 框架吗?
如果你从事前端工作,除非你并非与世隔绝,否则你可能听说过 SPA、React、Angular 甚至 Vue 等术语。你知道它是什么吗?什么时候应该使用 SPA 框架?我们常常很容易陷入“我必须找个借口使用 X 框架”的陷阱。我们很少问自己这个问题:它是适合这项工作的工具吗?
在本文中,我们将讨论什么是 SPA 以及何时使用它。我们还将共同构建一个微型 SPA 框架,并意识到只需很少的代码,我们就能构建出一个运行良好的应用。因此,让我们也来思考这个问题:
我是否需要一个包含所有电池的 SPA 框架,或者这个微型 SPA 框架是否足以应对大多数情况?
我知道你很可能会使用 SPA 框架,不管你对这个问题的回答是什么,但至少要知道你是否真的需要一个框架。
以下是包含完整解决方案的 repo 链接:
什么是 SPA 框架
SPA 是单页应用的缩写,意思是你的应用只存在于一个页面上。
这似乎不太有用,我的大多数应用程序都是多页的?
我并没有说你不能有多个页面,只是你永远不能离开那个页面
你在说谜语,请解释一下
好的,事情是这样的。你停留在这个页面上,但我们仍然可以切换该页面上的部分内容,给人一种你正在从一个页面切换到下一个页面的感觉。所以页面上会有静态部分,比如页眉和页脚,但中间部分会根据例如选择菜单选项而变化。
好的,这很有道理。但是浏览器中的 URL 看起来不一样吗?
实际上,我们正在改变的是一种叫做哈希的东西,#
因此您的路线不是从 开始home.html to products.html
,而是从 转变someBaseUrl#/home
为someBaseUrl#/products
。
但我确信即使人们使用 SPA 应用程序时我也见过正常的路线?
是的,大多数 SPA 框架都有一种方法可以使用重写 URL history.pushState
,并且还可以使用 catch-all 路由来确保您可以进行编写someBaseUrl/products
。
好的,足够公平。
为什么要使用 SPA 框架
就像科技和生活中的其他一切一样,使用合适的工具来完成工作。虽然使用 SPA 框架来处理所有前端工作很诱人,但这并不总是正确的方法。
那么它解决了什么问题呢?闪烁和迟缓的 UI 正是 SPA 的用武之地。在没有 SPA 框架的时代,应用程序在从一个页面切换到下一个页面时会完全重新加载页面。这导致它感觉不像客户端应用程序那样快速流畅。因此,有了 SPA 框架,我们突然拥有了类似客户端的Web 应用程序。
然而,这也带来了一个缺点,那就是搜索引擎收录效果不佳,因为大多数页面都是动态的,无法被抓取。大多数主流 SPA 框架已经并正在解决这个问题,解决方案通常是从应用生成静态页面。不过,并非所有应用都需要考虑这个问题。对于生产力应用来说,这其实无关紧要,但对于电商网站来说,SEO 排名的高低可能会决定你的公司成败。
因此一定要使用 SPA 框架,您将构建快速的应用程序,但也要了解其缺点并确保找到解决这些缺点的解决方案。
构建微型 SPA 框架
构建SPA框架,你确定你吃药了吗?:)
没关系,我们只构建了一小部分,以便理解那些最初的关键部分,并且在此过程中,我们希望能够展示它从“我可以用一个丑陋的黑客来做到这一点”到“我可能需要一个框架/库”的过程。
我们的计划如下:
- 实现路由,路由对于任何 SPA 应用程序都至关重要,我们需要能够定义页面的静态部分以及可以轻松替换的动态部分
- 定义模板并渲染数据。并非所有 SPA 都使用模板,但相当一部分 SPA 会使用模板,例如 Vue.js、AngularJS、Angular 和 Svelte。不过,我会在以后的文章中介绍 React 的方法 :) 我们想要实现的是能够在需要的地方准确地渲染数据,并且应该能够执行诸如渲染数据列表、有条件地渲染数据等操作。
实现路由
让我们首先创建两个文件:
app.js
index.html
正如我们在本文前面所说,SPA 中的路由与哈希#
符号及其更改时间有关。好消息是,我们可以使用以下代码监听该更改:
// app.js
async function hashHandler() {
console.log('The hash has changed!', location.hash);
}
window.addEventListener('hashchange', hashHandler, false);
好的,现在怎么办?
好吧,我们只需要将不同的路线映射到不同的动作,如下所示:
// app.js
const appEl = document.getElementById('app');
const routes = {
'#/': () => {
return 'default page'
},
'#/products':() => {
return 'Products'
}
}
async function hashHandler() {
console.log('The hash has changed!', location.hash);
const hash = !location.hash ? '#/' : location.hash;
appEl.innerHTML = await routes[hash]();
}
然后我们可以将我们的更新index.html
如下:
<!-- index.html -->
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500&display=swap" rel="stylesheet" />
</head>
<body>
<div class="menu">
<div class="item"><a href="#/">Home</a></div>
<div class="item"><a href="#/products">Products</a></div>
</div>
<div class="app" id="app">
</div>
</body>
</html>
模板
上面的代码不太好用,因为我们只能根据路由变化来渲染字符串。我们有一个路由器,但我们想要更多。
如果我们能够以某种方式定义一个模板和一些数据,然后能够将两者合并并显示出来,那就太好了,我们可以这样做吗?
可以,有很多模板库,但我们会选择 Handlebars。
您可以获取它的 CDN 链接或通过 NPM 下载它
npm install handlebars --save
现在怎么办?
现在我们做两件事:
- 定义模板
- 在路线改变时渲染模板
定义模板
我们可以将模板定义为外部文件或script
DOM 树中的元素,我们将采用后者以保持简单:
<script id="hello" type="text/x-handlebars-template">
<div>
{{title}}
</div>
<div>
{{description}}
</div>
</script>
请注意,我们为模板添加了一个id
值hello
,并将类型设置为text/x-handlebars-template
。这样就可以handlebars
找到这个模板。
渲染模板
渲染模板就像调用以下代码一样简单:
var template = $('#hello').html();
// Compile the template data into a function
var templateScript = Handlebars.compile(template);
var html = templateScript({ title: 'some title', description: 'some description' });
此时,我们的变量html
包含一段可以附加到 DOM 树的 HTML。让我们将这段代码嵌入到我们的应用中,如下所示:
// app.js
const appEl = document.getElementById('app');
function buildTemplate(tmpId, context) {
var template = $('#' + tmpId).html();
// Compile the template data into a function
var templateScript = Handlebars.compile(template);
var html = templateScript(context);
return html;
}
const routes = {
'#/': () => {
return buildTemplate('hello', { title: 'my title', description: 'my description' })
},
'#/products':() => {
return 'Products'
}
}
async function hashHandler() {
console.log('The hash has changed!', location.hash);
const hash = !location.hash ? '#/' : location.hash;
appEl.innerHTML = await routes[hash]();
}
好的,我们已经有了一些基本的模板,那么列表呢?handlebars 解决这个问题的方法是在模板中使用以下语法:
<script id="cats-list" type="text/x-handlebars-template">
<div class="products">
{{#each products}}
<div class="product">
{{title}} {{description}}
</div>
{{/each}}
</div>
</script>
让我们放大{{#each products}}
并查看结束标签{{/each}}
,这样我们就可以渲染一个列表。现在来app.js
更新我们的/products
路线:
// app.js
const appEl = document.getElementById('app');
function buildTemplate(tmpId, context) {
var template = $('#' + tmpId).html();
// Compile the template data into a function
var templateScript = Handlebars.compile(template);
var html = templateScript(context);
return html;
}
const routes = {
'#/': () => {
return buildTemplate('hello', { title: 'my title', description: 'my description' })
},
'#/products':() => {
return buildTemplate('products', { products: [{ id:1, title: 'IT', scary book }, { id:2, title: 'The Shining', 'not a fan of old houses' }] })
}
}
async function hashHandler() {
console.log('The hash has changed!', location.hash);
const hash = !location.hash ? '#/' : location.hash;
appEl.innerHTML = await routes[hash]();
}
它还能handlebars
为我们做更多的事情,比如条件逻辑、内置指令以及自定义指令。完整参考请见此处:
事件处理
那么事件呢?
嗯,它是纯 JavaScript,因此只需将您拥有的任何事件与处理程序连接起来,如下所示:
<script id="cats-list" type="text/x-handlebars-template">
<div class="products">
{{#each products}}
<div class="product">
{{title}} {{description}}
</div>
<button onclick="buy({{id}})">Buy</button>
{{/each}}
</div>
</script>
我们app.js
只需要一个方法buy()
,就像这样:
function buy(id) {
console.log('should call an endpoint', id);
}
异步数据
好的,我们如何处理后端,简单来说,通过fetch()
,像这样:
'#/products': async() => {
const res = await fetch('http://localhost:3000/products')
const json = await res.json();
return buildTemplate('products', { products: json })
}
概括
那么,你需要 SPA 吗?这取决于你是否只想渲染列表,并时不时地添加一些条件逻辑。我认为你不需要。不过,SPA 还附带很多其他功能,比如优化渲染。我敢打赌,这种方法在渲染几百个元素时就显得力不从心了。SPA 通常附带状态管理之类的功能,这些功能可以轻松地与 SPA 本身挂钩,你几乎毫不费力就能获得服务器端渲染和渐进式 Web 应用之类的功能。所以听起来我好像在提倡 YAGNI(你不需要它)?但众所周知,你周五做的那个小改动两年后就成了关键业务系统的一部分,所以你应该选择 React、Angular、Vue.js 或 Svelte 等等。
我希望至少我已经向你展示了如何在 30 分钟内实现许多类似 SPA 的行为。我想表达的重点是——要知道什么时候需要 SPA 方法,并且在某些情况下,采用完整的框架可能会有些过度,只是说说而已 ;)
文章来源:https://dev.to/itnext/do-i-really-need-a-spa-framework-3occ