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" } }
النظر في تنفيذ هذه القاعدة إزالة :
في الكود الموصوف أعلاه ، traverseClass
لوظيفة الأداة المساعدة للعثور على الفئة ، وليس من المهم للغاية فهم عام ، ولكن لا يزال من المنطقي إحضاره ، لمزيد من الدقة:
الاختبار ، بدوره ، قد يبدو كالتالي:
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
:
النظر في أبسط اختبار:
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
الآليات الأساسية فقط ، ولكن يمكن تحسينه بشكل كبير إذا كان المجتمع مهتمًا ومشتركًا. سأكون سعيدًا بمناقشة التعليقات والأفكار وأمثلة الاستخدام في التعليقات بالإضافة إلى الوظائف المفقودة.