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

لقد استخدمنا حتى اللحظة مخزن Redux بمساعدة واجهة خطافات أمنتها المكتبة react-redux. وقد استخدمنا بالتحديد الدالتين useSelector وuseDispatch.

ولنكمل هذا القسم علينا الاطلاع على طريقة أقدم وأكثر تعقيدًا لاستخدام Redux، وهي استخدام الدالة connect التي تؤمنها المكتبة Redux.

عليك قطعًا استخدام واجهة الخطافات البرمجية عندما تنشئ تطبيقات جديدة، لكن معرفة طريقة عمل connect أمر ضروري عند صيانة مشاريع Redux أقدم.

استخدام الدالة connect لمشاركة مخزن Redux بين المكوِّنات

لنعدّل المكوِّن Notes بحيث يستخدم الدالة connect بدلًا من واجهة الخطافات البرمجية (بدلًا من استخدام الدالتين useSelector وuseDispatch).علينا تعديل الإجزاء التالية من المكوِّن:

import React from 'react'
import { useDispatch, useSelector } from 'react-redux'import { toggleImportanceOf } from '../reducers/noteReducer'

const Notes = () => {
  const dispatch = useDispatch()   
  const notes = useSelector(({filter, notes}) => {    
      if ( filter === 'ALL' ) {      
          return notes    
      }    
      return filter  === 'IMPORTANT'       
          ? notes.filter(note => note.important)      
          : notes.filter(note => !note.important)  
  })
  return(
    <ul>
      {notes.map(note =>
        <Note
          key={note.id}
          note={note}
          handleClick={() => 
            dispatch(toggleImportanceOf(note.id))          
}
        />
      )}
    </ul>
  )
}

export default Notes

يمكن استخدام الدالة connect لتحويل مكونات React "النظامية" بحيث يتصل المكوِّن بحالة مخزن Redux عن طريق خصائصه. لنستخدم أولًا الدالة connect لتحويل المكوّن Notes إلى مكوّن متّصل (connected component):

import React from 'react'
import { connect } from 'react-redux'import { toggleImportanceOf } from '../reducers/noteReducer'

const Notes = () => {
  // ...
}

const ConnectedNotes = connect()(Notes)
export default ConnectedNotes

تُصدِّر الوحدة المكوِّن المتصل والذي سيعمل حاليًا كالمكوّن النظامي السابق تمامًا. يحتاج المكوِّن إلى قائمة بالملاحظات وإلى قيمة المُرشِّح من مخزن Redux. تستقبل الدالة connect دالة أخرى تدعى mapStateToProps كمعامل أول. حيث تُستخدم هذه الأخيرة في تعريف خصائص المكوِّن المتصل والتي تعتمد على حالة مخزن Redux. لو كتبنا الشيفرة التالية:

const Notes = (props) => {  
  const dispatch = useDispatch()
  const notesToShow = () => {    
      if ( props.filter === 'ALL ') {      
          return props.notes    
      }        
      return props.filter  === 'IMPORTANT'       
          ? props.notes.filter(note => note.important)      
      :props.notes.filter(note => !note.important)
  }
  return(
    <ul>
      {notesToShow().map(note =>        
       <Note
          key={note.id}
          note={note}
          handleClick={() => 
            dispatch(toggleImportanceOf(note.id))
          }
        />
      )}
    </ul>
  )
}

const mapStateToProps = (state) => {
  return {
    notes: state.notes,
    filter: state.filter,
  }
}

const ConnectedNotes = connect(mapStateToProps)(Notes)
export default ConnectedNotes

يمكن للمكون أن يلج حالة المخزن مباشرة من خلال الأمرprops.notes والذي يعطينا قائمة الملاحظات. كما يمكن الوصول إلى قيمة المرشِّح من خلال الأمر props.filter. كما يمكن توضيح الحالة التي تنتج عن استخدام الدالة connect مع الدالة mapStateToProps التي عرفناها كما يلي:

