useRouter

إذا كنت ترغب في الوصول إلى كائن الموجه داخل أي مكون وظيفي في تطبيقك، يمكنك استخدام خطاف 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، مما يعني أنه لا يمكن استخدامه مع الفئات. يمكنك إما استخدام withRouter أو تغليف الفصل الخاص بك في مكون وظيفي.

كائن router

التعريف التالي هو لكائن router الذي يتم إرجاعه بواسطة كل من useRouter و withRouter:

  • pathname: String - المسار لملف المسار الحالي الذي يأتي بعد /pages. لذلك، لا يتم تضمين basePath، locale والشرطة المائلة النهائية (trailingSlash: true).
  • query: Object - سلسلة الاستعلام المحللة إلى كائن، بما في ذلك معلمات المسار الديناميكي. سيكون كائنًا فارغًا أثناء التصيير المسبق إذا كانت الصفحة لا تستخدم التصيير من جانب الخادم (SSR). القيمة الافتراضية هي {}
  • 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 وليس للتصيير الشرطي على الخادم. انظر الوثائق ذات الصلة لحالة الاستخدام مع الصفحات المحسنة ثابتًا تلقائيًا
  • isPreview: boolean - ما إذا كان التطبيق حاليًا في وضع المعاينة (preview mode).

استخدام حقل asPath قد يؤدي إلى عدم تطابق بين العميل والخادم إذا تم تصيير الصفحة باستخدام التصيير من جانب الخادم أو التحسين الثابت التلقائي. تجنب استخدام 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، مفيد للصفحات خلف المصادقة:

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 لا يفصل المكون ما لم يتغير المكون الأب.

pages/[slug].js
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

في بعض الحالات (على سبيل المثال، إذا كنت تستخدم خادم مخصص)، قد ترغب في الاستماع إلى popstate والقيام بشيء ما قبل أن يتصرف الموجه بناءً عليه.

router.beforePopState(cb)
  • cb - الوظيفة التي تعمل على أحداث popstate الواردة. تستقبل الوظيفة حالة الحدث ككائن مع الخصائص التالية:
    • url: String - المسار للحالة الجديدة. هذا عادةً اسم page
    • as: String - عنوان URL الذي سيظهر في المتصفح
    • options: Object - خيارات إضافية مرسلة بواسطة router.push

إذا أعادت cb false، فلن يتعامل موجه Next.js مع popstate، وستكون مسؤولاً عن التعامل معه في تلك الحالة. انظر تعطيل توجيه نظام الملفات.

يمكنك استخدام 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} />
}

نستخدم تطبيق مخصص (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} />
}

أخطاء 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 للسطر التالي - هذا هو الحل الأنظف
    // 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 أيضًا إضافة نفس كائن router إلى أي مكون.

طريقة الاستخدام

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)