Angulareact

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


تنصل


أنا لست متخصص في Angular على الإطلاق. جربت الإصدار الأول في عام 2017 ، ثم نظرت إلى AngularDart قليلاً ، والآن صادفت دعم التطبيق على إصدار حديث من الإطار. إذا بدا لك أن القرارات غريبة أو "من عالم آخر" ، فهذا لا يبدو لك.


لم يتم اختبار الحل المقدم في المقال في مشاريع حقيقية وهو مجرد مفهوم. استخدامه على مسؤوليتك الخاصة.


المشكلة


أنا الآن أؤيد وأطور تطبيقًا كبيرًا إلى حد ما على Angular 8. بالإضافة إلى ذلك ، هناك بضعة تطبيقات صغيرة على React وتخطط لبناء أكثر من عشرة (أيضًا على React). تُستخدم جميع التطبيقات لتلبية الاحتياجات الداخلية للشركة ويجب أن تكون بنفس الأسلوب. الطريقة المنطقية الوحيدة هي إنشاء مكتبة مكونات على أساسها يمكنك بناء أي تطبيق بسرعة.


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


صورة


الموقع الرسمي @ الزاوي رد فعل / الأساسية


قرار


React هي مكتبة مصممة لحل مشكلة محددة واحدة - إدارة شجرة DOM في المستند. يمكننا وضع أي عنصر React في عقدة DOM عشوائية.


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

علاوة على ذلك ، لا توجد قيود على المكالمات المتكررة لوظيفة render . وهذا يعني أنه من الممكن تقديم كل مكون على حدة في DOM. وهذا هو بالضبط ما سيساعد على اتخاذ الخطوة الأولى في التكامل.


تدريب


بادئ ذي بدء ، من المهم أن نفهم أن React افتراضيًا لا يتطلب أي شروط خاصة أثناء التجميع. إذا كنت لا تستخدم JSX ، ولكن تنحصر في استدعاء createElement ، فلن تضطر إلى اتخاذ أي خطوات ، كل شيء سينتهي من العمل.


لكن ، بالطبع ، نحن معتادون على استخدام JSX ولا نريد أن نخسره. لحسن الحظ ، يستخدم Angular TypeScript افتراضيًا ، والذي يمكنه تحويل JSX إلى استدعاءات الوظائف. تحتاج فقط إلى إضافة علامة المحول البرمجي - --jsx=react أو في tsconfig.json في قسم compilerOptions أضف السطر "jsx": "react" .


عرض التكامل


للبدء ، نحتاج إلى التأكد من عرض مكونات React داخل التطبيق الزاوي. أي أن عناصر DOM الناتجة من عمل المكتبة تشغل الأماكن الصحيحة في شجرة العنصر.


في كل مرة تستخدم فيها مكون React ، يكون التفكير في استدعاء وظيفة render بشكل صحيح أمرًا بالغ الصعوبة. بالإضافة إلى ذلك ، سنحتاج في المستقبل إلى دمج المكونات على مستوى البيانات ومعالجات الأحداث. في هذه الحالة ، من المنطقي إنشاء مكون Angular يقوم بتغليف منطق إنشاء عنصر React والتحكم فيه بالكامل.


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

رمز المكون الزاوي بسيط للغاية. فإنه نفسه يجعل حاوية فارغة فقط ويحصل على رابط إليها. في هذه الحالة ، في وقت التهيئة ، يتم استدعاء تقديم عنصر React. يتم إنشاؤه باستخدام دالة createElement وتمريره إلى وظيفة render ، مما يضعه في عقدة DOM التي تم إنشاؤها من Angular. يمكنك استخدام هذا المكون مثل أي مكون Angulat آخر ، لا توجد شروط خاصة.


تكامل المدخلات


عادة ، عند عرض عناصر الواجهة ، تحتاج إلى نقل البيانات إليها. كل شيء هنا أيضًا عملي للغاية - عند استدعاء createElement يمكنك تمرير أي بيانات عبر الدعائم إلى المكون.


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

يمكنك الآن تمرير سلسلة name إلى المكون الزاوي ، وستقع في مكون React وسيتم عرضها. ولكن إذا تغير الخط بسبب بعض الأسباب الخارجية ، فلن يعرف React عنه وسنحصل على عرض قديم. يحتوي Angular على ngOnChanges دورة حياة ngOnChanges تتيح لك تتبع التغييرات في معلمات المكون وردود الفعل عليها. نطبق واجهة OnChanges ونضيف طريقة:


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

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


