أتمتة الانتقال إلى React Hooks

React 16.18 هو أول إصدار مستقر مع دعم لخطافات الرد . الآن يمكنك استخدام السنانير دون خوف من أن API سيتغير بشكل كبير. على الرغم من أن react تطوير react ينصح باستخدام التكنولوجيا الجديدة للمكونات الجديدة فقط ، فإن العديد منهم ، بمن فيهم أنا ، يودون استخدامها للمكونات القديمة التي تستخدم الفئات. ولكن نظرًا لأن إعادة التجهيز اليدوي عملية شاقة ، سنحاول تشغيلها تلقائيًا. تعتبر التقنيات الموضحة في هذه المقالة مناسبة لأتمتة إعادة تكوين مكونات react ليس فقط ، ولكن أيضًا أي JavaScript البرمجية الأخرى.


ميزات رد فعل السنانير


تفاصيل مقالة React Hooks ماهية السنانير وما يأكلون بها. باختصار ، هذه تقنية جديدة مجنونة لإنشاء مكونات خالية من state دون استخدام الفصول الدراسية.


النظر في ملف button.js :


 import React, {Component} from 'react'; export default Button; class Button extends Component { constructor() { super(); this.state = { enabled: true }; this.toogle = this._toggle.bind(this); } _toggle() { this.setState({ enabled: false, }); } render() { const {enabled} = this.state; return ( <button enabled={enabled} onClick={this.toggle} /> ); } } 

مع السنانير ، سيبدو كما يلي:


 import React, {useState} from 'react'; export default Button; function Button(props) { const [enabled, setEnabled] = useState(true); function toggle() { setEnabled(false); } return ( <button enabled={enabled} onClick={toggle} /> ); } 

يمكنك القول لفترة طويلة كيف يكون هذا النوع من التسجيل أكثر وضوحًا بالنسبة للأشخاص الذين ليسوا على دراية بالتكنولوجيا ، ولكن هناك شيء واحد واضح على الفور: الرمز أكثر سهولة وأسهل في إعادة الاستخدام. يمكن العثور على مجموعات مثيرة للاهتمام من السنانير المخصصة في usehooks.com و streamich.imtqy.com .


بعد ذلك ، سوف نقوم بتحليل الاختلافات في بناء الجملة إلى أصغر التفاصيل والتعامل مع عملية تحويل رمز البرنامج ، ولكن قبل ذلك أود أن أتحدث عن أمثلة لاستخدام هذا الشكل من الرموز.


الانحدار الغنائي: استخدام غير قياسي لبناء جملة التدمير


أعطى ES2015 العالم شيء رائع مثل إعادة هيكلة الصفائف . والآن ، بدلاً من استخراج كل عنصر على حدة:


 const letters = ['a', 'b']; const first = letters[0]; const second = letters[1]; 

يمكننا الحصول على جميع العناصر اللازمة في وقت واحد:


 const letters = ['a', 'b']; const [first, second] = letters; 

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


وهكذا ، توصلنا إلى es2015 أنه إذا لم يكن الأمر بالنسبة إلى es2015 فريق es2015 إلى طريقة غير عادية للعمل مع الدولة.


بعد ذلك ، أود أن أفكر في العديد من المكتبات التي تستخدم نهجًا مشابهًا.


جرب الصيد


قبل ستة أشهر من الإعلان عن الخطافات في رد الفعل ، كان لدي فكرة أن التدمير يمكن استخدامه ليس فقط للحصول على بيانات متجانسة من الصفيف ، ولكن أيضًا للحصول على معلومات حول خطأ أو نتيجة دالة ، عن طريق القياس مع عمليات الاسترجاعات في node.js. على سبيل المثال ، بدلاً من استخدام بناء جملة try-catch :


 let data; let error; try { data = JSON.parse('xxxx'); } catch (e) { error = e; } 

الأمر الذي يبدو مرهقًا للغاية ولكنه يحمل القليل من المعلومات ، ويجبرنا على استخدام let ، على الرغم من أننا لم نخطط لتغيير قيم المتغيرات. بدلاً من ذلك ، يمكنك استدعاء وظيفة try-catch ، والتي ستعمل كل ما تحتاجه ، مما يوفر لنا من المشاكل المذكورة أعلاه:


 const [error, data] = tryCatch(JSON.parse, 'xxxx'); 

بهذه الطريقة المثيرة للاهتمام ، تخلصنا من جميع الإنشاءات النحوية غير الضرورية ، ولم نترك سوى ما هو ضروري. هذه الطريقة لها المزايا التالية:


  • القدرة على تحديد أي أسماء متغيرة ملائمة لنا (عند استخدام تدمير الكائنات ، لن يكون لدينا مثل هذا الامتياز ، أو بالأحرى ، سيكون له سعره المرهق) ؛
  • القدرة على استخدام الثوابت للبيانات التي لا تتغير ؛
  • بناء جملة أكثر إيجازًا ، كل ما يمكن إزالته مفقود ؛

