Otomatis transisi ke React Hooks

Bereaksi 16.18 adalah rilis stabil pertama dengan dukungan untuk kait reaksi . Sekarang Anda dapat menggunakan kait tanpa takut API akan berubah secara dramatis. Meskipun react pengembang react menyarankan untuk menggunakan teknologi baru hanya untuk komponen baru, banyak orang, termasuk saya, ingin menggunakannya untuk komponen lama yang menggunakan kelas. Tetapi karena refactoring manual adalah proses yang melelahkan, kami akan mencoba mengotomatiskannya. Teknik yang dijelaskan dalam artikel ini cocok untuk mengotomatisasi refactoring tidak hanya komponen react , tetapi juga kode JavaScript lainnya.


Fitur Bereaksi Hooks


Artikel Pendahuluan React Hooks merinci apa kaitannya dan apa yang mereka makan. Singkatnya, ini adalah teknologi gila baru untuk membuat komponen bebas state tanpa menggunakan kelas.


Pertimbangkan file 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} /> ); } } 

Dengan kait, akan terlihat seperti ini:


 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} /> ); } 

Anda dapat berdebat untuk waktu yang lama bagaimana jenis rekaman ini lebih jelas bagi orang yang tidak terbiasa dengan teknologi, tetapi satu hal yang segera jelas: kodenya lebih ringkas dan lebih mudah untuk digunakan kembali. Set kait kustom yang menarik dapat ditemukan di usehooks.com dan streamich.imtqy.com .


Selanjutnya, kita akan menganalisis perbedaan sintaksis hingga detail terkecil dan memahami proses konversi kode program, tetapi sebelum itu saya ingin berbicara tentang contoh penggunaan bentuk notasi ini.


Penyimpangan lirik: penggunaan sintaksis penataan yang tidak standar


ES2015 memberi dunia hal yang luar biasa seperti restrukturisasi array . Dan sekarang, alih-alih mengekstraksi setiap elemen secara individual:


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

Kita bisa mendapatkan semua elemen yang diperlukan sekaligus:


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

Catatan seperti itu tidak hanya lebih ringkas, tetapi juga kurang rentan terhadap kesalahan, karena menghapus kebutuhan untuk mengingat indeks elemen dan memungkinkan Anda untuk fokus pada apa yang benar-benar penting: inisialisasi variabel.


Jadi, kita sampai pada es2015 bahwa jika bukan karena es2015 tim es2015 tidak akan datang dengan cara yang tidak biasa untuk bekerja dengan negara.


Selanjutnya, saya ingin mempertimbangkan beberapa perpustakaan yang menggunakan pendekatan serupa.


Coba tangkap


Enam bulan sebelum pengumuman kait dalam reaksi, saya datang dengan gagasan bahwa perusakan dapat digunakan tidak hanya untuk mendapatkan data homogen dari array, tetapi juga untuk mendapatkan informasi tentang kesalahan atau hasil dari suatu fungsi, dengan analogi dengan panggilan balik di node.js. Misalnya, alih-alih menggunakan sintaks try-catch :


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

Yang terlihat sangat rumit, tetapi membawa sedikit informasi, dan memaksa kita untuk menggunakan let , meskipun kami tidak berencana untuk mengubah nilai-nilai variabel. Sebagai gantinya, Anda dapat memanggil fungsi try-catch , yang akan melakukan semua yang Anda butuhkan, menyelamatkan kami dari masalah yang tercantum di atas:


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

Dengan cara yang menarik ini, kami menyingkirkan semua konstruksi sintaksis yang tidak perlu, hanya menyisakan yang diperlukan. Metode ini memiliki keunggulan sebagai berikut:


  • kemampuan untuk menentukan nama variabel apa saja yang sesuai untuk kita (saat menggunakan penghancuran objek, kita tidak akan memiliki hak istimewa seperti itu, atau lebih tepatnya, itu akan memiliki harga yang rumit sendiri);
  • kemampuan untuk menggunakan konstanta untuk data yang tidak berubah;
  • sintaksis yang lebih ringkas, semua yang bisa dihapus hilang;

