Angulareact

Saya punya masalah. Aplikasi ini ditulis dalam Angular, dan pustaka komponen di Bereaksi. Mengkloning perpustakaan terlalu mahal. Jadi, Anda perlu menggunakan komponen Bereaksi dalam aplikasi Angular dengan biaya minimal. Kami mencari cara untuk melakukannya.


Penafian


Saya bukan spesialis di Angular sama sekali. Saya mencoba versi pertama pada tahun 2017, kemudian melihat sedikit pada AngularDart, dan sekarang saya telah menemukan dukungan aplikasi pada versi modern dari framework. Jika Anda merasa bahwa keputusan itu aneh atau "dari dunia lain", itu tampaknya tidak bagi Anda.


Solusi yang disajikan dalam artikel ini belum diuji dalam proyek nyata dan hanya konsep. Gunakan dengan risiko Anda sendiri.


Masalah


Saya sekarang mendukung dan mengembangkan aplikasi yang cukup besar pada Angular 8. Plus, ada beberapa aplikasi kecil di Bereaksi dan berencana untuk membangun selusin lebih (juga di Bereaksi). Semua aplikasi digunakan untuk kebutuhan internal perusahaan dan harus dalam gaya yang sama. Satu-satunya cara logis adalah membuat pustaka komponen dengan dasar di mana Anda dapat dengan cepat membangun aplikasi apa pun.


Tetapi dalam situasi saat ini, Anda tidak bisa hanya mengambil dan menulis perpustakaan di Bereaksi. Anda tidak dapat menggunakannya dalam aplikasi terbesar - ini ditulis dalam Angular. Saya bukan orang pertama yang menghadapi masalah ini. Hal pertama yang ada di Google untuk "bereaksi dalam sudut" adalah repositori Microsoft . Sayangnya, solusi ini tidak memiliki dokumentasi sama sekali. Plus, dalam readme proyek itu jelas dikatakan bahwa paket tersebut digunakan secara internal oleh Microsoft, tim tidak memiliki rencana yang jelas untuk pengembangannya dan hanya risiko Anda sendiri untuk menggunakannya. Saya belum siap untuk menyeret paket ambigu ke dalam produksi, jadi saya memutuskan untuk menulis sepeda


gambar


Situs resmi @ angular-react / core


Solusi


Bereaksi adalah perpustakaan yang dirancang untuk memecahkan satu masalah khusus - mengelola pohon DOM dokumen. Kita bisa meletakkan elemen Bereaksi apa pun di simpul DOM yang arbitrer.


const Hello = () => <p>Hello, React!</p>; render(<Hello />, document.getElementById('#hello')); 

Selain itu, tidak ada batasan pada panggilan berulang ke fungsi render . Artinya, dimungkinkan untuk membuat setiap komponen secara terpisah di DOM. Dan inilah yang akan membantu mengambil langkah pertama dalam integrasi.


Persiapan


Pertama-tama, penting untuk dipahami bahwa Bereaksi secara default tidak memerlukan kondisi khusus selama perakitan. Jika Anda tidak menggunakan JSX, tetapi membatasi createElement untuk memanggil createElement , maka Anda tidak perlu mengambil langkah apa pun, semuanya hanya akan berjalan di luar kotak.


Tapi, tentu saja, kita terbiasa menggunakan JSX dan tidak mau kehilangan itu. Untungnya, Angular menggunakan TypeScript secara default, yang dapat mengubah JSX menjadi panggilan fungsi. Anda hanya perlu menambahkan flag compiler --jsx=react atau di tsconfig.json di bagian compilerOptions tambahkan baris "jsx": "react" .


Integrasi Tampilan


Untuk memulai, kita perlu memastikan bahwa komponen Bereaksi ditampilkan di dalam aplikasi Angular. Yaitu, sehingga elemen DOM yang dihasilkan dari pekerjaan perpustakaan mengambil tempat yang tepat di pohon elemen.


Setiap kali Anda menggunakan komponen Bereaksi, berpikir untuk memanggil fungsi render dengan benar terlalu sulit. Plus, di masa depan kita perlu mengintegrasikan komponen di tingkat data dan pengendali acara. Dalam hal ini, masuk akal untuk membuat komponen Angular yang akan merangkum seluruh logika membuat dan mengendalikan elemen Bereaksi.


 // Hello.tsx export const Hello = () => <p>Hello, React!</p>; // hello.component.ts import { createElement } from 'react'; import { render } from 'react-dom'; import { Hello } from './Hello'; @Component({ selector: 'app-hello', template: `<div #react></div>`, }) export class HelloComponent implements OnInit { @ViewChild('react', { read: ElementRef, static: true }) ref: ElementRef; ngOnInit() { this.renderReactElement(); } private renderReactElement() { const props = {}; const reactElement = createElement(Hello, props); render(reactElement, this.ref.nativeElement); } } 

