تطوير الويب

File-based routing مع TanStack Router: اصطلاحات وlayouts

5 دقائق للقراءة

السلسلة: هذا الدرس جزء من سلسلة TanStack. اقرأ المقال الرئيسي.

TanStack Router يقرأ محتوى src/routes لتوليد شجرة routes typée بلا كتابة إعداد يدوي. هذا النهج الإعلاني ينقل كل عمل التوجيه إلى شجرة الملفات: ملف يصير URL، مجلد يصير بادئة، بادئة خاصة تغيّر الدلالة. الملحق Vite يُعيد توليد routeTree.gen.ts عند كل حفظ.

المتطلبات

  • Node.js 22 LTS أدنى
  • مشروع Vite 6 أو 7 مع React 19
  • الحزم @tanstack/react-router، @tanstack/react-router-devtools، @tanstack/router-plugin
  • zod للتحقق من search params
  • TypeScript 5.4+، strict: true
npm install @tanstack/react-router @tanstack/react-router-devtools zod
npm install -D @tanstack/router-plugin

الخطوة 1 — توصيل الملحق في vite.config.ts

الملحق يجب تنفيذه قبل react()، لأنه يُولّد ملف الشجرة الذي يقرأه المُصرّف لاحقاً. عكس الترتيب يُنتج خطأ routeTree.gen.ts not found.

// vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { tanstackRouter } from '@tanstack/router-plugin/vite'

export default defineConfig({
  plugins: [
    tanstackRouter({
      target: 'react',
      autoCodeSplitting: true,
    }),
    react(),
  ],
})

الخطوة 2 — route الجذر __root.tsx

جذر الشجرة يُسمى إلزامياً __root.tsx (شرطتان سفليتان). يستخدم createRootRoute لا createFileRoute.

// src/routes/__root.tsx
import { createRootRoute, Outlet } from '@tanstack/react-router'
import { TanStackRouterDevtools } from '@tanstack/react-router-devtools'

export const Route = createRootRoute({
  component: () => (
    <>
      <header>
        <nav>Navigation globale</nav>
      </header>
      <main>
        <Outlet />
      </main>
      <TanStackRouterDevtools />
    </>
  ),
})

الخطوة 3 — route index وroute فرعي كلاسيكي

// src/routes/index.tsx
import { createFileRoute } from '@tanstack/react-router'

export const Route = createFileRoute('/')({
  component: HomePage,
})

function HomePage() {
  return <h1>Accueil</h1>
}

الخطوة 4 — معامل ديناميكي مع $

مقطع URL متغيّر يُحدَّد بالبادئة $ في اسم الملف. لمطابقة /posts/123، أنشئ src/routes/posts/$postId.tsx.

// src/routes/posts/$postId.tsx
import { createFileRoute, useParams } from '@tanstack/react-router'

export const Route = createFileRoute('/posts/$postId')({
  component: PostPage,
})

function PostPage() {
  const { postId } = useParams({ from: '/posts/$postId' })
  return <article>Article {postId}</article>
}

زر /posts/42: الصفحة تعرض Article 42. المعاملات ليست محوَّلة لأرقام؛ إن انتظرت معرّفاً عددياً، تحقّق منه في parseParams.

الخطوة 5 — Layout pathless مع _layout

ملف أو مجلد مسبوق بـ underscore هو layout pathless: يغلّف أبناءه دون إضافة مقطع URL.

// src/routes/_layout.tsx
import { createFileRoute, Outlet } from '@tanstack/react-router'

export const Route = createFileRoute('/_layout')({
  component: AppLayout,
})

function AppLayout() {
  return (
    <div className="shell">
      <aside>Sidebar partagee</aside>
      <section>
        <Outlet />
      </section>
    </div>
  )
}

أي route تحت src/routes/_layout/ يرث هذا الإطار. URL يبقى نظيفاً: /dashboard، لا /_layout/dashboard.

الخطوة 6 — حماية منطقة بـ admin/_authenticated

// src/routes/admin/_authenticated/route.tsx
import { createFileRoute, Outlet, redirect } from '@tanstack/react-router'

export const Route = createFileRoute('/admin/_authenticated')({
  beforeLoad: ({ location }) => {
    const token = localStorage.getItem('token')
    if (!token) {
      throw redirect({
        to: '/login',
        search: { redirect: location.href },
      })
    }
  },
  component: () => <Outlet />,
})

الـ hook beforeLoad يُنفَّذ قبل العرض. إن رفع redirect، المستخدم يذهب إلى /login مع الهدف محفوظاً.

الخطوة 7 — التقاط الباقي بـ $.tsx

// src/routes/$.tsx
import { createFileRoute, useParams } from '@tanstack/react-router'

