回头再看前端框架

马上又要到农历新年了,趁现在回顾一下过去一年的前端发展.当然是站在我的角度,因为有许多新的技术,对于不同领域的人以及不同层面的开发者都有不同的意味.
对于我来说,快捷、轻松的开发体验是比较重要的,臃肿的大型框架并不是优秀的代名词,所以我会尽量使用或者倾向喜欢一些生态发展好,开发者使用体验好,社区也比较活跃的框架或者技术.此外,随着事件发展肯定会不断涌现一些新型技术甚至新的思想,对于学习者来说,这些东西还需要一些观望.

除开一些基本概念,我来写一些我平常会用的框架和技术.当然我这里并不是想做一个技术栈的介绍,更多的是在做一个web前端项目时可以考虑的提升开发效率的工具.

Tailwind css

虽然有争议,但这个css工具已经是极为方便的工具了,目前浏览器已经支持许多css新的特性,所以我觉得可以说,类似less和scss的工具是不需要的了.

Bootstrap或许过于臃肿,使用tailwind css本身并没有组件库,这里我推荐两个组件库,一个是daisyUI — Tailwind CSS Components ( version 4 update is here )另一个是Introduction - shadcn/ui

搭配现代的css特性,这样写css基本就够了.实在觉得不行可以再往Element-Plus也就是更抽象高级的组件库上靠.

React

react的生态很好,状态管理工具以及搭配的一些动效库都很多.此外搭配Next.js,Remix.js等等成为一个较为完整的web前端解决方案.React是一个library,关键是使用了JSX,很多其他库也是受此影响,比如Solid,Preact,Millions等等.

Vue

Vue在国内用的很多,当然国外也不少.相对来说我认为较大的差别是Vue的官方本身已经提供了整套的解决方案,Vue也有Nuxt解决SSR的问题,它有Pinia解决状态管理的问题,不像React状态管理通常会选用第三方的Zusland或者jotai等等(当然,如果需要管理的不多直接使用context也可以).目前vue2已经不在维护了,我也大力推荐直接上vue3.Vue使用单独的一个.vue文件表示一个或多个组件,svelte框架也类似.

可以这么说,如果要做一个中大型的前端项目,使用vue或者react的以及它们各自的生态工具是完全没有问题,也没有太大差异的.而且vue对于国内开发者还是比较友好的,各类文档的中文支持还是要比react好.

React和Vue都有SSR的框架,分别是Next和Nuxt,当然React还有Gatsby,Expo这些

Svelte or Solid

在virtual dom上不再赘述.而svelte和solid都是使用的真实的dom.

有的时候你并不需要也并不想要写一个较大的应用,并不想引入过多依赖,但是又想使用一些方便的库.

这时候react,vue就可以稍微退退,使用一些既新颖又成熟的方案,比如Svelte,Solid,Preact或者Million等等.前端框架的发展特别快,在使用过程中掌握一些基本概念会更好.

在浏览这些框架时,我也体会到一个深刻的事情,作为普通开发者在选择这些框架时,即使文档宣传有多么好多么快,如果背后的社区不坚持做下去不坚持宣传,背后的生态,背后没有一个较为统一的解决方案,还是无法做的长久.

我说这些话的意思是,在这么多前端框架出现的今天,如果想要有一个顺畅的开发体验,除了框架本身宣传的技术,还有背后的生态工具链也很重要,毕竟,当项目体积变大,事情就更复杂了.

Svelte官方提供了一个SvelteKit工具,也支持SSR等功能.

Solid也是类似.可以简单粗暴地说在语法上Svelte更像Vue,Solid更像React.而且后两者都提供了SSR的能力.

对比一下Next,Nuxt以及SvelteKit和solid.js

平常我们写前端代码关注哪些点?

Next官网给了以下几点

image-20240203185447263

构建工具

对于Next,可以使用create-next-app

1
npx create-next-app@latest

