تُظهر الواجهة الأمامية لتطبيقنا محتويات دليل الهاتف بشكل جيد بمساعدة الخادم الذي يُحدَّث باستمرار. لكن إن أردنا إضافة أشخاص جدد، لابد من إضافة طريقة لتسجيل الدخول إلى الواجهة الأمامية.
تسجيل دخول المستخدم
لنضف المتغير 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
سنتمكن الآن من إضافة أشخاص جدد وتغيير الأرقام مجددًا. لكن لا تزال أمامنا مشكلة واحدة. فلو حاولنا إضافة شخص بلا رقم هاتف لن نستطيع ذلك.
سيخفق تقييم البيانات، ذلك أن الواجهة الأمامية سترسل نصًا فارغًا للحقل 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. تسجيل الدخول
لن تعمل وظيفة إضافة كتب جديدة، ولا تغيير عام ميلاد المؤلف لأنها تتطلب تسجيل دخول المستخدِم. أضف وظيفة تسجيل الدخول وأصلح الطفرات، ولا حاجة الآن لتعالج أخطاء التقييم.
يمكن أن تقرر الطريقة التي ستعرض بها واجهة تسجيل الدخول، ومن بين الحلول المقترحة هو وضع نموذج التسجيل ضمن واجهة منفصلة يمكن الوصول إليها عبر قائمة من خيارات التنقل.
نموذج تسجيل الدخول:
عندما يسجل المستخدم دخوله، تتغير قائمة التنقل لإظهار الوظائف التي ينفذها التطبيق فقط عندما يسجل المستخدم دخوله:
3. اختيار الكتب بناء على نوعها: القسم الأول
أكمل التطبيق بانتقاء الكتب بناء على نوعها. قد يبدو الحل كالتالي:
يمكن إنجاز عملية الانتقاء في هذا التمرين باستخدام React فقط.
4. اختيار الكتب بناء على نوعها: القسم الثاني
نفذ آلية لإظهار كل الكتب من النوع المفضل للمستخدم عند تسجيل دخوله.
5. اختار الكتب بناء على نوعها باستخدام GraphQL
أمكننا إنجاز عملية الانتقاء في التمرين السابق باستخدام React. لإتمام هذا التمرين، ينبغي عليك انتقاء الكتب في صفحة التوصيات بإرسال استعلام GraphQ إلى الخادم.
يمثل هذا التمرين والتمرين الذي يليه تحديًا حقيقيًا كما هو المفترض في هذه المرحلة من المنهاج. ربما عليك إكمال التمارين الأسهل أولًا والموجودة في القسم التالي.
هذه بعض النصائح:
-
قد يكون من الأفضل استخدام الخطاف
useLazyQuery
في الاستعلامات بدلًا منuseQuery
. - من المفيد في بعض الأحيان تخزين نتيجة استعلام GraphQL ضمن حالة المكوِّن.
-
انتبه إلى إمكانية إنجاز استعلامات GraphQL باستخدام الخطاف
useEffect
. -
يمكن أن يساعدك المعامل الثاني للخطاف
useEffect
، وذلك بناء على المقاربة التي ستسلكها في الحل.
6. ذاكرة مؤقتة محدثة والكتب الموصى بها
إن أحضرت الكتب الموصى بها مستخدمًا GraphQL، تأكد من أنّ واجهة عرض الكتب ستبقى محدّثة بشكل أو بآخر. فعندما يُضاف كتاب جديد، لابدّ من تحديث واجهة عرض الكتب على الأقل عند الضغط على زر اختيار نوع الكتاب، وإن لم تختر نوعًا محددًا من الكتب، فلا حاجة إذًا لتحديث واجهة عرض الكتب.
ترجمة -وبتصرف- للفصل Login and Updating the cache من سلسلة Deep Dive Into Modern Web Development
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.