ومرة أخرى ، كل هذا بفضل بناء الجملة لتدمير الصفائف. بدون بناء الجملة هذا ، سيبدو استخدام مكتبة أمرًا سخيفًا:


 const result = tryCatch(JSON.parse, 'xxxx'); const error = result[0]; const data = result[1]; 

لا يزال هذا رمزًا صالحًا ، لكنه يفقد بشكل كبير مقارنة بالتدمير. أرغب أيضًا في إضافة مثال على مكتبة try-to-catch ، مع ظهور async-await لا يزال بناء try-catch ذا صلة ، ويمكن كتابته على النحو التالي:


 const [error, data] = await tryToCatch(readFile, path, 'utf8'); 

إذا كانت فكرة استخدام مثل هذا التدمير تأتي إلي ، فلماذا لا يبتكر رد الفعل أيضًا ، لأنه في الواقع ، لدينا شيء يشبه وظيفة لها قيمتان للإرجاع: مجموعة هاسكل.


على هذا الانحدار الغنائي يمكن أن تكتمل والانتقال إلى مسألة التحول.


تحويل فئة في React Hooks


للتحويل ، سوف نستخدم محول AST putout ، والذي يسمح لك بتغيير ما هو مطلوب فقط والمكون الإضافي @ putout / plugin-react-hooks .


لتحويل الفئة الموروثة من Component إلى دالة باستخدام react-hooks ، يجب تنفيذ الخطوات التالية:


  • إزالة bind
  • إعادة تسمية الطرق الخاصة للجمهور (إزالة "_") ؛
  • تغيير this.state لاستخدام السنانير
  • تغيير this.setState لاستخدام السنانير
  • إزالة this من كل مكان
  • تحويل class إلى وظيفة
  • في عمليات الاستيراد ، استخدم useState بدلاً من Component

اتصال


قم بتثبيت putout مع @putout/plugin-react-hooks :


 npm i putout @putout/plugin-react-hooks -D 

بعد ذلك ، قم بإنشاء ملف .putout.json :


 { "plugins": [ "react-hooks" ] } 

ثم حاول أن putout .


المفسد رأس
 coderaiser@cloudcmd:~/example$ putout button.js /home/coderaiser/putout/packages/plugin-react-hooks/button.js 11:8 error bind should not be used react-hooks/remove-bind 14:4 error name of method "_toggle" should not start from under score react-hooks/rename-method-under-score 7:8 error hooks should be used instead of this.state react-hooks/convert-state-to-hooks 15:8 error hooks should be used instead of this.setState react-hooks/convert-state-to-hooks 21:14 error hooks should be used instead of this.state react-hooks/convert-state-to-hooks 7:8 error should be used "state" instead of "this.state" react-hooks/remove-this 11:8 error should be used "toogle" instead of "this.toogle" react-hooks/remove-this 11:22 error should be used "_toggle" instead of "this._toggle" react-hooks/remove-this 15:8 error should be used "setState" instead of "this.setState" react-hooks/remove-this 21:26 error should be used "state" instead of "this.state" react-hooks/remove-this 26:25 error should be used "setEnabled" instead of "this.setEnabled" react-hooks/remove-this 3:0 error class Button should be a function react-hooks/convert-class-to-function 12 errors in 1 files fixable with the `--fix` option 

عثر putout على 12 مكانًا يمكن إصلاحها ، جرب:


 putout --fix button.js 

الآن button.js يشبه هذا:


 import React, {useState} from 'react'; export default Button; function Button(props) { const [enabled, setEnabled] = useState(true); function toggle() { setEnabled(false); } return ( <button enabled={enabled} onClick={setEnabled} /> ); } 

تنفيذ البرامج


دعونا نفكر بمزيد من التفصيل في العديد من القواعد الموضحة أعلاه.


إزالة this من كل مكان


نظرًا لأننا لا نستخدم الفئات ، يجب تحويل كل تعبيرات النموذج this.setEnabled إلى setEnabled .


للقيام بذلك ، سنذهب عبر العقد من ThisExpression ، والتي بدورها هي تابعة للعلاقة مع MemberExpression ، وتقع في حقل object ، وبالتالي:


 { "type": "MemberExpression", "object": { "type": "ThisExpression", }, "property": { "type": "Identifier", "name": "setEnabled" } } 

