Tube - Video Categories

(此篇文章的流程,就是在当前项目中创建一个功能模块的基本全流程,建表→创建tRPC路由→创建组件并使用API路由)
  • Create categories schema
    • 添加表结构 categories => src/db/schema.ts
    • 根据定义的schema同步数据库,执行 bunx drizzle-kit push
    • 查看Neon Console或运行 bunx drizzle-kit studio 应该能看到数据库新增了一张 categories
    • seeding脚本预设categories数据 => src/scripts/seed-categories.ts
    • 执行脚本,将预设的数据写入数据库 => bun src/scripts/seed-categories.ts
  • Procedures
    • 创建 categories tRPC 路由 => src/modules/categories/server/procedures.ts,获取全部数据且无需参数
    import { db } from "@/db";
    import { categories } from "@/db/schema";
    import { baseProcedure, createTRPCRouter } from "@/trpc/init";
    
    export const categoriesRouter = createTRPCRouter({
      getAll: baseProcedure.query(async () => {
        const data = await db
          .select()
          .from(categories)
        return data;
      })
    })
    
    • 将 categories 路由添加进总路由 => src/trpc/routers/_app.ts
    import { createTRPCRouter } from '../init';
    
    import { categoriesRouter } from '@/modules/categories/server/procedures';
    
    // tRPC总路由
    export const appRouter = createTRPCRouter({
      categories: categoriesRouter
    });
    
    export type AppRouter = typeof appRouter;
    
  • 创建 Categories 组件,并使用API路由
    • Next.js 在 App Router 下会默认尽量静态化页面,当数据不会频繁发生变化时,每一次进页面都尽量使用缓存数据
    • 设置动态渲染:export const dynamic = 'force-dynamic';,页面不能使用静态优化(Static Rendering),必须每次请求时都动态渲染,每次运行组件都会prefetch一次数据
    • searchParams 是当前url的查询参数(Query Params)的固定写法,从url中自动解析的一个异步参数
    • prefetch() 的数据会通过 <HydrateClient> 传递,在客户端渲染时用 useSuspenseQuery() 获取缓存中的数据
// src/app/(home)/page.tsx

import { HydrateClient, trpc } from '@/trpc/server';
...
export const dynamic = 'force-dynamic'; 

interface PageProps {
  searchParams: Promise<{
    categoryId?: string;
  }>
}

const Page = async ({ searchParams }: PageProps) => {
  const { categoryId } = await searchParams;

  void trpc.categories.getAll.prefetch(); 

  return (
    <div>
      <HydrateClient>
        ...
      </HydrateClient>
    </div>
  );
}

export default Page
// src/modules/categories/ui/sections/categories-section.tsx

'use client'

import { trpc } from '@/trpc/client'

import { Suspense } from 'react';
import { ErrorBoundary } from 'react-error-boundary';

interface CategoriesSectionProps {
  categoryId?: string;
}

export const CategoriesSection = ({ categoryId }: CategoriesSectionProps) => {
  const [categories] = trpc.categories.getAll.useSuspenseQuery()

  return (
    // fallback 是加载时的占位符,类似loading,当前选择用骨架屏占位
    // ErrorBoundary 是错误边界,捕获子组件的错误,防止整个应用崩溃
    <Suspense fallback={<CategoriesSkeleton />}>
      <ErrorBoundary fallback={<p>Error...</p>}>
        {JSON.stringify(categories)}
      </ErrorBoundary>
    </Suspense>
  )
}
  • Carousel 组件
    • CarouselApi 是能够控制整个组件的一个控制对象,包含很多方法和事件
    • 通过 useEffect() 监听 apiapi初始化完成后执行
export const FilterCarousel = ({
  ...
}: FilterCarouselProps) => {
  const [api, setApi] = useState<CarouselApi>(); 
  const [current, setCurrent] = useState(0); // 轮播图当前索引
  const [count, setCount] = useState(0); // 轮播图总数

  // 依赖数组为监听项,监听api变化,若依赖数组为空,相当于mounted()
  useEffect(() => {
    if(!api) return;

    setCount(api.scrollSnapList().length);
    setCurrent(api.selectedScrollSnap() + 1);

    api.on('select', () => {
      setCurrent(api.selectedScrollSnap() + 1);
    })
  }, [api]);

  return (
    <Carousel
      setApi={setApi} // 把 setApi 传进去,这样 Carousel 内部会在初始化时把它的 API 对象传出来,赋值给上面的 api
      opts={{
        align: 'start',
        dragFree: true,
      }}
    >
      ...
      ...
    </Carosel>
  )
})
  • Tips
    1. react与vue组件通信的核心差异
      • react中没有$emit + 事件监听的机制,react更强调单项数据流
      • react中通常父组件把一个事件处理函数作为prop传给子组件,子组件调用这个函数向父组件“上报数据”
    2. flex-basis
      • 用于设置 子项(flex item)的“主轴方向上的初始尺寸
      • 默认主轴是水平方向,那 flex-basis 表示元素宽度;如果 flex-direction: column,那么flex-basis 表示元素高度
      • 默认值 flex-basis: auto,根据主轴方向决定使用width还是height,如果都没有则由内容撑开
      • flex-basis: 200px 时,只是设置了一个初始尺寸,最终大小还是会受缩小flex-shrink、放大flex-grow的影响
      • 与之对应的widthheight属性是固定尺寸,不会受flex伸缩规则影响
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容