connect_map_to_props_01.png

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

const Notes = (props) => {  const dispatch = useDispatch()

  return(
    <ul>
      {props.notes.map(note =>
        <Note
          key={note.id}
          note={note}
          handleClick={() => 
            dispatch(toggleImportanceOf(note.id))
          }
        />
      )}
    </ul>
  )
}

const mapStateToProps = (state) => {  
    if ( state.filter === 'ALL' ) {    
        return {      
            notes: state.notes    
        }  
    }  
    return {    
        notes: (state.filter  === 'IMPORTANT'       
                ? state.notes.filter(note => note.important)      
                : state.notes.filter(note => !note.important)    
               )  
    }}
const ConnectedNotes = connect(mapStateToProps)(Notes)
export default ConnectedNotes  

الدالة mapDispatchToProps

لقد تخلصنا الآن من الدالة useSelector، لكن المكوّن Note لايزال يستخدم الخطاف useDispatch ودالة الإيفاد التي تستخدمه:

const Notes = (props) => {
  const dispatch = useDispatch()
  return(
    <ul>
      {props.notes.map(note =>
        <Note
          key={note.id}
          note={note}
          handleClick={() => 
            dispatch(toggleImportanceOf(note.id))          
}
        />
      )}
    </ul>
  )
}

يستخدم المعامل الثاني للدالة connect لتعريف الدالة mapDispatchToProps، ويمثل هذا المعامل مجموعة من الدوال المولدة للأفعال، تُمرّر إلى الدالة connect كخاصية. لنجري التعديلات التالية على عملية الاتصال التي أنشأناها:

const mapStateToProps = (state) => {
  return {
    notes: state.notes,
    filter: state.filter,
  }
}

const mapDispatchToProps = {  toggleImportanceOf,}
const ConnectedNotes = connect(
  mapStateToProps,
  mapDispatchToProps)(Notes)

export default ConnectedNotes

يستطيع المكوّن الآن إيفاد الفعل الذي يعّرفه مولد الأفعال toggleImportanceOf مباشرةً، عن طريق طلب الدالة من خلال خصائصها:

const Notes = (props) => {
  return(
    <ul>
      {props.notes.map(note =>
        <Note
          key={note.id}
          note={note}
          handleClick={() => props.toggleImportanceOf(note.id)}
        />
      )}
    </ul>
  )
}

أي بدلًا من إيفاد الفعل بالطريقة التالية:

dispatch(toggleImportanceOf(note.id))

يمكننا ببساطة إنجاز ذلك باستخدام connect:

props.toggleImportanceOf(note.id)

لا حاجة لطلب الدالة dispatch بشكل منفصل، طالما أنّ الدالة connect قد عدلت مولد الفعل toggleImportanceOf إلى الشكل الذي يحتوي dispatch.

سيتطلب منك الأمر بعض الوقت لتقلب في ذهنك الطريقة التي تعمل بها الدالة mapDispatchToProps، وخاصة عندما سنلقي نظرة على طرق بديلة لاستخدامها لاحقًا في هذا الفصل.

يمكن إظهار النتيجة التي سنحصل عليها باستخدام connect من خلال الشكل التالي:

visualize_connect_02.png

وبالإضافة إلى قدرته على الولوج إلى حالة المخزن، يمكن للمكوِّن الإشارة إلى دالة يمكن أن تستخدم لإيفاد أفعال من النوع TOGGLE_IMPORTANCE من خلال الخاصية toggleImportanceOf.

تمثل الشيفرة التالية المكوّن Notes وقد كُتب من جديد:

import React from 'react'
import { connect } from 'react-redux' 
import { toggleImportanceOf } from '../reducers/noteReducer'

const Notes = (props) => {
  return(
    <ul>
      {props.notes.map(note =>
        <Note
          key={note.id}
          note={note}
          handleClick={() => props.toggleImportanceOf(note.id)}
        />
      )}
    </ul>
  )
}

