لنوسّع التطبيق الآن بحيث تُخزَّن الملاحظات في الواجهة الخلفية. سنستخدم في عملنا خادم Json الذي خبرناه في القسم 2.
خُزِّنت الحالة الأولية لقاعدة البيانات في الملف "db.json" الموجود في جذر المشروع:
{ "notes": [ { "content": "the app state is in redux store", "important": true, "id": 1 }, { "content": "state changes are made with actions", "important": false, "id": 2 } ] }
سنثبت خادم JSON من أجل مشروعنا:
npm install json-server --save-dev
أضف الشيفرة التالية إلى قسم السكربت في الملف "package.json":
"scripts": { "server": "json-server -p3001 --watch db.json", // ... }
سنشغّل خادم JSON باستخدام الأمر npm run server
. وسننشئ أيضًا تابعًا في الملف "services/notes.js" الذي سيستخدم المكتبة axios لإحضار البيانات من الواجهة الخلفية:
import axios from 'axios' const baseUrl = 'http://localhost:3001/notes' const getAll = async () => { const response = await axios.get(baseUrl) return response.data } export default { getAll }
يجب إضافة المكتبة axios إلى المشروع:
npm install axios
سنغيّر القيمة الأولية للحالة في دالة الاختزال noteReducer
بحيث لا تكون هناك أية ملاحظات:
const noteReducer = (state = [], action) => { // ... }
من الطرق السريعة لإعداد الحالة بناء على البيانات الموجودة في الخادم هو إحضار الملاحظات الموجودة في الملف "index.js" ثم إيفاد الفعلNEW_NOTE
لكلٍ منها:
// ... import noteService from './services/notes' const reducer = combineReducers({ notes: noteReducer, filter: filterReducer, }) const store = createStore(reducer) noteService .getAll() .then(notes => notes.forEach(note => { store.dispatch({ type: 'NEW_NOTE', data: note }) })) // ...
سندعم الفعل INIT_NOTES
ضمن دالة الاختزال حتى نستطيع إعادة التهيئة باستخدام عملية إيفاد واحدة. سننشئ كذلك الدالة المولدة للأفعال initializeNotes
:
// ... const noteReducer = (state = [], action) => { console.log('ACTION:', action) switch (action.type) { case 'NEW_NOTE': return [...state, action.data] case 'INIT_NOTES': return action.data // ... } } export const initializeNotes = (notes) => { return { type: 'INIT_NOTES', data: notes, } } // ...
سيصبح الملف "index.js" بالشكل:
import noteReducer, { initializeNotes } from './reducers/noteReducer' // ... noteService.getAll().then(notes => store.dispatch(initializeNotes(notes)) )
اقتباسملاحظة: لماذا لم نستخدم await بدلًا من الوعود ومعالجات الأحداث (المعرفة ضمن التابع then)؟ لا تعمل await إلا داخل الدالة async، والشيفرة في الملف index.js ليست ضمن دالة، لذلك لم نستخدم async لبساطة العملية.
لكننا قررنا مع ذلك نقل شيفرة التهيئة الأولية للملاحظات إلى المكوّن App
، وسنستخدم كالعادة خطاف التأثير لإحضار البيانات من الخادم:
import React, {useEffect} from 'react'import NewNote from './components/NewNote' import Notes from './components/Notes' import VisibilityFilter from './components/VisibilityFilter' import noteService from './services/notes' import { initializeNotes } from './reducers/noteReducer'import { useDispatch } from 'react-redux' const App = () => { const dispatch = useDispatch() useEffect(() => { noteService .getAll().then(notes => dispatch(initializeNotes(notes))) }, []) return ( <div> <NewNote /> <VisibilityFilter /> <Notes /> </div> ) } export default App
سيعطي المدقق ESlint تحذيرًا عند استخدام خطاف التأثير:
يمكن التخلص من هذا التحذير كالتالي:
const App = () => { const dispatch = useDispatch() useEffect(() => { noteService .getAll().then(notes => dispatch(initializeNotes(notes))) }, [dispatch]) // ... }
أضف المتغير الذي عرفناه ضمن المكوّن App
والذي يمثل عمليًا دالة الإيفاد إلى مخزن Redux، إلى المصفوفة التي يستقبلها خطاف التأثير كمعامل. فإن تغيرت قيمة متغير الإيفاد أثناء التشغيل، سيُنفَّذ التأثير مجددًا. لن يحدث هذا في تطبيقنا، لذلك فالتحذير السابق ليس بذي أهمية.
يمكن التخلص من التحذير السابق أيضًا بإلغاء تدقيق ذلك السطر:
const App = () => { const dispatch = useDispatch() useEffect(() => { noteService .getAll().then(notes => dispatch(initializeNotes(notes))) },[]) // eslint-disable-line react-hooks/exhaustive-deps // ... }
ليس جيدًا إلغاء عمل المدقق eslint عندما يعطي إنذارًا، وحتى لو سبب المدقق بعض الإشكاليات سنعتمد الحل الأول.
يمكنك الاطلاع على معلومات أكثر عن اعتماديات الخطافات في توثيق React.
يمكن أن نفعل المثل عندما ننشئ ملاحظة جديدة. لنوسّع شيفرة الاتصال مع الخادم كالتالي:
const baseUrl = 'http://localhost:3001/notes' const getAll = async () => { const response = await axios.get(baseUrl) return response.data } const createNew = async (content) => { const object = { content, important: false } const response = await axios.post(baseUrl, object) return response.data} export default { getAll, createNew, }
يتغير التابع addNote
العائد للمكوّن NewNote
قليلًا:
import React from 'react' import { useDispatch } from 'react-redux' import { createNote } from '../reducers/noteReducer' import noteService from '../services/notes' const NewNote = (props) => { const dispatch = useDispatch() const addNote = async (event) => { event.preventDefault() const content = event.target.note.value event.target.note.value = '' const newNote = await noteService.createNew(content) dispatch(createNote(newNote)) } return ( <form onSubmit={addNote}> <input name="note" /> <button type="submit">add</button> </form> ) } export default NewNote
سنغير مولد الأفعال createNote
لأنّ الواجهة الخلفية هي من تولد المعرفات الخاصة بالملاحظات (id):
export const createNote = (data) => { return { type: 'NEW_NOTE', data, } }
يمكن تغيير أهمية الملاحظة باستخدام المبدأ نفسه، أي بتنفيذ طلب غير متزامن إلى الخادم ومن ثم إيفاد الفعل المناسب.
يمكن إيجاد شيفرة التطبيق بوضعه الحالي في الفرع part6-3 ضمن المجلد الخاص بالتطبيق على GitHub
التمرينان 6.13 - 6.14
6.13 تطبيق الطرائف على الواجهة الخلفية: الخطوة 1
أحضر الملاحظات من الواجهة الخلفية التي تعمل على خادم JSON بمجرد تشغيل التطبيق. يمكنك الاستعانة بالشيفرة الموجودة على GitHub.
6.14 تطبيق الطرائف على الواجهة الخلفية: الخطوة 2
عدّل طريقة إنشاء طرفة جديدة لكي تُخزَّن في الواجهة الخلفية.
الأفعال غير المتزامنة والمكتبة Redux-Thunk
مقاربتنا التي اعتمدناها في تطوير التطبيق جيدة، لكن ليس جيدًا أن يجري الاتصال مع الخادم داخل دالة المكوّن. فمن الأفضل أن نفصل شيفرة الاتصال عن المكوّنات، لكي يكون عملها الوحيد هو طلب دالة مولد الأفعال.
في مثالنا السابق، سيهيء المكوّن App
حالة التطبيق كالتالي:
const App = () => { const dispatch = useDispatch() useEffect(() => { dispatch(initializeNotes())) },[dispatch]) // ... }
وسينشئ المكوّن NewNote
ملاحظة جديدة كالتالي:
const NewNote = () => { const dispatch = useDispatch() const addNote = async (event) => { event.preventDefault() const content = event.target.note.value event.target.note.value = '' dispatch(createNote(content)) } // ... }
سيستخدم المكونين الدوال التي تُمرّر إليهما كخصائص فقط، دون الالتفات إلى عملية الاتصال مع الخادم والتي تجري في الكواليس. لنثبت الآن المكتبة redux-thunk التي تُمكِّن من إنشاء أفعال غير متزامنة كالتالي:
npm install redux-thunk
تدعى المكتبة redux-thunk أحيانًا بأداة Redux الوسطية، والتي يجب أن تهيأ مع المخزن. وطالما وصلنا إلى هذه النقطة، لنفصل إذًا تعريف المخزن ونضعه في ملفه الخاص "src/store.js":
import { createStore, combineReducers, applyMiddleware } from 'redux' import thunk from 'redux-thunk' import { composeWithDevTools } from 'redux-devtools-extension' import noteReducer from './reducers/noteReducer' import filterReducer from './reducers/filterReducer' const reducer = combineReducers({ notes: noteReducer, filter: filterReducer, }) const store = createStore( reducer, composeWithDevTools( applyMiddleware(thunk) ) ) export default store
سيبدو الملف "src/index.js" بعد التغييرات كالتالي:
import React from 'react' import ReactDOM from 'react-dom' import { Provider } from 'react-redux' import store from './store' import App from './App' ReactDOM.render( <Provider store={store}> <App /> </Provider>, document.getElementById('root') )
يمكننا الآن باستخدام المكتبة redux-thunk أن نعرّف مولد أفعال يعيد دالة تحتوي التابع dispatch
العائد للمكتبة Redux كمعامل لها. ونتيجة لذلك يمكن إنشاء مولدات أفعال غير متزامنة تنتظر حتى تنتهي عملية ما، ثم توفد الفعل الحقيقي.
سنعرّف الآن مولد الأفعال initializeNotes
الذي يهيئ حالة الملاحظات كالتالي:
export const initializeNotes = () => { return async dispatch => { const notes = await noteService.getAll() dispatch({ type: 'INIT_NOTES', data: notes, }) } }
كفعل غير متزامن، تحضر العملية كل الملاحظات من الخادم ومن ثم توفدها إلى هذا الفعل الذي يضيفها إلى المخزن. سيُعرَّف المكوّن App
الآن كالتالي:
const App = () => { const dispatch = useDispatch() useEffect(() => { dispatch(initializeNotes()) },[dispatch]) return ( <div> <NewNote /> <VisibilityFilter /> <Notes /> </div> ) }
تقدّم الطريقة السابقة حلًا أنيقًا، حيث فُصل منطق عملية تهيئة الملاحظات كليًا خارج مكوّن React. سيبدو مولد الأفعال createNote
الذي يُنشئ الملاحظة الجديدة كالتالي:
export const createNote = content => { return async dispatch => { const newNote = await noteService.createNew(content) dispatch({ type: 'NEW_NOTE', data: newNote, }) } }
اتبعنا أيضًا المبدأ نفسه، حيث تُنفّذ بداية عملية غير متزامنة، ومن ثم يوفد الفعل الذي سيغير حالة المخزن. سيتغير المكوّن NewNote
ليصبح على النحو:
const NewNote = () => { const dispatch = useDispatch() const addNote = async (event) => { event.preventDefault() const content = event.target.note.value event.target.note.value = '' dispatch(createNote(content)) } return ( <form onSubmit={addNote}> <input name="note" /> <button type="submit">lisää</button> </form> ) }
يمكن إيجاد شيفرة التطبيق بوضعه الحالي في الفرع part6-4 ضمن المجلد الخاص بالتطبيق على GitHub.
التمارين 6.15 - 6.18
6.15 تطبيق الطرائف على الواجهة الخلفية: الخطوة 3
عدّل الطريقة التي تجري فيها تهيئة مخزن Redux لتستخدم مولدات الأفعال غير المتزامنة، اعتمادًا على المكتبة Redux-Thunk.
6.16 تطبيق الطرائف على الواجهة الخلفية: الخطوة 4
عدّل أيضًا طريقة إنشاء طرفة جديدة لتستخدم مولدات الأفعال غير المتزامنة بمساعدة المكتبة Redux-Thunk.
6.17 تطبيق الطرائف على الواجهة الخلفية: الخطوة 5
لا تخزّن نتائج عملية التصويت حتى اللحظة ضمن الواجهة الخلفية. أصلح ذلك بمساعدة المكتبة Redux-Thunk.
6.18 تطبيق الطرائف على الواجهة الخلفية: الخطوة 6
لاتزال طريقة إنشاء التنبيهات غير مناسبة، طالما أنها تحتاج إلى فعلين وإلى الدالة setTimeOut
حتى تُنفَّذ:
dispatch(setNotification(`new anecdote '${content}'`)) setTimeout(() => { dispatch(clearNotification()) }, 5000)
أنشئ مولد أفعال غير متزامنة، يؤمن الحصول على التنبيه كما يلي:
dispatch(setNotification(`you voted '${anecdote.content}'`, 10))
يمثل النص الذي ينبغي تصييره المعامل الأول، أما المعامل الثاني فهو الوقت الذي يعرض خلاله التنبيه بالثانية. أضف هذا الأسلوب في إظهار التنبيهات إلى تطبيقك.
ترجمة -وبتصرف- للفصل communication with server in redux من سلسلة Deep Dive Into Modern Web Development
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.