使用Next或者Nuxt一般主要目的是使用SSR功能.当然除了SSR之外这些框架本身也提供了很多功能,一些开箱即用的库,一些默认的目录结构.不过不是为了强调SSR,SEO,貌似也没有必要使用Next(事实上你也可以使用这些框架但不过多使用它们的一些feature,好处是不用费时费力安装一些常用库)

如果不使用Next而是用React,可以考虑使用Vite等构建工具.

目录结构

使用不同的构建工具或者框架会默认一些目录结构.

Next使用基于文件结构的路由,所录在目录结构上需要注意的.在Next的大更新也就是v13之后,路由默认使用App Router. App Router默认使用app目录进行路由,而Page Router 使用Page目录.

推荐只是用app router就够了.

app/layout.tsx中创建html,在app/page.tsx中创建组件,然后创建public文件夹放图像和字体等静态文件.

具体Next项目结构参看Getting Started: Project Structure | Next.js (nextjs.org),可以看到还是有很多默认规则的.

如何写一个组件

由于有了SSR功能,组件分为了服务端和客户端组件

服务端组件

服务器组件相比于原本的CSR应用有很多好处.比如数据获取更快,更安全,有缓存,客户端需要的Bundle更小,更好的SEO.

渲染过程:在服务器上,Next.js使用React的API来编排渲染。渲染工作分为多个块:按各个route segments和suspense boundaries。

每个块(chunks)分两个步骤呈现:React将服务器组件呈现为一种特殊的数据格式,称为React服务器组件有效载荷(RSC Payload)。Next.js使用RSC Payload和Client Component JavaScript指令在服务器上呈现HTML。

然后,在客户端上:HTML用于立即显示对应路由的快速非交互式预览-这仅用于初始页面加载。React服务器组件有效负载用于协调客户端和服务器组件树,并更新DOM。JavaScript指令用于水合客户端组件并使应用程序具有交互性。

渲染策略

有静态渲染,动态渲染

默认是静态渲染,使用静态渲染,路由在构建时渲染,或在数据重新验证后在后台渲染。结果被缓存,并且可以被推送到内容交付网络(CDN)。此优化允许您在用户和服务器请求之间共享渲染工作的结果。当路由包含的数据不是针对用户个性化的,并且可能在构建时已知时(例如静态博客文章或产品页面),静态渲染非常有用。

使用动态渲染,可以在请求时为每个用户渲染路由。当路由具有针对用户个性化的数据或具有只能在请求时才知道的信息(如cookie或URL的搜索参数)时,动态呈现非常有用。

作为开发人员,您不需要在静态和动态渲染之间进行选择,因为Next.js会根据所使用的功能和API自动为每条路由选择最佳渲染策略。相反,您可以选择何时缓存或重新验证特定数据,也可以选择流式处理UI的部分内容。

此外还有streaming,流式处理使您能够从服务器逐步渲染UI。工作被分割成块,并在准备就绪时流式传输到客户端。这允许用户在整个内容完成呈现之前立即查看页面的部分内容。

客户端组件

客户端组件可以使用state,effects以及事件监听.此外还可以使用浏览器的API.

“use client”用于声明服务器和客户端组件模块之间的边界。这意味着,通过在文件中定义“use clinet”,导入其中的所有其他模块,包括子组件,都被视为客户端捆绑包的一部分。

一旦定义了边界,导入其中的所有子组件和模块都将被视为客户端捆绑包的一部分。

styling

一般来说可以使用tailwind css,像这种主要关注的是组件化,也就是不同的组件之间的css不要互相污染.全局的css可以方便插入.除了tailwind css外还有css in js,css modules等方法,我建议使用其中两种搭配就行了,不然会让人confused(推荐css modules和tailwind css搭配).

路由

Terminology for URL Anatomy

Next的app router支持共享布局,嵌套路由,加载状态,错误处理等等.文件用于定义路由.

每个目录表示一个路由段,使用page.tsx表示对于那个该路由的组件.

page默认是服务端组件,可以改为客户端组件

layouts在不同pages中是共享的,跳转时layouts保留state,不再重新渲染.这个组件应该接受一个children属性作为一个child layout或者一个child page.

顶层有一个Root Layout,这是必须的.在所有pages中共享.包含html和body.

