在我正在开发的一个Next.js应用程序中,我有一个 "管理 "页面,它让我可以管理注册用户。
我最喜欢Next.js的一个特点是,单个路由可以选择加入服务器端渲染。虽然我倾向于大力提倡静态生成,但这是一个完美的服务器端渲染的用例;我可以在第一次渲染时获取并注入数据库数据,简化我的前端代码。
至少,我是这么想的......然后我遇到了一个障碍。😬
我的问题是这样的:在我的仪表板上,我可以编辑用户,以授予购买权或更新他们的名字。
注意到更新后的名字没有显示在表格中吗?这是因为页面不知道基础数据已经改变。在页面被服务器渲染后,这些道具是不可改变的。
我们如何告诉Next.js按需重新获取数据,而不对整个页面进行硬刷新?
在这个简短的教程中,我将分享我学到的解决这个问题的灵巧技巧。我们还将学习Next.js如何在后台工作,并涵盖一些相关的问题和解决方案。
该解决方案
这就是解决方案,对于忙碌的海狸来说,它是一个复制粘贴的胜利。
import { useRouter } from 'next/router';
function SomePage(props) {
const router = useRouter();
// Call this function whenever you want to
// refresh props!
const refreshData = () => {
router.replace(router.asPath);
}
}
export async function getServerSideProps(context) {
// Database logic here
}
每当你想从后端提取新的数据时,就会调用refreshData 函数。这将根据你的使用情况而变化。作为一个例子,这里是你在修改了一个用户之后如何刷新数据的。
async function handleSubmit() {
const userData = /* create an object from the form */
const res = await fetch('/api/user', {
method: 'PUT',
body: JSON.stringify(userData),
});
// Check that our status code is in the 200s,
// meaning the request was successful.
if (res.status < 300) {
refreshData();
}
}
但为什么这样做呢?为什么我们要让路由器参与这个过程,它到底在做什么?
事实上,这个解决方案需要一些背景设置。让我们来谈一谈服务器渲染的路由如何有一个秘密的替代者🦸🏾♀️
为什么它能发挥作用
当我们想到getServerSideProps ,我们通常会想象一个看起来像这样的流程。
-
用户从谷歌(或其他地方)跟踪一个链接到我们的网站。
-
Next.js调用我们的
getServerSideProps方法,并使用它来生成一个HTML文件。 -
用户收到了该HTML文件,React在客户端上进行补水。
这就是Next.js服务器路由的肉和土豆。这是克拉克-肯特在日常工作中所做的文书工作。
但Next.js还有另一种方式使用你的getServerSideProps--从客户端。
考虑一个不同的场景。
-
用户已经在你的网站上,他们点击Next.js的
Link,导航到服务器渲染的页面。 -
Next.js在服务器上调用你的
getServerSideProps方法,但它没有生成一个HTML文件,而是将数据以JSON格式发送到客户端。 -
React在浏览器中渲染新页面时,使用该数据作为初始道具。
Next.js之所以这么酷,是因为服务器渲染代码也可以作为一种API来使用。我们可以用Next实现闪电般的客户端路由,因为你的服务器渲染的路由可以跳进电话亭,穿上服装,成为一个API端点。白天是服务器渲染者,晚上是JSON发送者。
我们的解决方案是有效的,因为我们正在执行客户端过渡到同一路线。 router.asPath 是对当前路径的引用。如果我们在/admin-panel ,我们就告诉Next做一个客户端重定向到/admin-panel ,这将导致它重新获取JSON数据,并将其作为props传递给当前页面。🧨
如果你想知道router.replace :它就像router.push ,但它不会在历史栈中添加一个项目。我们不希望它被 "算作 "重定向,这样浏览器的 "返回 "按钮仍能按我们的意图工作。
简而言之:Next.js没有 "refetchProps "方法,但我们可以利用客户端的导航行为来达到同样的目的。💯
加载状态
当你调用这个方法时,在你的用户界面中不会有任何迹象表明正在发生重新获取。这在某些情况下是可以的,但我们不能假设一个快速的网络;即使你的getServerSideProps 呼叫是Blazing Fast™,一个在树林里的用户在1 bar的3G下仍然会被卡住等待一分钟的时间。
我们需要一个加载状态!下面是我们如何创建一个。
function SomePage({ theData }) {
const [isRefreshing, setIsRefreshing] = React.useState(false);
const refreshData = () => {
router.replace(router.asPath);
setIsRefreshing(true);
};
React.useEffect(() => {
setIsRefreshing(false);
}, [theData]);
}
我们有一个新的React状态变量,isRefreshing 。当我们发出请求时,我们把它设置为true ,当组件用新数据重新渲染时,它又被设置为false 。
我们不在每次渲染时启动它,而是把它放在一个效果钩中,并跟踪我们的theData 道具(theData 是一个占位符,代表你的服务器数据的样子)。当服务器返回新的数据时,钩子就会启动,而我们的加载状态就会终止。它可以保护我们免受 "偶然 "渲染的影响,如果在我们等待数据的时候,其他状态恰好发生了变化。
下面是它的样子。请注意右上角的加载指示灯。
突变数据
在我的管理-仪表盘案例中,我不需要对服务器渲染的数据做任何 "特别 "的修改。一个直接的刷新就是我所寻找的全部。
但如果我想以某种方式改变数据呢?也许我想做一个 "乐观的更新",在服务器确认之前显示新状态的数据?
在这种情况下,我们必须将道具转移到状态中,这样它就可以像其他React状态一样被改变。
function SomePage({ initialData }) {
const [theData, setTheData] = React.useState(initialData);
// Mutate whenever you want with `setTheData`!
}
现在,你可能在想:把道具复制到状态中不是一种反模式吗?React文档不是告诉我们不要做这件事吗?
并非如此。我们要避免的是重复真相的来源。如果多个组件定义了相同的状态,这通常是一个问题的标志。
在这种情况下,我们只有一个真理之源,而且是在我们的React树的最顶端。对我来说,这闻起来像Spring Breeze织物柔软剂。是代码的香味,不是代码的味道。
建议在prop前加上initial (例如:initialUsers ,而不是users ),这样就可以清楚地知道prop作为一个初始值,而不是一个持续的真理源。
替代方案
在我发现路由器刷新技巧之前,我的游戏计划是这样的。
-
把数据库的调用从
getServerSideProps,拉到一个函数中,getUsers。在getServerSideProps中调用该函数。 -
创建一个API路由,也使用
getUsers数据,并以JSON格式返回。 -
在页面组件中,使用一个像SWR这样的库来跟踪数据。它将从服务器端的道具中初始化,但连接到新的API路由以进行后续的数据获取。
-
根据需要使用SWR来改变数据。
在我的特定情况下,这条路径对我来说感觉过于复杂了;我不希望有两个独立的机制来获取用户!而且,虽然,但这是一个很好的库。虽然SWR 是一个伟大的库,我在我的应用程序中使用它来管理认证,但在这种情况下感觉有点沉重。
但是,在其他情况下,我认为这将是正确的方法。例如,如果你有复杂的数据获取或数据变异的要求,或者你已经有一个可以在客户端直接交互的API。
收尾工作
这是我在Next.js上的第一个教程!🍾