在clerk提供的user dropdown中添加studio选项及跳转
- 空标签表示不渲染任何DOM元素,在返回多个元素时,不用必须被div包裹,不影响布局、样式
javascript
复制代码
// src/modules/auth/ui/components/auth-button.tsx
"use client";
...
...
export const AuthButton = () => {
return (
<>
<SignedIn>
<UserButton>
<UserButton.MenuItems>
<UserButton.Link
label="Studio"
href="/studio"
labelIcon={<ClapperboardIcon className="size-4" />}
/>
</UserButton.MenuItems>
</UserButton>
</SignedIn>
...
</>
)
}
<Component>内容</Component>
- 像这样将内容写在组件中间,这个写法在React中默认会将 "内容" 传给组件的
props.children
,不需要手动传;"内容" 不管是什么,都会作为一个叫作 children
的 prop
传过去
xml
复制代码
<StudioLayout>内容</StudioLayout>
- 以下2种写法是一个意思,把
abc
的值作为 children
传进去了
xml
复制代码
<StudioLayout>{abc}</StudioLayout>
<StudioLayout children={abc} />
ini
复制代码
<StudioLayout abc={abc} />
studio 模块的文件结构及 children
的传递
- 之前有提到过,我们当前项目的
src/app
路径下只放路由相关的文件,其他页面文件或者组件都放在 src/modules
文件夹下,关于页面的layout部分也是放在 modules
下的
ruby
复制代码
src/
app/
studio/
layout.tsx // 在这个文件中导入 @/modules/studio/ui/layouts/studio-layout
typescript
复制代码
// src/app/(studio)/layout.tsx
import { StudioLayout } from "@/modules/studio/ui/layouts/studio-layout";
interface LayoutProps {
children: React.ReactNode
};
const Layout = ({children}:LayoutProps) => {
return (
<StudioLayout>{children}</StudioLayout>
);
}
export default Layout;
scss
复制代码
src/
modules/
studio/
ui/
layouts/
studio-layout.tsx // 在这个文件真正实现studio页面的layout
typescript
复制代码
// src/modules/studio/ui/layouts/studio-layout.tsx
interface StudioLayoutProps {
children: React.ReactNode;
}
export const StudioLayout = ({ children }: StudioLayoutProps) => {
return (
<div>
{children}
</div>
);
}
默认导出和命名导出
- 默认导出
- 可直接导入
import Page from ...
,也可以重命名导入 import StudioPage from ...
- 一个文件只能有 一个
export default
- 在 Next.js 的 app 路由 里,page.tsx 必须用 默认导出,不然识别不了页面入口,只会被当做一个普通的导出组件
javascript
复制代码
// src/app/(studio)/studio/page.tsx
const Page = async () => {
return (
<div>studio page</div>
)
}
export default Page;
- 命名导出
- 必须用花括号导出,且命名保持一致
import { StudioLayout } from ...
- 重命名需要用
as
修改import { PageLayout as StudioLayout } from ...
- 一个文件可以有多个
export const ...
javascript
复制代码
// src/modules/studio/ui/layouts/studio-layout.tsx
export const StudioLayout = ({ children }: StudioLayoutProps) => {
return (
...
)
}
Tips
- usePathname()
- 获取当前页面的路径,不返回查询参数;只能在 客户端组件
"use client"
里使用
- 例如路径
http://localhost:3000/studio?a=1&b=2
,会返回 /studio
- 查询参数使用
useSearchParams()
获取
javascript
复制代码
import { usePathname } from "next/navigation"
const pathname = usePathname()
- useUser()
- Clerk 提供的一个 React Hook,用来在客户端组件中获取当前登录用户的信息和状态
- 服务端组件中想要获取用户信息可以使用
useAuth()
,tRPC路由中使用 auth()
获取用户
- 常见返回值如下:
javascript
复制代码
import { useUser } from '@clerk/nextjs'
// 用户是否登录,用户信息是否加载完成,用户信息
const { isSignedIn, isLoaded, user } = useUser()
// 用户信息
const { id, imageUrl, fullName } = user
- useSidebar()
javascript
复制代码
import { useSidebar } from "@/components/ui/sidebar"
export function AppSidebar() {
const {
state, // 侧边栏折叠或展开
open,
setOpen,
openMobile,
setOpenMobile,
isMobile,
toggleSidebar,
} = useSidebar()
}
class-variance-authority (cva)
cva('', {...})
创建一个样式生成器函数(注意:avatarVariants
是一个函数) ,我们这里只有一个variant就是size
,defaultVariants
就是不传size
时使用的默认值
VariantProps<typeof avatarVariants>
会根据你在 cva()
里定义的 variants
,自动生成一个 TypeScript 类型,我们这里生成的就是 { size?: "default" | "xs" | "sm" | "lg" | "xl"; }
- 使用
extends
相当于我给 user-avatar
这个组件自定义了size属性,但这个属性的类型只能是 "default" | "xs" | "sm" | "lg" | "xl"
php
复制代码
// src/components/user-avatar.tsx
import { cva, type VariantProps } from 'class-variance-authority';
const avatarVariants = cva('', {
variants: {
size: {
default: 'h-9 w-9',
xs: 'h-4 w-4',
sm: 'h-6 w-6',
lg: 'h-10 w-10',
xl: 'h-[160px] w-[160px]',
}
},
defaultVariants: {
size: 'default',
}
})
interface UseAvatarProps extends VariantProps<typeof avatarVariants> {
imageUrl: string;
name: string;
className?: string;
onClick?: () => void;
}
- 我们在声明
UserAvatar
组件时,直接从解构中获取了 size
属性
- 前面说了
avatarVariants
是一个函数,那么avatarVariants({size}
会根据size
的传值返回对应的字符串,然后再通过 cn()
把多个class合并成一个
javascript
复制代码
// src/components/user-avatar.tsx
export const UserAvatar = ({
imageUrl,
name,
size,
className,
onClick
}: UseAvatarProps) => {
<Avatar className={cn(avatarVariants({size}), className)} onClick={onClick}>
<AvatarImage src={imageUrl} alt={name}></AvatarImage>
</Avatar>
}
- 如果使用组件时传的值比如
<UserAvatar size='big' />
,那么就会报错
ini
复制代码
// src/modules/studio/ui/components/studio-sidebar/studio-sidebar-header.tsx
<UserAvatar
imageUrl={user.imageUrl}
name={user.fullName ?? 'User'}
size="lg"
className="size-[112px] hover:opacity-80 transition-opacity"
/>