01-04-2019, 06:18 PM | #1 |
تاريخ التسجيل: Feb 2019
العمر: 38
المشاركات: 1,149
التقييم: 10
|
تاريخ التسجيل: Feb 2019
العمر: 38
المشاركات: 1,149
التقييم: 10
كيفية استعمال مكتبة Redux في مشاريع React.js
يهتم المطورون بتنظيم تطبيقاتهم و إدارة العمليات ضمنها، خصوصاً إذا كان المشروع كبيراً وعليه تحمل ضغط المستخدمين مع كفاءة عالية في معالجة المعلومات المدخلة والمخرجة (Input/Output). لذلك ظهرت مكتبات عديدة تقوم بادارة الحالات (State Management) مثل Redux و mobX، والأولى هي موضوع درسنا هذا، لا لشيء إلا لأنها المكتبة الأشهر والأكثر استخداما
سأحاول في هذه المقالة شرح مكتبة Redux وتبسيطها بأحسن أسلوب ليفهمها الجميع، أغلب المبتدئين يخافون استعمالها و يرونها معقّدة أكثر من اللزوم، لذلك كان لا بد أولا أن نقوم بتقديم المكتبة بالشرح النظري ثم بعد ذلك نقوم بإنجاز مثال عملي لاستيعاب ما تعلمناه بشكل جيد. الجزء النظري كما تعلمون جاءت مكتبة React – والتي سبق لنا الحديث عنها في المدونة هنا – لحل إشكالية العلاقة بين واجهة المستخدم (User Interface) والبيانات (Data) التي تتغير بشكل مستمر في تطبيقات الويب. هذه المكتبة هي بالأساس مسؤولة عن إنشاء المشاهد (Views) في تطبيقات الويب وليست إطار عمل متكامل مثل أنجولار على سبيل المثال، ولهذا يتم تمثيلها عادة على أنها الحرف V في نظام MVC. مكتبة React.js تعتمد نظام تدفق بيانات أحادي الإتجاه Unidirectional Data Flow، حيث أن المصدر الوحيد للحقيقة هو State، وبالتالي فكل تغيير يطرأ على حالة مكونات التطبيق يجب أن يمر أولا عبر تحديث هذا الكائن، ولهذا السبب أصلا سمي State أو حالة. وقد سبق لنا كتابة مقال خاص عن هذا الموضوع بيننا فيه وجه الإختلاف بين نظام Unidirectional Data Flow الذي تنتهجه React.js ونظام Bidirectional Data Flow الذي تعتمده جل أطر عمل جافاسكريبت الأخرى وعلى رأسها Angular. لماذا استخدام مكتبة لإدارة الحالة ؟ قد نستطيع إنشاء تطبيقات كاملة بواسطة React.js ولكن سرعان ما سنكتشف محدودية خياراتنا خصوصا عندما تزداد درجة تعقيد التطبيق وتشابك العلاقات بين مختلف مكوناته (Components). على سبيل المثال، لنفترض أنه لدينا مكون أب لديه ابن واحد، وهذا الأخير لديه حالته الخاصة. لاحظوا الصورة التالية : عندما بدأنا بالعمل على مشروعنا لم يكن لدينا تصور كامل للشكل النهائي الذي ستكون عليه شجرة مكونات التطبيق (ومن الصعب في الحقيقة معرفة ذلك)، ظننا بأن المكون الأب في الصورة سيكون لديه ابن وحيد ولذلك أعطينا للأخير حالة خاصة به (state). بعد مدة، أدركنا بأن الأب سيكون لديه أبناء آخرين يحتاجون للوصول إلى حالة الإبن الأول، وهذا مستحيل في React.js لأن الحالة يتم تمريرها من الأعلى للأسفل فقط عن طريق ال Props، ولا يمكن تمريرها أفقيا بين المكونات من نفس المستوى. إذن الحل الوحيد سيكون هو تجريد الإبن الأول من حالته (state) ورفعها إلى الأب ليقوم الأخير بإنزالها مرة أخرى للأبناء الثلاثة على شكل Prop عمل مضني تتضاعف صعوبته مع ازدياد حجم المشروع وتشابك مكوناته. فيبسوك كانت على بينة من هذه النقطة، لذلك قاموا بتصميم نمط جديد ليلعب دور Model، ولكن بالإعتماد على تدفق أحادي الإتجاه للبيانات (Unidirectional data flow) عوض التدفق ثنائي الإتجاه الذي ألفناه في أطر عمل مثل Angular. اسم هذا النمط هو FLUX وهناك مكتبة جافاسكريبت أيضا بنفس الإسم من فيسبوك لتمكين المطورين من الإستعانة بمميزات FLUX في مشاريعهم. من مميزات نمط Flux أنه يقوم بجمع حالات (States) جميع المكونات وتخزينها في ما يعرف بالمخازن أو Stores. ويمكن لأي مكون (Component) الإتصال بتلك المخازن عن طريق آلية محددة لتحديث حالته ومن ثم الحصول على الحالة الجديدة على شكل Prop. تعتبر مكتبة Redux من أشهر التطبيقات (Implementations) لنمط Flux، إلا أن لديها مميزاتها الخاصة، منها على سبيل المثال الإعتماد على مخزن واحد (Only One Store) للحالات عوض خاصية تعدد المخازن الموجودة في مكتبة Flux من فيسبوك. يعود أول ظهور لمكتبة ريداكس إلى عام 2015 على يد المبرمجان Dan Abramov و Andrew Clark، وهي مكتبة مستقلة بذاتها ويمكن استخدامها في أي مشروع جافاسكريبت، وليس مشاريع React فقط. كيف تعمل مكتبة Redux ؟ تُجمع المبادئ الأساسية ل Redux عادة في ثلاث جمل، وهي :
يبدأ كل شيء من واجهة المستخدم (User Interface)، حيث يقوم المستخدم بسلوك معين يترجم في Redux إلى ما يعرف ب Action. هذا ال action يستقبل في دوال نقية (Pure functions) معروفة بال Reducers. تقبل في العادة معاملين اثنين : الحالة القديمة و ال action الذي تم إطلاقه في السابق، وتقوم بإرجاع (return) الحالة الجديدة بناءً على المعلومات الواردة في ال action. هذا الأخير عبارة عن كائن يضم خاصيتين أساسيتين :
1 2 3 4 5 6 7 // مثال لبنية الإجراء أو Action في Redux (أو نمط Flux بصفة عامة) : { type: 'ADD_TODO', payload: { text: 'Learn Redux...' } } الجزء التطبيقي بعد أن شرحنا فكرة Redux نظرياً، سنشرح الفكرة تطبيقياً واخترت لكم مثالاً سهلاً مستعيناً به من مدونة daveceddia و هو تطبيق العداد أو Counter. فكرته سهلة و بسيطة جداً.. لنلقي نظرة على الكود البرمجي: import React from 'react'; class Counter extends React.Component { state = { count: 0 } increment = () => { this.setState({ count: this.state.count + 1 }); } decrement = () => { this.setState({ count: this.state.count - 1 }); } render() { return ( <div> <h2>Counter</h2> <div> <button onClick={this.decrement}>-</button> <span>{this.state.count}</span> <button onClick={this.increment}>+</button> </div> </div> ) } } export default Counter; 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 import React from 'react'; class Counter extends React.Component { state = { count: 0 } increment = () => { this.setState({ count: this.state.count + 1 }); } decrement = () => { this.setState({ count: this.state.count - 1 }); } render() { return ( <div> <h2>Counter</h2> <div> <button onClick={this.decrement}>-</button> <span>{this.state.count}</span> <button onClick={this.increment}>+</button> </div> </div> ) } } export default Counter; كمراجعة سريعة، إليك ما قمنا به :
في مثل هذا النوع من المشاريع الصغيرة لا نحتاج للإستعانة بمدير للحالة مثل Redux، ولكن للغرض التعليمي لهذا المقال سنقوم بإعادة إنشاء نفس التطبيق بطريقة Redux حتى تتوضح وتترسخ جيدا في عقولنا كيفية عمله. لنقم الآن بانشاء مشروع جديد بواسطة الحزمة create-react-app : create-react-app counter-with-redux 1 create-react-app counter-with-redux بعد إنشاء وتوليد المشروع، سنقوم بفتح الملف src/index.js ونعوض ما فيه بهذا الكود : import React from 'react'; import { render } from 'react-dom'; import Counter from './Counter'; const App = () => ( <div> <Counter /> </div> ); render(<App />, document.getElementById('root')); 1 2 3 4 5 6 7 8 9 10 11 import React from 'react'; import { render } from 'react-dom'; import Counter from './Counter'; const App = () => ( <div> <Counter /> </div> ); render(<App />, document.getElementById('root')); كما لا ننسى إنشاء الملف src/Counter.js ونضع فيه الكود الخاص بالمكون Counter الذي شرحناه سابقا. بعدها نقوم بتنصيب مكتبتي Redux و react-redux. $ yarn add Redux react-redux 1 $ yarn add Redux react-redux react-redux هي الحزمة التي تمكن مكتبة Redux من العمل بكفاءة وتوافقية ممتازة مع مكتبة React.js. نقوم بحذف الوظائف السابقة (increment و decrement) ونجعلها فارغة. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 import React from 'react'; class Counter extends React.Component { increment = () => { // fill in later } decrement = () => { // fill in later } render() { return ( <div> <h2>Counter</h2> <div> <button onClick={this.decrement}>-</button> <span>{this.props.count}</span> <button onClick={this.increment}> </button> </div> </div> ) } } export default Counter; نلاحظ أننا غيّرنا الحالة this.state.count إلى خاصية this.props.count إلى هنا، العداد لا يعرف شيئا عن خاصية بهذا الإسم.. المدير Redux هو من سيتولى توفير هذه الخاصية للمكون Counter. للحصول على العدد من Redux سنحتاج الى استدعاء (connect): import { connect } from 'react-redux'; 1 import { connect } from 'react-redux'; نحتاج الآن الى الاتصال بالمكوّن (Counter) لربطه مع Redux عبر اضافة الكود التالي، و لا تهتم للاخطاء التي ستظهر كلها ستختفي بعد قليل. // Add this function: function mapStateToProps(state) { return { count: state.count }; } // Then replace this: // export default Counter; // With this: export default connect(mapStateToProps)(Counter); 1 2 3 4 5 6 7 8 9 10 11 12 // Add this function: function mapStateToProps(state) { return { count: state.count }; } // Then replace this: // export default Counter; // With this: export default connect(mapStateToProps)(Counter); الدالة mapStateToProps مهمة للغاية، فهي المكلفة بتحويل أجزاء من الحالة (state) إلى خصائص تمرَّر إلى المكونات المعنية (Counter في هذه الحالة) عن طريقة الدالة عالية الرتبة connect (تعرف كذلك ب Higher Order Function) والتي تقوم بإرجاع ما يعرف في React بمكون عالي الرتبة Higher Order Component أو HOC ليقوم بتمرير الحالة count إلى المكون الإبن Counter على شكل خاصية أو Prop. تذكر الصورة أعلاه حيث الأبناء يرثون الحالة على شكل خصائص من الأب. الإبن هنا هو المكون Counter والأب هو المكون الغير المرئي الذي تقوم الدالة connect بإرجاعه. آمل أن يكون شرحي موفقا بعد ما أكملنا الجزء الأول من تطبيقنا، لنعد الان لتعديل الملف الرئيسي للتطبيق: (src/index.js) و نستدعي المزوّد أو الممون (Provider) و الذي يعمل كمكون رئيسي يغطي جميع المكوّنات (Components) التي نحتاج ربطها مع Redux.import { Provider } from 'react-redux'; ... const App = () => ( <Provider> <Counter/> </Provider> ); 1 2 3 4 5 6 7 8 9 import { Provider } from 'react-redux'; ... const App = () => ( <Provider> <Counter/> </Provider> ); الممون بحاجة لمعرفة المخزن الذي يأخد منه المعلومات التي يوفرها بدوره للمكونات الواقعة تحت نفوذه. لننشئ هذا المخرن عبر الدالة createStore التي نستدعيها من مكتبة Redux كما يلي : import { createStore } from 'redux'; const store = createStore(); const App = () => ( <Provider store={store}> <Counter/> </Provider> ); 1 2 3 4 5 6 7 8 9 import { createStore } from 'redux'; const store = createStore(); const App = () => ( <Provider store={store}> <Counter/> </Provider> ); لاحظوا بأننا قمنا بتمرير المخزن للممون على شكل خاصية اسمها store. هل تتذكرون ال Reducers ؟ لدينا الآن الممون (Provider) ولدينا المخرن (Store) ولدينا واجهة المستخدم (UI). يتبقى لنا أن نقوم بإنشاء وظيفة Reducer واحدة على الأقل لكي نتمكن من تمرير الحالة للمخرن الذي قمنا بتعريفه. ال Reducer هو مجرد دالة جافاسكريبت عادية تقبل معاملين اثنين : الحالة الآنية (Current state)، والإجراء المتخذ (Action)، بينما تقوم بإرجاع كائن يمثل حالة التطبيق الجديدة (New state). لنقم بتعريف هذه الدالة ونمررها كمعامل للمخرن الذي أنشأناه قبل هنيهة : import { createStore } from 'redux'; function counterReducer(state = {count: 0}, action) { return state; } const store = createStore(counterReducer); const App = () => ( <Provider store={store}> <Counter/> </Provider> ); 1 2 3 4 5 6 7 8 9 10 11 12 13 import { createStore } from 'redux'; function counterReducer(state = {count: 0}, action) { return state; } const store = createStore(counterReducer); const App = () => ( <Provider store={store}> <Counter/> </Provider> ); نلاحظ أننا قمنا بإعطاء قيمة افتراضية للكائن state حيث count يساوي 0. وبما أننا لم نقم بعد بأي إجراء داخل ال Reducer فإننا نقوم بإرجاع الحالة الإفتراضية نفسها. سنقوم بإجراء تعديلات على دالة counterReducer حتى تأخذ بعين الإعتبار الإجراءات التي ستتوصل بها، وهي في مثالنا البسيط هذا إجراءين اثنين لا ثالث لهما : INCREMENT و DECREMENT. نتذكر جميعا عندما تحدثنا عن بنية ال actions، حيث أنها عبارة عن كائنات تضم خاصية واحد إجبارية هي type وخاصيات أخرى اختيارية لتمرير عدد من البيانات إلى ال Reducer. في تطبيق العداد لا نريد تمرير أي معلومة لهذا ال Reducer، فكلما نسعى إليه هو إضافة أو طرح العدد 1 بحسب نوع (Type) الإجراء (INCREMENT أو DECREMENT). إذن الإجراء (action) سيكون على هذه الهيئة البسيطة : { type: "INCREMENT" } 1 2 3 { type: "INCREMENT" } لنعد الآن إلى دالتنا counterReducer ونستثمر فيها ما تعلمناه : function counterReducer(state = {count: 0}, action) { switch(action.type) { case 'INCREMENT': return { count: state.count + 1 }; case 'DECREMENT': return { count: state.count - 1 }; default: return state; } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 function counterReducer(state = {count: 0}, action) { switch(action.type) { case 'INCREMENT': return { count: state.count + 1 }; case 'DECREMENT': return { count: state.count - 1 }; default: return state; } } يجب الإنتباه إلى أنه يجب على ال Reducer إرجاع حالة جديدة حتى ولو لم يتم اتخاذ أي إجراء بداخله، ولذلك نقوم دائما بإرجاع الحالة الإفتراضية في منطقة default وإلا فإن قيمة الحالة ستكون undefined. ملاحظة مهمة : State is immutable تتبع مكتبة Redux نمط ومبادئ البرمجة الوظيفية في جافاسكريبت، ومن أهم تجلياتها أن الكائن state غير قابل للتغيير بصفة مباشرة (Immutable). هذا المبدأ معروف في functional programming باسم Immutability. هذا يعني أنه لا يمكننا كتابة أكواد كهذه : function badCounterReducer(state = {count: 0}, action) { switch(action.type) { case 'INCREMENT': // NO! BAD: this is changing state! state.count++; return state; case 'DECREMENT': // NO! BAD: this is changing state too! state.count--; return state; default: // this is fine. return state; } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 function badCounterReducer(state = {count: 0}, action) { switch(action.type) { case 'INCREMENT': // NO! BAD: this is changing state! state.count++; return state; case 'DECREMENT': // NO! BAD: this is changing state too! state.count--; return state; default: // this is fine. return state; } } لا يمكن كذلك استخدام وظائف المصفوفات مثل push لأنها تقوم بتغيير المصفوفة عوضا عن إنشاء مصفوفة جديدة كما تفعل الوظيفة concat مثلا، وبالتالي ينصح دائما باستخدام الأخيرة في مثل هذه الحالات حتى نظل محافظين على مبدأ الغير قابلية للتغيير أو Immutability. على كل حال، موضوع Immutability يحتاج شرحا موسعا ومقالا خاصا، ننجزه في قادم الأيام إن شاء الله لحظة من فضلك : من أين جاءت هذه الإجراءات ؟ قد تسأل نفسك وأنت تقرأ الدرس: من أين جاءت هذه ال actions ؟ وكيف وصلت لدالة counterReducer ؟ إذا سألت هذا السؤال فأهنئك على تركيزك العالي، فطرح الأسئلة الجيدة هي أولى وأهم مراحل التعلم. ببساطة الإجراء ات يتم إطلاقها من داخل مكونات React عن طريق الوظيفة disptach التي يوفرها Redux، وهي في الأصل وظيفة خاصة بالمخزن أو Store يمكن استدعاؤها على النحو التالي : store.dispatch(someAction) 1 store.dispatch(someAction) ولكن الدالة connect التي تمنحها الحزمة react-redux، جعلت مهمة إطلاق الإجراءات أكثر عملية وملاءمة لبيئة React.js، حيث أنها توفر الوظيفة disptach كخاصية (prop) للمكون، تماما كما تحول state إلى props خاصة بالمكون مثلما رأينا في فقرة سابقة. هكذا سيصبح شكل المكون Counter الجديد على هذا النحو : import React from 'react'; import { connect } from 'react-redux'; class Counter extends React.Component { increment = () => { this.props.dispatch({ type: 'INCREMENT' }); } decrement = () => { this.props.dispatch({ type: 'DECREMENT' }); } render() { return ( <div> <h2>Counter</h2> <div> <button onClick={this.decrement}>-</button> <span>{this.props.count}</span> <button onClick={this.increment}>+</button> </div> </div> ) } } function mapStateToProps(state) { return { count: state.count }; } export default connect(mapStateToProps)(Counter); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 import React from 'react'; import { connect } from 'react-redux'; class Counter extends React.Component { increment = () => { this.props.dispatch({ type: 'INCREMENT' }); } decrement = () => { this.props.dispatch({ type: 'DECREMENT' }); } render() { return ( <div> <h2>Counter</h2> <div> <button onClick={this.decrement}>-</button> <span>{this.props.count}</span> <button onClick={this.increment}>+</button> </div> </div> ) } } function mapStateToProps(state) { return { count: state.count }; } export default connect(mapStateToProps)(Counter); النهاية هكذا نكون قد أنهينا هذا الدرس الذي يمثل مدخلا لفهم عميق وجيد لمكتبة Redux وآلية عملها. هناك جزئيات وتفاصيل عديدة لم نتطرق إليها حتى لا يطول الدرس أكثر مما هو طويل الآن وحتى لا يستشكل فهمه على المبتدئين الذين نولي لهم دائما أهمية قصوى في مدونة توتومينا. المصدر: منتديات رحيل لكل العرب - من قسم: قسم تطوير المواقع ومحركات البحث والسيو Seo والووردبريس WordPress |
اقتباس |
أضف رد جديد |
الذين يشاهدون محتوى الموضوع الآن : 1 ( الأعضاء 0 والزوار 1) | |
|
|
المواضيع المتشابهه | ||||
الموضوع | كاتب الموضوع | المنتدى | مشاركات | آخر مشاركة |
كل شء عن أطر العمل Angular ،React.js و Vue.js | raheel | قسم تطوير المواقع ومحركات البحث والسيو Seo والووردبريس WordPress | 0 | 10-04-2019 08:34 PM |
كيفية تنسيق المكونات في React.js | raheel | قسم تطوير المواقع ومحركات البحث والسيو Seo والووردبريس WordPress | 0 | 01-04-2019 06:05 PM |
كيفية إعداد مشروع React.js باستخدام Webpack 4 و Babel | raheel | قسم تطوير المواقع ومحركات البحث والسيو Seo والووردبريس WordPress | 0 | 01-04-2019 06:04 PM |
تعرف علي الكتب الممتازة التي تساعدك علي تعلم React.js في 2019 | raheel | قسم تطوير المواقع ومحركات البحث والسيو Seo والووردبريس WordPress | 0 | 01-04-2019 05:27 PM |
كيفية استغلال وقت الفراغ | رفيق الدرب | المواضيع العامة | 0 | 07-02-2019 03:39 AM |