在认证的Next.js应用程序中动态获取数据的方法

893 阅读4分钟

Next.js有五种数据获取模式,用于确定你希望在应用中看到的内容:静态网站生成(SSG)、服务器端渲染(SSR)、客户端渲染(CSR)、增量静态再生(ISR)和动态路由。

你可以选择这些模式中适合你的应用程序结构的任何一种。要了解更多关于这些模式的信息,请在官方文档中阅读它们。

本文重点介绍静态网站生成和动态路由。使用这些模式需要使用getStaticPropsgetStaticPaths 数据获取方法。这些方法在获取数据方面起着独特的作用。

我们已经谈论动态数据有一段时间了。让我们了解一下这真正的含义。

假设我们有一个正在网页上呈现的应用程序中的用户列表,当我们点击用户的名字时,我们想获得用户特有的信息--我们得到的信息将根据我们执行的动作(点击用户的名字)而改变。

我们希望有一种方法在应用程序中的一个独特的页面(或屏幕)上呈现这些数据,而getStaticPaths 数据获取方法允许我们获得对一个用户来说是唯一的数据。这通常常见于一个具有唯一键的用户对象数组中(id_id),这取决于响应对象的结构方式。

export async function getStaticPaths() {
  return {
    paths: {
      [{
        params: {
          uniqueId: id.toString()
        }
      }],
      fallback: false
    }
  }
}

getStaticPaths 方法中获得的唯一键(通常被称为参数,或简称为params)作为参数通过context 参数在getStaticProps

这使我们回到了这样一个事实:没有getStaticPropsgetStaticPaths 就无法工作。它们两个一起发挥作用,因为你需要将静态生成的路径中唯一的id 作为参数传递给getStaticProps 中的context 参数。下面的代码片段说明了这一点。

export async function getStaticProps(context) {
  return {
    props: {
      userData: data,
    },
  }
}

使用Next.js的本地数据获取方法的缺点

现在我们对Next.js中的动态数据获取有了一些了解,让我们看看使用上述两种数据获取方法的缺点。

从公共API获取数据,在数据获取过程中不需要用某种API密钥的授权,可以用getStaticPropsgetStaticPaths

看一下下面这两个方法。

// getStaticPaths
export async function getStaticPaths() {
  const response = fetch("https://jsonplaceholder.typicode.com/users")
  const userData = await response.json()

 // Getting the unique key of the user from the response
 // with the map method of JavaScript.
  const uniqueId = userData.map((data) => {
    return data.id
  })

  return {
    paths: {
      [{
        params: {
          uniqueId: uniqueId.toString()
        }
      }],
      fallback: false
    }
  }
}

你会注意到,唯一的id 是从JavaScript的map 方法中得到的,我们要通过getStaticPropscontext 参数将其分配为一个值。

