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

الاتصال مع الخادم في تطبيق React معتمد على Redux


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

لنوسّع التطبيق الآن بحيث تُخزَّن الملاحظات في الواجهة الخلفية. سنستخدم في عملنا خادم 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 تحذيرًا عند استخدام خطاف التأثير:

effecthook_eslint_warn_01.png

يمكن التخلص من هذا التحذير كالتالي:

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


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

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

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



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

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

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

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   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.


×
×
  • أضف...