Dan, sekali lagi, semua ini berkat sintaksis penataan array. Tanpa sintaks ini, menggunakan perpustakaan akan terlihat konyol:


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

Ini masih kode yang valid, tetapi kehilangan signifikan dibandingkan dengan merusak. Saya juga ingin menambahkan contoh perpustakaan try-to-catch , dengan munculnya async-await konstruksi try-catch masih relevan, dan dapat ditulis seperti ini:


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

Jika ide penggunaan penghancuran seperti itu datang kepada saya, maka mengapa tidak pencipta reaksi juga, karena pada kenyataannya, kita memiliki sesuatu seperti fungsi yang memiliki 2 nilai kembali: tupel Haskel.


Pada penyimpangan liris ini dapat diselesaikan dan beralih ke masalah transformasi.


Konversi kelas di React Hooks


Untuk konversi, kami akan menggunakan transformator AST putout , yang memungkinkan Anda untuk hanya mengubah apa yang diperlukan dan plugin @ putout / plugin-react-hooks .


Untuk mengonversi kelas yang diwarisi dari Component menjadi fungsi menggunakan react-hooks , langkah-langkah berikut harus dilakukan:


  • hapus bind
  • ganti nama metode pribadi ke publik (hapus "_");
  • ubah this.state untuk menggunakan kait
  • ubah this.setState untuk menggunakan kait
  • hapus this dari mana-mana
  • convert class to function
  • di impor gunakan useState bukan Component

Koneksi


Instal putout dengan @putout/plugin-react-hooks :


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

Selanjutnya, buat file .putout.json :


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

Lalu coba putout beraksi.


Header spoiler
 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 menemukan 12 tempat yang dapat diperbaiki, coba:


 putout --fix button.js 

Sekarang button.js terlihat seperti ini:


 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} /> ); } 

Implementasi perangkat lunak


Mari kita perhatikan secara lebih rinci beberapa aturan yang dijelaskan di atas.


Hapus this dari mana-mana


Karena kita tidak menggunakan kelas, semua ekspresi dari bentuk this.setEnabled harus dikonversi ke setEnabled .


Untuk melakukan ini, kita akan melalui node ThisExpression , yang, pada gilirannya, adalah anak-anak dari relasi dengan MemberExpression , dan terletak di bidang object , dengan demikian:


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

Pertimbangkan penerapan aturan remove-this :


 //      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, }); }, }); }; 

Dalam kode yang dijelaskan di atas, fungsi utilitas traverseClass untuk menemukan kelas, itu tidak begitu penting untuk pemahaman umum, tetapi masih masuk akal untuk membawanya, untuk akurasi yang lebih besar:


Header spoiler
 //      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; } 

Tes, pada gilirannya, akan terlihat seperti ini:


 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(); }); 

Dalam impor, gunakan useState alih-alih Component


Pertimbangkan penerapan aturan konversi-impor-komponen-ke-gunakan-negara .


Untuk mengganti ekspresi:


 import React, {Component} from 'react' 

pada


 import React, {useState} from 'react' 

Anda harus memproses simpul 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" } } 

Kita perlu menemukan ImportDeclaration dengan source.value = react , dan kemudian pergi berkeliling array specifiers dalam mencari ImportSpecifier dengan name = Component bidang 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); } }, }); }; 

Pertimbangkan tes paling sederhana:


 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(); }); 

Jadi, kami memeriksa secara umum implementasi perangkat lunak dari beberapa aturan, sisanya dibangun sesuai dengan skema yang sama. Anda dapat berkenalan dengan semua simpul pohon dari file parsed button.js di astexplorer . Kode sumber dari plugin yang dijelaskan dapat ditemukan di repositori .


Kesimpulan


Hari ini kami melihat salah satu metode untuk refactoring otomatis kelas reaksi untuk bereaksi kait. Saat ini, @putout/plugin-react-hooks hanya mendukung mekanisme dasar, tetapi dapat ditingkatkan secara signifikan jika komunitas tertarik dan terlibat. Saya akan senang untuk membahas komentar di komentar, ide, contoh penggunaan, serta fungsionalitas yang hilang.

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


All Articles