Next13 新功能整理
系统要求:
- Node.js 14.6.0 或更新版本 (测试版本 Node.js v16.8)
- 支持 Mac OS、Windows(包括 WSL)和 Linux
安装:
npx create-next-app@latest --typescript
# or yarn create next-app --typescript
# or pnpm create next-app --typescript
React 服务器组件
服务端组件
Next13 支持 React 服务器组件,服务器组件能够在服务器端执行和渲染 React 组件,以前会影响客户端上 JavaScript 包大小的大型依赖项现在可以完全保留在服务器上,从而提高性能。
当一个路由被加载时,Next.js和React runtime将被加载,它是可缓存的,而且大小可预测。这个运行时不会随着你的应用程序的增长而增加大小。此外,运行时是异步加载的,使你的HTML从服务器上被逐步增强到客户端上。
只有当客户端的交互性通过客户端组件在你的应用程序中使用时,才会添加额外的JavaScript。
个人理解:
渲染服务器组件实际上是一个 API 调用,以获取序列化的虚拟 DOM,然后在浏览器中实现它。
最重要的是,服务器组件用于呈现非交互式内容,因此没有事件处理程序、没有 React 勾子,也没有仅限浏览器的 API。
最显着的好处是可以自由访问服务器组件中的任何后端资源和机密。它更安全(数据不会泄漏)和更快(代码不会泄漏)。
客户端组件
客户端组件是在客户端渲染的。 客户端组件也可以在服务器上预渲染,并在客户端进行水合化。 要使用客户端组件,在app中创建一个文件,并在文件的顶部(在任何导入之前)添加 "use client" 。
'use client'
import { useEffect } from 'react'
export default function Client() {
console.log(
'Client page rendering: this should only be printed on the server during ssr, and client when routing'
)
useEffect(() => {
console.log('Client component rendered')
})
return (
<div>
<h1>Client Page</h1>
{/* Uncommenting this will result in an error complaining about inconsistent
rendering between client and server, which is very true */}
{/* <p>My secret env: {process.env.MY_SECRET_ENV}</p> */}
<br></br>
<p>Render log and useEffect log should be printed in browser console</p>
</div>
)
}
当页面首次加载时,它是由 SSR 渲染的;因此您应该在服务器控制台中看到第一个日志;在客户端路由期间,两条日志消息都将出现在浏览器控制台中。
Next.js 13 路由系统
增加 app 目录(测试版本)
目前还在测试阶段,需要在 next.config.js 中开启才能使用, 默认情况下app下的所有文件都属于服务器组件:
/** @type {import('next').NextConfig} */
const nextConfig = {
experimental: {
appDir: true,
},
}
module.exports = nextConfig
route | Next 12 | Next 13 |
---|---|---|
/ | pages/index.js | app/page.js |
/blog | pages/blog.js | app/blog/page.js |
/blog/new | pages/blog/new.js | app/blog/new/page.js |
另外,在服务器启动的时候 Next 会在 app 目录下检测是否有根布局组件,如果没有会帮自动创建。 Next.js 13引入了一个全新的 app 文件夹,其中有一个完全翻新的路由系统。 它包括许多改进,其中最好的礼物是新的布局机制。 只要它们的路由不发生冲突,app文件夹可以与旧的页面文件夹共存,这使你可以逐步采用。
新的文件夹结构
app 文件夹要求每个路由都是一个文件夹,使定义路由更加明确。 一个路由文件夹通常包含以下路由文件(有.js|.ts|.jsx|.tsx后缀)。
- page - 为这个路由提供特定的用户界面。
- layout - 为这个路由和所有后代路由提供布局UI。
- loading - 当路由的服务器组件正在加载时,提供一个加载的用户界面
- error - 为处理此路由内和下的错误提供UI(除非由子层的另一个错误路由处理)。
Layout and nesting
在以前的版本中:
// pages/index.js
export default function Page() {
return (
/** Your content */
<div>Your content</div>
)
}
Page.getLayout = function getLayout(page) {
return (
<Layout>
<NestedLayout>{page}</NestedLayout>
</Layout>
)
}
// pages/_app.js
export default function MyApp({ Component, pageProps }) {
// 使用在页面级别定义的布局(如果可用)
const getLayout = Component.getLayout || (page => page)
return getLayout(<Component {...pageProps} />)
}
使用Next.js 13
新的路由系统使 "layout "成为一流的公民。
在app下的任何一级文件夹(即任何一级路由),都可以使用layout.tsx来明确定义一个容器组件。
这个容器组件将自动环绕该文件夹内和下的所有页面。
如果在不同级别有多个layout.tsx组件,就会自动构建一个包裹的层次结构。
layout 官方文档
错误处理
React有一个Error Boundaries的概念,用于以结构化的方式捕获错误。
Next.js的路由文件夹自然形成了一个组件层次结构。
为什么我们不发明一个公约来处理任何层次的路由的错误呢?
这就是error.tsx文件的作用。它只不过是在中包装下级组件树的语法糖。
官方文档
Next.js 13 数据获取
在 Next.js 13 之前,页面级数据获取模式非常简单
- 如果页面(大部分)是静态的,使用 getStaticProps 获取数据,以便在构建时(和ISR时)进行获取。
- 如果页面是动态的,使用 getServerSideProps 在服务器端获取数据。
- 对于依赖于用户交互的数据,在 useEffect 页面呈现后在钩子中在客户端进行抓取。
这已在 Next.js 13 中完全翻新(如果您选择实验性功能)。但在我们看到新东西之前,让我们反思一下旧世界的问题。
旧模式的问题
- 看起来不自然
export default function Page({ data }) {
// Render data...
}
export async function getServerSideProps() {
const res = await fetch(`https://.../data`)
const data = await res.json()
return { props: { data } }
}
你必须严格按照约定来书写代码。
- Prop drilling
引发props嵌套传递问题,层层传递 , 解决办法是使用 Context API 或者 组件组合模式 ,但这要么会阻碍组件的可重用性,要么需要仔细设计它们。
- 不成功便成仁 (要么有要么无)
当使用 getServerSideProps 时,无论页面加载是由浏览器重载还是由客户端路由触发,新的页面内容都不会显示,直到数据获取完全完成(异步getServerSideProps函数解析)。如果页面同时包含 "快速数据 "和 "慢速数据",这可能是个问题。 例如,数据仪表板是一个典型的场景:一些卡片可以瞬间加载,而另一些则需要很多秒。
Next.js 13中修改了什么?
Next.js 13中引入的新数据获取模式放弃了之前你所熟悉的一切:
- getStaticProps
- getServerSideProps
甚至对于客户端的获取,也有一个新的使用钩子,有可能取代useEffect中的旧的获取方式。
异步服务器组件
在旧世界里,组件是同步的,你不能在组件的顶层等待。在Next.js 13中,所有组件默认为 "服务器",可以是异步的。最后,我们可以在React组件中使用我们熟悉的async/await语法。 这使得数据的获取变得更加容易和灵活。最重要的是,你可以将服务器端的获取逻辑分布到多个地方,并将它们与使用数据的组件搭配在一起。 示例:
// app/server-async-fetching/page.tsx
import { Suspense } from 'react'
import Quote from '../../components/server/quote'
export default function AsyncLoading() {
return (
<>
<h1>Server Component Async Fetching</h1>
<div className="flex flex-col gap-4 w-full h-full">
{/* @ts-ignore */}
<Quote />
{/* @ts-ignore */}
<Quote slow={true} />
</div>
</>
)
}
// lib/quote.ts
import sleep from 'sleep-promise'
export async function getQuote(delay = 0) {
if (delay) {
await sleep(delay)
}
console.log('Getting quote')
return (await fetch('https://api.quotable.io/random?tags=technology')).json()
}
// components/server/Quote.tsx
import { getQuote } from '../../lib/quote'
import os from 'os'
export default async function Quote({ slow }: { slow?: boolean }) {
const quote = await getQuote(slow ? 2000 : 0)
return (
<div className="container border border-blue-600 rounded p-4">
<p>
{slow ? 'Slow' : 'Fast'} component rendered on{' '}
<span className="text-orange-600">${os.hostname()}</span>
</p>
<blockquote className='"text-xl italic font-semibold text-gray-900 p-4'>
{quote.content}
</blockquote>
</div>
)
}
// app/server-async-fetching/Loading.tsx
export default function Loading() {
return <p>Loading...</p>
}
以上代码,在数据没有请求回来之前,Loading 组件一直展示到客户端以及服务端组件都可以渲染为止,这也是典型的 不成功便成仁(all-or-nothing) 🥶 。 我们可以通过在组件周围添加 来通过一个小的修复来改进它。 Suspense 最初是由 React 添加的,用于支持代码拆分;现在它可用于为尚未解析的异步组件提供后备 UI,因此它们可以无阻塞地呈现:
import { Suspense } from 'react'
import Quote from '../../components/server/quote'
export default function AsyncLoading() {
return (
<>
<h1>Server Component Async Fetching</h1>
<div className="flex flex-col gap-4 w-full h-full">
<Suspense fallback={<p>Fast component loading...</p>}>
{/* @ts-ignore */}
<Quote />
</Suspense>
<Suspense fallback={<p>Slow component loading...</p>}>
{/* @ts-ignore */}
<Quote slow={true} />
</Suspense>
</div>
</>
)
}
现在好多了。😊 你可以看到,页面及其两个子组件的渲染是完全异步的。 React已经扩展了Suspense的功能,以支持任意的异步操作。它现在可以完美地与异步服务器组件协同工作。Suspense最酷的地方在于,"取消暂停 "一个组件不需要额外的API请求或WebSocket连接。相反,新的页面内容是通过向HTML文档追加虚拟DOM(用
自动去除重复 Fetch
另一个有趣的事情你可能已经注意到了,尽管快速和慢速组件分别进行了API请求(使用fetch),但它们得到的内容是一样的。 这是由于来自 React 的另一个重要更新——获取调用(在服务器端)被自动删除重复数据: 如果你需要在一棵树上的多个组件中获取相同的数据(如当前用户),Next.js会自动将有相同输入的fetch请求缓存在一个临时缓存中。 这个功能有助于我们解决旧(nextjs12)的数据获取模式的另一个问题--Prop drilling。 有了自动提取重复数据,在一次渲染过程中提取相同的资源只产生一个HTTP请求,所以你可以自由地在你需要渲染的地方提取数据,而不用担心额外的费用问题。很酷,不是吗?🥳
客户端数据获取
在Next.js(和React)的前几个版本中,客户端的数据获取不在框架的关注范围内。你可以使用任何你想要的库,第三方工具如SWR和react-query都很好地解决了这个问题。 Next.js 13(应该更公平地说,最新的React)向前迈进了一步,提供了一个内置的使用钩子,作为从承诺中解包数据的通用API。它不像直接使用async/await那样理想(正如React所解释的那样),但它使客户端的获取感觉与服务器端足够接近。 再次通过一个例子来看看它是如何工作的(所有的组件都是客户端组件,因为它们被标记为 'use client' ):
// app/client-fetching/page.tsx
'use client'
import { useState, Suspense } from 'react'
import Quote from '../../components/client/quote'
export default function ClientFetching() {
// use a button to toggle loading of components to make sure they're loaded client-side
const [show, setShow] = useState(false)
return (
<>
<h1>Client Fetching</h1>
<button className="btn" onClick={() => setShow(true)}>
Show Components
</button>
{show && (
<>
<div className="flex flex-col gap-4 w-full h-full">
<Suspense fallback={<p>Fast component loading...</p>}>
<Quote />
</Suspense>
<Suspense fallback={<p>Slow component loading...</p>}>
<Quote slow={true} />
</Suspense>
</div>
</>
)}
</>
)
}
// components/client/Quote.tsx
'use client'
import { getQuote } from '../../lib/quote'
import { use } from 'react'
const quoteFetch = getQuote()
const quoteFetchSlow = getQuote(2000)
export default function Quote({ slow }: { slow?: boolean }) {
const quote = use(slow ? quoteFetchSlow : quoteFetch)
return (
<div className="box">
<p>{slow ? 'Slow' : 'Fast'} component rendered</p>
<blockquote>{quote.content}</blockquote>
</div>
)
}
它比我们习惯的更干净:在 useEffect 中进行抓取并将结果存储在状态变量中。 它也非常接近它在服务器组件中的外观。 但是这里的 fetch 调用不会在客户端进行重复数据删除。 我不知道这是设计使然还是有待修复。😅
Next.js 13 Turbopack
作为 "Webpack的继任者",一个名为Turbopack的新JavaScript捆绑器是Next.js 13版本的最后一个重要更新。最受欢迎的JavaScript构建工具之一,Webpack,具有难以置信的可定制性和强大的功能,但偶尔也会出现迟缓和繁琐的情况。 Webpack的开发者创建了Turbopack,它是用Rust构建的,并承诺比原来的Webpack快700倍(比Vite这个更现代的替代品快10倍)。 一个变化是,Rust可以创建插件而不是JavaScript。精通系统语言的JavaScript开发人员的数量明显少于JS开发人员的总人数。 使用 next dev --turbo 启动你的开发服务器,如果你建立一个新的 Next.js 13 应用程序,你可以测试新的 Turbopack 捆绑器。
其他升级
- next/image
Next.js 13引入了一个强大的新图像组件,使您能够轻松地显示图像,而无需进行布局转移,并按需优化文件以提高性能。
- 减少了客户端的JavaScript
- 更容易设计和配置
- 更容易访问,默认需要alt标签
- 与网络平台保持一致
- 更快,因为原生的懒惰加载不需要水化
单独升级 image 组件,查看文档
- next/font
Next.js 13引入了一个全新的字体系统:
- 自动优化你的字体,包括自定义字体
- 移除外部网络请求以提高隐私和性能
- 为任何字体文件提供内置的自动自我托管功能
- 使用CSS的大小调整属性自动实现布局零的转变
这个新的字体系统允许你方便地使用所有谷歌字体,并考虑到性能和隐私。CSS和字体文件在构建时被下载,并与你的其他静态资产一起自我托管。浏览器不会向谷歌发送任何请求。
import { Inter } from '@next/font/google';
const inter = Inter();
<html className={inter.className}>
还支持自定义字体,包括支持字体文件的自动自我托管、缓存和预加载。
import localFont from '@next/font/local';
const myFont = localFont({ src: './my-font.woff2' });
<html className={myFont.className}>
- ** next/link**
next/link 不再需要手动添加 作为子项。 这是在 v12.2 中作为实验性选项添加的,现在是默认选项。在 Next.js 13 中, 始终呈现一个 并允许您将 props 转发到底层标签。例如:
import Link from 'next/link'
// Next.js 12: `<a>`必须是嵌套的,否则会被排除。
<Link href="/about">
<a>About</a>
</Link>
// Next.js 13: `<Link>`总是显示为`<a>`。
<Link href="/about">
About
</Link>
13.1 的更新
最近的版本发布了, 下面是对13版本的巩固以及问题的修复:
- 改进了可靠性和对 app 目录的支持:
- No Layout Divs: 以前,应用程序目录添加了额外的元素,以便在导航时将布局滚动到视图中。在13.1版本中,这些额外的元素不再被创建。滚动行为被保留了。issue
- **TypeScript Plugin: **新的TypeScript插件,为页面和布局配置选项提供建议,将文档直接引入你的IDE,并围绕服务器和客户端组件提供有用的使用提示(例如防止在服务器组件中使用useState)Learn more.
- **Reliability Improvements: **修补了许多错误,包括改进对CSS模块的支持,正确去除布局和页面的cache()和fetch(),内存泄漏,以及其他。
- **Less Client-Side JavaScript: **与pages目录相比,app 目录现在包含的客户端JavaScript少了9.3kB。无论你在应用程序中添加1个还是1000个服务器组件,这一基线都不会增加。React运行时暂时略大,增加的原因是React服务器组件运行时,它处理Next.js以前处理的机械。
- No Layout Divs: 以前,应用程序目录添加了额外的
- 内置模块转换(稳定)
- Import resolution for smaller bundles
- A light Node.js runtime for the edge, now stable for API routes
- Turbopack 的改进
- Next.js advanced Middleware
- Other improvements
最后附上文章的github地址
转载自:https://juejin.cn/post/7185830116352589882