Kode komponen Angular sangat sederhana. Itu sendiri hanya membuat wadah kosong dan mendapat tautan ke sana. Dalam hal ini, pada saat inisialisasi, render dari elemen React disebut. Itu dibuat menggunakan fungsi createElement dan diteruskan ke fungsi render , yang menempatkannya di simpul DOM yang dibuat dari Angular. Anda dapat menggunakan komponen seperti itu seperti komponen Angulat lainnya, tanpa syarat khusus.


Integrasi Input


Biasanya, saat menampilkan elemen antarmuka, Anda perlu mentransfer data kepada mereka. Semuanya di sini juga cukup membosankan - saat memanggil createElement Anda dapat meneruskan data apa pun melalui alat peraga ke komponen.


 // Hello.tsx export const Hello = ({ name }) => <p>Hello, {name}!</p>; // hello.component.ts import { createElement } from 'react'; import { render } from 'react-dom'; import { Hello } from './Hello'; @Component({ selector: 'app-hello', template: `<div #react></div>`, }) export class HelloComponent implements OnInit { @ViewChild('react', { read: ElementRef, static: true }) ref: ElementRef; @Input() name: string; ngOnInit() { this.renderReactElement(); } private renderReactElement() { const props = { name: this.name, }; const reactElement = createElement(Hello, props); render(reactElement, this.ref.nativeElement); } } 

Sekarang Anda dapat meneruskan string name ke komponen Angular, itu akan jatuh ke komponen Bereaksi dan akan diberikan. Tetapi jika garis berubah karena beberapa alasan eksternal, Bereaksi tidak akan mengetahuinya dan kami akan mendapatkan tampilan yang ketinggalan zaman. Angular memiliki ngOnChanges siklus hidup ngOnChanges yang memungkinkan Anda untuk melacak perubahan dalam parameter komponen dan reaksi terhadapnya. Kami mengimplementasikan antarmuka OnChanges dan menambahkan metode:


 // ... ngOnChanges(_: SimpleChanges) { this.renderReactElement(); } // ... 

Cukup memanggil fungsi render lagi dengan elemen yang dibuat dari properti baru, dan perpustakaan itu sendiri akan mencari tahu bagian mana dari pohon yang harus dirender. Status lokal di dalam komponen juga akan dipertahankan.


Setelah manipulasi ini, komponen Sudut dapat digunakan dengan cara biasa dan meneruskan data ke sana.


 <app-hello name="Angular"></app-hello> <app-hello [name]="name"></app-hello> 

Untuk pekerjaan yang lebih baik dengan memperbarui komponen, Anda dapat melihat ke arah strategi deteksi perubahan . Saya tidak akan mempertimbangkan ini secara rinci.


Integrasi Output


Masalah lain tetap - reaksi aplikasi terhadap peristiwa di dalam komponen-React. Mari kita @Output dekorator @Output dan meneruskan panggilan balik ke komponen melalui alat peraga.


 // Hello.tsx export const Hello = ({ name, onClick }) => ( <div> <p>Hello, {name}!</p> <button onClick={onClick}>Say "hello"</button> </div> ); // hello.component.ts import { createElement } from 'react'; import { render } from 'react-dom'; import { Hello } from './Hello'; @Component({ selector: 'app-hello', template: `<div #react></div>`, }) export class HelloComponent implements OnInit { @ViewChild('react', { read: ElementRef, static: true }) ref: ElementRef; @Input() name: string; @Output() click = new EventEmitter<string>(); ngOnInit() { this.renderReactElement(); } private renderReactElement() { const props = { name: this.name, onClick: () => this.lick.emit(`Hello, ${this.name}!`), }; const reactElement = createElement(Hello, props); render(reactElement, this.ref.nativeElement); } } 

Selesai Saat menggunakan komponen, Anda dapat mendaftarkan penangan acara dan meresponsnya.


  <app-hello [name]="name" (click)="sayHello($event)"></app-hello> 

Hasilnya adalah pembungkus yang berfungsi penuh untuk komponen Bereaksi untuk digunakan di dalam aplikasi Angular. Itu dapat mengirimkan data dan menanggapi peristiwa di dalamnya.


Satu hal lagi ...


Bagi saya, hal yang paling nyaman di Angular adalah Pengikatan Data Dua ngModel , ngModel . Lebih mudah, sederhana, hanya membutuhkan sedikit kode. Namun dalam implementasi saat ini, integrasi tidak dimungkinkan. Ini bisa diperbaiki. Sejujurnya, saya tidak begitu mengerti bagaimana mekanisme ini bekerja dari sudut pandang perangkat internal. Oleh karena itu, saya akui bahwa solusi saya super-suboptimal dan saya akan senang jika Anda menulis di komentar cara yang lebih indah untuk mendukung ngModel .


