四个文件中的完整堆栈
文件 1:数据库
文件 2:服务器
文件 3:组件
文件 4:前端
结论
如今学习开发全栈应用真是难上加难!虽然有一些工具可以简化开发,但通常以牺牲可扩展性或灵活性为代价。如果学习的热情能够更直接地转化为可扩展的全栈应用,那就太好了。为什么我们不能抽象出更多样板代码,同时仍然构建一个可扩展的应用呢?我想我们可以!
接下来是一个由四个文件构建的应用程序,以及其脚手架框架的描述。所描述的框架尚未完全实现,但许多所需的部分已经存在,并已提供下方链接,供您立即使用。该框架的初衷并非解决应用程序开发中的所有边缘情况,而是为学习和构建全栈应用程序提供一个简化的基础。
为此,我们可以让它变得多么简单?如果可以用一个可读的模式文件定义数据库、类型和访问方法会怎样?如果构建和访问服务器 API 就像导入和导出函数一样简单会怎样?'missing'
这些代码示例中包含一个交钥匙解决方案,它使用以下文件编写这些工具的脚本来实现一个可运行的应用程序。我分享我的想法是为了收集一些更好的想法,所以也请分享你的想法!
文件 1:数据库
十多年前,我开始学习 Web 应用中的数据库。这期间,很多技术都取得了进步,但创建和管理关系数据库的难度似乎依然如故。归根结底,关系数据库仍然是解释关联数据项的有效方法,并且对于大多数应用程序来说,它们都是不错的选择。
然而,有一些创新团队正在开发基于GraphQL的系统,他们使得用代码简洁地表示数据库成为可能。借助Fauna或Prisma等服务,我们可以使用单个 GraphQL 模式文件定义一个有状态的关系数据库。太酷了!下面是我为这个例子从 FaunaDB 改编而来的一个示例!
模式.gql
type Counter {
title: String!
count: Number!
}
type Query {
allCounters [Counter!]
}
由此,Fauna 可以直观地了解内部数据库结构,以及用于访问和修改它的 GraphQL API。这些模式文件还可以用于为模式中的对象生成 TypeScript 类型types
。为什么这些 TypeScript 类型很有用?例如,使用 Prisma,这种模式到类型的转换允许生成完整的类型化客户端,因此,一个文件即可生成数据库和访问层!
文件 2:服务器
现在的服务器需要什么?以前,你需要一个庞大的配置框架来包含远程过程。随着函数即服务 ( FaaS ) 的出现,对于大多数应用程序来说,你只需要定义远程过程或函数。所以,定义一个无状态服务器,我们只需要做这些:
服务器.ts
import { Get, Post, Patch } from 'missing'
import { db } from './schema.gql'
export const getAllCounters: Get = async () => db.allCounters()
export const createCounter: Post = async (
title: string,
count: number = 0,
) =>
db.createCounter({
title,
count,
})
export const updateCounter: Patch = async (
id: string,
count: number,
) =>
db.updateCounter({
id,
count,
})
这些异步函数逻辑简单,在本例中只是对传递给数据库方法的参数进行重组。然而,你可以想象这些函数能够处理更多的事情。'missing'
库中的类型提供了关于函数作为服务器端点应如何存在的额外信息。我在这里将类型作为 HTTP 动词,但它们可以指示函数的其他“元数据”,并指导与云系统的更深入集成。借助这些额外的类型信息,这些函数可以被包装并输入到任意数量的云专用 FaaS 产品(AWS Lambda、Google Cloud Functions等)或云无关系统(例如Kubeless、Fission或OpenFaaS )中。
然而,真正的好处在于,它能将函数运行位置以及如何远程调用的问题抽象出来,而只关注import
前端。所以,现在让我们从服务器跨过去,开始看看客户端应用程序!
文件 3:组件
啊,前端!我的专长。事实上,我认为我构建这个框架的全部原因就是为了更容易地奠定基础,并更快地实现一个有状态的交互式前端!过去十年来,前端领域涌现出许多创新,并且有各种各样的前端框架可以让你的生活更加……嗯,更加丰富多彩。
虽然fetch
比 有所改进XMLHttpRequest
,但我认为我们可以做得更好,因为我们也部署了服务器函数。如果框架也调用我们的服务器函数——那么我们应该能够根据环境抽象该调用——并使用与服务器函数具有相同签名的简化导入函数来呈现我们的前端组件,正如我们在React示例中看到的那样:
组件.tsx
import React from 'react'
import {
getAllCounters,
createCounter,
updateCounter,
} from './server'
import { Counter } from './schema.gql'
const useCounters = () => {
const [loaded, updateLoaded] = React.useState<boolean>(false)
const [counters, updateCounters] = React.useState<Counter[]>([])
React.useEffect(() => {
if (!loaded) {
getAllCounters().then(counters => {
updateCounters(counters)
updateLoaded(true)
})
}
}, [loaded])
return {
loaded,
counters,
triggerReload: () => updateLoaded(false)
}
}
export const Counters: React.FunctionComponent = () => {
const nameRef = React.useRef<HTMLInputElement>(null)
const {loaded, counters, triggerReload} = useCounters()
const renderCounters = () => {
if (!loaded) {
return <li>loading</li>
} else if (!counters.length) {
return <li>no counters</li>
}
return (
<>
{counters.map(({ count, id, title }) => (
<li
key={id}
onClick={async () => {
await updateCounter(id, count + 1)
triggerReload()
}}
>
{title} {count}
</li>
))}
</>
)
}
return (
<ul>
<li>
<input ref={nameRef} title="new counter name" />
<button
onClick={async () => {
await createCounter(nameRef.current.value)
triggerReload()
}}
>
new counter
</button>
</li>
{renderCounters()}
<ul/>
)
}
本示例使用了一个 React 组件,但我们导入的这些函数./server
可能集成到了其他框架的组件中。或者,我们可以完全跳过基于框架的组件,只使用浏览器的功能来做一些事情。真正的重点是……你不需要了解 AJAX 就能构建任何东西!
文件 4:前端
最后,我们需要将这些组件(或者仅仅是我们的客户端脚本)整合在一起,并将其表示为 HTML 页面,Parcel已经很好地支持了这一点。由于此示例使用 React,因此我将使用MDX ,它可以以简单的基于Markdown的格式表示我们的组件标记和内容:
索引.mdx
import { Counters } from './components'
# Check out these counters!
<Counters />
每个前端入口文件都会映射到一个 HTML 文件和一组捆绑资源,这些资源可以通过多种方式部署。例如,使用OpenFaaS,这些捆绑包可以映射到可扩展的 NGINX 实例或其他静态服务,这些服务的部署方式与服务器功能相同。这样做的目的是在本地提供与生产环境几乎相同的体验,同时仍然允许扩展。
结论
所以结论就是,你看到的就是我拥有的。我对此深思熟虑,这篇文章就是把我的实验和想法公之于众,因为我所描述的是一个我希望看到的框架。
可扩展、灵活,但最重要的是更简单!是不是更简单?告诉我!我很想知道你是否希望看到这个工具,尤其是如果你有任何添加或修改的想法。感谢阅读,也感谢你的想法!🔰
鏂囩珷鏉ユ簮锛�https://dev.to/shiftyp/full-stack-in-four-files-46m3