useRouter
إذا كنت تريد الوصول إلى كائن الموجه (router
) داخل أي مكون وظيفي في تطبيقك، يمكنك استخدام خطاف useRouter
. إليك المثال التالي:
import { useRouter } from 'next/router'
function ActiveLink({ children, href }) {
const router = useRouter()
const style = {
marginRight: 10,
color: router.asPath === href ? 'red' : 'black',
}
const handleClick = (e) => {
e.preventDefault()
router.push(href)
}
return (
<a href={href} onClick={handleClick} style={style}>
{children}
</a>
)
}
export default ActiveLink
useRouter
هو خطاف React (React Hook)، مما يعني أنه لا يمكن استخدامه مع الفئات. يمكنك إما استخدام withRouter أو تغليف الفصل الخاص بك في مكون وظيفي.
كائن الموجه (router
)
فيما يلي تعريف كائن router
الذي يتم إرجاعه بواسطة كل من useRouter
و withRouter
:
pathname
:String
- مسار ملف المسار الحالي الذي يأتي بعد/pages
. لذلك، لا يتم تضمينbasePath
أوlocale
أو الشرطة المائلة الزائدة (trailingSlash: true
).query
:Object
- سلسلة الاستعلام المحللة إلى كائن، بما في ذلك معلمات المسار الديناميكي. سيكون كائنًا فارغًا أثناء التصيير المسبق إذا لم تستخدم الصفحة التصيير من جانب الخادم (Server-side Rendering). القيمة الافتراضية هي{}
.asPath
:String
- المسار كما يظهر في متصفح المستخدم بما في ذلك معلمات البحث مع مراعاة إعداداتtrailingSlash
. لا يتم تضمينbasePath
وlocale
.isFallback
:boolean
- ما إذا كانت الصفحة الحالية في وضع الاحتياطي (fallback mode).basePath
:String
- basePath النشط (إذا تم تمكينه).locale
:String
- اللغة النشطة (إذا تم تمكينها).locales
:String[]
- جميع اللغات المدعومة (إذا تم تمكينها).defaultLocale
:String
- اللغة الافتراضية الحالية (إذا تم تمكينها).domainLocales
:Array<{domain, defaultLocale, locales}>
- أي لغات مجال تم تكوينها.isReady
:boolean
- ما إذا كانت حقول الموجه محدثة من جانب العميل وجاهزة للاستخدام. يجب استخدامها فقط داخل طرقuseEffect
وليس للتحكم في التصيير الشرطي على الخادم. راجع الوثائق ذات الصلة لاستخدامها مع صفحات التحسين الثابت التلقائي (automatically statically optimized pages).isPreview
:boolean
- ما إذا كان التطبيق حاليًا في وضع المعاينة (preview mode).
استخدام حقل
asPath
قد يؤدي إلى عدم تطابق بين العميل والخادم إذا تم تصيير الصفحة باستخدام التصيير من جانب الخادم أو التحسين الثابت التلقائي (automatic static optimization). تجنب استخدامasPath
حتى يصبح حقلisReady
true
.
تتضمن الطرق التالية داخل router
:
router.push
يتحكم في الانتقالات من جانب العميل، هذه الطريقة مفيدة للحالات التي لا يكفي فيها next/link
.
router.push(url, as, options)
url
:UrlObject | String
- عنوان URL للانتقال إليه (راجع وثائق وحدة URL في Node.JS لخصائصUrlObject
).as
:UrlObject | String
- مزخرف اختياري للمسار الذي سيظهر في شريط عنوان URL للمتصفح. قبل Next.js 9.5.3 كان هذا يستخدم للمسارات الديناميكية.options
- كائن اختياري مع خيارات التكوين التالية:scroll
- قيمة منطقية اختيارية، تتحكم في التمرير إلى أعلى الصفحة بعد التنقل. القيمة الافتراضية هيtrue
.shallow
: تحديث مسار الصفحة الحالية دون إعادة تشغيلgetStaticProps
أوgetServerSideProps
أوgetInitialProps
. القيمة الافتراضية هيfalse
.locale
- سلسلة اختيارية، تشير إلى لغة الصفحة الجديدة.
لا تحتاج إلى استخدام
router.push
لعناوين URL الخارجية. window.location أكثر ملاءمة لتلك الحالات.
الانتقال إلى pages/about.js
، وهو مسار محدد مسبقًا:
import { useRouter } from 'next/router'
export default function Page() {
const router = useRouter()
return (
<button type="button" onClick={() => router.push('/about')}>
انقر هنا
</button>
)
}
الانتقال إلى pages/post/[pid].js
، وهو مسار ديناميكي:
import { useRouter } from 'next/router'
export default function Page() {
const router = useRouter()
return (
<button type="button" onClick={() => router.push('/post/abc')}>
انقر هنا
</button>
)
}
إعادة توجيه المستخدم إلى pages/login.js
، مفيد للصفحات خلف نظام المصادقة (authentication):
import { useEffect } from 'react'
import { useRouter } from 'next/router'
// هنا يمكنك جلب وإعادة بيانات المستخدم
const useUser = () => ({ user: null, loading: false })
export default function Page() {
const { user, loading } = useUser()
const router = useRouter()
useEffect(() => {
if (!(user || loading)) {
router.push('/login')
}
}, [user, loading])
return <p>جاري التوجيه...</p>
}
إعادة تعيين الحالة بعد التنقل
عند الانتقال إلى نفس الصفحة في Next.js، لن يتم إعادة تعيين حالة الصفحة افتراضيًا لأن React لا يفك تركيب المكون إلا إذا تغير المكون الأب.
import Link from 'next/link'
import { useState } from 'react'
import { useRouter } from 'next/router'
export default function Page(props) {
const router = useRouter()
const [count, setCount] = useState(0)
return (
<div>
<h1>الصفحة: {router.query.slug}</h1>
<p>العدد: {count}</p>
<button onClick={() => setCount(count + 1)}>زيادة العدد</button>
<Link href="/one">واحد</Link> <Link href="/two">اثنان</Link>
</div>
)
}
في المثال أعلاه، الانتقال بين /one
و /two
لن يعيد تعيين العدد. يتم الحفاظ على useState
بين عمليات التصيير لأن مكون React الأساسي، Page
، هو نفسه.
إذا كنت لا تريد هذا السلوك، لديك خياران:
-
تحديث كل حالة يدويًا باستخدام
useEffect
. في المثال أعلاه، يمكن أن يبدو كما يلي:useEffect(() => { setCount(0) }, [router.query.slug])
-
استخدم مفتاح React
key
لإخبار React بإعادة تركيب المكون. للقيام بذلك لجميع الصفحات، يمكنك استخدام تطبيق مخصص:pages/_app.js import { useRouter } from 'next/router' export default function MyApp({ Component, pageProps }) { const router = useRouter() return <Component key={router.asPath} {...pageProps} /> }
مع كائن URL
يمكنك استخدام كائن URL بنفس الطريقة التي يمكنك استخدامه بها في next/link
. يعمل لكل من معلمتي url
و as
:
import { useRouter } from 'next/router'
export default function ReadMore({ post }) {
const router = useRouter()
return (
<button
type="button"
onClick={() => {
router.push({
pathname: '/post/[pid]',
query: { pid: post.id },
})
}}
>
انقر هنا لقراءة المزيد
</button>
)
}
router.replace
مشابه لخاصية replace
في next/link
، router.replace
سيمنع إضافة إدخال URL جديد إلى مكدس history
.
router.replace(url, as, options)
- واجهة برمجة التطبيقات (API) لـ
router.replace
هي نفسها تمامًا لواجهةrouter.push
.
انظر إلى المثال التالي:
import { useRouter } from 'next/router'
export default function Page() {
const router = useRouter()
return (
<button type="button" onClick={() => router.replace('/home')}>
انقر هنا
</button>
)
}
router.prefetch
جلب الصفحات مسبقًا لتحقيق انتقالات أسرع من جانب العميل. هذه الطريقة مفيدة فقط للتنقلات بدون next/link
، حيث أن next/link
يتولى جلب الصفحات مسبقًا تلقائيًا.
هذه ميزة خاصة ببيئة الإنتاج فقط. لا يقوم Next.js بجلب الصفحات مسبقًا في بيئة التطوير.
router.prefetch(url, as, options)
url
- عنوان URL المطلوب جلبها مسبقًا، بما في ذلك المسارات الصريحة (مثل/dashboard
) والمسارات الديناميكية (مثل/product/[id]
).as
- مزخرف اختياري لـurl
. قبل Next.js 9.5.3 كان هذا يستخدم لجلب المسارات الديناميكية مسبقًا.options
- كائن اختياري مع الحقول المسموح بها التالية:locale
- يسمح بتوفير لغة مختلفة عن اللغة النشطة. إذا كانتfalse
، يجب أن يتضمنurl
اللغة حيث لن يتم استخدام اللغة النشطة.
لنفترض أن لديك صفحة تسجيل دخول، وبعد تسجيل الدخول، تقوم بتوجيه المستخدم إلى لوحة التحكم. لتلك الحالة، يمكننا جلب لوحة التحكم مسبقًا لتحقيق انتقال أسرع، كما في المثال التالي:
import { useCallback, useEffect } from 'react'
import { useRouter } from 'next/router'
export default function Login() {
const router = useRouter()
const handleSubmit = useCallback((e) => {
e.preventDefault()
fetch('/api/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
/* بيانات النموذج */
}),
}).then((res) => {
// قم بالانتقال السريع من جانب العميل إلى صفحة لوحة التحكم التي تم جلبها مسبقًا
if (res.ok) router.push('/dashboard')
})
}, [])
useEffect(() => {
// جلب صفحة لوحة التحكم مسبقًا
router.prefetch('/dashboard')
}, [router])
return (
<form onSubmit={handleSubmit}>
{/* حقول النموذج */}
<button type="submit">تسجيل الدخول</button>
</form>
)
}
router.beforePopState
في بعض الحالات (على سبيل المثال، إذا كنت تستخدم خادم مخصص (Custom Server))، قد ترغب في الاستماع إلى حدث popstate والقيام بشيء ما قبل أن يتصرف الموجه بناءً عليه.
router.beforePopState(cb)
cb
- الوظيفة التي سيتم تشغيلها عند أحداثpopstate
الواردة. تستقبل الوظيفة حالة الحدث ككائن مع الخصائص التالية:url
:String
- المسار للحالة الجديدة. هذا عادةً ما يكون اسمpage
.as
:String
- عنوان URL الذي سيظهر في المتصفح.options
:Object
- خيارات إضافية مرسلة بواسطة router.push.
إذا أعادت cb
false
، فلن يتعامل موجه Next.js مع popstate
، وسيكون مسؤوليتك التعامل معه في تلك الحالة. راجع تعطيل توجيه نظام الملفات (Disabling file-system routing).
يمكنك استخدام beforePopState
لمعالجة الطلب، أو إجبار تحديث SSR، كما في المثال التالي:
import { useEffect } from 'react'
import { useRouter } from 'next/router'
export default function Page() {
const router = useRouter()
useEffect(() => {
router.beforePopState(({ url, as, options }) => {
// أريد فقط السماح بهذين المسارين!
if (as !== '/' && as !== '/other') {
// اجعل SSR يصيير المسارات السيئة كـ 404.
window.location.href = as
return false
}
return true
})
}, [router])
return <p>مرحبًا بك في الصفحة</p>
}
router.back
الانتقال للخلف في السجل. يعادل النقر على زر الرجوع في المتصفح. ينفذ window.history.back()
.
import { useRouter } from 'next/router'
export default function Page() {
const router = useRouter()
return (
<button type="button" onClick={() => router.back()}>
انقر هنا للعودة
</button>
)
}
router.reload
إعادة تحميل عنوان URL الحالي. يعادل النقر على زر التحديث في المتصفح. ينفذ window.location.reload()
.
import { useRouter } from 'next/router'
export default function Page() {
const router = useRouter()
return (
<button type="button" onClick={() => router.reload()}>
انقر هنا لإعادة التحميل
</button>
)
}
router.events
يمكنك الاستماع إلى أحداث مختلفة تحدث داخل موجه Next.js. فيما يلي قائمة بالأحداث المدعومة:
routeChangeStart(url, { shallow })
- يتم تشغيله عندما يبدأ المسار في التغيير.routeChangeComplete(url, { shallow })
- يتم تشغيله عندما يتغير المسار بالكامل.routeChangeError(err, url, { shallow })
- يتم تشغيله عند حدوث خطأ أثناء تغيير المسارات، أو إلغاء تحميل المسار.err.cancelled
- يشير إلى ما إذا تم إلغاء التنقل.
beforeHistoryChange(url, { shallow })
- يتم تشغيله قبل تغيير سجل المتصفح.hashChangeStart(url, { shallow })
- يتم تشغيله عندما يتغير الهاش ولكن ليس الصفحة.hashChangeComplete(url, { shallow })
- يتم تشغيله عندما يتغير الهاش ولكن ليس الصفحة.
ملاحظة مهمة: هنا
url
هو عنوان URL الذي يظهر في المتصفح، بما في ذلكbasePath
.
على سبيل المثال، للاستماع إلى حدث الموجه routeChangeStart
، افتح أو أنشئ pages/_app.js
واشترك في الحدث، كما يلي:
import { useEffect } from 'react'
import { useRouter } from 'next/router'
export default function MyApp({ Component, pageProps }) {
const router = useRouter()
useEffect(() => {
const handleRouteChange = (url, { shallow }) => {
console.log(
`التطبيق يتغير إلى ${url} ${
shallow ? 'مع' : 'بدون'
} التوجيه السطحي`
)
}
router.events.on('routeChangeStart', handleRouteChange)
// إذا تم فك تركيب المكون، قم بإلغاء الاشتراك
// من الحدث باستخدام طريقة 'off':
return () => {
router.events.off('routeChangeStart', handleRouteChange)
}
}, [router])
return <Component {...pageProps} />
}
نستخدم تطبيقًا مخصصًا (Custom App) (
pages/_app.js
) في هذا المثال للاشتراك في الحدث لأنه لا يتم فك تركيبه عند التنقل بين الصفحات، ولكن يمكنك الاشتراك في أحداث الموجه في أي مكون في تطبيقك.
يجب تسجيل أحداث الموجه عندما يتم تركيب المكون (useEffect أو componentDidMount / componentWillUnmount) أو بشكل إلزامي عند حدوث حدث.
إذا تم إلغاء تحميل المسار (على سبيل المثال، بالنقر على رابطين بسرعة متتالية)، سيتم تشغيل routeChangeError
. وسيحتوي err
المرسل على خاصية cancelled
مضبوطة على true
، كما في المثال التالي:
import { useEffect } from 'react'
import { useRouter } from 'next/router'
export default function MyApp({ Component, pageProps }) {
const router = useRouter()
useEffect(() => {
const handleRouteChangeError = (err, url) => {
if (err.cancelled) {
console.log(`تم إلغاء المسار إلى ${url}!`)
}
}
router.events.on('routeChangeError', handleRouteChangeError)
// إذا تم فك تركيب المكون، قم بإلغاء الاشتراك
// من الحدث باستخدام طريقة 'off':
return () => {
router.events.off('routeChangeError', handleRouteChangeError)
}
}, [router])
return <Component {...pageProps} />
}
تصدير next/compat/router
هذا هو نفس خطاف useRouter
، ولكن يمكن استخدامه في كل من دليل app
و pages
.
يختلف عن next/router
في أنه لا يرمي خطأً عندما لا يكون مسار الصفحات مثبتًا، وبدلاً من ذلك يكون نوع الإرجاع NextRouter | null
.
هذا يسمح للمطورين بتحويل المكونات لدعم التشغيل في كل من app
و pages
أثناء انتقالهم إلى مسار app
.
المكون الذي كان يبدو سابقًا هكذا:
import { useRouter } from 'next/router'
const MyComponent = () => {
const { isReady, query } = useRouter()
// ...
}
سيؤدي إلى خطأ عند التحويل إلى next/compat/router
، حيث لا يمكن تفكيك null
. بدلاً من ذلك، سيتمكن المطورون من الاستفادة من الخطافات الجديدة:
import { useEffect } from 'react'
import { useRouter } from 'next/compat/router'
import { useSearchParams } from 'next/navigation'
const MyComponent = () => {
const router = useRouter() // قد يكون null أو مثيل NextRouter
const searchParams = useSearchParams()
useEffect(() => {
if (router && !router.isReady) {
return
}
// في `app/`، ستكون searchParams جاهزة فورًا بالقيم،
// في `pages/` ستكون متاحة بعد أن يصبح المسار جاهزًا.
const search = searchParams.get('search')
// ...
}, [router, searchParams])
// ...
}
هذا المكون سيعمل الآن في كل من دليل pages
و app
. عندما لا يتم استخدام المكون في pages
بعد الآن، يمكنك إزالة الإشارات إلى مسار التوافق:
import { useSearchParams } from 'next/navigation'
const MyComponent = () => {
const searchParams = useSearchParams()
// نظرًا لأن هذا المكون يستخدم فقط في `app/`، يمكن إزالة مسار التوافق.
const search = searchParams.get('search')
// ...
}
استخدام useRouter
خارج سياق Next.js في الصفحات
حالة استخدام أخرى محددة هي عند عرض المكونات خارج سياق تطبيق Next.js، مثل داخل getServerSideProps
في دليل pages
. في هذه الحالة، يمكن استخدام مسار التوافق لتجنب الأخطاء:
import { renderToString } from 'react-dom/server'
import { useRouter } from 'next/compat/router'
const MyComponent = () => {
const router = useRouter() // قد يكون null أو مثيل NextRouter
// ...
}
export async function getServerSideProps() {
const renderedComponent = renderToString(<MyComponent />)
return {
props: {
renderedComponent,
},
}
}
أخطاء ESLint المحتملة
بعض الطرق المتاحة على كائن router
تُرجع Promise. إذا كان لديك قاعدة ESLint، no-floating-promises مفعلة، ففكر في تعطيلها إما عالميًا أو للسطر المتأثر.
إذا كان تطبيقك يحتاج إلى هذه القاعدة، يجب إما استخدام void
مع الـ Promise - أو استخدام دالة async
، انتظار الـ Promise باستخدام await
، ثم استخدام void
مع استدعاء الدالة. هذا لا ينطبق عندما يتم استدعاء الطريقة من داخل معالج onClick
.
الطرق المتأثرة هي:
router.push
router.replace
router.prefetch
الحلول المحتملة
import { useEffect } from 'react'
import { useRouter } from 'next/router'
// هنا يمكنك جلب وإرجاع المستخدم
const useUser = () => ({ user: null, loading: false })
export default function Page() {
const { user, loading } = useUser()
const router = useRouter()
useEffect(() => {
// تعطيل التحقق على السطر التالي - هذا هو الحل الأكثر نظافة
// eslint-disable-next-line no-floating-promises
router.push('/login')
// استخدام void مع الـ Promise الذي تُرجعها router.push
if (!(user || loading)) {
void router.push('/login')
}
// أو استخدام دالة async، انتظار الـ Promise باستخدام await، ثم استخدام void مع استدعاء الدالة
async function handleRouteChange() {
if (!(user || loading)) {
await router.push('/login')
}
}
void handleRouteChange()
}, [user, loading])
return <p>جاري التوجيه...</p>
}
withRouter
إذا لم يكن useRouter
مناسبًا لك، يمكن لـ withRouter
أيضًا إضافة نفس كائن المسار
إلى أي مكون.
الاستخدام
import { withRouter } from 'next/router'
function Page({ router }) {
return <p>{router.pathname}</p>
}
export default withRouter(Page)
TypeScript
لاستخدام مكونات الفئة مع withRouter
، يجب أن يقبل المكون خاصية router:
import React from 'react'
import { withRouter, NextRouter } from 'next/router'
interface WithRouterProps {
router: NextRouter
}
interface MyComponentProps extends WithRouterProps {}
class MyComponent extends React.Component<MyComponentProps> {
render() {
return <p>{this.props.router.pathname}</p>
}
}
export default withRouter(MyComponent)