export const Route = createFileRoute('/$')({
  component: NotFound,
})

function NotFound() {
  const { _splat } = useParams({ from: '/$' })
  return <p>Page introuvable : /{_splat}</p>
}

اكتب URL غير موجود مثل /foo/bar/baz: الصفحة تعرض Page introuvable : /foo/bar/baz. الـ splat يجب إعلانه في أعلى مستوى لمنطقته لئلا يحجب route أدقّ.

الخطوة 8 — التجميع بلا بادئة مع (أقواس)

الأقواس حول اسم مجلد تُنشئ مجموعة. المجلد يختفي من URL لكنه يجمع routes للتنظيم.

src/routes/
  (marketing)/
    pricing.tsx       // => /pricing
    contact.tsx       // => /contact
  (auth)/
    login.tsx         // => /login
    register.tsx      // => /register

المجموعات لا تُنشئ layout مشترك؛ للمشاركة، اجمع المجموعة مع layout pathless: (auth)/_layout/login.tsx.

الخطوة 9 — تنميط search params مع Zod

query strings مدخلات غير موثوقة. TanStack Router يفرض التحقق عند الإعلان عبر validateSearch. مع schema موضوع، useSearch يُرجع كائناً typé، وuseNavigate يرفض عند التصريف أي قيمة خارج schema.

// src/routes/search.tsx
import { createFileRoute, useNavigate, useSearch } from '@tanstack/react-router'
import { z } from 'zod'

const searchSchema = z.object({
  q: z.string().optional(),
  page: z.number().int().min(1).default(1),
})

export const Route = createFileRoute('/search')({
  validateSearch: searchSchema,
  component: SearchPage,
})

function SearchPage() {
  const { q, page } = useSearch({ from: '/search' })
  const navigate = useNavigate({ from: '/search' })

  return (
    <div>
      <input
        defaultValue={q ?? ''}
        onChange={(e) =>
          navigate({ search: (prev) => ({ ...prev, q: e.target.value }) })
        }
      />
      <p>Page {page}</p>
    </div>
  )
}

إن زار شخص /search?page=abc، Zod يرفض القيمة والـ router يطبّق افتراض 1.

اصطلاحات التسمية

اسم الملف السلوك
__root.tsx جذر الشجرة، يحوي Outlet العالمي وproviders
index.tsx route تماماً للمجلد الأب
about.tsx route ثابت /about
$postId.tsx معامل ديناميكي، متاح عبر useParams
$.tsx Splat catch-all، يلتقط الباقي في _splat
_layout.tsx Layout pathless، يغلّف بلا مقطع URL
route.tsx يمثّل route للمجلد الحاوي
(group)/ مجموعة تنظيم، لا تظهر في URL
posts.index.tsx تدوين مسطّح مكافئ لـ posts/index.tsx

أخطاء شائعة

العرَض السبب التصحيح
routeTree.gen.ts not found الملحق Vite بعد react() ضع tanstackRouter أولاً في plugins
useParams يُرجع undefined اسم ملف بلا $ أعد التسمية postId.tsx إلى $postId.tsx
Layout pathless مُتجاهَل ملف layout.tsx بدل _layout.tsx أضف underscore بادئة
Search param يبقى string بدل number لا validateSearch وصّل Zod مع z.number()
404 splat يحجب route حقيقي Splat في مجلد خاطئ انقل $.tsx إلى الجذر
Devtools غير مرئية build إنتاج أطلق npm run dev

أسئلة شائعة

هل ينبغي إصدار routeTree.gen.ts؟ artifact يُعاد توليده عند كل build. الممارسة الشائعة إضافته إلى .gitignore وتوليده في CI.

كيف نحصل على URL الحالي؟ استخدم useLocation. لرابط typé، فضّل مكوّن Link مع prop to الذي يقترح autocompletion.

هل يمكن خلط file-based وcode-based؟ نعم. routeTree.gen.ts كائن قابل للمعالجة. تستطيع حقن routes إضافية عبر API code-based للحالات الديناميكية.

الفرق بين _layout.tsx و(group)؟ Layout pathless يضيف مكوّن يغلّف. المجموعة حيلة تنظيم ملفات، بلا أثر على URL ولا على العرض.

كيف نمرّر بيانات من loader إلى المكوّن؟ أعلن loader في route، ثم استدع Route.useLoaderData() في المكوّن. التنميط يُستدلّ آلياً.

مقالات ذات صلة

Sponsoriser ce contenu

Cet emplacement est à vous

Position premium en fin d'article — c'est l'instant où les lecteurs sont le plus engagés. Réservez cet espace pour votre marque, votre formation ou votre offre.

Recevoir nos tarifs
Publicité