Tube - Studio Layout

  • 在clerk提供的user dropdown中添加studio选项及跳转
    • 空标签表示不渲染任何DOM元素,在返回多个元素时,不用必须被div包裹,不影响布局、样式
// 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,不需要手动传;“内容” 不管是什么,都会作为一个叫作 childrenprop 传过去
    <StudioLayout>内容</StudioLayout>
    
    • 以下2种写法是一个意思,把 abc 的值作为 children 传进去了
    <StudioLayout>{abc}</StudioLayout>
    <StudioLayout children={abc} />
    
    • 这才是把 abc 作为命名 prop 传进去
    <StudioLayout abc={abc} />
    
  • studio 模块的文件结构及 children 的传递
    • 之前有提到过,我们当前项目的 src/app 路径下只放路由相关的文件,其他页面文件或者组件都放在 src/modules 文件夹下,关于页面的layout部分也是放在 modules 下的
    src/
      app/
        studio/
          layout.tsx   // 在这个文件中导入 @/modules/studio/ui/layouts/studio-layout
    
    // 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;
    
    src/
      modules/  
        studio/
          ui/
            layouts/
              studio-layout.tsx  // 在这个文件真正实现studio页面的layout
    
    // 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 必须用 默认导出,不然识别不了页面入口,只会被当做一个普通的导出组件
    // 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 ...
    // src/modules/studio/ui/layouts/studio-layout.tsx
    
    export const StudioLayout = ({ children }: StudioLayoutProps) => {
      return (
        ...
      )
    }
    
  • Tips
    1. usePathname()
    • 获取当前页面的路径,不返回查询参数;只能在 客户端组件"use client"里使用
    • 例如路径 http://localhost:3000/studio?a=1&b=2,会返回 /studio
    • 查询参数使用 useSearchParams() 获取
    import { usePathname } from "next/navigation"
    const pathname = usePathname()
    
    1. useUser()
    • Clerk 提供的一个 React Hook,用来在客户端组件中获取当前登录用户的信息和状态
    • 服务端组件中想要获取用户信息可以使用 useAuth(),tRPC路由中使用 auth() 获取用户
    • 常见返回值如下:
    import { useUser } from '@clerk/nextjs'
    
    // 用户是否登录,用户信息是否加载完成,用户信息
    const { isSignedIn, isLoaded, user } = useUser()
    
    // 用户信息
    const { id, imageUrl, fullName } = user
    
    1. useSidebar()
    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就是sizedefaultVariants就是不传size时使用的默认值
    • VariantProps<typeof avatarVariants> 会根据你在 cva() 里定义的 variants,自动生成一个 TypeScript 类型,我们这里生成的就是 { size?: "default" | "xs" | "sm" | "lg" | "xl"; }
    • 使用extends相当于我给 user-avatar 这个组件自定义了size属性,但这个属性的类型只能是 "default" | "xs" | "sm" | "lg" | "xl"
    // 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合并成一个
    // 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' /> ,那么就会报错
    // 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"
    />
    
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容