const mapStateToProps = (state) => {
  if ( state.filter === 'ALL' ) {
    return {
      notes: state.notes
    }
  }

  return {
    notes: (state.filter  === 'IMPORTANT' 
    ? state.notes.filter(note => note.important)
    : state.notes.filter(note => !note.important)
    )
  }
}

const mapDispatchToProps = {
  toggleImportanceOf
}

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(Notes)

لنستخدم أيضًا الدالة connect لإنشاء ملاحظة جديدة:

import React from 'react'
import { connect } from 'react-redux' 
import { createNote } from '../reducers/noteReducer'

const NewNote = (props) => {  
  const addNote = async (event) => {
    event.preventDefault()
    const content = event.target.note.value
    event.target.note.value = ''
    props.createNote(content)  
  }

  return (
    <form onSubmit={addNote}>
      <input name="note" />
      <button type="submit">add</button>
    </form>
  )
}

export default connect(  null,   { createNote })(NewNote)

وطالما أن المكوّن لن يحتاج الوصول إلى حالة المخزن، يمكننا أن نمرر ببساطة القيمة null كمعامل أول للدالة connect.

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

الإشارة المرجعية إلى مولدات الأفعال الممررة كخصائص

لنوجه اهتمامنا إلى الميزة الهامة التي يمتلكها المكوّن NewNote:

import React from 'react'
import { connect } from 'react-redux' 
import { createNote } from '../reducers/noteReducer'
const NewNote = (props) => {

  const addNote = async (event) => {
    event.preventDefault()
    const content = event.target.note.value
    event.target.note.value = ''
    props.createNote(content)  
  }

  return (
    <form onSubmit={addNote}>
      <input name="note" />
      <button type="submit">add</button>
    </form>
  )
}

export default connect(
  null, 
  { createNote })(NewNote)

سيشعر المطورون حديثو المعرفة بالدالة connect بالحيرة أمام نسختي مولد الأفعال createNote في هذا المكوّن. يجب أن يُشار إلى دالة مولد الأفعال السابقة بالأمر props.createNote من خلال خصائص المكوّن، لأنها النسخة التي تنجز الإيفاد الذي تولده الدالة connect تلقائيًا. ونظرًا للطريقة التي يُدرج فيها مولد الفعل:

import { createNote } from './../reducers/noteReducer'

يمكن أن يُشار إلى مولد الفعل مباشرة بطلب الدالة creatNote. لكن لا تفعل ذلك، لأنها النسخة غير المعدلة من دالة مولد الفعل وبالتالي لا تمتلك آلية الإيفاد التلقائي.

إن طبعنا الدالتين على الطرفية (لم نتطرق إلى هذه الحيلة في التنقيح بعد) كالتالي:

const NewNote = (props) => {
  console.log(createNote)
  console.log(props.createNote)

  const addNote = (event) => {
    event.preventDefault()
    const content = event.target.note.value
    event.target.note.value = ''
    props.createNote(content)
  }

  // ...
}

يمكن أن نجد الفرق بين الدالتين كما في الشكل التالي:

action_creator_virsions_03.png

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

طرائق أخرى لاستخدام الدالة mapDispatchToProps

لقد عرفنا دالة إيفاد الأفعال من خلال المكوّن المتصل NewNote على النحو التالي:

const NewNote = () => {
  // ...
}

export default connect(
  null,
  { createNote }
)(NewNote)

