اذهب إلى المحتوى

إدارة عملية تسجيل الدخول وتحديث الذاكرة المؤقتة في تطبيق React


ابراهيم الخضور

تُظهر الواجهة الأمامية لتطبيقنا محتويات دليل الهاتف بشكل جيد بمساعدة الخادم الذي يُحدَّث باستمرار. لكن إن أردنا إضافة أشخاص جدد، لابد من إضافة طريقة لتسجيل الدخول إلى الواجهة الأمامية.

تسجيل دخول المستخدم

لنضف المتغير token إلى حالة التطبيق. سيحتوي هذا المتغير على شهادة تحقق المستخدم عندما يسجل دخوله. فإن لم يكن المتغير token مُعرّفًا، سنُصيّر render المكوّن LoginForm المسؤول عن عملية تسجيل الدخول. سيتلقى المكوّن معاملين هما معالج خطأ والدالة setToken:

const App = () => {
  const [token, setToken] = useState(null)
  // ...

  if (!token) {
    return (
      <div>
        <Notify errorMessage={errorMessage} />
        <h2>Login</h2>
        <LoginForm
          setToken={setToken}
          setError={notify}
        />
      </div>
    )
  }

  return (
    // ...
  )
}

سنعرّف تاليًا طفرة لتسجيل الدخول:

export const LOGIN = gql`
  mutation login($username: String!, $password: String!) {
    login(username: $username, password: $password)  {
      value
    }
  }
`

سيعمل المكوّن LoginForm بشكل مشابه لبقية المكوّنات التي أنشأناها سابقًا والتي تنفذ طفرات:

import React, { useState, useEffect } from 'react'
import { useMutation } from '@apollo/client'
import { LOGIN } from '../queries'

const LoginForm = ({ setError, setToken }) => {
  const [username, setUsername] = useState('')
  const [password, setPassword] = useState('')

  const [ login, result ] = useMutation(LOGIN, {
      onError: (error) => {
      setError(error.graphQLErrors[0].message)
    }
  })

  useEffect(() => {
      if ( result.data ) {
          const token = result.data.login.value
          setToken(token)
          localStorage.setItem('phonenumbers-user-token', token)
      }
  }, [result.data]) // ألغيت قاعدة المدقق لهذا السطر
  const submit = async (event) => {
    event.preventDefault()
    login({ variables: { username, password } })
  }

  return (
    <div>
      <form onSubmit={submit}>
        <div>
          username <input
            value={username}
            onChange={({ target }) => setUsername(target.value)}
          />
        </div>
        <div>
          password <input
            type='password'
            value={password}
            onChange={({ target }) => setPassword(target.value)}
          />
        </div>
        <button type='submit'>login</button>
      </form>
    </div>
  )
}

export default LoginForm

لقد استعملنا خطاف التأثير effect-hook مجددًا. وقد استخدم لتخزين قيمة شهادة التحقق ضمن حالة المكوِّن App وضمن الذاكرة المحلية بعد أن يستجيب الخادم للطفرة. واستعمال خطاف التأثير ضروري لتلافي حلقات التصيير اللانهائية.

لنضف أيضًا زرًَا لتسجيل خروج المسستخدم الذي سجّل دخوله. يغيّر معالج الحدث onClick الخاص بالزر قيمة قطعة الحالة token إلى null، كما يزيل شهادة التحقق المخزنة في الذاكرة المحلية، ويعيد ضبط الذاكرة المؤقتة الخاصة بالمكتبة Apollo client. إن هذه الخطوة الأخيرة مهمة لأن بعض الاستعلامات قد تحضر بيانات إلى الذاكرة المؤقتة والتي لا يجب للمستخدم الوصول إليها قبل تسجيل دخوله.

يمكن إعادة ضبط الذاكرة المؤقتة باستخدام التابع resetStore العائد لكائن ApolloClient. ويمكن الوصول إلى هذا الكائن عبر الخطاف useApolloClient:

const App = () => {
  const [token, setToken] = useState(null)
  const [errorMessage, setErrorMessage] = useState(null)
  const result = useQuery(ALL_PERSONS)
  const client = useApolloClient()
  if (result.loading)  {
    return <div>loading...</div>
  }

  const logout = () => {
      setToken(null)
      localStorage.clear()
      client.resetStore()
  }
}

يمكنك إيجاد شيفرة التطبيق بوضعه الحالي ضمن الفرع part8-6 في المستودع المخصص للتطبيق على GitHub.

إضافة شهادة التحقق إلى الترويسة