بعد هذه العمليات ، يمكن استخدام المكون الزاوي بالطريقة المعتادة وتمرير البيانات إليه.


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

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


تكامل الإخراج


لا تزال هناك مشكلة أخرى - رد فعل التطبيق على الأحداث داخل مكونات التفاعل. دعنا @Output الديكور @Output وتمرير رد الاتصال إلى المكون من خلال الدعائم.


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

القيام به. عند استخدام المكون ، يمكنك تسجيل معالجات الأحداث والرد عليها.


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

والنتيجة هي غلاف كامل الوظائف لمكون React للاستخدام داخل تطبيق الزاوي. يمكن أن ينقل البيانات والرد على الأحداث داخلها.


شيء آخر ...


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


بادئ ذي ControlValueAccessor ، تحتاج إلى تطبيق واجهة ControlValueAccessor (من حزمة @angular/forms وإضافة موفر جديد إلى المكون.


 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 { // ... } 

تتطلب هذه الواجهة تنفيذ الأساليب على writeValue ، writeValue ، registerOnChange ، registerOnTouched . كلها موصوفة جيدا في الوثائق. نحن ندركهم.


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

بعد ذلك ، تحتاج إلى التأكد من أن كل هذا يتم تمريره إلى مكون React. لسوء الحظ ، React غير قادر على العمل مع ربط البيانات في اتجاهين ، لذلك سنمنحه قيمة واستدعاء لتغييره. renderReactElement طريقة 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); } // ... 

وفي مكون React ، سوف نستخدم هذه القيمة و رد الاتصال.


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

الآن ، لقد دمجنا React في الزاوي. يمكنك استخدام المكون الناتج كما تريد.


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

يؤدي


React هي مكتبة بسيطة للغاية يسهل دمجها مع أي شيء. باستخدام الطريقة الموضحة ، لا يمكنك فقط استخدام أي مكونات React داخل تطبيقات Angular على أساس مستمر ، ولكن أيضًا ترحيل التطبيق بالكامل تدريجيًا.


في هذه المقالة ، لم أتطرق إلى قضايا التصميم على الإطلاق. إذا كنت تستخدم حلول CSS-in-JS الكلاسيكية (مكونات نمطية ، عاطفة ، JSS) ، فلن تحتاج إلى اتخاذ أي إجراءات إضافية. ولكن إذا كان المشروع يتطلب حلولًا أكثر إنتاجية (astroturf و linaria و CSS Modules) ، فستحتاج إلى العمل على تكوين حزمة الويب. قصة الميزة - تخصيص تكوين Webpack في تطبيقك الزاوي .


لترحيل التطبيق بالكامل من Angular إلى React ، لا تزال بحاجة إلى حل مشكلة تنفيذ الخدمات في React-components. طريقة بسيطة هي الحصول على الخدمة في أحد مكونات المجمّع وتمريرها عبر الدعائم. الطريقة الصعبة هي كتابة طبقة ستحصل على الخدمات من الحاقن بواسطة الرمز المميز. النظر في هذه المسألة خارج نطاق المادة.


علاوة


من المهم أن نفهم أنه مع هذا النهج إلى 85 كيلو بايت من الزاوي ، تتم إضافة حوالي 40 كيلو بايت من كود react-dom . هذا يمكن أن يكون لها تأثير كبير على سرعة التطبيق. أوصي بالتفكير في استخدام Preact المصغر الذي يزن 3 كيلوبايت فقط. تكاملها لا يختلف تقريبا.


  1. في tsconfig.json نضيف خيارًا جديدًا "jsxFactory": "h" - "jsxFactory": "h" ، سيشير إلى أنك بحاجة إلى استخدام الدالة h لتحويل JSX. الآن ، في كل ملف برمز JSX - import { h } from 'preact' .
  2. React.createElement استبدال كافة المكالمات إلى React.createElement بـ Preact.h .
  3. ReactDOM.render استبدال كافة المكالمات إلى ReactDOM.render بواسطة Preact.render .

القيام به! اقرأ إرشادات الترحيل من React إلى Preact . لا توجد فروق عمليا.


UPD 19.9.19 16.49


رابط مواضيعي من التعليقات - Micro Frontends


UPD 20.9.19 14.30


رابط موضوعي آخر من التعليقات - Micro Frontends

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


All Articles