假设我在localStorage
(客户端)中存储了一个用户的帐户信息。我需要Next.JS应用程序根据localStorage
(登录或注销按钮)中存储的内容来呈现网页的导航栏。如何首先从客户端获取值,然后然后呈现页面?也许这甚至不是Next.JS的本意?
答案 0 :(得分:1)
您可以执行以下操作:
componentDidMount
从localStorage
加载数据这是一个反应性问题,而不是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? 所述,useEffect
是 componentDidMount
的钩子类比。
检查 typeof localStorage === 'undefined'
导致警告
React 不喜欢这样,并警告如下:
<块引用>预期的服务器 HTML 包含匹配的“
因为它注意到了水合页面和非水合页面之间的区别:React 16: Warning: Expected server HTML to contain a matching <div> in <body>
在 Next.js 10.2.2 上测试。