Pertama-tama, Anda perlu mengimplementasikan antarmuka ControlValueAccessor (dari paket @angular/forms dan menambahkan penyedia baru ke komponen.


 import { NG_VALUE_ACCESSOR, ControlValueAccessor } from '@angular/forms'; const REACT_VALUE_ACCESSOR: any = { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => HelloComponent), multi: true }; @Component({ selector: 'app-hello', template: `<div #react></div>`, providers: [PREACT_VALUE_ACCESSOR], }) export class PreactComponent implements OnInit, OnChanges, ControlValueAccessor { // ... } 

Antarmuka ini membutuhkan implementasi metode onBlur , writeValue , registerOnChange , registerOnTouched . Semuanya dijelaskan dengan baik dalam dokumentasi. Kami menyadarinya.


 //    const noop = () => {}; @Component({ selector: 'app-hello', template: `<div #react></div>`, providers: [PREACT_VALUE_ACCESSOR], }) export class PreactComponent implements OnInit, OnChanges, ControlValueAccessor { // ... private innerValue: string; //      ngModel private onTouchedCallback: Callback = noop; private onChangeCallback: Callback = noop; //       //       get value(): string { return this.innerValue; } set value(v: string) { if (v !== this.innerValue) { this.innerValue = v; //    ,   this.onChangeCallback(v); } } writeValue(value: string) { if (value !== this.innerValue) { this.innerValue = value; //        this.renderReactElement(); } } //   registerOnChange(fn: Callback) { this.onChangeCallback = fn; } registerOnTouched(fn: Callback) { this.onTouchedCallback = fn; } //    onBlur() { this.onTouchedCallback(); } } 

Setelah itu, Anda perlu memastikan bahwa semua ini diteruskan ke komponen Bereaksi. Sayangnya, Bereaksi tidak dapat bekerja dengan Pengikatan Data Dua Arah, jadi kami akan memberinya nilai dan panggilan balik untuk mengubahnya. renderReactElement metode renderReactElement .


 // ... private renderReactElement() { const props = { name: this.name, onClick: () => this.lick.emit(`Hello, ${this.name}!`), model: { value: this.value, onChange: v => { this.value = v; } }, }; const reactElement = createElement(Hello, props); render(reactElement, this.ref.nativeElement); } // ... 

Dan dalam komponen Bereaksi, kita akan menggunakan nilai ini dan panggilan balik.


 export const Hello = ({ name, onClick, model }) => ( <div> <p>Hello, {name}!</p> <button onClick={onClick}>Say "hello"</button> <input value={model.value} onChange={e => model.onChange(e.target.value)} /> </div> ); 

Sekarang, kami benar-benar mengintegrasikan Bereaksi ke Angular. Anda dapat menggunakan komponen yang dihasilkan sesuka Anda.


 <app-hello [name]="name" (click)="sayHello($event)" [(ngModel)]="name" ></app-hello> 

Ringkasan


React adalah perpustakaan yang sangat sederhana yang mudah diintegrasikan dengan apa pun. Dengan menggunakan pendekatan yang ditunjukkan, Anda tidak hanya dapat menggunakan komponen Bereaksi apa pun di dalam aplikasi Angular secara berkelanjutan, tetapi juga secara bertahap memigrasikan seluruh aplikasi.


Pada artikel ini, saya tidak menyentuh masalah styling sama sekali. Jika Anda menggunakan solusi CSS-in-JS klasik (komponen gaya, emosi, JSS), Anda tidak perlu mengambil tindakan tambahan apa pun. Tetapi jika proyek membutuhkan solusi yang lebih produktif (astroturf, linaria, Modul CSS), Anda harus bekerja pada konfigurasi webpack. Cerita fitur - Sesuaikan Konfigurasi Webpack di Aplikasi Sudut Anda .


Untuk memigrasikan aplikasi sepenuhnya dari Angular ke React, Anda masih perlu menyelesaikan masalah penerapan layanan dalam komponen React. Cara sederhana adalah dengan mendapatkan layanan dalam komponen pembungkus dan menyebarkannya melalui alat peraga. Cara yang sulit adalah menulis layer yang akan mendapatkan layanan dari injektor dengan token. Pertimbangan masalah ini di luar cakupan artikel.


Bonus


Penting untuk dipahami bahwa dengan pendekatan ini ke 85KB Angular, hampir 40KB kode react dan react-dom react . Ini dapat memiliki dampak signifikan pada kecepatan aplikasi. Saya sarankan mempertimbangkan menggunakan miniatur Preact, yang beratnya hanya 3KB. Integrasi hampir tidak berbeda.


  1. Di tsconfig.json menambahkan opsi kompilasi baru - "jsxFactory": "h" , ini akan menunjukkan bahwa Anda perlu menggunakan fungsi h untuk mengkonversi JSX. Sekarang, di setiap file dengan kode JSX - import { h } from 'preact' .
  2. Semua panggilan ke React.createElement digantikan oleh Preact.h .
  3. Semua panggilan ke ReactDOM.render digantikan oleh Preact.render .

Selesai! Baca instruksi untuk bermigrasi dari React ke Preact . Praktis tidak ada perbedaan.


UPD 19.9.19 16.49


Tematik tautan dari komentar - Micro Frontends


UPD 20.9.19 14.30


Link tematis lain dari komentar - Micro Frontends

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


All Articles