每个路由段可以选择性定义自己的layout,路由中的layout默认被包含,每个父layout把子layout通过children包含在一起.layout和page可以在同一个目录下,layoput会包含这个page.

此外Next还有template,跟layouts类似,但是它并不会保持routes之间的state,这意思是当一个用户在共享一个template的不同路由之间跳转时,不会保持状态.会重新渲染元素.

在定义metadata信息时可以在page或者layout中导出metadata对象.

链接与页面导航

Next有四种跳转路由的方式

  1. 使用Link组件
  2. 使用useRouter hook(客户端组件)
  3. 使用redirect函数(服务端组件)
  4. 浏览器的History API

Link组件

继承了\标签并且使得在客户端跳转提供了prefetching功能,优选方式

跳转路由时默认行为会到顶端,可以使用属性scroll={false}

useRouter

使用useRouter hook

1
2
3
4
5
6
7
8
9
10
11
12
13
'use client'

import { useRouter } from 'next/navigation'

export default function Page() {
const router = useRouter()

return (
<button type="button" onClick={() => router.push('/dashboard')}>
Dashboard
</button>
)
}

对于服务端组件,可以使用redirect.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import { redirect } from 'next/navigation'

async function fetchTeam(id: string) {
const res = await fetch('https://...')
if (!res.ok) return undefined
return res.json()
}

export default async function Profile({ params }: { params: { id: string } }) {
const team = await fetchTeam(params.id)
if (!team) {
redirect('/login')
}

// ...
}

Next.js路由默认缓存等机制.

使用loading.js加载一些内容,原本是React中的suspense.

Loading UI

1
2
3
4
export default function Loading() {
// You can add any UI inside Loading, including a Skeleton.
return <LoadingSkeleton />
}

使用loading.js与suspense可以获得Streaming Server RenderingSelective Hydration的效果.

错误处理

使用error.js进行错误处理.如果要处理根目录下的错误,需要使用global-error.tsx文件,它可以处理包括根下layout.tsx和template.tsx中丢出的错误,此外也推荐再定义error.tsx处理根下page.tsx中的错误.

error.tsx文件会被嵌入到layout之中,所以不会处理layout.tsx中丢出的错误.

重定向

image-20240206130511356

重定向跟路由类似,也分在服务端组件和客户端使用.

redirect

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
'use server'

import { redirect } from 'next/navigation'
import { revalidatePath } from 'next/cache'

export async function createPost(id: string) {
try {
// Call database
} catch (error) {
// Handle errors
}

revalidatePath('/posts') // Update cached posts
redirect(`/post/${id}`) // Navigate to the new post page
}

也可以在next.config.js中设置redirects使得在请求的页面渲染之前进行跳转.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
module.exports = {
async redirects() {
return [
// Basic redirect
{
source: '/about',
destination: '/',
permanent: true,
},
// Wildcard path matching
{
source: '/blog/:slug',
destination: '/news/:slug',
permanent: true,
},
]
},
}

此外还有在Middleware中的跳转,方便在进行验证之后进行选择性跳转.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import { NextResponse, NextRequest } from 'next/server'
import { authenticate } from 'auth-provider'

export function middleware(request: NextRequest) {
const isAuthenticated = authenticate(request)

// If the user is authenticated, continue as normal
if (isAuthenticated) {
return NextResponse.next()
}

// Redirect to login page if not authenticated
return NextResponse.redirect(new URL('/login', request.url))
}

export const config = {
matcher: '/dashboard/:path*',
}

路由组

路由组可以将路由分成一组,同时使得在一个路由段建立多个嵌套的layout.

使用()包含一个目录名,这个目录不会被当作一个路由.在这个路由组中的路由可以共享一个layout.这个目录不会被处理成路由段,此外可以利用这个创建多个root layout.

Route Groups with Multiple Root Layouts

项目结构和文件安排

在目录前加_使得其不可访问.

:_folderName这表示文件夹是一个专用的实现细节,路由系统不应考虑它,从而选择不路由文件夹及其所有子文件夹。

这样做可以使得UI与路由分开.

