Next.JS-在渲染页面之前访问`localStorage`

时间:2019-02-22 03:40:31

标签: node.js reactjs next.js

假设我在localStorage(客户端)中存储了一个用户的帐户信息。我需要Next.JS应用程序根据localStorage(登录或注销按钮)中存储的内容来呈现网页的导航栏。如何首先从客户端获取值,然后然后呈现页面?也许这甚至不是Next.JS的本意?

3 个答案:

答案 0 :(得分:1)

您可以执行以下操作:

  1. 在状态下使用变量以防止呈现页面
  2. 使用componentDidMountlocalStorage加载数据
  3. 加载数据后,请设置状态以允许呈现组件。

这是一个反应性问题,而不是next.js问题。 您可以将Conditional rendering用于步骤1。 还有read up on state here,最后还有componentDidMount

答案 1 :(得分:0)

本地存储在服务器上不可用,有两个选项可以解决这个问题 1:创建 HOC 或自定义钩子来检查本地存储是否有数据(这是正常的反应方式) 2:您可以使用cookies在客户端和服务器端存储数据,然后可以使用getServerSideProps提取数据,然后您可以使用这些数据在初始渲染时相应地显示信息。

答案 2 :(得分:0)

https://stackoverflow.com/a/54819843/895245 所述,我无法在第一次渲染之前真正获得 localStorage,在此之前只显示后备页面。我不确定这是 JavaScript/浏览器的基本限制,还是 Next.js 的限制。

但是,当我测试时,使用 localStorage 的正确渲染很快就会显示中间渲染根本不明显,我不确定它是否会发生。

我想做的是对那个答案中提到的内容给出一个稍微具体一点的想法。

SWR 示例

这是一个完整的可运行示例,其中导航栏显示登录状态:https://github.com/cirosantilli/node-express-sequelize-nextjs-realworld-example-app 该存储库是 this one 的一个分支,两者都是令人敬畏的 realworld project 的 Next.js 实现。

在这种情况下,回退只是博客页面的退出视图,其中已经包含用户可能想要查看的关键信息,并且可以缓存,例如与 ISR。

该演示使用 SWR 使代码稍微简单一些。关键部分是:

代码的关键部分有:

导航栏:

import useSWR from "swr";

const Navbar = () => {
  const { data: currentUser } = useSWR("user", key => {
    const value = localStorage.getItem(key);
    return !!value ? JSON.parse(value) : undefined;
  });

登录:

import { mutate } from "swr";

const LoginForm = () => {
  const handleSubmit = async (e) => {
    // Get `user` data structure from API.
    mutate("user", data?.user);

我们看到,当用户登录时,我们在 mutate 全局标识符上调用 "user"

这会重绘包含该钩子的所有组件,其中包括导航栏,因为它使用 useSWR 调用设置钩子。

这样,login先重绘导航栏,然后重定向到首页,这样首页就会立即有重绘的导航栏。没有 mutate,只有页面主体会重绘,导航栏不会。

使用此设置:

  • 如果您在 console.log(currentUser) 正下方放置一个 useSWR,您会看到它被调用了两次。

    所以发生的情况是它首先立即返回一个缓存值 (undefined) 并开始第一次渲染。

    然后它开始对缓存的异步调用,当它返回时,钩子触发组件的重新渲染,并使用当前用户值再次打印。

    这仅在刷新/第一次点击期间的初始水合时发生。在内部页面更改期间,只有一个渲染。

    所有这一切发生得如此之快,以至于我根本无法在没有本地存储的情况下看到页面绘制,即使使用 Chromium 调试器时间线帧检查也无法看到。

  • 如果我们向 localStorage getter 添加 2 秒延迟:

    const { data: currentUser } = useSWR("user", async (key) => {
      await new Promise(resolve => setTimeout(resolve, 2000))
      const value = localStorage.getItem(key);
      return !!value ? JSON.parse(value) : undefined;
    });
    

    我们确实观察到用户注销时的中间页面状态,因此理论上可能会发生这种情况。

没有 SWR 的情况

当然,我们不需要使用 SWR 来实现这一点。

SWR 文档为我们提供了在没有 SWR https://swr.vercel.app/getting-started 来激励他们的图书馆的情况下会是什么样子的基本原理。

您需要将状态向上移动到登录表单 + 导航栏的公共父级,或者您可以使用 Context

function Page () {
  const [user, setUser] = useState(null)

  // fetch data
  useEffect(() => {
    const value = localStorage.getItem(key);
    const user = !!value ? JSON.parse(value) : undefined;
    setUser(user)
  }, [])

  // global loading state
  if (!user) return <Spinner/>

  return <div>
    <Navbar user={user} />
    <Content user={user} />
  </div>
}

// child components

function Navbar ({ user }) {
  return <div>
    ...
    <Avatar user={user} />
  </div>
}

function Content ({ user }) {
  return <h1>Welcome back, {user.name}</h1>
}

function Avatar ({ user }) {
  return <img src={user.avatar} alt={user.name} />
}

What is difference between lifecycle method and useEffect hook? 所述,useEffectcomponentDidMount 的钩子类比。

检查 typeof localStorage === 'undefined' 导致警告

React 不喜欢这样,并警告如下:

<块引用>

预期的服务器 HTML 包含匹配的“

因为它注意到了水合页面和非水合页面之间的区别:React 16: Warning: Expected server HTML to contain a matching <div> in <body>

在 Next.js 10.2.2 上测试。