النظر في تنفيذ هذه القاعدة إزالة :


 //      module.exports.report = ({name}) => `should be used "${name}" instead of "this.${name}"`; //    module.exports.fix = ({path}) => { // : MemberExpression -> Identifier path.replaceWith(path.get('property')); }; module.exports.find = (ast, {push}) => { traverseClass(ast, { ThisExpression(path) { const {parentPath} = path; const propertyPath = parentPath.get('property'); //      const {name} = propertyPath.node; push({ name, path: parentPath, }); }, }); }; 

في الكود الموصوف أعلاه ، traverseClass لوظيفة الأداة المساعدة للعثور على الفئة ، وليس من المهم للغاية فهم عام ، ولكن لا يزال من المنطقي إحضاره ، لمزيد من الدقة:


المفسد رأس
 //      function traverseClass(ast, visitor) { traverse(ast, { ClassDeclaration(path) { const {node} = path; const {superClass} = node; if (!isExtendComponent(superClass)) return; path.traverse(visitor); }, }); }; //       Component function isExtendComponent(superClass) { const name = 'Component'; if (isIdentifier(superClass, {name})) return true; if (isMemberExpression(superClass) && isIdentifier(superClass.property, {name})) return true; return false; } 

الاختبار ، بدوره ، قد يبدو كالتالي:


 const test = require('@putout/test')(__dirname, { 'remove-this': require('.'), }); test('plugin-react-hooks: remove-this: report', (t) => { t.report('this', `should be used "submit" instead of "this.submit"`); t.end(); }); test('plugin-react-hooks: remove-this: transform', (t) => { const from = ` class Hello extends Component { render() { return ( <button onClick={this.setEnabled}/> ); } } `; const to = ` class Hello extends Component { render() { return <button onClick={setEnabled}/>; } } `; t.transformCode(from, to); t.end(); }); 

في عمليات الاستيراد ، استخدم useState بدلاً من Component


ضع في اعتبارك تطبيق قاعدة تحويل حالة الاستيراد إلى المكون .


من أجل استبدال التعبيرات:


 import React, {Component} from 'react' 

على


 import React, {useState} from 'react' 

يجب عليك معالجة عقدة ImportDeclaration :


  { "type": "ImportDeclaration", "specifiers": [{ "type": "ImportDefaultSpecifier", "local": { "type": "Identifier", "name": "React" } }, { "type": "ImportSpecifier", "imported": { "type": "Identifier", "name": "Component" }, "local": { "type": "Identifier", "name": "Component" } }], "source": { "type": "StringLiteral", "value": "react" } } 

نحتاج إلى العثور على ImportDeclaration مع source.value = react ، ثم انتقل حول صفيف specifiers للبحث عن ImportSpecifier name = Component حقل name = Component :


 //     module.exports.report = () => 'useState should be used instead of Component'; //    module.exports.fix = (path) => { const {node} = path; node.imported.name = 'useState'; node.local.name = 'useState'; }; //    module.exports.find = (ast, {push, traverse}) => { traverse(ast, { ImportDeclaration(path) { const {source} = path.node; //   react,    if (source.value !== 'react') return; const name = 'Component'; const specifiersPaths = path.get('specifiers'); for (const specPath of specifiersPaths) { //    ImportSpecifier -    if (!specPath.isImportSpecifier()) continue; //    Compnent -    if (!specPath.get('imported').isIdentifier({name})) continue; push(specPath); } }, }); }; 

النظر في أبسط اختبار:


 const test = require('@putout/test')(__dirname, { 'convert-import-component-to-use-state': require('.'), }); test('plugin-react-hooks: convert-import-component-to-use-state: report', (t) => { t.report('component', 'useState should be used instead of Component'); t.end(); }); test('plugin-react-hooks: convert-import-component-to-use-state: transform', (t) => { t.transformCode(`import {Component} from 'react'`, `import {useState} from 'react'`); t.end(); }); 

وهكذا ، درسنا بشكل عام تنفيذ البرامج لعدة قواعد ، ويتم إنشاء الباقي وفقًا لمخطط مماثل. يمكنك التعرف على جميع العقد من شجرة button.js تحليل ملف في astexplorer . يمكن العثور على الكود المصدري للمكونات الإضافية الموصوفة في المستودع .


الخاتمة


نظرنا اليوم إلى إحدى طرق إعادة التجهيز الآلي لفئات رد الفعل لخطافات التفاعل. حاليًا ، @putout/plugin-react-hooks الآليات الأساسية فقط ، ولكن يمكن تحسينه بشكل كبير إذا كان المجتمع مهتمًا ومشتركًا. سأكون سعيدًا بمناقشة التعليقات والأفكار وأمثلة الاستخدام في التعليقات بالإضافة إلى الوظائف المفقودة.

Source: https://habr.com/ru/post/ar441722/


All Articles