An example folder structure using private folders

此外可以配置module aliases方便导入.

1
2
3
4
5
6
7
8
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/components/*": ["components/*"]
}
}
}

项目组织策略

可以将组件和lib库放在根目录,跟app目录同层.

An example folder structure with project files outside of app

还可以将这些目录放在app目录下,此外还可以放在路由目录下.

An example folder structure with project files split by feature or route

动态路由

给目录命名的时候使用[],这样在layout,page,route或者generateMetadata函数中可以访问动态路由的参数.

此外使用[...slug]不仅可以捕获该路由下其下的子路由也能被捕获

使用dynamicParams控制generateStaticParams得到的结果之外的能否访问.

1
export const dynamicParams = false

存在multiple-dynamic route以及catch-all segments

image-20240206163014573

此外还有Optional Catch-all Segments,[[...]]既可以匹配原本路由也可以匹配子路由

并行路由

并行路由允许您同时或有条件地呈现同一布局中的一个或多个页面。它们适用于应用程序的高度动态部分,如社交网站上的仪表板和提要。

使用@开头的目录,类似slot可以插入到layout中.

插入路由

截取路由允许您在当前布局中从应用程序的另一部分加载路由。当您希望在不让用户切换到不同上下文的情况下显示路线的内容时,这种路由范例可能很有用。

路由处理器

这也是Next能作为全栈框架的原因,当然在这方面不如Nest.

路由处理器类似page.js而且不能跟其重合.

1
2
3
export const dynamic = 'force-dynamic' // defaults to auto
export async function GET(request: Request) {}

使用GET方法并用Response返回能自动缓存.并且可以使用revalidate验证缓存数据.

1
2
3
4
5
6
7
8
export async function GET() {
const res = await fetch('https://data.mongodb-api.com/...', {
next: { revalidate: 60 }, // Revalidate every 60 seconds
})
const data = await res.json()

return Response.json(data)
}

page,layout和route handler有个dynamic属性,控制静态和动态的渲染和缓存.

默认为auto表示尽可能地缓存,force-dynamic表示强制动态渲染,使得每此渲染在每次请求的时候渲染.error如果每个组件使用了动态函数或者没有缓存的数据基会报错.

force-static强制静态渲染并且缓存数据,cookies和headers等返回空值.

动态函数包括cookies和headers.

动态路由也可以处理请求的参数.

Middleware

Middleware允许在请求完成之前运行代码。然后,根据传入的请求,您可以通过重写、重定向、修改请求或响应标头或直接响应来修改响应。

在根目录使用middleware.ts进行对请求的拦截相应.

image-20240207234054276

Data Fetching

由于引入了SSR等,数据获取也有了变化.分为在server上和在client上.

fetch

在Next.js服务端中,fetch可以配置缓存和重验证.

在客户端中可以调用route handler获取数据,也可以使用React Query等第三方库.

Server Actions和Mutations

Server Actions是在服务上的异步函数,既可以在服务和客户端的组件来处理提交和和数据改变.

对于服务端组件,将”use server”放在一个异步函数的最顶部.

1
2
3
4
5
6
7
8
9
10
11
12
13
// Server Component
export default function Page() {
// Server Action
async function create() {
'use server'

// ...
}

return (
// ...
)
}

对于客户端组件,将”use server”放在客户端组件最顶部.

Nuxt

Vue的SSR框架,Nuxt默认使用Vite构建工具.官方文档将它的和核心概念分为以下几个部分

image-20240206000905397

总体看来跟Next差别不大,具体的可以查看Introduction · Get Started with Nuxt.

SvelteKit

SvelteKit默认使用Vite构建,推荐使用SvelteKit创建一个svelte项目

SvelteKit将处理调用Svelte编译器,将.Svelte文件转换为.js文件,这些文件创建DOM和.css文件。它还提供构建web应用程序所需的所有其他部分,如开发服务器、路由、部署以及SSR支持.

image-20240203190856868

值得注意的是,Svelte是的更新是基于赋值,使用数组的push等操作不会自动更新.

sveltekit类似Next也在路由,fetch数据,headers,cookies等有很多opinioned的配置.

Solid

Solid也是使用Vite构建,使用createSignal来管理状态实现交互性.目前github上的star没有svelte多,因为发展成熟度还没有svelte高,但是solid目前更像一个方便的动态库,比较轻巧.

Solid的SSR支持有点特别.

Solid有一个动态服务器端渲染解决方案,可以实现真正同构的开发体验。通过使用我们的Resource原语,可以轻松地进行异步数据请求,更重要的是,可以在客户端和浏览器之间自动序列化和同步。

Solid的响应式基于Signal,Memo以及Effect.

目前Solid正在开发Solid Start,目的也是提供一个较为整体的Solid生态的解决方案.

image-20240208002623304

Solid和Svelte都有很好的tutorial而且在掌握vue或者react之后再去看它们的文档也并不困难.

小结

对于这一类前端框架,以后可能还会不断涌现,但是其中的生态还是很重要的,如果想要快速没有太多心智负担的开发一个较为成熟生产应用,还是推荐使用React或者Vue等较为成熟的框架或者库.此外这些框架其中的要解决的问题是一致的,解决方法也是趋同的.

响应性,组件创建,生命周期等等,此外附带路由,状态管理,数据获取操作等等都有解决方法.

Astro

我一看到这个框架的官网介绍就觉得这个框架适合写博客.当然它很灵活,功能很强大.

Astro本身并不与React和Vue冲突,所以一起用并没问题.使用集成 | Docs (astro.build)

Typescript

现在流行用ts代替js了,所以还是学习一下.其实并不是很难.

Bun?Deno?

node之后的运行时.Deno现在没声了,可以考虑用用Bun.

开发、测试、运行和捆绑JavaScript&TypeScript项目——所有这些都使用Bun。Bun是一个一体化的JavaScript运行时和工具包,专为速度而设计,配有bundler、测试运行程序和Node.js兼容的包管理器。

htmlx??

最近比较火,但其实本身就是使用html的语法增加了一些原本用js才能实现的比如传文件、发送请求的功能.

但我总感觉定制化能力有点弱,可能本身也是为了快速开发一些web工具用的.

最后

有很多技术我并没有提到,比如还有前端框架Angular以及WASMWebAssembly | MDN (mozilla.org),Qwik等等,因为我现实中并没有过多使用它们,还是让子弹飞一会吧.

一些前端框架的黑盒有点多,对于初学者还是要多多学习基础,学习框架时建议从简单入手,慢慢深入,毕竟很多框架的一些功能其实平常可能用不上.我推荐在掌握React,Next之后可以往Svelte或者Solid框架学习.因为Vue从一些概念上我觉得并没有与React太大的差异,主要是生态上差别,Vue的官方给的工具链已经比较齐全了,而React的生态可以说比较多样化,但也会导致在选择工具上的犹豫和困难.

Solid目前正在开发Solid Start框架,预计应该是类似于SvelteKit之于Svelte,期待后续发展.

而Astro这类框架用于个人博客等等还是很不错的.最后推荐几位Youtube上的前端博主,Web Dev Simplified - YouTube,Traversy Media - YouTube,Fireship - YouTube以及freeCodeCamp.org - YouTube.当然不只是前端以及一些技术趋势.

可以观看的一些视频和文章

  1. YouTube
  2. Front End Developer Roadmap 2024 - YouTube
  3. Web Development In 2024 - A Practical Guide (youtube.com)
  4. 2023 JavaScript Rising Stars
  5. Getting Started: Gatsby vs. Next.js vs. Remix | Satellytes
  6. [Next.js] 三种渲染方式 - SSR、CSR、SSG:优缺点分析 - CloneCoding
  7. 后起之秀svelte和solid是否值得花时间学习? - 掘金 (juejin.cn)
  8. 比较前端框架ReactJs、SolidJS、Svelte和Lit底层逻辑 - Smashing - 极道 (jdon.com)
-------------本文结束感谢您的阅读-------------
感谢阅读.

欢迎关注我的其它发布渠道