تُمكننا عبارة connect السابقة من إيفاد الأفعال بغية إنشاء ملاحظات جديدة، وذلك باستعمال الأمر ('props.createNote('a new note. ينبغي أن تكون الدوال الممررة إلى الدالة mapDispatchToProps مولدات أفعال، أي دوال تعيد أفعال Redux، فلا فائدة من تمرير كائن JavaScript كمعامل لهذه الدالة.

فالتعريف التالي:

{
  createNote
}

هو اختصار لتعريف كائن مجرّد:

{
  createNote: createNote
}

وهو كائن يمتلك خاصية واحدة هي createNote تأتي مع الدالة createNote كقيمة لها. بدلًا من ذلك، يمكن تمرير التعريف التالي لدالة كمعامل آخر للدالة connect:

const NewNote = (props) => {
  // ...
}

const mapDispatchToProps = dispatch => {  
    return {    
        createNote: value => {      
            dispatch(createNote(value))    
        },  
    }}
export default connect(
  null,
  mapDispatchToProps
)(NewNote)

بهذه الطريقة ستدفع الدالة connect بالدالة mapDispatchtoProps بتمريرها لدالة الإيفاد dispatch كمعامل لها. وستكون النتيجة كائن يعرّف مجموعة من الدوال التي ستمرر إلى المكوّن المتصل كخصائص.

يعرّف تطبيقنا الدالة التي ستُمرر إلى connect على أنها الخاصية createNote:

value => {
  dispatch(createNote(value))
}

حيث توفد ببساطة الفعل الذي أنشأته دالة مولد الأفعال createNote. يشير بعدها المكوِّن إلى الدالة عبر خصائصه من خلال الأمر props.creatNote:

const NewNote = (props) => {
  const addNote = (event) => {
    event.preventDefault()
    const content = event.target.note.value
    event.target.note.value = ''
    props.createNote(content)
  }

  return (
    <form onSubmit={addNote}>
      <input name="note" />
      <button type="submit">add</button>
    </form>
  )
}

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

أعدّ مصمم Redux دان آبراموف دورة تعليمية مميزة بعنوان Getting started with Redux يمكن أن تجدها على Egghead.io. ننصح الجميع بالاطلاع عليها. وستركز الفيديوهات الأربعة الأخيرة من الدورة على connect وخاصة الطرق الأكثر تعقيدًا في استخدامها.

مرور آخر على المكونات التقديمية ومكونات الحاويات

يُركز المكون الذي أعدنا تشكيله كليًا على تصيير الملاحظات، فهو قريب جدًا مما ندعوه بالمكونات التقديمية. وبناء على الوصف الذي قدمه دان آبراموف، فالمكونات التقديمية:

  • تهتم بكيفية عرض الأشياء
  • يمكن أن تضم مكوّنات تقديمية أو حاويات، كما قد تحتوي على شيفرة DOM وتنسيقات CSS خاصة بها.
  • غالبًا ما تسمح باحتواء المكوّنات من خلال الخصائص الأبناء pops.children.
  • لا تتعلق ببقية التطبيق، كأفعال Redux أو مخازنها.
  • لا تحدد الطريقة التي تُحمّل بها البيانات أو كيف تتغير.
  • تستقبل البيانات والاستدعاءات من خلال الخصائص حصرًا.
  • بالنادر ما تمتلك حالة مكوّن خاصة بها، وعندما تمتلك حالة فهي حالة واجهة مستخدم أكثر من كونها حالة تخزين بيانات.
  • تُنشأ كمكوّنات وظيفية، إلّا عندما تحتاج إلى حالة أو خطافات دورة عمل أو استمثال أداء.

تحقق المكونات المتصلة التي أنشأتها الدالة connect:

const mapStateToProps = (state) => {
  if ( state.filter === 'ALL' ) {
    return {
      notes: state.notes
    }
  }

  return {
    notes: (state.filter  === 'IMPORTANT' 
    ? state.notes.filter(note => note.important)
    : state.notes.filter(note => !note.important)
    )
  }
}

const mapDispatchToProps = {
  toggleImportanceOf,
}

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(Notes)

وصف مكونات الحاويات، كما جاء في وصف دان آبراموف لمكونات الحاويات:

  • تركز على كيفية عمل الأشياء
  • يمكن أن تضم مكونات تقديمية ومكونات حاويات، لكنها لا تحتوي عادة على شيفرة DOM خاصة بها ماعدا بعض عناصر div التي تغلف أجزاء من الشيفرة، ولا تمتلك أية تنسيقات CSS.
  • تزود المكوِّنات التقديمية أو مكونات الحاويات الأخرى بالبيانات أوتزودها بطريقة سلوكها.
  • تطلب أفعال Redux وتزود المكونات التقديمية بها كاستدعاءات.
  • غالبًا ما تحتوي على حالة، فطبيعتها تجعلها تميل إلى تزويد غيرها بالبيانات.
  • تُنشئها عادة المكونات الأعلى مثل connect العائدة للمكتبة React-Redux

إنّ تصنيف المكونات إلى تقديمية ومكونات حاويات هي طريقة لتنظيم هيكلية تطبيقات React. وقد يكون ذلك ميزة تصميمة جيدة وقد لا يكون، حسب الوضع المدروس.

وقد أشار آبراموف إلى الحسنات التالية لهذا التقسيم:

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

ويشير آبراموف إلى مصطلح المكوّن من المرتبة العليا، فالمكوّن Note هو مثال عن المكوّن النظامي، بينما تمثل الدالة connect التي تتبع إلى المكتبة React-Redux مكوّنًا من مرتبة عليا. فالمكونات من مراتب عليا هي بشكل أساسي دوال تقبل مكونات نظامية كمعاملات لها، ومن ثم تعيد مكوّنًا نظاميًا.

تمثل المكوّنات من مراتب عليا (HOCs) طريقة في تعريف الدوال المعمّمة التي يمكن أن تُطبق على المكوّنات. وهو مفهوم يعود أصلًا إلى أسلوب البرمجة بالدوال ويقابل بشكل ما مفهوم الوراثة في البرمجة كائنية التوجه.

تعتبر في واقع الأمر المكونات من المراتب العليا تعميمًا لمفهوم الدوال من المراتب العليا (HOF). وهي دوال قد تقبل دوال أخرى كمعاملات أو أن تعيد دوال. لقد استخدمنا بشكل مستمر خلال المنهاج هذا النوع من الدوال، وكمثال عليها التوابع التي تتعامل مع المصفوفات مثل map وfilter وfind.

انخفضت شعبية HOCs بعد ظهور واجهة الخطافات البرمجية في React. وعُدّلت جميع المكتبات التي اعتمدت عليها لتستخدم الخطافات. فالخطافات في أغلب الأوقات أسهل استعمالًا من HOC كما هي الحال في Redux.

المكتبة Redux وحالة المكون

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

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

هل ينبغي استخدام Redux دائمًا؟ ربما لا، فقد ناقش دان آبراموف ذلك في مقالته You Might Not Need Redux.

تتوفر حاليًا طرق أخرى لإدارة الحالة بطرق مشابهة للمكتبة Redux كاستخدام الواجهة البرمجية التي تؤمنها React واستخدام الخطاف useReducer. يمكن الاطلاع على طريقة عمل الواجهة وعمل الخطاف من خلال الانترنت، كما سنناقش ذلك في قسم لاحق.

التمارين 6.19 - 6.21

6.19 تطبيق الطرائف باستخدام connect: الخطوة 1

يصل المكوّن حاليًا إلى مخزن Redux عبر الخطافين useSelector وuseDispatch. عدّل المكوّن AnecdoteList لكي يستخدم الدالة connect بدلًا من الخطافات.

يمكنك استخدام الدوال mapStateToProps وmapDispatchToProps بما تراه مناسبًا.

6.20 تطبيق الطرائف باستخدام connect: الخطوة 2

عدّل كما فعلت سابقًا المكوّنين Filter وAncedoteForm.

6.211 تطبيق الطرائف: الخاتمة

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

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

وهكذا نكون قد وصلنا إلى آخر تمارين القسم وحان الوقت لإرسال الحلول إلى GitHub والإشارة إلى أنك أكملت التمارين ضمن منظومة تسليم التمارين.

ترجمة -وبتصرف- للفصل connect من سلسلة 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.


×
×
  • أضف...