أهلاً بيك يا صديقي المطور في ورشة الهندسة المعمارية المتقدمة لمحرر Gutenberg: المثلث الذهبي لإدارة الحالة (Stores, Selectors, and Actions) داخل ووردبريس.
لو أنت بتبني مشروع كبير في ووردبريس—زي بوابة تجارة إلكترونية، أو منصة كورسات وتعليم عن بُعد، أو نظام حجز عيادات وصيدليات—فهيجي عليك وقت تلاقي فيه إن تمرير البيانات بين البلوكات المختلفة أصبح كابوساً لا يطاق! لما يكون عندك بلوك في أول الصفحة بيعرض “سلة الأدوية”، وبلوك في نص المقال بيعرض “منتجات مقترحة”، وبلوك في الشريط الجانبي بيحسب “الخصومات والضرائب”، فمحاولة ربطهم بـ React Props أو Local State عادية بتنتهي دائماً بحالة من الفوضى والبطء القاتل وتضارب البيانات.
الحل الهندسي النظيف هو بناء مخزن بيانات مخصص ومستقل (Dedicated Scoped Store) يمتلك حالة نقية، ويوفر Selectors مذكرنة وسريعة للقراءة، و Actions واضحة وموثقة للتعديل. في المقال ده، هنشرح تشريح المخزن من الألف للياء، إزاي تبني Reducer نقي يحترم قواعد الـ Immutability، إزاي تستخدم مكتبات المذكرنة (Memoization) لمنع إعادة حساب العمليات المعقدة، ودراسة حالة حقيقية لشبكة صيدليات في الإسكندرية بالعامية المصرية التقنية الأصيلة.
| المعيار المعماري | محددات القراءة المذكرنة (Memoized Selectors) | القراءة المباشرة من الحالة (Direct State Access) | دوال الحساب اللحظي داخل الـ JSX (Inline Computations) |
|---|---|---|---|
| زمن المعالجة والأداء (Performance) | فائق السرعة (أقل من 2ms)؛ يسترجع النتيجة من الكاش طالما المدخلات لم تتغير. | سريع جداً في القراءة الأولية، لكنه يفتقر لأي قدرة على تصفية أو دمج البيانات. | بطيء جداً؛ يعيد تشغيل العمليات الحسابية والفرز مع كل إطار رسم في المتصفح. |
| إعادة رسم المكونات (Re-rendering) | مضبوط بدقة؛ لا يعيد رسم المكون إلا إذا تغيرت النتيجة المرتجعة فعلياً. | يعيد رسم المكون مع أي تعديل طفيف في شجرة الحالة حتى لو لم يؤثر على القيمة المطلوبة. | يسبب وميضاً وتهنيجاً مستمراً أثناء كتابة المستخدم السريعة. |
| قابلية إعادة الاستخدام (Reusability) | عالية جداً؛ دالة محدد واحدة يمكن استدعاؤها في 50 بلوكاً ومكوناً مختلفاً بالموقع. | منخفضة؛ تضطر لتكرار كتابة مسارات الكائنات (Object Paths) في كل ملف. | منعدمة؛ الكود محبوس ومحشور داخل كود الـ JSX الخاص بمكون واحد فقط. |
| سهولة الاختبار والتوثيق (Unit Testing) | سهلة للغاية؛ دوال نقية (Pure Functions) يمكن اختبار مدخلاتها ومخرجاتها بـ Jest. | صعبة وتتطلب محاكاة شجرة الـ Redux الكاملة في بيئة الاختبار. | شبه مستحيلة دون فحص شجرة الـ DOM بالكامل ومحاكاة دورة حياة المكون. |
| دعم تمرير المعاملات (Parameterized Selectors) | طبيعي ومثالي؛ تقبل وسائط مثل getCourseById(state, courseId) مع كاش منفصل. | يتطلب كتابة شروط يدوية ومصفوفات فلترة مكررة في كل مكان. | يكتب كـ inline filter يستهلك الذاكرة. |
تشريح المخزن: الأركان الأربعة لمعمارية @wordpress/data
أي مخزن بيانات حقيقي في جوتنبرج يتكون من أربعة أركان هندسية متكاملة تتحدث مع بعضها بدقة:
-
الحالة (State):
كائن جافاسكريبت نقي في الذاكرة الحية يمثل مصدر الحقيقة الوحيد (Single Source of Truth). لا يجوز لأي كود تعديله مباشرة.
-
منشئات الإجراءات (Action Creators):
دوال بسيطة تصف “ماذا حدث في التطبيق”. كل Action يرجع كائناً يحتوي على
typeإلزامي (مثل:'ADD_PRESCRIPTION') ومصفوفة بيانات مرافقةpayload. -
المحول النقي (Reducer):
الدالة الصافية المسؤولة الوحيدة عن استلام الحالة الحالية والإجراء، وتوليد حالة جديدة تماماً (New Immutable State) دون لمس الحالة القديمة.
-
المحددات المذكرنة (Selectors):
بوابات القراءة الرسمية؛ دوال تستخرج البيانات من الـ State وتجري العمليات الحسابية والفرز مع تخزين النتيجة مؤقتاً في الذاكرة لسرعة الاسترجاع.
تطبيق عملي كامل: بناء مخزن شبكة الصيدليات وفحص تعارض الأدوية (Pharmacy Store)
تعال نبني كود إنتاجي متكامل لمخزن صيدليات (enterprise/pharmacy) يدير قائمة الأدوية الموصوفة، ويفحص التداخلات الدوائية الخطيرة (Drug-Drug Interactions) باستخدام محددات مذكرنة فائقة الأداء:
1. ملف منشئات الإجراءات: actions.js
/**
* ملف الإجراءات الرسمية لمخزن الصيدلية
* File: store/actions.js
*/
export function addMedication( medication ) {
return {
type: 'ADD_MEDICATION',
payload: {
id: medication.id || Date.now().toString(),
name: medication.name,
dosage: medication.dosage,
activeIngredient: medication.activeIngredient, // المادة الفعالة
price: parseFloat( medication.price ) || 0,
},
};
}
export function removeMedication( id ) {
return {
type: 'REMOVE_MEDICATION',
payload: id,
};
}
export function clearPrescription() {
return {
type: 'CLEAR_PRESCRIPTION',
};
}2. ملف المحول النقي: reducer.js
/**
* المحول الصافي لحالة الصيدلية مع الحفاظ على Immutability
* File: store/reducer.js
*/
const INITIAL_STATE = {
medications: [],
patientAge: 35,
isCheckingInteractions: false,
};
export default function reducer( state = INITIAL_STATE, action ) {
switch ( action.type ) {
case 'ADD_MEDICATION': {
// منع إضافة نفس الدواء مرتين بالخطأ
const exists = state.medications.some( ( med ) => med.name.toLowerCase() === action.payload.name.toLowerCase() );
if ( exists ) {
return state;
}
return {
...state,
medications: [ ...state.medications, action.payload ],
};
}
case 'REMOVE_MEDICATION':
return {
...state,
medications: state.medications.filter( ( med ) => med.id !== action.payload ),
};
case 'CLEAR_PRESCRIPTION':
return {
...state,
medications: [],
};
default:
return state;
}
}3. ملف المحددات المذكرنة: selectors.js (أعلى درجات الأداء)
/**
* محددات قراءة البيانات مع مذكرنة العمليات الحسابية
* File: store/selectors.js
*/
import createSelector from 'rememo';
// 1. محدد أساسي خفيف: قراءة قائمة الأدوية
export function getMedications( state ) {
return state.medications;
}
// 2. محدد أساسي: قراءة عدد الأدوية
export function getMedicationsCount( state ) {
return state.medications.length;
}
// 3. محدد مذكرن (Memoized): حساب السعر الإجمالي
export const getTotalPrescriptionPrice = createSelector(
( state ) => {
return state.medications.reduce( ( sum, med ) => sum + med.price, 0 );
},
( state ) => [ state.medications ] // يعاد الحساب فقط إذا تغيرت مصفوفة الأدوية!
);
// 4. محدد مذكرن فائق الذكاء: فحص تعارض المواد الفعالة في الروشتة
export const checkDrugInteractions = createSelector(
( state ) => {
const warnings = [];
const ingredients = state.medications.map( ( m ) => m.activeIngredient.toLowerCase() );
// نموذج لفحص تعارض خطير: مثلاً تعارض الأسبرين مع مضادات التجلط
if ( ingredients.includes( 'aspirin' ) && ingredients.includes( 'warfarin' ) ) {
warnings.push( 'تحذير حرج: تعارض شديد بين الأسبرين والوارفارين يزيد من خطر النزيف!' );
}
// تعارض الباراسيتامول مع جرعات مهدئة معينة
if ( ingredients.includes( 'paracetamol' ) && ingredients.includes( 'alcohol_extract' ) ) {
warnings.push( 'تنبيه: تجنب تناول هذا المستحضر مع مستخلصات كحولية لحماية الكبد.' );
}
return warnings;
},
( state ) => [ state.medications ]
);4. ملف تجميع وتسجيل المخزن: index.js
/**
* تجميع وتسجيل المخزن في سجل @wordpress/data
* File: store/index.js
*/
import { createReduxStore, register } from '@wordpress/data';
import reducer from './reducer';
import * as actions from './actions';
import * as selectors from './selectors';
export const PHARMACY_STORE_KEY = 'enterprise/pharmacy';
export const pharmacyStore = createReduxStore( PHARMACY_STORE_KEY, {
reducer,
actions,
selectors,
} );
// تسجيل المخزن رسمياً في النواة
register( pharmacyStore );5. كود استخدام المخزن داخل بلوك التحرير: edit.js
import { useBlockProps } from '@wordpress/block-editor';
import { useSelect, useDispatch } from '@wordpress/data';
import { Button, TextControl, Notice } from '@wordpress/components';
import { useState } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
import { pharmacyStore } from './store';
import './editor.scss';
export default function Edit() {
const blockProps = useBlockProps( { className: 'az-pharmacy-checker-block' } );
// حالات محلية لحقول الإدخال
const [ medName, setMedName ] = useState( '' );
const [ activeIngredient, setActiveIngredient ] = useState( '' );
const [ price, setPrice ] = useState( '' );
// استخراج البيانات من المخزن المخصص عبر المحددات المذكرنة
const { medications, totalPrice, warnings } = useSelect( ( select ) => {
const store = select( pharmacyStore );
return {
medications: store.getMedications(),
totalPrice: store.getTotalPrescriptionPrice(),
warnings: store.checkDrugInteractions(),
};
}, [] );
// استخراج دوال التحديث
const { addMedication, removeMedication, clearPrescription } = useDispatch( pharmacyStore );
const handleAdd = () => {
if ( ! medName || ! activeIngredient ) return;
addMedication( {
name: medName,
activeIngredient,
price: parseFloat( price ) || 50,
} );
setMedName( '' );
setActiveIngredient( '' );
setPrice( '' );
};
return (
{ __( 'نظام فحص تعارض الأدوية والروشتات الطبية (Store & Selectors)', 'az-pharma' ) }
{ /* إظهار تحذيرات التعارض الطبي فوراً وبأعلى سرعة */ }
{ warnings.length > 0 && (
{ warnings.map( ( warn, idx ) => (
{ warn }
) ) }
) }
{ __( 'الأدوية المضافة حالياً', 'az-pharma' ) } ({ medications.length })
{ medications.map( ( med ) => (
-
{ med.name } ({ med.activeIngredient }) - { med.price } ج.م
) ) }
إجمالي قيمة الروشتة: { totalPrice.toLocaleString() } ج.م
);
}دراسة حالة واقعية: أزمة شبكة صيدليات الإسكندرية وفحص التعارضات الدوائية
خلفية المشروع وأبعاد الأزمة
شبكة صيدليات ومستودعات أدوية كبرى في الإسكندرية والساحل الشمالي تضم أكثر من 45 فرعاً، تمتلك مدونة صحية وبوابة استشارات دوائية إلكترونية يقدم من خلالها الصيادلة مراجعات للروشتات الطبية المعقدة لمرضى القلب والسكر. البوابة تستخدم بلوك ووردبريس مخصص لعرض “جدول الجرعات وفحص التداخلات الدوائية”.
الأزمة التشخيصية (The Crisis)
المطور السابق قام بكتابة خوارزمية فحص التداخلات الدوائية كدالة عادية يتم استدعاؤها مباشرة داخل جسم الـ JSX في كل رندرة:
- قاعدة بيانات التداخلات كانت تحتوي على مصفوفة تضم أكثر من 2,500 تداخل دوائي معروف عالمياً.
- مع كل حرف يكتبه الصيدلي في حقل البحث أو اسم الدواء، كانت الدالة تقوم بالدوران في حلقتين متداخلتين (Nested Loops: $O(n^2)$) لفحص كل مادة فعالة ضد الـ 2,500 مادة الأخرى!
- أدى ذلك إلى تجمد متصفح الصيدلي لمدة 3 إلى 4 ثوانٍ كاملة في كل ضغطة زر، مع صوت دوران مراوح أجهزة اللاب توب واستهلاك كامل للرامات!
- الأزمة الأخطر: بدأ بعض الصيادلة في تجاوز استخدام الأداة بسبب البطء القاتل، وقاموا بصرف روشتة تحتوي على تداخل خطير لمريض يعاني من سيولة الدم، مما أدخل المريض المستشفى في حالة نزيف حاد وكاد أن يودي بحياته، وفتح تحقيقات موسعة مع نقابة الصيادلة في الإسكندرية!
الحل المعماري الجذري (The Architectural Solution)
تم استدعاء استشاري هندسة برمجيات ووردبريس لإعادة بناء المنظومة بالكامل:
- عزل منطق الأدوية بالكامل داخل مخزن مخصص ومستقل
enterprise/pharmacyعبرcreateReduxStore. - تحويل دالة فحص التعارضات إلى محدد مذكرن (Memoized Selector) باستخدام مكتبة
rememoالرسمية لنواة ووردبريس. - أصبح فحص التعارضات يعمل مرة واحدة فقط عند إضافة أو حذف دواء فعلي؛ أما أثناء كتابة الحروف في حقول الإدخال، فإن المحدد يسترجع النتيجة المحسوبة مسبقاً من الرام في أقل من 0.2 مللي ثانية!
- إلزام النظام بقفل زر اعتماد الروشتة في حالة وجود أي تعارض حرج، مع إظهار إشعار أحمر صارم لا يمكن للمحرر إغلاقه.
جدول قياس مؤشرات الأداء والتحول الطبي (Metrics Table)
| مؤشر الأداء والسرعة | أثناء الأزمة (الحساب المباشر بالـ JSX) | بعد تطبيق المخزن والمحددات المذكرنة | نسبة التحسن والوفر المحقق |
|---|---|---|---|
| زمن فحص التداخلات الدوائية لكل ضغطة | 3,400 مللي ثانية (تجمد كامل للمتصفح) | 0.2 مللي ثانية فقط (استرجاع فوري من الكاش) | تسريع خوارزمية الفحص بـ 17,000 ضعف! |
| أخطاء الصرف وتجاوز التداخلات الخطيرة | تجاوز يدوي متكرر بسبب بطء النظام | 0.00% (قفل برمجي إلزامي عند التعارض) | حماية أرواح المرضى وسلامة الرعاية |
| استهلاك معالج جهاز الصيدلي (CPU) | 100% مستمر مع تهنيج لوحة التحكم | أقل من 3% في أوقات الذروة | توفير كامل للطاقة واستقرار المتصفح |
| زمن إعداد ومراجعة الروشتة المركبة | 12 دقيقة بسبب البطء والانتظار | دقيقة واحدة فقط بسلاسة مطلقة | توفير 91.6% في وقت عمل الصيدلي |
الوصايا العشر لمعمارية Stores و Selectors و Actions
- دائماً ذكرن العمليات الحسابية في Selectors: استخدم
createSelectorمنrememoلأي عملية تصفية أو حساب لتفادي إرهاق المعالج. - اجعل الـ Reducer دالة صافية خالية من الآثار (Pure Function): لا تستدعِ API، لا تضع تواريخ لحظية، ولا تولد أرقاماً عشوائية داخل الـ Reducer.
- احترم قواعد الـ Immutability بدقة: لا تعدل الحالة الأصلية بيدك؛ استخدم دائماً مشغلات النشر
{ ...state }لإنشاء نسخ جديدة. - سمّ الإجراءات بصيغة الفعل والحدث الواضح: مثل
ADD_ITEMأوREMOVE_ITEMواجعل الـ Type معبراً بوضوح عما حدث. - افصل مخازن إضافتك بنطاق خاص (Namespace): لا تسجل مخزنك باسم عام؛ استخدم بادئة فريدة مثل
my-company/my-store. - لا تخزن بيانات مشتقة في الـ State: إذا كان بإمكانك حساب “الإجمالي” عبر Selector، فلا تخزنه في الـ State لتجنب تضارب القيم.
- استخدم useSelect مع مصفوفة الاعتماديات: مرر مصفوفة فارغة
[]أو المتغيرات المستهدفة لضمان عدم إعادة تشغيل الـ Hook دون مبرر. - اكتب اختبارات Jest للـ Reducer والـ Selectors: اعزل منطق العمليات الحسابية واختبره بدون الحاجة لتشغيل ووردبريس أو المتصفح.
- استخدم Redux DevTools لتتبع الـ Actions: راقب تدفق البيانات وتأكد من أن الإجراءات تطلق بالترتيب المتوقع وببيانات صحيحة.
- اجعل المخزن هو مصدر الحقيقة الوحيد للبلوكات المتصلة: لا توزع نفس المعلومة بين Attributes و Local State؛ اجعل المخزن هو المرجع الأوحد.
معمارية الإجراءات المولدة والضوابط (Generator Actions & Controls)
يا صديقي المطور، من أعظم الأسرار الهندسية في حزمة @wordpress/data هي كيفية التعامل مع العمليات غير المتزامنة (Async Operations) دون تلويث الـ Reducer أو كتابة وعود (Promises) متداخلة ومعقدة داخل الـ Actions.
ووردبريس بتستخدم مفهوم عبقري مأخوذ من مكتبة Redux-Routine يعتمد على دوال التوليد في جافاسكريبت (Generator Functions – function*) مع ما يسمى بـ Controls:
كود عملي: استدعاء الـ API بأسلوب Generator Actions النقي
/**
* إدارة جلب البيانات غير المتزامنة عبر دوال التوليد والضوابط
*/
// 1. تعريف الإجراء كدالة توليد (Generator Function)
export function* fetchMedicationDetails( medicationId ) {
// إطلاق إجراء أولي للإشارة ببدء التحميل
yield { type: 'SET_IS_FETCHING', payload: true };
try {
// yield تستدعي الـ Control المخصص لجلب البيانات دون await مباشرة!
const result = yield {
type: 'FETCH_FROM_API',
path: `/enterprise/v1/medications/${ medicationId }`,
};
// عند وصول النتيجة، يتم تمريرها للـ Reducer
yield { type: 'ADD_MEDICATION_SUCCESS', payload: result };
} catch ( error ) {
yield { type: 'SET_FETCH_ERROR', payload: error.message };
} finally {
yield { type: 'SET_IS_FETCHING', payload: false };
}
}
// 2. تعريف الضابط (Control) الذي ينفذ الاتصال الشبكي الفعلي
export const controls = {
FETCH_FROM_API( action ) {
return wp.apiFetch( { path: action.path } );
},
};
لماذا هذه المعمارية هي الأقوى على الإطلاق؟
لأن دالة fetchMedicationDetails أصبحت دالة قابلة للاختبار بنسبة 100% دون الحاجة لمكتبات محاكاة الشبكة (No Mock Network Needed)! في اختبارات Jest، كل ما تفعله هو استدعاء generator.next() والتأكد من أنها ترجع كائن الـ Action المتوقع بالترتيب الدقيق!
هندسة الفحص الآلي للمخزن عبر Jest (Unit Testing Stores)
في المؤسسات الطبية والمالية، لا يمكنك رفع كود المخزن إلى السيرفر الحي دون التأكد من أن حسابات الأسعار وفحص التعارضات تعمل بدقة متناهية. إليك نموذج اختبار Jest متكامل ومستقل:
/**
* فحص سلامة محول ومحددات مخزن الصيدلية
* File: test/pharmacy-store.test.js
*/
import reducer from '../src/store/reducer';
import { addMedication, removeMedication } from '../src/store/actions';
import { getTotalPrescriptionPrice, checkDrugInteractions } from '../src/store/selectors';
describe( 'فحص سلامة مخزن الصيدلية (enterprise/pharmacy)', () => {
it( 'يجب أن يضيف الدواء ويحدث الإجمالي بدقة دون تحوير الحالة الأصلية', () => {
const initialState = { medications: [] };
// إطلاق إضافة دواء الأسبرين
const stateAfterFirst = reducer( initialState, addMedication( {
id: '1',
name: 'Aspirin',
activeIngredient: 'aspirin',
price: 50,
} ) );
// إطلاق إضافة دواء ثانٍ
const finalState = reducer( stateAfterFirst, addMedication( {
id: '2',
name: 'Vitamin C',
activeIngredient: 'ascorbic_acid',
price: 70,
} ) );
// فحص عدد الأدوية
expect( finalState.medications ).toHaveLength( 2 );
// فحص عمل المحدد المذكرن لحساب الإجمالي
const total = getTotalPrescriptionPrice( finalState );
expect( total ).toBe( 120 );
} );
it( 'يجب أن يطلق تحذيراً فورياً عند وجود تعارض بين الأسبرين والوارفارين', () => {
const stateWithConflict = {
medications: [
{ id: '1', name: 'Aspirin', activeIngredient: 'aspirin', price: 50 },
{ id: '2', name: 'Coumadin', activeIngredient: 'warfarin', price: 110 },
],
};
const warnings = checkDrugInteractions( stateWithConflict );
expect( warnings ).toHaveLength( 1 );
expect( warnings[0] ).toContain( 'تعارض شديد بين الأسبرين والوارفارين' );
} );
} );هذا الفصل المعماري التام بين إدارة البيانات وبين شاشة العرض يضمن أن موقعك يمتلك كوداً برمجياً يمكن الوثوق به في أعقد الأنظمة المصرفية والطبية في العالم العربي.
الأسئلة الشائعة حول Stores و Selectors و Actions (20 سؤالاً وجواباً تفصيلياً)
1. ما هو الفرق الجوهري بين Store و Selector و Action؟
الـ Store هو وعاء حفظ البيانات في الذاكرة، والـ Action هو الأمر الذي يصف حدثاً لتعديل البيانات، بينما الـ Selector هو دالة القراءة والاستعلام من الـ Store.
2. ما هي فائدة المذكرنة (Memoization) في الـ Selectors؟
تقوم بتخزين نتيجة الحساب في كاش الذاكرة، وإذا تم استدعاء الـ Selector بنفس المدخلات مجدداً، تعيد النتيجة فوراً دون إعادة تنفيذ العمليات الحسابية.
3. كيف أنشئ مخزن بيانات مخصص في Gutenberg الحديث؟
باستخدام دالة createReduxStore( storeName, { reducer, actions, selectors } ) ثم تسجيله في النواة عبر register( store ).
4. لماذا يجب أن يكون الـ Reducer دالة صافية (Pure Function)؟
لأنه إذا احتوى على آثار جانبية أو أرقام عشوائية، فلن تتمكن React وأدوات DevTools من تتبع التغييرات أو التراجع عنها بدقة واستقرار.
5. كيف أصل لمخزن مخصص تم إنشاؤه في بلوك آخر؟
باستدعاء select( 'enterprise/my-store' ) أو تمرير كائن الـ Store المنشأ مباشرة لدالة select أو useSelect.
6. ما هي مكتبة rememo المستخدمة في ووردبريس؟
هي مكتبة مذكرنة خفيفة وسريعة جداً طورتها نواة ووردبريس لبناء محددات Selectors ذكية تعيد استخدام النتائج المخبأة بدقة فائقة.
7. هل يمكن لمحدد (Selector) أن يقبل معاملات إضافية؟
نعم؛ يمرر كائن الـ state دائماً كأول وسيط، وتليه أي معاملات إضافية مثل getItemById( state, itemId ).
8. ماذا يحدث لو عدلت كائن الـ State بيدي داخل الـ Reducer؟
ستفشل المقارنة المرجعية (Reference Equality)، ولن تكتشف React حدوث التغيير، ولن يتم إعادة رسم المكونات المعتمدة على تلك الخاصية.
9. كيف أربط مخزناً مخصصاً ببيانات قادمة من WordPress REST API؟
بإضافة كائن resolvers داخل إعدادات createReduxStore؛ حيث يتم استدعاء الـ Resolver تلقائياً لجلب البيانات وتحديث المخزن عند أول طلب.
10. ما الفارق بين createReduxStore ودالة registerStore القديمة؟
الدالة createReduxStore هي المعيار الحديث الأكثر تنظيماً وفصلاً للمسؤوليات، وتتيح تمرير كائن المخزن مباشرة لـ useSelect دون الحاجة لسلاسل نصية صلبة.
11. هل تستهلك المخزنات المخصصة مساحة في قاعدة البيانات؟
لا؛ المخازن تعمل بالكامل في الذاكرة الحية (RAM) لمتصفح الزائر، ولا يتم حفظ أي شيء في قاعدة البيانات إلا ما تختاره لحفظه عبر setAttributes أو REST API.
12. كيف أمنع الـ Selectors من إطلاق Re-renders غير ضرورية؟
بالحرص على إرجاع قيم أولية (Strings, Numbers) أو كائنات مذكرنة بنفس المرجع في الذاكرة لتفادي خداع خوارزمية الـ Shallow Compare في React.
13. ما هي الضوابط (Controls) في معمارية @wordpress/data؟
هي معالجات للآثار الجانبية (Side Effects Handlers) تعتمد على Generator Functions للتعامل مع الوعود والـ APIs بأسلوب نظيف ومنفصل عن الـ Reducer.
14. هل يمكن استخدام مخزن واحد لمشاركة البيانات بين 10 بلوكات مختلفة؟
نعم، وهذه هي القوة المعمارية الأساسية للـ Centralized Stores؛ حيث يمكن لكافة البلوكات القراءة والتحديث من نفس المخزن في تزامن تام.
15. كيف أختبر الـ Reducer باستخدام Jest؟
باستدعاء دالة reducer( initialState, action ) مباشرة وفحص الكائن المرتجع باستخدام expect( newState ).toEqual(...).
16. ماذا أفعل إذا كنت أحتاج لمسح بيانات المخزن بالكامل؟
قم بإنشاء Action مخصص مثل RESET_STORE يعيد الحالة الأولية INITIAL_STATE في الـ Reducer.
17. كيف أمنع تضارب أسماء المخازن مع إضافات ووردبريس الأخرى؟
باستخدام بادئة فريدة للمخزن تتبع اسم إضافتك أو شركتك، مثل: company-slug/feature-name.
18. هل تعمل المخازن والـ Selectors في الواجهة الأمامية للزوار؟
نعم، إذا قمت بتضمين حزمة @wordpress/data في سكربتات الواجهة الأمامية، يمكنك تشغيل نفس المخزن في الواجهة التفاعلية بكل قوة.
19. ما هو دور خاصية payload في كائن الإجراء؟
هي الحاوية القياسية التي تحمل كافة البيانات والمُدخلات الجديدة المرافقة للإجراء لتمريرها إلى الـ Reducer.
20. ما هي القاعدة الذهبية لتصميم مخازن بيانات احترافية في Gutenberg؟
“اجعل الحالة خفيفة ونقية، واجعل الـ Reducer صانعاً صامتاً للحقيقة، واجعل الـ Selectors عيوناً مذكرنة تسترجع البيانات في أجزاء من الملي ثانية”.
المراجع الرسمية والتوثيق المعتمد
- WordPress Data Reference: createReduxStore and Custom Stores Architecture
- Gutenberg Core: @wordpress/data Source Code on GitHub
- rememo: Memoization Library for JavaScript Redux Selectors
- Redux Architecture: Computing Derived Data and Reselect Memoization
- WordPress Developer Guide: Data Basics and Store Registration


اترك تعليقاً
يجب أنت تكون مسجل الدخول لتضيف تعليقاً.