بعد التغييرات التي طرأت على الواجهة الخلفية، سيتطلب إنشاء أشخاص جدد شهادة تحقق صالحة خاصة بالمستخدم عند إرسال الطلبات إلى الخادم. ولكي نرسل الشهادة، علينا تغيير الطريقة التي عرّفنا بها الكائن ApolloClient في الملف "index.js".

import { setContext } from 'apollo-link-context'
const authLink = setContext((_, { headers }) => {
    const token = localStorage.getItem('phonenumbers-user-token')
    return {
        headers: {
            ...headers,
            authorization: token ? `bearer ${token}` : null,
        }
    }})
const httpLink = new HttpLink({ uri: 'http://localhost:4000' })
const client = new ApolloClient({
  cache: new InMemoryCache(),
  link: authLink.concat(httpLink)})

يحدد المعامل link الذي أُسند إلى الكائن client كيف تتصل Apollo مع الخادم. ولاحظ كيف عُدِّل اتصال رابط HTTP لكي تتضمن ترويسة التصريح شهادة التحقق إن كانت مخزّنة في الذاكرة المحليّة.

ولابدّ من تثبيت المكتبة اللازمة لهذا التعديل كالتالي:

npm install apollo-link-context

سنتمكن الآن من إضافة أشخاص جدد وتغيير الأرقام مجددًا. لكن لا تزال أمامنا مشكلة واحدة. فلو حاولنا إضافة شخص بلا رقم هاتف لن نستطيع ذلك.

error_person_without_phone_01.png

سيخفق تقييم البيانات، ذلك أن الواجهة الأمامية سترسل نصًا فارغًا للحقل phone. لنغيّر إذًا الدالة التي تنشئ الأشخاص الجدد لكي تعطي القيمة null للحقل phone إن لم يدخل المستخدم قيمة له.

const PersonForm = ({ setError }) => {
  // ...
  const submit = async (event) => {
    event.preventDefault()
    createPerson({
      variables: { 
        name, street, city,
          phone: phone.length > 0 ? phone : null
      }
    })

  // ...
  }

  // ...
}

يمكنك إيجاد شيفرة التطبيق بوضعه الحالي ضمن الفرع part8-7 في المستودع المخصص للتطبيق على GitHub.

تحديث الذاكرة المؤقتة (مرور ثان)

ينبغي علينا تحديث الذاكرة المؤقتة للمكتبة Apollo client عند إنشاء أشخاص جدد. ويمكننا ذلك باستخدام الخيار refetchQueries للطفرة والذي يسمح بتنفيذ الاستعلام ALL_PERSONS مرة أخرى.

