TanStack Router Type-Safe Search Params باستخدام
٤ يونيو ٢٠٢٦
في TanStack Router، تعتبر معاملات البحث في URL (search params) حالة مُتحقق منها وليست مجرد نصوص عشوائية. تقوم بربط مخطط Zod بمسار معين باستخدام validateSearch، وتصبح كل عملية قراءة عبر useSearch() مكتوبة بالكامل (fully typed)، ومُحللة، ولها قيم افتراضية. يبني هذا الدليل مساراً قابلاً للتشغيل لقائمة منتجات — يتضمن الترقيم، الترتيب، فلاتر الوسوم، وتبديل حالة inStock — مع معالجة مرنة للمدخلات غير الصالحة، وبرمجيات وسيطة للبحث تحافظ على نظافة URL، وإجابة واضحة على سؤال يربك الكثيرين في عام 2026: هل يعمل محول Zod مع إصدار Zod 4؟ (الإجابة هي لا — والحل يتكون من سطر واحد). كل شيء هنا يتم التحقق من نوعه (type-checks) مقابل @tanstack/React-router@1.170.11 في تاريخ الكتابة.
ما ستتعلمه
- كيفية التحقق من صحة معاملات البحث في URL في TanStack Router باستخدام مخطط Zod عبر
validateSearchوzodValidator. - كيفية جعل المعاملات غير الصالحة أو المفقودة تتراجع بأمان باستخدام
fallback()مدمجاً مع.default(). - لماذا يرفض
@tanstack/zod-adapter@1.167.0التثبيت مع Zod 4، والطريقتان النظيفتان لإصلاح ذلك. - كيفية قراءة المعاملات المكتوبة بالكامل في مكون باستخدام
Route.useSearch()وخطافuseSearch({ from, select })المستقل. - كيفية الاحتفاظ ببعض المعاملات وإسقاط البعض الآخر عبر التنقل باستخدام البرمجيات الوسيطة للبحث
retainSearchParamsوstripSearchParams. - كيفية تحديث المعاملات بطريقة آمنة النوع (type-safely) باستخدام المحدثات الوظيفية لـ
Linkوnavigate()، وتغذيتها فيloaderالمسار.
المتطلبات الأساسية
- Node.js 22.12+ أو 24 LTS. أسقط Vite 8 دعم Node 18 ويتطلب Node 20.19+ / 22.12+1. إصدار Node 24 هو خط LTS النشط الحالي2.
- تطبيق Vite + React 19 (
React@19.2.7،React-dom@19.2.7). - TypeScript في الوضع الصارم (strict mode) مع
moduleResolution: "bundler"(أوnodenext). توصي وثائق TanStack Router بأكثر إعدادات TypeScript صرامة للحصول على تجربة أمان نوع كاملة3. - الإلمام بـ React ومخططات Zod الأساسية. لا يشترط خبرة سابقة بـ TanStack Router.
يستخدم هذا البرنامج التعليمي التوجيه القائم على الملفات (الإعداد الموصى به)، ولكن كل استدعاء لـ API هو نفسه تحت التوجيه القائم على الكود إذا كنت تفضل ذلك.
الخطوة 1 — تثبيت الحزم
قم بتثبيت إصدارات محددة ليتطابق بناؤك مع هذا الدليل. سطرا التثبيت أدناه متعمدان — اختيار Zod يعتمد على مسار التحقق الذي ستختاره، والموضح في الخطوة 4.
# الراوتر + إضافة Vite للتوجيه القائم على الملفات
npm install --save-exact @tanstack/React-router@1.170.11
npm install --save-exact -D @tanstack/router-plugin@1.168.14
# التحقق: Zod 3 + المحول الرسمي (الخطوة 4 تشرح بديل Zod 4)
npm install --save-exact zod@3.25.76 @tanstack/zod-adapter@1.167.0
النتيجة المتوقعة — يظهر npm ls الإصدارات المحددة دون تحذيرات من تبعيات النظراء (peer-dependency):
├── @tanstack/React-router@1.170.11
├── @tanstack/zod-adapter@1.167.0
├── zod@3.25.76
└── @tanstack/router-plugin@1.168.14
إذا قمت بدلاً من ذلك بتشغيل npm install zod@4، فستواجه خطأ ERESOLVE. هذا أمر متوقع وليس خطأ من جانبك — توضح الخطوة 4 بالضبط لماذا وكيف تختار مسارك.
الخطوة 2 — ربط إضافة Vite للتوجيه القائم على الملفات
يقوم التوجيه القائم على الملفات بإنشاء شجرة مسارات مكتوبة من دليل src/routes الخاص بك. أضف الإضافة إلى vite.config.ts قبل @vitejs/plugin-React — تتطلب الوثائق هذا الترتيب، وتثير الإضافة خطأً صريحاً إذا جاءت @vitejs/plugin-React أولاً4.
// 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(),
],
})
بإعداداتها الافتراضية، تراقب الإضافة ./src/routes وتكتب شجرة مسارات تم إنشاؤها إلى ./src/routeTree.gen.ts في كل عملية تشغيل تطوير أو بناء4. لا تقم أبداً بتعديل هذا الملف يدوياً — فهو الغراء آمن النوع الذي يجعل كل مسار وكل مخطط بحث معروفاً للمترجم. قم بتشغيل npm run dev مرة واحدة وتأكد من ظهور src/routeTree.gen.ts.
الخطوة 3 — تسجيل الراوتر لأمان نوع شامل
يجب تسجيل مثيل الراوتر مع نظام النوع حتى تعرف Link و navigate و useSearch شجرة المسارات الخاصة بك. أنشئ الراوتر وقم بتوسيع واجهة Register5.
// src/router.ts
import { createRouter } from '@tanstack/React-router'
import { routeTree } from './routeTree.gen'
export const router = createRouter({ routeTree })
declare module '@tanstack/React-router' {
interface Register {
router: typeof router
}
}
كتلة declare module هذه هي ما يرفع كل API للراوتر من "مكتوب نصياً" إلى "يعرف مساراتك الدقيقة". إذا تخطيتها، فلن يكون للراوتر شجرة مسارات ليستنتج منها — وستنهار نتائج useSearch إلى never بدلاً من نوع المخطط الخاص بك. قم بتثبيت الراوتر في src/main.tsx باستخدام <RouterProvider router={router} /> كالمعتاد.
الخطوة 4 — تعريف مخطط البحث (وقرار Zod 4)
هنا يكمن جوهر البرنامج التعليمي. تحتاج صفحة المنتجات إلى استعلام بحث، ورقم صفحة، وترتيب فرز، وقائمة بفلاتر الوسوم، وتبديل لحالة التوفر في المخزن. نريد التحقق من كل منها، وتعيين قيم افتراضية، وجعلها مرنة تجاه المدخلات السيئة التي يتم لصقها في شريط URL.
هل يعمل محول Zod الخاص بـ TanStack Router مع Zod 4؟ لا. يصرح @tanstack/zod-adapter@1.167.0 بـ peerDependencies: { "zod": "^3.23.8" }، لذا فإن تثبيته بجانب zod@4.x يفشل مع خطأ تعارض الأقران ERESOLVE6. لديك خياران نظيفان، وكلاهما من الدرجة الأولى:
- المسار أ — Zod 3 + المحول. استخدم
zod@3.25.76وzodValidator(). يفتح هذا ميزة المساعدfallback()في المحول، والتي تلتقط أخطاء التحقق لكل حقل وتستبدلها بقيمة آمنة بدلاً من إثارة خطأ. هذا هو المسار الذي يستخدمه هذا الدليل. - المسار ب — Zod 4، بدون محول. يقبل TanStack Router أي مدقق Standard Schema مباشرة. تنفذ مخططات Zod 4 الـ Standard Schema، لذا يمكنك تمرير المخطط مباشرة إلى
validateSearchبدون محول وبدون تعارض أقران. ستفقد مساعدfallback()الخاص بالمحول وتعتمد على.catch()الخاص بـ Zod بدلاً من ذلك. تعرض الخطوة 9 هذا البديل بالكامل.
بالنسبة للمسار أ، أنشئ المخطط مع تغليف كل حقل بـ fallback() وتوفير القيمة عبر .default() عند غياب المعامل:
// src/routes/products.tsx
import { createFileRoute } from '@tanstack/React-router'
import { zodValidator, fallback } from '@tanstack/zod-adapter'
import { z } from 'zod'
const SORT_KEYS = ['newest', 'price-asc', 'price-desc'] as const
export const productSearchSchema = z.object({
q: fallback(z.string(), '').default(''),
page: fallback(z.number().int().min(1), 1).default(1),
sort: fallback(z.enum(SORT_KEYS), 'newest').default('newest'),
tags: fallback(z.array(z.string()), []).default([]),
inStock: fallback(z.boolean(), false).default(false),
})
يقوم الثنائي fallback() و .default() بمهمتين متميزتين. يقوم .default(value) بملء القيمة عندما يكون المعامل مفقوداً من URL. بينما يلتقط fallback(schema, value) الحالة التي يكون فيها المعامل موجوداً ولكنه غير صالح — على سبيل المثال ?page=-3 أو ?sort=banana — ويستبدله بالقيمة الآمنة بدلاً من إظهار خطأ تحقق للمستخدم5. معاً، يضمنان أن useSearch() تعيد دائماً كائناً كاملاً وصالحاً، بغض النظر عما يكتبه أي شخص في شريط العنوان.
لاحظ أن المخطط (schema) يتعامل مع أنواع حقيقية، وليس مجرد نصوص: page هو رقم، و inStock هو قيمة منطقية (boolean)، و tags هي مصفوفة. يقوم TanStack Router بتسلسل البحث باستخدام JSON.stringify/JSON.parse افتراضيًا، لذا فإن الأرقام والقيم المنطقية والمصفوفات والكائنات المتداخلة تنتقل عبر الرابط (URL) وتعود دون الحاجة لتحويل يدوي7. الكائن { page: 1, tags: ["sale"] } يصبح ?page=1&tags=["sale"] — مع ترميز الأقواس وعلامات الاقتباس في شريط العنوان الفعلي (tags=%5B%22sale%22%5D) — ويتم تحليله مباشرة إلى قيم ذات أنواع محددة.
الخطوة 5 — ربط المخطط بالمسار باستخدام validateSearch
قم بتغليف المخطط في zodValidator() ومرره إلى خيار validateSearch الخاص بالمسار. من هذه النقطة فصاعدًا، يصبح بحث المسار محدد النوع في كل مكان5.
// src/routes/products.tsx (continued)
export const Route = createFileRoute('/products')({
validateSearch: zodValidator(productSearchSchema),
component: ProductsPage,
})
يقوم zodValidator() بتكييف مخطط Zod ليناسب شكل المدقق الذي يتوقعه TanStack Router ويمرر النوع المستنتج من Zod إلى المسار. النوع الذي يخرج من الطرف الآخر هو بالضبط z.infer<typeof productSearchSchema> — وهو { q: string; page: number; sort: 'newest' | 'price-asc' | 'price-desc'; tags: string[]; inStock: boolean }. لا يوجد استخدام لـ as للتحويل القسري في أي مكان، ولا يوجد مكان ثانٍ للتصريح عن النوع.
الخطوة 6 — قراءة المعاملات محددة النوع في المكون
يعيد Route.useSearch() الكائن الذي تم التحقق منه مع استنتاج كامل للأنواع. لا حاجة لـ generics، ولا كتابة يدوية للأنواع — المحرر يعرف أن search.sort هو اتحاد ثلاثي (three-way union) وأن search.page هو رقم.
// src/routes/products.tsx (continued)
function ProductsPage() {
const search = Route.useSearch()
const navigate = Route.useNavigate()
return (
<div>
<h1>Products</h1>
<p>
Page {search.page} · sorted by {search.sort}
{search.inStock ? ' · in stock only' : ''}
{search.tags.length > 0 ? ` · tags: ${search.tags.join(', ')}` : ''}
</p>
<button
onClick={() =>
navigate({ search: (prev) => ({ ...prev, page: prev.page + 1 }) })
}
>
Next page
</button>
</div>
)
}
هناك شيئان يجعلان هذا مريحًا في الاستخدام. أولاً، Route.useSearch() مرتبط بهذا المسار، لذا لا يحتاج إلى وسيط from. ثانياً، navigate({ search: (prev) => ... }) يقبل مُحدِّثًا وظيفيًا (functional updater): prev هو البحث الحالي محدد النوع، والكائن الذي تعيده يتم التحقق من نوعه مقابل المخطط. إعادة { ...prev, page: 'two' } ستؤدي إلى خطأ في الترجمة (compile error)، يتم اكتشافه قبل النشر.
إذا كنت بحاجة إلى المعاملات من مكون ليس هو مكون المسار — شريط أدوات مشترك مثلاً — استخدم الـ hook المستقل مع وسيط from صريح، واختياريًا وظيفة select للاشتراك في حقل واحد فقط وتجنب إعادة الرندر غير الضرورية:
import { useSearch } from '@tanstack/React-router'
function ResultsCount() {
// Only re-renders when `page` changes, not on every search update.
const page = useSearch({ from: '/products', select: (s=> s.page })
return <span>Viewing page {page}</span>
}
الخطوة 7 — بناء عناصر تحكم التصفية باستخدام Link محدد النوع
تُستخدم navigate() للتحديثات الأمرية (imperative) من معالجات الأحداث؛ أما بالنسبة للروابط التصريحية (declarative) — أزرار الترتيب، تبديل الفلتر، الترقيم — استخدم Link مع نفس المُحدِّث الوظيفي. تستقبل خاصية search المعاملات الحالية محددة النوع وتعيد المعاملات التالية، لذا فإن أي مفتاح أو قيمة خاطئة ستؤدي لخطأ ترجمة. تقوم activeProps بتنسيق الرابط عندما يتطابق هدفه مع الموقع الحالي.
// src/routes/products.tsx — a sort + filter bar
import { Link } from '@tanstack/React-router'
const SORT_OPTIONS = [
{ value: 'newest', label: 'Newest' },
{ value: 'price-asc', label: 'Price ↑' },
{ value: 'price-desc', label: 'Price ↓' },
] as const
function ProductsToolbar() {
const search = Route.useSearch()
return (
<nav>
{SORT_OPTIONS.map((opt=> (
<Link
key={opt.value}
to="/products"
// Reset to page 1 whenever the sort changes — a common UX rule.
search={(prev=> ({ ...prev, sort: opt.value, page: 1 })}
activeProps={{ className: 'is-active' }}
>
{opt.label}
</Link>
)}
<Link
to="/products"
search={(prev=> ({ ...prev, inStock: !prev.inStock, page: 1 }}
>
{search.inStock ? 'Showing in-stock only' : 'Show in-stock only'}
</Link>
</nav>
)
}
كل رابط هو URL حقيقي وقابل للمشاركة — بمجرد أن تقوم برمجيات الخطوة 8 الوسيطة (middlewares) بتجريد القيم الافتراضية، فإن النقر بزر الماوس الأيمن على "Copy link address" في زر "Price ↑" من حالة افتراضية يمنحك رابطًا مرتبًا /products?sort=price-asc، جاهزًا للصقه في Slack أو حفظه كإشارة مرجعية. (بدون تلك البرمجيات الوسيطة، يحمل الرابط كائن البحث الكامل الذي تم التحقق منه، بما في ذلك القيم الافتراضية.) ولأن المُحدِّث ينشر prev أولاً، يتم الحفاظ على كل فلتر نشط آخر بينما يتغير فقط sort (وإعادة تعيين page). استخدام as const في SORT_OPTIONS هو ما يحافظ على opt.value محصوراً في الاتحاد الحرفي (literal union) الذي يتوقعه المخطط، بدلاً من تحويله إلى string عام.
الخطوة 8 — الحفاظ على نظافة الروابط باستخدام برمجيات البحث الوسيطة
تظهر مشكلتان بمجرد أن يبدأ المستخدمون الحقيقيون في التنقل. أولاً، القيم الافتراضية تملأ الرابط: التنقل الذي يمرر كائن البحث الكامل ينتج ?q=&page=1&sort=newest&tags=[]&inStock=false بدلاً من رابط نظيف /products. ثانياً، يتم إعادة تعيين المعاملات بصمت: أي تنقل يوفر كائن بحث جديدًا — مثل navigate({ search: { page: 2 } }) — يعيد التحقق من المعاملات المحذوفة لتعود إلى قيمها الافتراضية، مما يمسح قيم q و sort الخاصة بالمستخدم في هذه العملية.
يحل TanStack Router كلتا المشكلتين باستخدام برمجيات البحث الوسيطة (search middlewares) المصرح عنها في المسار. يقوم stripSearchParams بإزالة المعاملات التي تساوي قيمتها الافتراضية، ويقوم retainSearchParams بحمل المعاملات المختارة عبر عمليات التنقل في نفس المسار التي تحذفها5.
// src/routes/products.tsx — extend the Route definition
import {
createFileRoute
retainSearchParams
stripSearchParams
} from '@tanstack/React-router'
export const Route = createFileRoute('/products')({
validateSearch: zodValidator(productSearchSchema)
search: {
middlewares: [
// Drop any param that currently equals its default value.
stripSearchParams({
q: ''
page: 1
sort: 'newest'
tags: []
inStock: false
})
// Keep q and sort when a same-route navigation omits them.
retainSearchParams(['q', 'sort'])
]
}
component: ProductsPage
})
الترتيب مهم، وليس بالاتجاه الذي قد تتوقعه: retainSearchParams يعيد تطبيق معاملاته بعد تشغيل بقية السلسلة، لذا إذا وضعته قبل stripSearchParams، فإن المفاتيح المحتفظ بها ستنجو من التجريد وسيحافظ الرابط في الحالة الافتراضية على ?q=&sort=newest زائدة. مع وضع stripSearchParams أولاً — كما هو موضح أعلاه، وتم التحقق منه مقابل @tanstack/React-router@1.170.11 — فإن الزيارة في الحالة الافتراضية تنتهي عند رابط مجرد /products، والبحث النشط مثل /products?q=boots&page=2 يحافظ بالضبط على المعاملات التي تحمل معنى، و navigate({ search: { page: 2 } }) يحافظ على قيم q و sort الحالية بدلاً من إعادة تعيينها. الكائن الذي تمرره إلى stripSearchParams يجب أن يطابق القيم الافتراضية للمخطط — وهذا هو ما يميز المعامل بأنه "آمن للحذف".
ملاحظة بخصوص النطاق: هذه البرمجيات الوسيطة تنتمي إلى مسار /products، لذا فهي تعمل لعمليات التنقل التي تستهدف /products. التنقل إلى مسار مختلف (/about) يحذف معاملات المنتجات تمامًا — وهذا مقصود، لأن المسارات الأخرى لا تصرح عنها. النمط الرسمي لحمل معامل عبر جميع المسارات هو التصريح عنه في مخطط بحث المسار الجذري (root-route) وربط retainSearchParams بالمسار الجذري بدلاً من ذلك8.
الخطوة 9 — نسخة Zod 4 (بدون محول)
إذا كان تطبيقك يستخدم بالفعل Zod 4، فتجاوز المحول تمامًا. مرر مخطط Z 4 مباشرة إلى validateSearch — يستهلكه TanStack Router كمدقق Standard Schema، ويأتي النوع المستنتج بشكل متطابق9. استخدم .catch() المدمج في Zod بدلاً من fallback() الخاص بالمحول لاستعادة البيانات من المدخلات غير الصالحة.
// src/routes/products.tsx — Zod 4, no @tanstack/zod-adapter
import { createFileRoute } from '@tanstack/React-router'
import { z } from 'zod' // zod@4.x
const SORT_KEYS = ['newest' 'price-asc' 'price-desc'] as const
const productSearchSchema = z.object({
q: z.string().catch('').default('')
page: z.number(.int(.min(1.catch(1.default(1
sort: z.enum(SORT_KEYS.catch('newest'.default('newest'
tags: z.array(z.string(.catch([].default([]
inStock: z.boolean(.catch(false.default(false
})
export const Route = createFileRoute('/products'({
validateSearch: productSearchSchema // schema passed directly
component: ProductsPage
})
المقايضة صغيرة وواضحة. إن fallback() الخاص بالمحول مصمم خصيصًا لحالة "معامل URL صالح ولكنه خارج النطاق" ويُقرأ بشكل أوضح قليلاً في مخطط الراوتر، بينما يقوم .catch() المدمج في Zod بنفس المهمة بدون أي محول — كلا النسختين تحولان ?page=-5 إلى 1 و ?sort=banana إلى 'newest'، وتم التحقق من ذلك مقابل نفس المدخلات. كل من Route.useSearch()، وبرمجيات البحث الوسيطة، والمُحدِّثات الوظيفية من الخطوات 6 إلى 8 تعمل بشكل متطابق — فقط بناء المخطط هو ما يختلف.
الخطوة 10 — تمرير المعاملات التي تم التحقق منها إلى loader
تُعد بارامترات البحث التي تم التحقق من صحتها هي المدخل الصحيح لجلب البيانات. قم بالتصريح عن loaderDeps لسحب البارامترات التي يهتم بها الـ loader، ثم اقرأها كـ deps مكتوبة النوع (typed) داخل الـ loader. تصبح هذه الـ deps جزءًا من مفتاح التخزين المؤقت (cache key) للـ loader، لذا فإن التنقل الذي يغير فقط البارامترات غير التابعة (non-dep) لن يؤدي إلى إعادة جلب البيانات8.
// src/routes/products.tsx — add a loader to the Route
export const Route = createFileRoute('/products')({
validateSearch: zodValidator(productSearchSchema),
loaderDeps: ({ search }) => ({
q: search.q,
page: search.page,
sort: search.sort,
}),
loader: async ({ deps }) => {
// deps.q is string, deps.page is number, deps.sort is the union — all typed.
const params = new URLSearchParams({
q: deps.q,
page: String(deps.page),
sort: deps.sort,
})
const res = await fetch(`/API/products?${params.toString()}`)
return { items: (await res.json()) as Array<{ id: number; name: string }> }
},
component: ProductsPage,
})
داخل المكون، اقرأ النتيجة باستخدام Route.useLoaderData()، وهي أيضًا مكتوبة النوع بالكامل. نظرًا لأن الـ loader يعتمد على loaderDeps، فإن تغيير inStock في واجهة المستخدم لن يؤدي إلى إعادة جلب البيانات إلا إذا أضفته إلى الـ deps — أنت من يقرر بالضبط أي تغيير في البارامترات يستحق طلبًا جديدًا للسيرفر.
التحقق
تأكد من أن كل شيء يعمل بشكل صحيح من حيث الأنواع (type-checks) والسلوك قبل أن تعتمده. أسرع إشارة هي المترجم (compiler): قم بتشغيل فحص الأنواع وتوقع عدم وجود أخطاء.
npx tsc --noEmit
# (no output, exit code 0)
ثم تحقق من سلوك وقت التشغيل (runtime) في المتصفح عن طريق تعديل عنوان URL مباشرة:
| ما تكتبه | ما يرجعه useSearch() | السبب |
|---|---|---|
/products | { q:'', page:1, sort:'newest', tags:[], inStock:false } | .default() يملأ كل بارامتر مفقود |
/products?page=2&sort=price-asc | { ..., page:2, sort:'price-asc' } | البارامترات الصالحة يتم تحليلها إلى قيم مكتوبة النوع |
/products?page=-5 | { ..., page:1 } | fallback() يلتقط القيمة غير الصالحة |
/products?sort=banana | { ..., sort:'newest' } | fallback() يرفض القيمة غير الموجودة في الـ enum |
الصفان الثالث والرابع هما الثمرة: عنوان URL عدائي أو قديم لن يتسبب أبدًا في تعطل الصفحة أو تسريب قيمة غير صالحة إلى طبقة البيانات الخاصة بك. بل يتم حله بهدوء إلى حالة آمنة.
الأخطاء الشائعة
هذه هي الإخفاقات الحقيقية التي يواجهها الأشخاص، مأخوذة من الوثائق وسجل المشكلات بدلاً من اختراعها.
- فشل
npm installمع خطأERESOLVEفي تبعية النظيرzod@"^3.23.8". لقد قمت بتثبيت Zod 4 بجانب@tanstack/zod-adapter، والذي يعتمد حاليًا على Zod 36. إما أن تثبت إصدارzod@3.25.76(المسار أ) أو تتخلى عن الـ adapter وتمرر مخطط Zod 4 مباشرة (المسار ب، الخطوة 9). لا تتجاهل الأمر باستخدام--force— فالـ adapter يتوقع فعليًا المكونات الداخلية لـ Zod 3. useSearch()يظهر بنوعneverأوunknown. توسعةRegisterمن الخطوة 3 مفقودة أو لم يتم إنشاء شجرة المسارات (route tree). تأكد من وجودsrc/routeTree.gen.ts(قم بتشغيلnpm run dev) وأنdeclare module '@tanstack/React-router'موجود في ملف يتضمنه المترجم.- البارامترات الافتراضية لا تختفي من عنوان URL. هناك سببان. إما أن الكائن الممرر إلى
stripSearchParamsلا يطابق تمامًا القيم الافتراضية لمخططك (على سبيل المثالpage: 0بينما الافتراضي هو1)، أو أنretainSearchParamsمدرج قبلstripSearchParams— حيث يتم إعادة تطبيق المفاتيح المحتفظ بها بعد الإزالة، مما يترك?q=&sort=newestمعلقًا. ضعstripSearchParamsأولاً (الخطوة 8). - الأرقام أو القيم المنطقية تصل كسلاسل نصية (strings) في الـ loader. لقد قمت بتجاوز التسلسل (serialization)، أو أنك تقرأ من
window.locationبدلاً منuseSearch()/loaderDeps. مع محول JSON الافتراضي، يمنحكvalidateSearchأرقامًا وقيمًا منطقية حقيقية7 — اقرأ دائمًا من خلال الـ router، وليس من سلسلة الاستعلام الخام. - البارامتر المحتفظ به لا ينتقل إلى مسار مختلف. نطاق
retainSearchParamsيقتصر على المسار الذي تم التصريح عنه فيه — فهو يحفظ البارامترات للتنقلات التي تستهدف ذلك المسار، وليس للتنقلات بعيدًا عنه. التنقل من/products?q=bootsإلى/aboutسينتج عنه/aboutمجردًا. لنقل بارامتر عبر جميع المسارات، قم بالتصريح عنه في مخطط بحث المسار الجذر (root-route) وأرفق الوسيط (middleware) بالمسار الجذر بدلاً من ذلك.
الخطوات التالية ومزيد من القراءة
لديك الآن بارامترات بحث URL مكتوبة النوع، ومتحقق من صحتها، وبقيم افتراضية، ومنظفة ذاتيًا — نفس النمط يتوسع ليشمل لوحات التحكم، وجداول البيانات، وأي عرض قابل للتصفية. من هنا:
- انقل حالة التصفية لشبكة بيانات بالكامل إلى بارامترات البحث بحيث يكون كل عرض قابلاً للمشاركة وللحفظ في الإشارات المرجعية.
- اجمع بين هذا وبين طبقة بيانات مكتوبة النوع. يوضح دليل Astro Actions للنماذج آمنة النوع نفس فكرة "المخطط هو المصدر الوحيد للحقيقة" على جانب السيرفر، ويطبقها دليل Next.js 16 Server Actions وواجهة المستخدم المتفائلة على العمليات (mutations).
- لتخزين البيانات التي تحركها تلك البارامترات مؤقتًا، يغطي دليل Next.js 16 Cache Components لتعدد المستأجرين كيفية جعل مفاتيح التخزين المؤقت تعتمد على القيم المستمدة من الطلب.
تتعمق الوثائق الرسمية أكثر في أنماط التسلسل والتحقق المخصصة58.
Footnotes
-
Vite — الهجرة / المتطلبات. يتطلب Vite 8 إصدار Node.js 20.19+ أو 22.12+. https://vite.dev/guide/migration ↩
-
إصدارات Node.js (جدول LTS). https://nodejs.org/en/about/previous-releases ↩
-
TanStack Router — دليل أمان النوع (إعداد TypeScript الموصى به). https://tanstack.com/router/latest/docs/framework/React/guide/type-safety ↩
-
TanStack Router — التثبيت مع Vite (القيم الافتراضية لـ router-plugin: routesDirectory
./src/routes، generatedRouteTree./src/routeTree.gen.ts). https://tanstack.com/router/latest/docs/installation/with-vite ↩ ↩2 -
TanStack Router — التحقق من معايير البحث باستخدام المخططات (Schemas). https://tanstack.com/router/latest/docs/framework/React/how-to/validate-search-params ↩ ↩2 ↩3 ↩4 ↩5
-
@tanstack/zod-adapter على npm —
peerDependencies: { zod: "^3.23.8" }. https://www.npmjs.com/package/@tanstack/zod-adapter ↩ ↩2 -
TanStack Router — تسلسل معايير البحث المخصص (الافتراضي يستخدم
JSON.stringify/JSON.parse). https://tanstack.com/router/latest/docs/framework/React/guide/custom-search-param-serialization ↩ ↩2 -
TanStack Router — دليل معايير البحث (Search Params). https://tanstack.com/router/latest/docs/guide/search-params ↩ ↩2 ↩3
-
Standard Schema — واجهة مشتركة يتم تنفيذها بواسطة Zod و Valibot و ArkType، ويتم استهلاكها مباشرة بواسطة
validateSearchفي TanStack Router. https://standardschema.dev ↩