التنقل في النظام البيئي React.js
نشرت: 2022-03-11سرعة الابتكار في JavaScript Land عالية جدًا ، حتى أن بعض الناس يعتقدون أنها تأتي بنتائج عكسية. يمكن للمكتبة أن تنتقل من لعبة تم تبنيها في وقت مبكر ، إلى أحدث ما توصلت إليه التكنولوجيا ، إلى عفا عليها الزمن في غضون بضعة أشهر. إن القدرة على تحديد أداة ستظل ذات صلة لمدة عام آخر على الأقل تصبح فنًا بحد ذاته.
عندما تم إصدار React.js قبل عامين ، كنت أتعلم Angular ، وسرعان ما رفضت React على أنها مكتبة غامضة ولكنها نموذجية أخرى. خلال هذين العامين ، اكتسب Angular حقًا موطئ قدم بين مطوري JavaScript ، وأصبح مرادفًا تقريبًا لتطوير JS الحديث. حتى أنني بدأت في رؤية Angular في بيئات شركات محافظة للغاية ، واعتبرت أن مستقبلها المشرق أمر مفروغ منه.
لكن فجأة حدث شيء غريب. يبدو أن Angular أصبح ضحية لتأثير Osborne ، أو "الموت بالإعلان المسبق". أعلن الفريق ، أولاً ، أن Angular 2 ستكون مختلفة تمامًا ، بدون مسار انتقال واضح من Angular 1 ، وثانيًا ، أن Angular 2 لن تكون متاحة لمدة عام آخر أو نحو ذلك. ماذا يخبر ذلك الشخص الذي يريد أن يبدأ مشروع ويب جديد؟ هل تريد كتابة مشروعك الجديد في إطار عمل سيصبح قديمًا بسبب إصدار إصدار جديد؟
هذا القلق بين المطورين لعب لصالح React ، التي كانت تسعى إلى ترسيخ نفسها في المجتمع. لكن React كانت تسوّق نفسها دائمًا على أنها "V" في "MVC". تسبب هذا في قدر من الإحباط بين مطوري الويب ، الذين اعتادوا العمل مع أطر عمل كاملة. كيف يمكنني ملء القطع المفقودة؟ هل يجب أن أكتب خاصتي؟ هل يجب علي استخدام مكتبة موجودة فقط؟ إذا كان كذلك؛ أيهما؟
من المؤكد أن Facebook (منشئو React.js) كان لديهم آس آخر في الحفرة: سير عمل Flux ، والذي وعد بملء وظائف "M" و "C" المفقودة. لجعل الأمور أكثر إثارة للاهتمام ، ذكر Facebook أن Flux هو "نمط" وليس إطار عمل ، وتنفيذهم لـ Flux هو مجرد مثال واحد على هذا النمط. ووفقًا لكلمتهم ، كان تنفيذها مبسطًا حقًا ، واشتمل على كتابة الكثير من النصوص المعيارية المطولة والمتكررة من أجل المضي قدمًا في الأمور.
جاء مجتمع المصادر المفتوحة للإنقاذ ، وبعد عام ، لدينا العشرات من مكتبات Flux ، وحتى بعض المشاريع الوصفية التي تهدف إلى مقارنتها. هذا أمر جيد؛ بدلاً من إطلاق بعض إطار عمل الشركة الجاهز ، نجح Facebook في إثارة الاهتمام في المجتمع ، وشجع الناس على التوصل إلى حلول خاصة بهم.
هناك أثر جانبي مثير للاهتمام لهذا النهج: عندما تضطر إلى الجمع بين الكثير من المكتبات المختلفة للحصول على إطار عمل كامل ، فأنت تهرب بشكل فعال من قفل البائع ، ويمكن إعادة استخدام الابتكارات التي تظهر أثناء إنشاء إطار العمل الخاص بك بسهولة في مكان آخر.
لهذا السبب فإن الأشياء الجديدة حول React مثيرة جدًا للاهتمام ؛ يمكن إعادة استخدام معظمها بسهولة في بيئات JavaScript الأخرى. حتى إذا كنت لا تخطط لاستخدام React ، فإن إلقاء نظرة على نظامها الإيكولوجي أمر ملهم. قد ترغب في تبسيط نظام الإنشاء الخاص بك باستخدام حزمة Webpack القوية ، ولكن سهلة التكوين نسبيًا ، أو البدء في كتابة ECMAScript 6 وحتى ECMAScript 7 اليوم باستخدام مترجم Babel.
في هذه المقالة ، سأستعرض بعض الميزات والمكتبات الشيقة المتوفرة. لذا ، دعنا نستكشف نظام React البيئي!
بناء النظام
يمكن القول إن نظام البناء هو أول شيء يجب أن تهتم به عند إنشاء تطبيق ويب جديد. نظام البناء ليس فقط أداة لتشغيل البرامج النصية ، ولكن في عالم JavaScript ، فإنه عادة ما يشكل الهيكل العام لتطبيقك. أهم المهام التي يجب أن يغطيها نظام البناء هي كما يلي:
- إدارة التبعيات الخارجية والداخلية
- تشغيل المجمعين والمعالجات الأولية
- تحسين الأصول للإنتاج
- تشغيل خادم الويب للتطوير ومراقب الملفات وأداة إعادة تحميل المتصفح
في السنوات الأخيرة ، تم تقديم سير عمل Yeoman مع Bower و Grunt باعتباره الثالوث المقدس لتطوير الواجهة الحديثة ، وحل مشاكل إنشاء النماذج المعيارية ، وإدارة الحزم ، وتشغيل المهام المشتركة على التوالي ، مع التحول الشعبي الأكثر تقدمًا من Grunt إلى Gulp مؤخرًا.
في بيئة React ، يمكنك نسيانها بأمان. لا يعني ذلك أنه لا يمكنك استخدامها ، ولكن من المحتمل أن تفلت من استخدام Webpack و NPM القديم الجيد. كيف يعقل ذلك؟ Webpack عبارة عن مجمع للوحدات النمطية ، والذي ينفذ بناء جملة الوحدة النمطية CommonJS ، الشائع في عالم Node.js ، في المتصفح أيضًا. إنه في الواقع يجعل الأمور أبسط لأنك لست بحاجة إلى تعلم مدير حزم آخر للواجهة الأمامية ؛ يمكنك فقط استخدام NPM ومشاركة التبعيات بين الخادم والواجهة الأمامية. لا تحتاج أيضًا إلى التعامل مع مشكلة تحميل ملفات JS بالترتيب الصحيح لأنه يتم استنتاجها من عمليات استيراد التبعية المحددة في كل ملف ، ويتم ربط السرب بالكامل بشكل صحيح في برنامج نصي واحد قابل للتحميل.
لجعل الشيء أكثر جاذبية ، يمكن لـ Webpack ، على عكس ابن عمها الأقدم Browserify ، التعامل مع أنواع الأصول الأخرى أيضًا. على سبيل المثال ، باستخدام برامج التحميل ، يمكنك تحويل أي ملف أصل إلى وظيفة JavaScript إما مضمنة أو تحمّل الملف المشار إليه. لذلك ، انس أمر المعالجة المسبقة يدويًا والإشارة إلى الأصول من HTML. ما عليك سوى require
ملفات CSS / SASS / LESS من JavaScript ، وسيتولى Webpack الاهتمام بالباقي من خلال ملف تكوين بسيط. يتضمن Webpack أيضًا خادم ويب للتطوير ومراقب للملفات. بالإضافة إلى ذلك ، يمكنك استخدام مفتاح "scripts"
في package.json
لتحديد سطر واحد من shell:
{ "name": "react-example-filmdb", "version": "0.0.1", "description": "Isomorphic React + Flux film database example", "main": "server/index.js", "scripts": { "build": "./node_modules/.bin/webpack --progress --stats --config ./webpack/prod.config.js", "dev": "node --harmony ./webpack/dev-server.js", "prod": "NODE_ENV=production node server/index.js", "test": "./node_modules/.bin/karma start --single-run", "postinstall": "npm run build" } ... }
وهذا كل ما تحتاجه لاستبدال Gulp و Bower. بالطبع ، لا يزال بإمكانك استخدام Yeoman لإنشاء تطبيق Boilerplate. لا تثبط عزيمتك عندما لا يكون هناك منشئ Yeoman للأشياء التي تريدها (معظم المكتبات المتطورة غالبًا لا تحتوي على واحد). لا يزال بإمكانك استنساخ بعض النماذج المعيارية من GitHub والاختراق بعيدًا.
ECMAScript of Tomorrow، Today. برنامج ECMAScript of Tomorrow، Today
لقد زادت وتيرة تطوير لغة JavaScript بشكل كبير في السنوات الأخيرة ، وبعد فترة من إزالة المراوغات واستقرار اللغة ، نرى الآن ميزات جديدة قوية قادمة. تم الانتهاء من مسودة مواصفات ECMAScript 6 (ES6) ، وعلى الرغم من ذلك لم يتم إصداره رسميًا بعد ، فقد تم اعتماده على نطاق واسع بالفعل. لا يزال العمل على ECMAScript 7 (ES7) قيد التقدم ، ولكن يتم اعتماد العديد من ميزاته بالفعل من قبل المكتبات الأكثر حداثة.
كيف يكون هذا ممكنا؟ ربما تعتقد أنه لا يمكنك الاستفادة من ميزات JavaScript الجديدة اللامعة هذه حتى يتم دعمها في Internet Explorer ، لكن فكر مرة أخرى. لقد أصبحت ترانسبيلرز ES بالفعل في كل مكان حتى أنه يمكننا الاستغناء عن دعم المتصفح المناسب. أفضل ناقل ES متاح الآن هو Babel: سيأخذ أحدث كود ES6 + الخاص بك ، ويحوله إلى Vanilla ES5 ، لذلك ، يمكنك استخدام أي ميزة ES جديدة بمجرد اختراعها (وتنفيذها في Babel ، وهو ما يحدث عادةً تمامًا. بسرعة).
تُعد أحدث ميزات JavaScript مفيدة في جميع أطر عمل الواجهة الأمامية ، وقد تم تحديث React مؤخرًا للعمل بشكل جيد مع مواصفات ES6 و ES7. يجب أن تقضي هذه الميزات الجديدة على الكثير من الصداع عند التطوير باستخدام React. دعنا نلقي نظرة على بعض الإضافات الأكثر فائدة ، وكيف يمكنها الاستفادة من مشروع React. سنرى لاحقًا كيفية استخدام بعض الأدوات والمكتبات المفيدة مع React مع الاستفادة من بناء الجملة المحسَّن هذا.
فصول ES6
تعد البرمجة الموجهة للكائنات نموذجًا قويًا ومُعتمد على نطاق واسع ، لكن يعتبره JavaScript غريبًا بعض الشيء. تبنت معظم أطر الواجهة الأمامية ، سواء كانت Backbone أو Ember أو Angular أو React ، طرقها الخاصة في تحديد الفئات وإنشاء الكائنات. ولكن مع ES6 ، لدينا الآن فئات تقليدية في JavaScript ، ومن المنطقي ببساطة استخدامها بدلاً من كتابة تطبيقنا الخاص. لذلك ، بدلاً من:
React.createClass({ displayName: 'HelloMessage', render() { return <div>Hello {this.props.name}</div>; } })
يمكننا أن نكتب:
class HelloMessage extends React.Component { render() { return <div>Hello {this.props.name}</div>; } }
للحصول على مثال أكثر تفصيلاً ، ضع في اعتبارك هذا الرمز ، باستخدام الصيغة القديمة:
React.createClass({ displayName: 'Counter', getDefaultProps: function(){ return {initialCount: 0}; }, getInitialState: function() { return {count: this.props.initialCount} }, propTypes: {initialCount: React.PropTypes.number}, tick() { this.setState({count: this.state.count + 1}); }, render() { return ( <div onClick={this.tick}> Clicks: {this.state.count} </div> ); } });
وقارن بإصدار ES6:
class Counter extends React.Component { static propTypes = {initialCount: React.PropTypes.number}; static defaultProps = {initialCount: 0}; constructor(props) { super(props); this.state = {count: props.initialCount}; } state = {count: this.props.initialCount}; tick() { this.setState({count: this.state.count + 1}); } render() { return ( <div onClick={this.tick.bind(this)}> Clicks: {this.state.count} </div> ); } }
هنا ، لم تعد طرق دورة حياة getDefaultProps
و getInitialState
ضرورية. يصبح getDefaultProps
defaultProps
لمتغير الفئة الثابتة ، ويتم تحديد الحالة الأولية فقط في المُنشئ. العيب الوحيد هو أن الطرق لم تعد مرتبطة تلقائيًا ، لذلك عليك استخدام bind
عند استدعاء معالجات من JSX.
مصممون
الزخرفة ميزة مفيدة من ES7. إنها تسمح لك بزيادة سلوك وظيفة أو فئة عن طريق لفها داخل وظيفة أخرى. على سبيل المثال ، لنفترض أنك تريد أن يكون لديك نفس معالج التغيير في بعض مكوناتك ، لكنك لا تريد الالتزام بمضاد الميراث. يمكنك استخدام مصمم فئة بدلا من ذلك. دعنا نحدد المصمم على النحو التالي:
addChangeHandler: function(target) { target.prototype.changeHandler = function(key, attr, event) { var state = {}; state[key] = this.state[key] || {}; state[key][attr] = event.currentTarget.value; this.setState(state); }; return target; }
الشيء المهم هنا هو أن الوظيفة addChangeHandler
تضيف وظيفة changeHandler
إلى النموذج الأولي للفئة الهدف.
لتطبيق المصمم يمكننا أن نكتب:
MyClass = changeHandler(MyClass)
أو بشكل أكثر أناقة ، مع بناء جملة ES7:
@addChangeHandler class MyClass { ... }
بالنسبة إلى محتوى دالة changeHandler
نفسها ، مع غياب React لربط البيانات ثنائي الاتجاه ، فإن العمل مع المدخلات في React قد يكون مملاً. تحاول وظيفة changeHandler
تسهيل الأمر. تحدد المعلمة الأولى key
في كائن الحالة ، والذي سيكون بمثابة كائن بيانات للإدخال. المعلمة الثانية هي السمة ، والتي سيتم حفظ القيمة من حقل الإدخال إليها. يتم تعيين هاتين المعلمتين من JSX باستخدام الكلمة الأساسية bind
.
@addChangeHandler class LoginInput extends React.Component { constructor(props) { super(props); this.state = { login: {} }; } render() { return ( <input type='text' value={this.state.login.username} onChange={this.changeHandler.bind(this, 'login', 'username')} /> <input type='password' value={this.state.login.password} onChange={this.changeHandler.bind(this, 'login', 'password')} /> ) } }
عندما يغير المستخدم حقل اسم المستخدم ، يتم حفظ قيمته في this.state.login.username
، دون الحاجة إلى تحديد المزيد من المعالجات المخصصة.
وظائف السهم
لقد كان this
السياق الديناميكي لجافا سكريبت مصدر إزعاج مستمر للمطورين لأنه ، بشكل غير متوقع إلى حد ما ، يتم إعادة تعيين سياق الوظيفة المتداخلة this
إلى عام ، حتى داخل الفصل. لإصلاح ذلك ، من الضروري عادةً حفظ this
في بعض متغيرات النطاق الخارجي (عادةً _this
) واستخدامها في الدوال الداخلية:
class DirectorsStore { onFetch(directors) { var _this = this; this.directorsHash = {}; directors.forEach(function(x){ _this.directorsHash[x._id] = x; }) } }
باستخدام بناء جملة ES6 الجديد ، يمكن إعادة كتابة function(x){
كـ (x) => {
. لا يربط تعريف طريقة "السهم" this
بالنطاق الخارجي بشكل صحيح فحسب ، ولكنه أيضًا أقصر بشكل كبير ، وهو أمر مهم بالتأكيد عند كتابة الكثير من التعليمات البرمجية غير المتزامنة.
onFetch(directors) { this.directorsHash = {}; directors.forEach((x) => { this.directorsHash[x._id] = x; }) }
تدمير التخصيصات
تتيح لك تخصيصات التدمير ، المقدمة في ES6 ، الحصول على كائن مركب على الجانب الأيسر من المهمة:
var o = {p: 42, q: true}; var {p, q} = o; console.log(p); // 42 console.log(q); // true
هذا جميل ، لكن كيف يساعدنا فعلاً في React؟ ضع في اعتبارك المثال التالي:
function makeRequest(url, method, params) { var config = { url: url, method: method, params: params }; ... }
مع التدمير ، يمكنك حفظ بعض ضغطات المفاتيح. يتم تعيين قيم للمفاتيح الحرفية {url, method, params}
تلقائيًا من النطاق بنفس أسماء المفاتيح. يتم استخدام هذا المصطلح في كثير من الأحيان ، والقضاء على التكرار يجعل الكود أقل عرضة للخطأ.
function makeRequest(url, method, params) { var config = {url, method, params}; ... }
يمكن أن يساعدك التدمير أيضًا في تحميل مجموعة فرعية فقط من الوحدة:
const {clone, assign} = require('lodash'); function output(data, optional) { var payload = clone(data); assign(payload, optional); }
الحجج: الافتراضي والراحة والانتشار
تعتبر الحجج الوظيفية أكثر قوة في ES6. أخيرًا ، يمكنك تعيين الوسيطة الافتراضية :
function http(endpoint, method='GET') { console.log(method) ... } http('/api') // GET
تعبت من العبث مع وجوه arguments
غير العملية؟ باستخدام المواصفات الجديدة ، يمكنك الحصول على باقي الوسائط كمصفوفة:
function networkAction(context, method, ...rest) { // rest is an array return method.apply(context, rest); }
وإذا كنت لا تحب استدعاء apply()
، فيمكنك فقط نشر مصفوفة في وسيطات دالة:
myArguments = ['foo', 'bar', 123]; myFunction(...myArguments);
المولدات والوظائف غير المتزامنة
قدم ES6 مولدات JavaScript. المولد هو في الأساس وظيفة JavaScript يمكن إيقاف تنفيذها مؤقتًا ثم استئنافها لاحقًا ، مع تذكر حالتها. في كل مرة يتم العثور على الكلمة الأساسية للإنتاجية ، يتم إيقاف التنفيذ مؤقتًا ، ويتم تمرير وسيطة yield
yield
الكائن المستدعي:
function* sequence(from, to) { console.log('Ready!'); while(from <= to) { yield from++; } }
إليك مثال على هذا المولد أثناء العمل:
> var cursor = sequence(1,3) Ready! > cursor.next() { value: 1, done: false } > cursor.next() { value: 2, done: false } > cursor.next() { value: 3, done: false } > cursor.next() { value: undefined, done: true }
عندما نسمي وظيفة المولد ، فإنها تنفذ حتى yield
الأول ، ثم تتوقف. بعد أن نطلق على next()
، تُرجع القيمة الأولى ، وتستأنف التنفيذ. يُرجع كل yield
قيمة أخرى ، ولكن بعد الاستدعاء الثالث ، تنتهي وظيفة المولد ، وستُرجع كل استدعاء لاحق لـ next()
{ value: undefined, done: true }
.
بالطبع ، ليس الهدف من المولدات إنشاء تسلسلات رقمية معقدة. الجزء المثير هو قدرتها على إيقاف واستئناف تنفيذ الوظيفة ، والتي يمكن استخدامها للتحكم في تدفق البرنامج غير المتزامن والتخلص أخيرًا من وظائف رد الاتصال المزعجة.
لتوضيح هذه الفكرة ، نحتاج أولاً إلى وظيفة غير متزامنة. عادة ، لدينا بعض عمليات الإدخال / الإخراج ، ولكن من أجل التبسيط ، دعنا فقط نستخدم setTimeout
الوعد. (لاحظ أن ES6 قدم أيضًا وعودًا أصلية لـ JavaScript.)
function asyncDouble(x) { var deferred = Promise.defer(); setTimeout(function(){ deferred.resolve(x*2); }, 1000); return deferred.promise; }
بعد ذلك ، نحتاج إلى وظيفة المستهلك:
function consumer(generator){ var cursor = generator(); var value; function loop() { var data = cursor.next(value); if (data.done) { return; } else { data.value.then(x => { value = x; loop(); }) } } loop(); }
تأخذ هذه الوظيفة أي مُنشئ كوسيطة ، وتستمر في استدعاء next()
طالما أن هناك قيمًا يجب أن yield
عنها. في هذه الحالة ، تعتبر القيم الناتجة وعودًا ، ولذا فمن الضروري انتظار حل الوعود ، واستخدام التكرار مع loop()
لتحقيق التكرار عبر الوظائف المتداخلة.
يتم حل قيمة الإرجاع في معالج then()
، وتمريرها إلى value
المحددة في النطاق الخارجي ، والتي سيتم تمريرها إلى استدعاء next(value)
. يجعل هذا الاستدعاء القيمة نتيجة لتعبير العائد المقابل. هذا يعني أننا الآن قادرون على الكتابة بشكل غير متزامن دون أي عمليات رد نداء على الإطلاق:
function* myGenerator(){ const data1 = yield asyncDouble(1); console.log(`Double 1 = ${data1}`); const data2 = yield asyncDouble(2); console.log(`Double 2 = ${data2}`); const data3 = yield asyncDouble(3); console.log(`Double 3 = ${data3}`); } consumer(myGenerator);
سيتم إيقاف مولد myGenerator
مؤقتًا عند كل yield
، في انتظار أن يفي المستهلك بالوعد الذي تم حله. وبالفعل ، سنرى الأرقام المحسوبة تظهر في لوحة التحكم بفواصل زمنية مدتها ثانية واحدة.
Double 1 = 2 Double 2 = 4 Double 3 = 6
يوضح هذا المفهوم الأساسي ، ومع ذلك ، لا أوصي باستخدام هذا الرمز في الإنتاج. بدلاً من ذلك ، اختر مكتبة تم اختبارها جيدًا مثل co. سيسمح لك ذلك بكتابة رمز غير متزامن مع العوائد ، بما في ذلك معالجة الأخطاء:
co(function *(){ var a = yield Promise.resolve(1); console.log(a); var b = yield Promise.resolve(2); console.log(b); var c = yield Promise.resolve(3); console.log(c); }).catch(function(err){ console.error(err.stack); });
لذلك ، يوضح هذا المثال كيفية كتابة رمز غير متزامن دون عمليات الاسترجاعات باستخدام مولدات ES6. يأخذ ES7 هذا النهج خطوة أخرى إلى الأمام عن طريق إدخال الكلمات الرئيسية غير async
await
، وإزالة الحاجة إلى مكتبة المولدات تمامًا. بهذه الإمكانية ، سيبدو المثال السابق كما يلي:
async function (){ try { var a = await Promise.resolve(1); console.log(a); var b = await Promise.resolve(2); console.log(b); var c = await Promise.resolve(3); console.log(c); } catch (err) { console.error(err.stack); } };
في رأيي ، فإن هذا يزيل عناء العمل مع التعليمات البرمجية غير المتزامنة في JavaScript. ليس فقط في React ، ولكن في كل مكان آخر أيضًا.
المولدات ليست فقط أكثر إيجازًا ووضوحًا ، ولكنها تتيح لنا أيضًا استخدام التقنيات التي سيكون من الصعب جدًا تنفيذها مع عمليات الاسترجاعات. من الأمثلة البارزة على جودة المولد هي مكتبة البرامج الوسيطة koa لـ Node.js. تهدف إلى استبدال Express ، وتحقيقًا لهذا الهدف ، تأتي بميزة قاتلة واحدة: لا تتدفق سلسلة البرامج الوسيطة فقط إلى المصب (مع طلب العميل) ، ولكن أيضًا في المنبع ، مما يسمح بمزيد من التعديل على استجابة الخادم. ضع في اعتبارك مثال خادم koa التالي:
// Response time logger middleware app.use(function *(next){ // Downstream var start = new Date; yield next; // Upstream this.body += ' World'; var ms = new Date - start; console.log('%s %s - %s', this.method, this.url, ms); }); // Response handler app.use(function *(){ this.body = 'Hello'; }); app.listen(3000);

yield
البرنامج الوسيط للاستجابة s downstream إلى معالج الاستجابة ، والذي يحدد جسم الاستجابة ، وفي التدفق العلوي (بعد تعبير yield
) ، يُسمح بإجراء مزيد من التعديل على هذا this.body
، بالإضافة إلى وظائف أخرى مثل تسجيل الوقت ، وهو أمر ممكن لأن المنبع والمصب يشتركان في نفس سياق الوظيفة. هذا أقوى بكثير من Express ، حيث تنتهي محاولة تحقيق الشيء نفسه على النحو التالي:
var start; // Downstream middleware app.use(function(req, res, next) { start = new Date; next(); // Already returned, cannot continue here }); // Response app.use(function (req, res, next){ res.send('Hello World') next(); }); // Upstream middleware app.use(function(req, res, next) { // res already sent, cannot modify var ms = new Date - start; console.log('%s %s - %s', this.method, this.url, ms); next(); }); app.listen(3000);
ربما يمكنك بالفعل اكتشاف الخطأ هنا ؛ استخدام متغير start
"عالمي" سينتج عنه حالة سباق ، مما يعيد الهراء مع الطلبات المتزامنة. الحل عبارة عن حل بديل غير واضح ، ويمكنك أن تنسى تعديل الاستجابة في التدفق الأولي.
أيضًا ، عند استخدام koa ، ستحصل على تدفق عمل غير متزامن للمولد مجانًا:
app.use(function *(){ try { const part1 = yield fs.readFile(this.request.query.file1, 'utf8'); const part2 = yield fs.readFile(this.request.query.file2, 'utf8'); this.body = part1 + part2; } catch (err) { this.status = 404 this.body = err; } }); app.listen(3000);
يمكنك أن تتخيل الوعود وعمليات رد النداء المتضمنة في إعادة إنشاء هذا المثال الصغير في Express.
كيف يرتبط كل حديث Node.js هذا بـ React؟ حسنًا ، Node هو الخيار الأول عند التفكير في نهاية خلفية مناسبة لـ React. نظرًا لأن Node مكتوب بلغة JavaScript أيضًا ، فهو يدعم مشاركة الكود بين الواجهة الخلفية والواجهة الأمامية ، مما يسمح لنا ببناء تطبيقات ويب React متشابهة الشكل. لكن ، المزيد عن هذا لاحقًا.
مكتبة الجريان
تعتبر React رائعة في إنشاء مكونات عرض قابلة للتكوين ، لكننا بحاجة إلى طريقة ما لإدارة البيانات والحالة عبر التطبيق بأكمله. لقد تم الاتفاق عالميًا تقريبًا على أن React يتم استكماله بشكل أفضل من خلال بنية تطبيق Flux. إذا كنت جديدًا تمامًا على Flux ، فإنني أوصي بتحديث سريع.
ما لم يتم الاتفاق عليه عالميًا هو اختيار تطبيقات Flux العديدة. سيكون Facebook Flux هو الخيار الواضح ، لكنه مطول للغاية بالنسبة لمعظم الناس. تركز التطبيقات البديلة في الغالب على تقليل كمية النموذج المعياري المطلوب باستخدام نهج الإفراط في التهيئة ، وأيضًا مع بعض وظائف الراحة للمكونات ذات الترتيب الأعلى ، والعرض من جانب الخادم ، وما إلى ذلك. يمكن رؤية بعض أفضل المتنافسين ، بمقاييس شعبية مختلفة ، هنا. لقد بحثت في Alt و Reflux و Flummox و Fluxxor و Marty.js.
طريقتي في اختيار المكتبة الصحيحة ليست موضوعية بأي حال من الأحوال ، لكنها قد تساعد على أي حال. كانت Fluxxor واحدة من هذه المكتبات الأولى التي قمت بسحبها ، لكنها تبدو الآن قديمة بعض الشيء. يعد Marty.js مثيرًا للاهتمام ، ويحتوي على الكثير من الميزات ، ولكنه لا يزال يتضمن الكثير من النماذج المعيارية ، وبعض الوظائف تبدو غير ضرورية. يبدو Reflux رائعًا ولديه بعض التأثير ، ولكنه يشعر بصعوبة بعض الشيء بالنسبة للمبتدئين ، ويفتقر أيضًا إلى الوثائق المناسبة. يتشابه Flummox و Alt إلى حد كبير ، ولكن يبدو أن Alt يحتوي على عدد أقل من النماذج المعيارية ، وتطوير نشط للغاية ، ووثائق محدثة ، ومجتمع Slack مفيد ، لذلك اخترت Alt.
الجريان البديل
مع Alt ، يصبح سير عمل Flux أبسط بكثير دون فقد أي من قوته. توضح وثائق Flux على Facebook الكثير عن المرسل ، لكننا أحرار في تجاهل ذلك لأنه ، في Alt ، يكون المرسل مرتبطًا ضمنيًا بالإجراءات عن طريق الاتفاقية ، وعادة لا يتطلب أي رمز مخصص. هذا يتركنا فقط بالمخازن والإجراءات والمكونات . يمكن استخدام هذه الطبقات الثلاث بطريقة تجعلها تتناسب بشكل جيد مع نموذج التفكير MVC : المتاجر عبارة عن نماذج ، والإجراءات عبارة عن وحدات تحكم ، والمكونات هي طرق عرض. يتمثل الاختلاف الرئيسي في تدفق البيانات أحادي الاتجاه بشكل مركزي في نمط التدفق ، مما يعني أن وحدات التحكم (الإجراءات) لا يمكنها تعديل طرق العرض (المكونات) بشكل مباشر ، ولكن بدلاً من ذلك ، يمكنها فقط تشغيل تعديلات النموذج (المخزن) ، والتي ترتبط بها العروض بشكل سلبي. كان هذا بالفعل أفضل ممارسة لبعض مطوري Angular المستنيرين.
سير العمل كما يلي:
- المكونات تبدأ الإجراءات.
- مخازن الاستماع إلى الإجراءات وتحديث البيانات.
- ترتبط المكونات بالمخازن ، ويتم عرضها عند تحديث البيانات.
أجراءات
عند استخدام مكتبة Alt Flux ، تأتي الإجراءات بشكل عام في نوعين: تلقائي ويدوي. يتم إنشاء الإجراءات التلقائية باستخدام وظيفة generateActions
الإجراءات ، وتنتقل مباشرة إلى المرسل. يتم تعريف الطرق اليدوية على أنها طرق لفئات الإجراءات الخاصة بك ، ويمكن أن تذهب إلى المرسل بحمولة إضافية. حالة الاستخدام الأكثر شيوعًا للإجراءات التلقائية هي إخطار المتاجر ببعض الأحداث في التطبيق. الإجراءات اليدوية هي ، من بين أشياء أخرى ، الطريقة المفضلة للتعامل مع تفاعلات الخادم.
لذا فإن استدعاءات REST API تنتمي إلى الإجراءات. سير العمل الكامل كما يلي:
- المكون يطلق العمل.
- يقوم منشئ الإجراء بتشغيل طلب خادم غير متزامن ، وتنتقل النتيجة إلى المرسل كحمولة.
- يستمع المتجر إلى الإجراء ، ويتلقى معالج الإجراء المقابل النتيجة كوسيطة ، ويقوم المتجر بتحديث حالته وفقًا لذلك.
بالنسبة لطلبات AJAX ، يمكننا استخدام مكتبة axios ، التي تتعامل ، من بين أشياء أخرى ، مع بيانات ورؤوس JSON بسلاسة. بدلاً من الوعود أو عمليات رد النداء ، يمكننا استخدام نمط ES7 غير async
/ await
. إذا لم تكن حالة استجابة POST
هي 2XX ، فسيتم طرح خطأ ، ونقوم بإرسال البيانات التي تم إرجاعها أو تلقي خطأ.
لنلقِ نظرة على صفحة تسجيل الدخول للحصول على مثال بسيط لسير عمل Alt. لا يحتاج إجراء تسجيل الخروج إلى القيام بأي شيء خاص ، فقط قم بإخطار المتجر ، حتى نتمكن من إنشائه تلقائيًا. إجراء تسجيل الدخول يدوي ، ويتوقع بيانات تسجيل الدخول كمعامل لمنشئ الإجراء. بعد أن نحصل على استجابة من الخادم ، إما أن نرسل بيانات النجاح ، أو في حالة حدوث خطأ ، نرسل الخطأ الذي تم تلقيه.
class LoginActions { constructor() { // Automatic action this.generateActions('logout'); } // Manual action async login(data) { try { const response = await axios.post('/auth/login', data); this.dispatch({ok: true, user: response.data}); } catch (err) { console.error(err); this.dispatch({ok: false, error: err.data}); } } } module.exports = (alt.createActions(LoginActions));
المتاجر
يخدم متجر Flux غرضين: لديه معالجات إجراءات ، ويحمل الحالة. دعنا نواصل مثال صفحة تسجيل الدخول الخاصة بنا لنرى كيف يعمل هذا.
لنقم بإنشاء LoginStore
، مع سمتين للحالة: user
، للمستخدم الحالي الذي قام بتسجيل الدخول ، error
، للخطأ الحالي المتعلق بتسجيل الدخول. انطلاقاً من روح الحد من الصيغة المعيارية ، يسمح لنا Alt بالربط بجميع الإجراءات من فئة واحدة باستخدام عمليات ربط ذات وظيفة bindActions
.
class LoginStore { constructor() { this.bindActions(LoginActions); this.user = null; this.error = null; } ...
يتم تحديد أسماء المعالجات من on
الاصطلاح ، قبل اسم الإجراء المقابل. لذلك يتم التعامل مع إجراء login
بواسطة onLogin
وما إلى ذلك. لاحظ أنه سيتم كتابة الحرف الأول من اسم الإجراء بأحرف كبيرة في نمط حالة الجمل. في LoginStore
لدينا ، لدينا المعالجات التالية ، التي تم استدعاؤها بواسطة الإجراءات المقابلة:
... onLogin(data) { if (data.ok) { this.user = data.user; this.error = null; router.transitionTo('home'); } else { this.user = null; this.error = data.error } } onLogout() { this.user = null; this.error = null; } }
عناصر
الطريقة المعتادة لربط المخازن بالمكونات هي استخدام نوع من مزيج React. ولكن نظرًا لأن المزيجات أصبحت عتيقة الطراز ، فلا بد من وجود طريقة أخرى. أحد الأساليب الجديدة هو استخدام مكونات ذات ترتيب أعلى. نأخذ المكون الخاص بنا ونضعه داخل مكون غلاف ، والذي سيهتم بالاستماع إلى المتاجر واستدعاء إعادة التصيير. سيتلقى المكون الخاص بنا حالة المتجر في props
. هذا النهج مفيد أيضًا في تنظيم الكود الخاص بنا في مكونات ذكية وغبية ، والتي أصبحت عصرية مؤخرًا. بالنسبة إلى Alt ، يتم تنفيذ غلاف المكون بواسطة AltContainer
:
export default class Login extends React.Component { render() { return ( <AltContainer stores={{LoginStore: LoginStore}}> <LoginPage/> </AltContainer> )} }
يستخدم مكون LoginPage
الخاص بنا أيضًا أداة الديكور changeHandler
التي تم تقديمها مسبقًا. يتم استخدام البيانات من LoginStore
لعرض الأخطاء في حالة تسجيل الدخول غير الناجح ، ويتم إعادة العرض من قبل AltContainer
. يؤدي النقر فوق زر تسجيل الدخول إلى تنفيذ إجراء login
، وإكمال سير عمل Alt flux:
@changeHandler export default class LoginPage extends React.Component { constructor(props) { super(props); this.state = { loginForm: {} }; } login() { LoginActions.login(this.state.loginForm) } render() { return ( <Alert>{{this.props.LoginStore.error}}</Alert> <Input label='Username' type='text' value={this.state.login.username} onChange={this.changeHandler.bind(this, 'loginForm', 'username')} /> <Input label='Password' type='password' value={this.state.login.password} onChange={this.changeHandler.bind(this, 'loginForm', 'password')} /> <Button onClick={this.login.bind(this)}>Login</Button> )} }
تقديم متماثل
تعد تطبيقات الويب المتشابهة موضوعًا ساخنًا هذه الأيام لأنها تحل بعضًا من أكبر الأعمال الروتينية لتطبيقات الصفحة الواحدة التقليدية. في هذه التطبيقات ، يتم إنشاء الترميز ديناميكيًا بواسطة JavaScript في المتصفح. والنتيجة هي أن المحتوى غير متاح للعملاء الذين تم إيقاف تشغيل JavaScript ، وعلى الأخص برامج زحف الويب لمحركات البحث. هذا يعني أن صفحة الويب الخاصة بك غير مفهرسة ولا تظهر في نتائج البحث. هناك طرق للتغلب على هذا ، لكنها بعيدة كل البعد عن المثالية. يحاول الأسلوب المتماثل إصلاح هذه المشكلة عن طريق العرض المسبق لعنوان URL المطلوب لتطبيق صفحة واحدة على الخادم. باستخدام Node.js ، لديك JavaScript على الخادم ، مما يعني أن React يمكنها أيضًا تشغيل جانب الخادم. لا ينبغي أن يكون هذا صعبًا جدًا ، أليس كذلك؟
تتمثل إحدى العوائق في أن بعض مكتبات Flux ، خاصة تلك التي تستخدم مفردات ، تواجه صعوبات في العرض من جانب الخادم. عندما يكون لديك متاجر Single Flux وطلبات متعددة متزامنة إلى الخادم الخاص بك ، فسيتم خلط البيانات. تحل بعض المكتبات هذا باستخدام مثيلات Flux ، ولكن هذا يأتي مع عيوب أخرى ، لا سيما الحاجة إلى تمرير تلك الحالات في التعليمات البرمجية الخاصة بك. يوفر Alt مثيلات Flux أيضًا ، ولكنه حل أيضًا مشكلة العرض من جانب الخادم مع المفردات ؛ يقوم بمسح المخازن بعد كل طلب ، بحيث يبدأ كل طلب متزامن بسجل نظيف.
يتم توفير جوهر وظيفة العرض من جانب الخادم بواسطة React.renderToString
. يتم تشغيل تطبيق الواجهة الأمامية لـ React بالكامل على الخادم أيضًا. بهذه الطريقة ، لا نحتاج إلى انتظار JavaScript من جانب العميل لإنشاء الترميز ؛ تم إنشاؤه مسبقًا على الخادم لعنوان URL الذي تم الوصول إليه ، وإرساله إلى المتصفح بتنسيق HTML. عندما يتم تشغيل JavaScript العميل ، فإنه ينتقل من حيث توقف الخادم. لدعم هذا ، يمكننا استخدام مكتبة Iso ، والتي من المفترض استخدامها مع Alt.
أولاً ، نقوم بتهيئة Flux على الخادم باستخدام alt.bootstrap
. من الممكن ملء مخازن Flux مسبقًا ببيانات للتقديم. من الضروري أيضًا تحديد المكون الذي سيتم عرضه لعنوان URL ، وهي وظيفة Router
من جانب العميل. نحن نستخدم الإصدار الفردي من alt
، لذلك بعد كل عملية تصيير ، نحتاج إلى alt.flush()
المتاجر لتنظيفها لطلب آخر. باستخدام الوظيفة الإضافية iso
، يتم إجراء تسلسل لحالة Flux إلى ترميز HTML ، بحيث يعرف العميل مكان الالتقاط:
// We use react-router to run the URL that is provided in routes.jsx var getHandler = function(routes, url) { var deferred = Promise.defer(); Router.run(routes, url, function (Handler) { deferred.resolve(Handler); }); return deferred.promise; }; app.use(function *(next) { yield next; // We seed our stores with data alt.bootstrap(JSON.stringify(this.locals.data || {})); var iso = new Iso(); const handler = yield getHandler(reactRoutes, this.request.url); const node = React.renderToString(React.createElement(handler)); iso.add(node, alt.flush()); this.render('layout', {html: iso.render()}); });
من جانب العميل ، نلتقط حالة الخادم و bootstrap alt
بالبيانات. ثم نقوم بتشغيل Router
و React.render
على الحاوية الهدف ، والتي ستقوم بتحديث الترميز الذي تم إنشاؤه بواسطة الخادم حسب الضرورة.
Iso.bootstrap(function (state, _, container) { // Bootstrap the state from the server alt.bootstrap(state) Router.run(routes, Router.HistoryLocation, function (Handler, req) { let node = React.createElement(Handler) React.render(node, container) }) })
محبوب!
مكتبات مفيدة للواجهة الأمامية
لن يكتمل دليل نظام React البيئي بدون ذكر بعض مكتبات الواجهة الأمامية التي تعمل بشكل جيد مع React. تعالج هذه المكتبات المهام الأكثر شيوعًا الموجودة في كل تطبيق ويب تقريبًا: تخطيطات وحاويات CSS ، والنماذج والأزرار المصممة ، وعمليات التحقق من الصحة ، واختيار التاريخ ، وما إلى ذلك. ليس هناك فائدة من إعادة اختراع العجلة بعد أن تم حل هذه المشكلات بالفعل.
رد فعل التمهيد
أصبح إطار عمل Bootstrap في Twitter أمرًا شائعًا نظرًا لأنه يقدم مساعدة هائلة لكل مطور ويب لا يرغب في قضاء الكثير من الوقت في العمل في CSS. In the prototyping phase especially, Bootstrap is indispensable. To leverage the power of bootstrap in a React application, it's good to use it with React-Bootstrap, which comes with nice React syntax and re-implements Bootstrap's jQuery plugins using native React components. The resulting code is terse and easy to understand:
<Navbar brand='React-Bootstrap'> <Nav> <NavItem eventKey={1} href='#'>Link</NavItem> <NavItem eventKey={2} href='#'>Link</NavItem> <DropdownButton eventKey={3} title='Dropdown'> <MenuItem eventKey='1'>Action</MenuItem> <MenuItem eventKey='2'>Another action</MenuItem> <MenuItem eventKey='3'>Something else here</MenuItem> <MenuItem divider /> <MenuItem eventKey='4'>Separated link</MenuItem> </DropdownButton> </Nav> </Navbar>
Personally, I cannot escape the feeling that this is what HTML should always have been like.
If you want to use Bootstrap source with Webpack, consider using less-loader or bootstrap-sass-loader, depending on the preprocessor you prefer. It will allow you to easily customize which Bootstrap components to include, and also allow the usage of LESS or SASS global variables in your CSS code.
React Router
React Router has become the de-facto standard for routing in React. It allows nested routing, support for redirections, plays nicely with isomorphic rendering, and has a simple JSX-based syntax:
<Router history={new BrowserHistory}> <Route path="/" component={App}> <Route path="about" name="about" component={About}/> <Route path="users" name="users" component={Users} indexComponent={RecentUsers}> <Route path="/user/:userId" name="user" component={User}/> </Route> <Route path="*" component={NoMatch}/> </Route> </Router>
React Router also provides a Link
component that you can use for navigation in your application, specifying only the route name:
<nav> <Link to="about">About</Link> <Link to="users">Users</Link> </nav>
There is even a library for integration with React-Bootstrap, so if you are using Bootstrap's components and don't feel like setting the active
class on them manually all the time, you can use react-router-bootstrap and write code like this:
<Nav> <NavItemLink to="about">About</NavItemLink> <NavItemLink to="users">Users</NavItemLink> </Nav>
No additional setup is necessary. Active links will take care of themselves.
Formsy-React
Working with forms can be tedious, so let's get some help from the formsy-react library, which will help us manage validations and data models. The Formsy-React library, strangely enough, does not include the actual form inputs because users are encouraged to write their own (which is great). But if you are content with the common ones, just use formsy-react-components. Bootstrap classes are included:
import Formsy from 'formsy-react'; import {Input} from 'formsy-react-components'; export default class FormsyForm extends React.Component { enableButton() { this.setState({canSubmit: true}); } disableButton() { this.setState({canSubmit: true}); } submit(model) { FormActions.saveEmail(model.email); } render() { return ( <Formsy.Form onValidSubmit={this.submit} onValid={this.enableButton} onInvalid={this.disableButton}> <Input name="email" validations="isEmail" validationError="This is not a valid email" required/> <button type="submit" disabled={!this.state.canSubmit}>Submit</button> </Formsy.Form> )} }
Calendar and Typeahead
Calendar and typeahead are icing on the cake of every UI toolkit. Sadly, these two components were removed from Bootstrap 3, probably because they are too specialized for a general purpose CSS framework. Luckily, I've been able to find worthy replacements in react-pikaday and react-select. I've tested more than 10 libraries, and these two came out as the best. They are dead easy to use, as well:
import Pikaday from 'react-pikaday'; import Select from 'react-select'; export default class CalendarAndTypeahead extends React.Component { constructor(props){ super(props); this.options = [ { value: 'one', label: 'One' }, { value: 'two', label: 'Two' } ]; } dateChange(date) { this.setState({date: date}); }, selectChange(selected) { this.setState({selected: selected}); }, render() { return ( <Pikaday value={this.state.date} onChange={this.dateChange} /> <Select name="form-field-name" value={this.state.selected} options={this.options} onChange={selectChange} /> )} }
Conclusion - React.JS
In this article I've presented libraries and techniques that I consider some of the most productive in current web development. Some of them are React-specific, but due to React's open nature, many of them are usable in other environments as well. Technological progress is sometimes hindered by fear of the newest stuff, so I hope this article will help to dissipate doubts concerning React, Flux and the newest features in ECMAScript.
If you are interested, you can take a look at my example application built with these technologies. The source code is available on GitHub.
شكرا للقراءة!