Tube - Studio Layout

  • 空标签表示不渲染任何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} />
  • 这才是把 abc 作为命名 prop 传进去
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
  1. usePathname()
  • 获取当前页面的路径,不返回查询参数;只能在 客户端组件"use client"里使用
  • 例如路径 http://localhost:3000/studio?a=1&b=2,会返回 /studio
  • 查询参数使用 useSearchParams() 获取
javascript 复制代码
import { usePathname } from "next/navigation"
const pathname = usePathname()
  1. useUser()
  • Clerk 提供的一个 React Hook,用来在客户端组件中获取当前登录用户的信息和状态
  • 服务端组件中想要获取用户信息可以使用 useAuth(),tRPC路由中使用 auth() 获取用户
  • 常见返回值如下:
javascript 复制代码
import { useUser } from '@clerk/nextjs'

// 用户是否登录,用户信息是否加载完成,用户信息
const { isSignedIn, isLoaded, user } = useUser()

// 用户信息
const { id, imageUrl, fullName } = user
  1. 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就是sizedefaultVariants就是不传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"
/>
相关推荐
xianyinsuifeng2 分钟前
概念篇:ReactJS + AppSync + DynamoDB 性能优化核心概念
前端·react.js·性能优化·aws
OEC小胖胖6 分钟前
SEO 优化:元数据 (Metadata) API 和站点地图 (Sitemap) 生成
前端·javascript·前端框架·html·web·next.js
YuspTLstar1 小时前
Redux从入门到进阶
前端·react.js
前端小小鱼2 小时前
React学习(记录贴)
react.js
鹏多多2 小时前
React跨组件数据共享useContext详解和案例
前端·javascript·react.js
江城开朗的豌豆3 小时前
React生命周期:从诞生到更新的完整旅程
前端·javascript·react.js
江城开朗的豌豆3 小时前
Redux vs Context+Hooks:前端状态管理的双雄对决
前端·javascript·react.js
知识分享小能手11 小时前
React学习教程,从入门到精通,React Router 语法知识点及使用方法详解(28)
前端·javascript·学习·react.js·前端框架·vue·react
黄毛火烧雪下11 小时前
React中Class 组件 vs Hooks 对照
前端·javascript·react.js
前端达人12 小时前
「React实战面试题」useEffect依赖数组的常见陷阱
前端·javascript·react.js·前端框架·ecmascript