You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

传递currentUser至UserMenu组件时的TypeScript类型错误求助

TypeScript类型不匹配问题:currentUser无法传递给UserMenu组件

报错信息

Type '{ createdAt: string; updatedAt: string; emailVerified: string | null; id: string; name: string | null; email: string | null; image: string | null; hashedPassword: string | null; role: Role; } | null | undefined' is not assignable to type 'SafeUser | null'.
Type 'undefined' is not assignable to type 'SafeUser | null'.ts(2322)
UserMenu.tsx(13, 5): 预期类型来自属性'currentUser',该属性在类型'IntrinsicAttributes & UserMenuProps'上声明

问题核心

getCurrentUser返回的currentUser被TypeScript推断为SafeUser | null | undefined类型,但UserMenu组件的currentUser属性仅接受SafeUser | null,不允许undefined。尽管getCurrentUser的返回类型声明为Promise<SafeUser | null>,但在异步组件场景下,TypeScript会额外推断出undefined的可能性。

解决方案

方案1:传递时将undefined转为null

在MainComponent中使用空值合并运算符,把undefined转为null,匹配组件要求的类型:

// MainComponent.tsx
<UserMenu currentUser={currentUser ?? null} />

方案2:显式标注currentUser类型

给currentUser指定明确类型,消除TypeScript的额外推断:

// MainComponent.tsx
const currentUser: SafeUser | null = await getCurrentUser();

// 正常传递即可
<UserMenu currentUser={currentUser} />

方案3:调整UserMenuProps兼容undefined

如果业务逻辑允许,修改组件属性类型,直接兼容undefined:

// UserMenu.tsx
interface UserMenuProps {
  currentUser: SafeUser | null | undefined;
}

组件内部的currentUser ? ... : ...逻辑会自动处理undefined(和null逻辑一致),无需额外修改。

验证要点

  • 确保getCurrentUser的所有分支都返回SafeUser或null(当前代码已满足)
  • 组件接收的currentUser类型与传递的类型完全匹配

相关代码参考

MainComponent.tsx(方案1实现)

const MainComponent = async () => {
  const currentUser = await getCurrentUser();

  return (
    <div className="sticky top-0 w-full bg-slate-200 z-30 shadow-sm">
      <div className="py-4 border-b-[1px]">
        <Container>
          <div className="flex items-center justify-between gap-3 md-gap-0">
            <Link href="/" className={`${redressed.className} font-bold text-2xl`}>E-Shop</Link>
            <div className="hidden md:block">搜索</div>
            <div className="flex items-center gap-8 md:gap-12">
              <CartCount />
              <UserMenu currentUser={currentUser ?? null} />
            </div>
          </div>
        </Container>
      </div>
    </div>
  );
};

export default MainComponent;

UserMenu.tsx(方案3实现)

import React, { useState, useCallback } from 'react';
import Link from 'next/link';
import { AiFillCaretDown } from 'react-icons/ai';
import Avatar from './Avatar';
import MenuItem from './MenuItem';
import BackDrop from './BackDrop';
import Container from './Container';
import CartCount from './CartCount';
import { signOut } from 'next-auth/react';
import { redressed } from '@/styles/fonts';

interface UserMenuProps {
  currentUser: SafeUser | null | undefined;
}

const UserMenu: React.FC<UserMenuProps> = ({ currentUser }) => {
  const [isOpen, setIsOpen] = useState(false);

  const toggleOpen = useCallback(() => {
    setIsOpen((prev) => !prev);
  }, []);

  return (
    <>
      <div className="relative z-30">
        <div
          onClick={toggleOpen}
          className="p-2 border-[1px] border-slate-400 flex flex-row items-center gap-1 rounded-full cursor-pointer hover:shadow-md transition text-slate-700"
        >
          <Avatar />
          <AiFillCaretDown />
        </div>
        {isOpen && (
          <div className="absolute rounded-md shadow-md w-[170px] bg-white overflow-hidden right-0 top-12 text-sm flex flex-col cursor-pointer">
            {currentUser ? (
              <div>
                <Link href="/orders">
                  <MenuItem onClick={toggleOpen}>你的订单</MenuItem>
                </Link>
                <Link href="/admin">
                  <MenuItem onClick={toggleOpen}>管理员面板</MenuItem>
                </Link>
                <hr />
                <MenuItem
                  onClick={() => {
                    toggleOpen();
                    signOut();
                  }}
                >
                  退出登录
                </MenuItem>
              </div>
            ) : (
              <div>
                <Link href="/login">
                  <MenuItem onClick={toggleOpen}>登录</MenuItem>
                </Link>
                <Link href="/register">
                  <MenuItem onClick={toggleOpen}>注册</MenuItem>
                </Link>
              </div>
            )}
          </div>
        )}
      </div>
      {isOpen && <BackDrop onClick={toggleOpen} />}
    </>
  );
};

export default UserMenu;

getCurrentUser.ts

import { authOptions } from "@/pages/api/auth/[...nextauth]";
import { getServerSession } from "next-auth";
import prisma from "@/libs/prismadb";

export async function getSession() {
  return await getServerSession(authOptions);
}

export async function getCurrentUser(): Promise<SafeUser | null> {
  try {
    const session = await getSession();

    if (!session?.user?.email) {
      return null;
    }

    const currentUser = await prisma.user.findUnique({
      where: {
        email: session.user.email,
      },
    });

    if (!currentUser) {
      return null;
    }

    return {
      ...currentUser,
      createdAt: currentUser.createdAt.toISOString(),
      updatedAt: currentUser.updatedAt.toISOString(),
      emailVerified: currentUser.emailVerified?.toISOString() || null,
    };
  } catch (error: any) {
    console.log(error);
    return null;
  }
}

SafeUser类型定义

import { User } from "@prisma/client";

export type SafeUser = Omit<User, "createdAt" | "updatedAt" | "emailVerified"> & {
  createdAt: string;
  updatedAt: string;
  emailVerified: string | null;
};

内容的提问来源于stack exchange,提问作者Viteazu Mihai

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 22:03:10