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"
/>
相关推荐
再学一点就睡8 分钟前
初探 React Router:为手写路由筑牢基础
前端·react.js
潘小安4 小时前
『译』React useEffect:早知道这些调试技巧就好了
前端·react.js·面试
@大迁世界4 小时前
告别 React 中丑陋的导入路径,借助 Vite 的魔法
前端·javascript·react.js·前端框架·ecmascript
薛定谔的算法6 小时前
面试官问hooks函数,如何高效准确的回答?
前端·react.js·面试
weixin_423391938 小时前
详解Vue2、Vue3与React的Diff算法
前端·javascript·react.js
ZZHow10248 小时前
React前端开发_Day10
前端·笔记·react.js·前端框架·web
小Lu的开源日常10 小时前
Mathcheap v0.9.x 发布的第一个月,从想法到 MVP(最小可行性产品)
前端·图像识别·next.js
水冗水孚10 小时前
一文理解React和Vue中封装右键菜单的操作步骤思路——附在线预览地址、github完整源码
vue.js·react.js·github