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، مما يعني أنه لا يمكن استخدامه مع الفئات. يمكنك إما استخدام 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])
  • استخدم key في React لإخبار 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)