const PersonForm = ({ setError }) => {
  // ...

  const [ createPerson ] = useMutation(CREATE_PERSON, {
    refetchQueries: [  {query: ALL_PERSONS} ],
      onError: (error) => {
      setError(error.graphQLErrors[0].message)
    }
  })

يعتبر ما فعلناه سابقًا مقاربة جيدة، لكن العقبة التي ستعترضنا هي أن الاستعلام سيُنفّذ من جديد عند أي تحديث. يمكن استمثال الحل بمعالجة موضوع التحديث بأنفسنا، وذلك بتعريف دالة استدعاء للتحديث خاصة بالطفرة، بحيث تستدعيها المكتبة Apollo بعد الطفرة:

const PersonForm = ({ setError }) => {
  // ...

  const [ createPerson ] = useMutation(CREATE_PERSON, {
    onError: (error) => {
      setError(error.graphQLErrors[0].message)
    },
    update: (store, response) => {
        const dataInStore = store.readQuery({ query: ALL_PERSONS })      store.writeQuery({
            query: ALL_PERSONS,
            data: {
                ...dataInStore,
                allPersons: [ ...dataInStore.allPersons, response.data.addPerson ]
            }
        })
    }
  })

  // ..
}  

تُعطى دالة الاستدعاء دلالة مرجعية إلى الذاكرة المؤقتة وإلى البيانات التي تعيدها الطفرة على شكل معاملات. في حالتنا على سبيل المثال، عند إنشاء شخص جديد ستقرأ الشيفرة حالة الذاكرة المؤقتة للاستعلام ALL_PERSONS باستخدام الدالة readQuery، وستحدث الذاكرة المؤقتة باستخدام الدالة writeQuery التي ستضيف الشخص الجديد إليها.

انتبه إلى الدالة readQuery التي ستعطي خطأً إن لم تحتوي الذاكرة المؤقتة على كل البيانات التي تلبي متطلبات الاستعلام. يمكن التقاط الخطأ باستخدام الكتلة try/catch.

إنّ الحل الأكثر منطقية لتحديث الذاكرة المؤقتة في بعض الحالات هو استخدام دالة استدعاء للتحديث.

يمكن عند الضرورة تعطيل الذاكرة المؤقتة للتطبيق ككل أو لاستعلامات مفردة بضبط الحقل fetchPolicy الذي يدير الذاكرة المؤقتة على القيمة no-cache.

انتبه دائمًا للذاكرة المؤقتة، فالبيانات القديمة فيها قد تسبب ثغرات صعبة الإيجاد. وكما نعرف إن الحفاظ على الحالة المحدّثة للذاكرة المؤقتة أمر صعب و نستشف ذلك من المقولة التالية لأحد المبرمجين:

اقتباس

ليس هناك سوى أمرين شاقين فقط في علوم الحاسب: مشاكل الذاكرة المؤقتة وتسمية الأشياء.

يمكنك إيجاد شيفرة التطبيق بوضعه الحالي ضمن الفرع part8-8 في المستودع المخصص للتطبيق على GitHub.

التمارين

1. إنشاء قائمة كتب

لن تعمل قائمة الكتب بعد التغييرات التي أجريناها على الواجهة الخلفية، لذا جد حلًا.

2. تسجيل الدخول

لن تعمل وظيفة إضافة كتب جديدة، ولا تغيير عام ميلاد المؤلف لأنها تتطلب تسجيل دخول المستخدِم. أضف وظيفة تسجيل الدخول وأصلح الطفرات، ولا حاجة الآن لتعالج أخطاء التقييم.

يمكن أن تقرر الطريقة التي ستعرض بها واجهة تسجيل الدخول، ومن بين الحلول المقترحة هو وضع نموذج التسجيل ضمن واجهة منفصلة يمكن الوصول إليها عبر قائمة من خيارات التنقل.

login_form_nav_menu_02.png

نموذج تسجيل الدخول:

login_view_03.png

عندما يسجل المستخدم دخوله، تتغير قائمة التنقل لإظهار الوظائف التي ينفذها التطبيق فقط عندما يسجل المستخدم دخوله:

loggedin_functions_04.png

3. اختيار الكتب بناء على نوعها: القسم الأول

أكمل التطبيق بانتقاء الكتب بناء على نوعها. قد يبدو الحل كالتالي:

filter_books_by_genre_05.png

يمكن إنجاز عملية الانتقاء في هذا التمرين باستخدام React فقط.

4. اختيار الكتب بناء على نوعها: القسم الثاني

نفذ آلية لإظهار كل الكتب من النوع المفضل للمستخدم عند تسجيل دخوله.

filter_by_user_favorites_06.png

5. اختار الكتب بناء على نوعها باستخدام GraphQL

أمكننا إنجاز عملية الانتقاء في التمرين السابق باستخدام React. لإتمام هذا التمرين، ينبغي عليك انتقاء الكتب في صفحة التوصيات بإرسال استعلام GraphQ إلى الخادم.

يمثل هذا التمرين والتمرين الذي يليه تحديًا حقيقيًا كما هو المفترض في هذه المرحلة من المنهاج. ربما عليك إكمال التمارين الأسهل أولًا والموجودة في القسم التالي.

هذه بعض النصائح:

  • قد يكون من الأفضل استخدام الخطاف useLazyQuery في الاستعلامات بدلًا من useQuery.
  • من المفيد في بعض الأحيان تخزين نتيجة استعلام GraphQL ضمن حالة المكوِّن.
  • انتبه إلى إمكانية إنجاز استعلامات GraphQL باستخدام الخطاف useEffect.
  • يمكن أن يساعدك المعامل الثاني للخطاف useEffect، وذلك بناء على المقاربة التي ستسلكها في الحل.

6. ذاكرة مؤقتة محدثة والكتب الموصى بها

إن أحضرت الكتب الموصى بها مستخدمًا GraphQL، تأكد من أنّ واجهة عرض الكتب ستبقى محدّثة بشكل أو بآخر. فعندما يُضاف كتاب جديد، لابدّ من تحديث واجهة عرض الكتب على الأقل عند الضغط على زر اختيار نوع الكتاب، وإن لم تختر نوعًا محددًا من الكتب، فلا حاجة إذًا لتحديث واجهة عرض الكتب.

ترجمة -وبتصرف- للفصل Login and Updating the cache من سلسلة Deep Dive Into Modern Web Development


تفاعل الأعضاء

أفضل التعليقات

لا توجد أية تعليقات بعد



انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أضف تعليق

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • أضف...