预期想在next.js中根据登录情况选择性在header中显示头像或登录按钮,但是往往并不能在组件中获取localstorage的值。若尝试这么做,将会报错:nextjs ReferenceError: localStorage is not defined

会发生这个问题,是因为next.js默认使用ssr。程序运行在服务器端,自然不存在window对象

为解决这一问题,我尝试使用如下方法解决:

  1. 通过context建立authprovider,使得登录情况可以在全局获取
  2. 通过hooks建立useauth,用usestate和usememo保存登录情况
  3. 通过swr以发送请求的方式请求localstorage的数据

显然,2方法会报错;1方法理论上可行,但实际操作时没有表现出很好的效果

最后使用了swr封装了一层请求localstorage的函数,代码如下:

import useSWR from 'swr';

// 需要判断代码是否运行在浏览器端
const isSSR = typeof window ==='undefined'

const useStorage = (key: string, defaultValue?: any) => {
    if(isSSR)   return;
    const {data} = useSWR(key, async(key)=>{
        const value = localStorage.getItem(key)||defaultValue;
        return value;
    });
    const getToken = () => {
        return data;
    };
    const setToken = (newVal: string) => useSWR([key, newVal], async([key, newVal])=>{
        localStorage.setItem(key, newVal)
    })
    const delToken = () => useSWR(key, async(key)=>{
        localStorage.removeItem(key)
    });
    return {
        getToken,
        setToken,
        delToken
    }
}

export default useStorage;

在组件中使用方式如下:

// src/components/header
import useStorage from "@/hooks/useStorage";

export default function Head() {
  const authStorage = useStorage(tokenName);
  if(!Boolean(authStorage?.getToken())){
    Router.push('/login')
    return <></>
  }
  return (
    <section>
        {
            Boolean(authStorage?.getToken()) ?
                <Avatar /> : <Link href="login">登录</Link>
        }
    </section>
  )
}

需要注意的是,如果涉及到请求相关的函数(如自行创建请求示例来拦截每次请求,并尝试将token添加到auth头时),则不应该使用上文提到的authstorage,直接localstorage获取即可。

// src/service/request.ts
instance.interceptors.request.use(
  (config: any) => {
    const token = localStorage.getToken(token_name);
    if (token) {
      config.headers.Authorization = token;
    }
    return config
  },
  function (error) {
    return Promise.reject(error)
  }
)