export async function getStaticProps(context) {
  // Obtain the user’s unique ID.
  const userId = context.params.uniqueId

  // Append the ID as a parameter to the API endpoint.
  const response = fetch(https://jsonplaceholder.typicode.com/users/${userId})
  const userData = await response.json()
  return {
    props: {
      userData,
    },
  }
}

在上面的片段中,你会看到一个名为userId 的变量被初始化,它的值是从context 参数中得到的。

然后该值被作为一个参数添加到API的基本URL中。

注意: getStaticPropsgetStaticPaths 数据获取方法只能从 Next.js 的pages 文件夹中导出。

对于公共API来说,这就差不多了。但是当你在构建一个需要用户登录、注销,或许在他们用账户登录时在应用中执行一些数据获取的应用时,应用流程就不同了。

在一个经过认证的系统中获取数据。

在一个经过认证的系统中,获取数据的流程与我们从公共API中获取数据的正常方式完全不同。

想象一下这个场景。一个用户登录到一个网络应用,然后访问他们的个人资料。在他们的个人资料页面(一旦呈现出来),他们能够看到并改变他们在注册时提供的信息。

要做到这一点,必须对建立界面的开发者发送给用户的数据进行某种验证。幸运的是,当用户登录到一个系统时,有一个通用的模式来授权他们。JSON网络令牌(JWTs)。

当用户第一次注册使用你的应用程序时,他们的详细资料被存储在数据库中,一个独特的JWT被分配给该用户的模式(取决于后端API是如何设计的)。

当用户试图登录你的应用程序,并且他们的凭证与他们最初注册的凭证相符时,我们前端工程师要做的下一件事就是为用户提供一个认证状态,这样我们就可以获得所需的细节,其中之一就是JWT。

关于如何保存用户的auth-state ,有不同的流派,包括使用Redux、React中的Composition以及React的Context API(我推荐Context API)。Átila Fassina的文章介绍了Next.js的状态管理范式。

常见的方法是将JWT存储在localStorage --如果我们严格考虑安全问题的话,至少可以起步了。将JWT存储在httpOnly cookie中是可取的,以防止安全攻击,如跨站请求伪造(CSRF)和跨站脚本(XSS)。

再次,只有在后端工程师建立的API中提供了适当的cookie中间件时,这种方法才可能实现。

如果你不想麻烦地弄清楚后端工程师是如何设计API的,在Next.js中进行认证的另一种途径是利用开源的认证项目NextAuth.js。

一旦客户端的令牌进入localStorage ,需要用户令牌作为授权手段的API调用就可以通过,而不会抛出501(未授权)的错误。

headers: {
  "x-auth-token": localStorage.getItem("token")
}

使用useRouter 钩子进行数据获取

在第一节中,我们看到在Next.js中,动态数据获取的过程是如何在一个不需要认证的应用程序中进行的。

在这一节中,我们将看看如何绕过getStaticPropsgetStaticPaths 数据获取方法的问题,当我们试图从localStorage 获取用户的令牌时,抛出一个referenceError ("localStorage is undefined") 。

出现这个错误是因为这两个数据获取方法总是在后台的服务器上运行,这又使得localStorage 对象无法使用,因为它是在客户端(浏览器中)。

Next.js的路由器API创造了很多可能性,当我们我们处理动态路由和数据时。通过useRouter 钩子,我们应该能够根据用户的唯一ID来获得对用户来说是唯一的数据。

让我们看看下面的片段来开始吧。

// pages/index.js

import React from "react";
import axios from "axios";
import { userEndpoints } from "../../../routes/endpoints";
import Link from "next/link";

const Users = () => {
  const [data, setData] = React.useState([])
  const [loading, setLoading] = React.useState(false)

  const getAllUsers = async () => {
    try {
      setLoading(true);
      const response = await axios({
        method: "GET",
        url: userEndpoints.getUsers,
        headers: {
          "x-auth-token": localStorage.getItem("token"),
          "Content-Type": "application/json",
        },
      });
      const { data } = response.data;
      setData(data);
    } catch (error) {
      setLoading(false);
      console.log(error);
    }
  };

  return (
    <React.Fragment>
      <p>Users list</p>
      {data.map((user) => {
          return (
            <Link href={`/${user._id}`} key={user._id}>
              <div className="user">
                <p className="fullname">{user.name}</p>
                <p className="position">{user.role}</p>
              </div>  
            </Link>
          );
        })}
    </React.Fragment>
  );
};

export default Users;

在上面的代码段中,我们使用了useEffect 钩子来获取页面首次呈现后的数据。你还会注意到,JWT被分配给请求头中的x-auth-token key。

当我们点击一个用户时,Link 组件将根据该用户的唯一ID将我们路由到一个新的页面。一旦我们进入该页面,我们要用id 来渲染专门为该用户提供的信息。

useRouter 钩子使我们能够访问浏览器的URL标签中的pathname 。有了这个,我们就可以得到那个独特路由的查询参数,也就是id

下面的片段说明了整个过程。

// [id].js

import React from "react";
import Head from "next/head";
import axios from "axios";
import { userEndpoints } from "../../../routes/endpoints";
import { useRouter } from "next/router";

const UniqueUser = () => {
  const [user, setUser] = React.useState({
    fullName: "",
    email: "",
    role: "",
  });
  const [loading, setLoading] = React.useState(false);
  const { query } = useRouter();

  // Obtaining the user’s unique ID with Next.js'
  // useRouter hook.
  const currentUserId = query.id;

  const getUniqueUser = async () => {
    try {
      setLoading(true);
      const response = await axios({
        method: "GET",
        url: `${userEndpoints.getUsers}/${currentUserId}`,
        headers: {
          "Content-Type": "application/json",
          "x-auth-token": localStorage.getItem("token"),
        },
      });
      const { data } = response.data;
      setUser(data);
    } catch (error) {
      setLoading(false);
      console.log(error);
    }
  };

  React.useEffect(() => {
    getUniqueUser();
  }, []);

  return (
    <React.Fragment>
      <Head>
        <title>
          {`${user.fullName}'s Profile | "Profile" `}
        </title>
      </Head>
        <div>
          <div className="user-info">
            <div className="user-details">
              <p className="fullname">{user.fullName}</p>
              <p className="role">{user.role}</p>
              <p className="email">{user.email}</p>
            </div>
          </div>
        </div>
      )}
    </React.Fragment>
  );
};
export default UniqueUser;

在上面的片段中,你会看到我们已经从useRouter 钩子中解构了查询对象,我们将用它来获取用户的唯一ID,并将其作为参数传递给API端点。

const {query} = useRouter()
const userId = query.id

一旦唯一的ID被附加到API端点,一旦页面被加载,为该用户准备的数据就会被呈现出来。

总结

如果你不完全了解你的应用程序的用例,Next.js中的数据获取会变得很复杂。

我希望这篇文章能帮助你了解如何使用Next.js的路由器API在你的应用程序中获取动态数据。