Υπάρχουν αμέτρητα άρθρα εκεί έξω που συζητούν αν το React ή το Angular είναι η καλύτερη επιλογή για ανάπτυξη ιστοσελίδων. Χρειαζόμαστε άλλο;
Ο λόγος που έγραψα αυτό το άρθρο είναι επειδή κανένα ο άρθρα ήδη δημοσίευσε - Παρόλο που περιέχουν εξαιρετικές ιδέες - πηγαίνουν βαθιά για έναν πρακτικό προγραμματιστή front-end για να αποφασίσει ποιος μπορεί να ικανοποιήσει τις ανάγκες του.
Σε αυτό το άρθρο, θα μάθετε πώς η Angular and React στοχεύει στην επίλυση παρόμοιων προβλημάτων front-end, αν και με πολύ διαφορετικές φιλοσοφίες, και αν η επιλογή του ενός ή του άλλου είναι απλώς θέμα προσωπικής προτίμησης. Για να τα συγκρίνουμε, πρόκειται να δημιουργήσουμε την ίδια εφαρμογή δύο φορές, μία φορά με το Angular και στη συνέχεια ξανά με το React.
Πριν από δύο χρόνια, έγραψα ένα άρθρο Αντιδράστε το οικοσύστημα . Μεταξύ άλλων σημείων, το άρθρο υποστήριξε ότι ο Angular είχε πέσει θύμα «θανάτου λόγω προηγούμενης ανακοίνωσης». Τότε, η επιλογή μεταξύ Angular και σχεδόν οτιδήποτε άλλο ήταν εύκολη για όσους δεν ήθελαν το έργο τους να λειτουργεί με ένα ξεπερασμένο πλαίσιο. Το Angular 1 καταργήθηκε και το Angular 2 δεν ήταν καν διαθέσιμο σε άλφα.
Αναδρομικά, οι φόβοι ήταν λίγο πολύ δικαιολογημένοι. Το Angular 2 άλλαξε δραματικά και μάλιστα πέρασε από μια μεγάλη επανεγγραφή λίγο πριν από την τελική κυκλοφορία.
Δύο χρόνια αργότερα, έχουμε το Angular 4 με μια υπόσχεση σχετικής σταθερότητας.
Και τώρα αυτό;
Μερικοί άνθρωποι λένε ότι η σύγκριση του React και του Angular είναι σαν να συγκρίνουμε τα μήλα με τα πορτοκάλια. Ενώ η μία είναι μια βιβλιοθήκη που ασχολείται με απόψεις, η άλλη είναι ένα πλήρες πλαίσιο.
Φυσικά, τα περισσότερα Αντιδράστε προγραμματιστές θα προσθέσουν μερικές βιβλιοθήκες στο React για να το κάνουν ένα πλήρες πλαίσιο. Επιπλέον, η ροή εργασίας που προκύπτει από αυτήν τη στοίβα είναι συχνά πολύ διαφορετική από τη Γωνιακή, επομένως η συγκρισιμότητα εξακολουθεί να είναι περιορισμένη.
Η μεγαλύτερη διαφορά έγκειται στην κρατική διοίκηση. Το Angular συνοδεύεται από τη δέσμευση δεδομένων, ενώ το React σήμερα συνήθως αυξάνεται από την Redux για να παρέχει μονόδρομη ροή δεδομένων και να λειτουργεί με αμετάβλητα δεδομένα. Αυτές είναι αντίθετες προσεγγίσεις από μόνες τους, και αμέτρητα επιχειρήματα αναφλέγουν τώρα εάν η μεταβλητή / ένωση δεδομένων είναι καλύτερη ή χειρότερη από την αμετάβλητη / μονόδρομη.
Δεδομένου ότι το React είναι γνωστότερα πιο εύκολο να χακαριστεί, αποφάσισα, για τους σκοπούς αυτής της σύγκρισης, να δημιουργήσω μια διαμόρφωση React που να αντικατοπτρίζει τη γωνία αρκετά κοντά ώστε να επιτρέπει τη διπλανή σύγκριση των αποσπασμάτων κώδικα.
Ορισμένες γωνιακές δυνατότητες που ξεχωρίζουν αλλά δεν βρίσκονται στο React από προεπιλογή είναι:
χαρακτηριστικό | Γωνιακό πακέτο | Αντιδράστε τη βιβλιοθήκη |
---|---|---|
Σύνδεση δεδομένων, έγχυση εξάρτησης (DI) | @ γωνιακό / πυρήνα | MobX |
Υπολογισμένες ιδιότητες | rxjs | MobX |
Δρομολόγηση βάσει στοιχείων | @ γωνιακός / δρομολογητής | React Router v4 |
Εξαρτήματα σχεδιασμού υλικών | @ γωνιακό / υλικό | Αντιδράστε την εργαλειοθήκη |
Το CSS κάλυψε στοιχεία | @ γωνιακό / πυρήνα | Ενότητες CSS |
Επικυρώσεις φόρμας | @ γωνιακές / μορφές | FormState |
Γεννήτρια έργου | @ γωνιακό / cli | Αντιδράστε σενάρια TS |
Η σύνδεση δεδομένων είναι αναμφισβήτητα πιο εύκολο να ξεκινήσετε από τη μονόδρομη προσέγγιση. Φυσικά, θα ήταν δυνατόν να πάμε στην εντελώς αντίθετη κατεύθυνση και να το χρησιμοποιήσουμε Redux ή όχλος-κράτος-δέντρο με το React, και ngrx με γωνιακό. Αλλά αυτό θα ήταν ένα θέμα για μια άλλη ανάρτηση.
Όσον αφορά την απόδοση, οι επιβάτες επιπέδου στο Angular είναι απλώς εκτός ερώτησης καθώς καλούνται σε κάθε επεξεργασία. Είναι δυνατή η χρήση Συμπεριφορά Αντικείμενο από RsJS , που εκτελεί το έργο.
Με το React, μπορείτε να το χρησιμοποιήσετε @computed από το MobX, το οποίο επιτυγχάνει τον ίδιο στόχο, με ένα ελαφρώς καλύτερο API.
Η έγχυση εξάρτησης είναι λίγο αμφιλεγόμενη επειδή έρχεται σε αντίθεση με το τρέχον παράδειγμα του React του λειτουργικού προγραμματισμού και του αμετάβλητου. Όπως αποδεικνύεται, κάποιο είδος έγχυσης εξάρτησης είναι σχεδόν απαραίτητο σε περιβάλλοντα δέσμευσης δεδομένων, καθώς βοηθά στην αποσύνδεση (και ως εκ τούτου κοροϊδία και δοκιμή) όπου δεν υπάρχει ξεχωριστή αρχιτεκτονική επιπέδου δεδομένων.
Ένα ακόμη πλεονέκτημα του DI (υποστηρίζεται από την Angular) είναι η ικανότητα να έχουν διαφορετικούς κύκλους ζωής από διαφορετικά καταστήματα. Τα περισσότερα από τα τρέχοντα παραδείγματα React χρησιμοποιούν κάποιο είδος παγκόσμιας κατάστασης εφαρμογής που χαρτογραφεί διαφορετικά στοιχεία, αλλά από την εμπειρία μου είναι πολύ εύκολο να εισαγάγει σφάλματα κατά τον καθαρισμό της καθολικής κατάστασης κατά την αποσυναρμολόγηση στοιχείων.
Η ύπαρξη ενός καταστήματος που είναι χτισμένο στη συναρμολόγηση εξαρτημάτων (και είναι απόλυτα διαθέσιμο για τα παιδιά αυτού του εξαρτήματος) φαίνεται να είναι πολύ χρήσιμο και η ιδέα συχνά παραβλέπεται.
Έξω από το κουτί σε γωνιακό, αλλά μπορεί να παιχτεί πολύ εύκολα και με το MobX.
Η δρομολόγηση βάσει στοιχείων επιτρέπει σε στοιχεία να διαχειρίζονται τις δικές τους θυγατρικές διαδρομές αντί να έχουν μια μεγάλη καθολική διαμόρφωση δρομολογητή. Αυτή η προσέγγιση κατάφερε τελικά να react-router
στην έκδοση 4.
Είναι πάντα καλό να ξεκινάμε με λίγα συστατικά υψηλού επιπέδου και το Material Design έχει γίνει μια καθολικά αποδεκτή προεπιλεγμένη επιλογή, ακόμη και σε έργα εκτός Google.
Έχω επιλέξει σκόπιμα Αντιδράστε την εργαλειοθήκη σχετικά με το συνιστώμενο Υλικό διεπαφής χρήστη , δεδομένου ότι το υλικό UI έχει σοβαρή αυτο-ομολογία ζητήματα επιδόσεων με το CSS-in-line, το οποίο σκοπεύουν να λύσουν στην επόμενη έκδοση.
Περαιτέρω, PostCSS / cssnext Χρησιμοποιήθηκε στο React Toolbox και αρχίζει να αντικαθιστά Sass / LESS ούτως ή άλλως.
Τα μαθήματα CSS μοιάζουν με καθολικές μεταβλητές. Υπάρχουν πολλές προσεγγίσεις για την οργάνωση CSS για την αποφυγή συγκρούσεων (συμπεριλαμβανομένων ΚΑΛΟΣ ), αλλά υπάρχει μια σαφής τάση στη χρήση βιβλιοθηκών που βοηθούν στην επεξεργασία CSS για την αποφυγή αυτών των διενέξεων χωρίς την ανάγκη προγραμματιστή μπροστινό μέρος να δημιουργήσει περίπλοκα συστήματα ονομάτων CSS.
Οι επικυρώσεις φόρμας είναι μια ασήμαντη και ευρέως χρησιμοποιούμενη δυνατότητα. Είναι καλό να καλύπτετε αυτά από μια βιβλιοθήκη για να αποφύγετε την επανάληψη κώδικα και τα λάθη.
Το να έχετε γεννήτρια CLI για ένα έργο είναι λίγο πιο βολικό από το να κλωνοποιείτε φύλλα λέβητα από το GitHub.
Άρα πρόκειται να δημιουργήσουμε την ίδια εφαρμογή στο React and Angular. Τίποτα εντυπωσιακό, απλά ένα Shoutboard που επιτρέπει σε οποιονδήποτε να δημοσιεύει μηνύματα σε μια κοινή σελίδα.
Μπορείτε να δοκιμάσετε τις εφαρμογές εδώ:
Εάν θέλετε να έχετε όλο τον πηγαίο κώδικα, μπορείτε να τον λάβετε από το GitHub:
Θα παρατηρήσετε ότι έχουμε χρησιμοποιήσει επίσης TypeScript για την εφαρμογή React. Τα οφέλη του ελέγχου τύπου στο TypeScript είναι προφανή. Και τώρα με καλύτερο χειρισμό των εισαγωγών, η διάδοση async / waiting και rest έφτασε τελικά στο TypeScript 2, αφήνοντας το Babel / ES7 / Ροή στη σκόνη.
Επίσης, θα προσθέσουμε Πελάτης Απόλλωνα και στους δύο επειδή θέλουμε να χρησιμοποιήσουμε το GraphQL. Εννοώ, το REST είναι υπέροχο, αλλά μετά από μια δεκαετία περίπου γερνά.
Αρχικά, ας ρίξουμε μια ματιά στα σημεία εισόδου και των δύο εφαρμογών.
Γωνιώδης
const appRoutes: Routes = [ { path: 'home', component: HomeComponent }, { path: 'posts', component: PostsComponent }, { path: 'form', component: FormComponent }, { path: '', redirectTo: '/home', pathMatch: 'full' } ] @NgModule({ declarations: [ AppComponent, PostsComponent, HomeComponent, FormComponent, ], imports: [ BrowserModule, RouterModule.forRoot(appRoutes), ApolloModule.forRoot(provideClient), FormsModule, ReactiveFormsModule, HttpModule, BrowserAnimationsModule, MdInputModule, MdSelectModule, MdButtonModule, MdCardModule, MdIconModule ], providers: [ AppService ], bootstrap: [AppComponent] })
@Injectable() export class AppService { username = 'Mr. User' }
Βασικά όλα τα στοιχεία που θέλουμε να χρησιμοποιήσουμε στην εφαρμογή πρέπει να μεταβούν στις δηλώσεις. Όλα τα βιβλιοπωλεία τρίτων για εισαγωγές και όλα τα παγκόσμια καταστήματα σε προμηθευτές. Τα εξαρτήματα για παιδιά έχουν πρόσβαση σε όλα αυτά, με την ευκαιρία να προσθέσουν περισσότερα τοπικά πράγματα.
Αντιδρώ
const appStore = AppStore.getInstance() const routerStore = RouterStore.getInstance() const rootStores = { appStore, routerStore } ReactDOM.render( , document.getElementById('root') )
Το εξάρτημα χρησιμοποιείται για ένεση εξάρτησης στο MobX. Αποθηκεύστε καταστήματα σε περιβάλλον, ώστε τα στοιχεία του React να μπορούν να τα εγχύσουν αργότερα. Ναι, μπορεί να χρησιμοποιηθεί το πλαίσιο React (αναμφισβήτητα) Ασφαλώς .
Η έκδοση React είναι λίγο πιο σύντομη, επειδή δεν υπάρχουν δηλώσεις λειτουργικής μονάδας - συνήθως είναι απλώς εισαγωγή και είστε έτοιμοι να ξεκινήσετε. Μερικές φορές αυτό το είδος σκληρής εξάρτησης είναι ανεπιθύμητο (στις δοκιμές), οπότε για παγκόσμια καταστήματα singleton έπρεπε να το χρησιμοποιήσω GoF Πρότυπο παλιά δεκαετία:
export class AppStore { static instance: AppStore static getInstance() (AppStore.instance = new AppStore()) @observable username = 'Mr. User' }
Το Angular’s Router είναι ενέσιμο, οπότε μπορεί να χρησιμοποιηθεί από οπουδήποτε και όχι μόνο από εξαρτήματα. Για να επιτύχουμε το ίδιο στην αντίδραση, χρησιμοποιούμε το πακέτο mobx-react-router και εγχύουμε το routerStore
.
Περίληψη: Το bootstrapping και των δύο εφαρμογών είναι αρκετά απλό. Το React έχει ένα πλεονέκτημα ότι είναι απλούστερο, χρησιμοποιώντας μόνο εισαγωγές αντί για ενότητες, αλλά, όπως θα δούμε αργότερα, αυτές οι ενότητες μπορεί να είναι πολύ χρήσιμες. Το να κάνεις ημιτελικά στο χέρι είναι λίγο πόνο. Όσον αφορά τη σύνταξη της δήλωσης δρομολόγησης, το JSON εναντίον JSX είναι απλώς θέμα προτίμησης.
Υπάρχουν δύο περιπτώσεις για να αλλάξετε μια διαδρομή. Δηλωτικά, χρησιμοποιώντας στοιχεία
και υποχρεωτικά, καλέστε το API δρομολόγησης (και επομένως την τοποθεσία) απευθείας.
Γωνιώδης
Home Posts {this.props.children}
Το React Router μπορεί επίσης να ορίσει την ενεργή κλάση συνδέσμου με activeClassName
.
Εδώ δεν μπορούμε να παρέχουμε το όνομα της τάξης απευθείας επειδή έχει γίνει μοναδικό από τον μεταγλωττιστή μονάδων CSS και πρέπει να χρησιμοποιήσουμε τον βοηθό style
Θα το συζητήσουμε αργότερα.
Όπως φαίνεται παραπάνω, το React Router χρησιμοποιεί το στοιχείο μέσα σε ένα στοιχείο. Δεδομένου ότι το στοιχείο απλώς τυλίγει και προσαρτά την τρέχουσα διαδρομή, σημαίνει ότι οι δευτερεύουσες διαδρομές του τρέχοντος στοιχείου είναι απλά this.props.children
. Οπότε είναι συνθετικό.
export class FormStore { routerStore: RouterStore constructor() { this.routerStore = RouterStore.getInstance() } goBack = () => { this.routerStore.history.push('/posts') } }
Το πακέτο mobx-router-store
Επιτρέπει επίσης την εύκολη ένεση και πλοήγηση.
Περίληψη: Και οι δύο προσεγγίσεις δρομολόγησης είναι αρκετά συγκρίσιμες. Το Angular φαίνεται να είναι πιο διαισθητικό, ενώ το React Router είναι λίγο πιο εύκολο στη σύνθεση.
Ο διαχωρισμός του επιπέδου δεδομένων από το επίπεδο παρουσίασης έχει ήδη αποδειχθεί ευεργετικός. Αυτό που προσπαθούμε να επιτύχουμε με το DI είναι να κάνουμε τα στοιχεία των επιπέδων δεδομένων (εδώ ονομάζονται μοντέλο / κατάστημα / υπηρεσία) να ακολουθούν τον κύκλο ζωής των οπτικών στοιχείων και επομένως να επιτρέπουν τη δημιουργία μίας ή περισσότερων παρουσιών των εν λόγω στοιχείων χωρίς να αγγίζει την παγκόσμια κατάσταση . Επίσης, θα πρέπει να είναι δυνατή η ανάμιξη και αντιστοίχιση υποστηριζόμενων δεδομένων και επιπέδων οθόνης.
Τα παραδείγματα σε αυτό το άρθρο είναι πολύ απλά, οπότε όλα τα είδη DI μπορεί να φαίνονται υπερβολικά, αλλά είναι χρήσιμο καθώς η εφαρμογή μεγαλώνει.
Γωνιώδης
@Injectable() export class HomeService { message = 'Welcome to home page' counter = 0 increment() { this.counter++ } }
Έτσι μπορεί να γίνει οποιαδήποτε κλάση @inyectable
και οι ιδιότητες και οι μέθοδοι της διατίθενται σε στοιχεία.
@Component({ selector: 'app-home', templateUrl: './home.component.html', providers: [ HomeService ] }) export class HomeComponent { constructor( public homeService: HomeService, public appService: AppService, ) { } }
Με την εγγραφή του HomeService
α providers
του στοιχείου, το διαθέτουμε αποκλειστικά σε αυτό το στοιχείο. Δεν είναι πλέον μισό σφάλμα, αλλά κάθε παρουσία του στοιχείου θα λάβει ένα νέο αντίγραφο, νέο στη συναρμολόγηση του στοιχείου. Αυτό σημαίνει ότι δεν υπάρχουν ξεπερασμένα δεδομένα από προηγούμενη χρήση.
Αντίθετα, το AppService
έχει εγγραφεί στο app.module
(βλ. παραπάνω), επομένως είναι ημι-αγρανάπαυση και παραμένει το ίδιο για όλα τα εξαρτήματα, αν και η διάρκεια της εφαρμογής. Η δυνατότητα ελέγχου του κύκλου ζωής των υπηρεσιών εξαρτημάτων είναι μια πολύ χρήσιμη ιδέα, αλλά ελάχιστα εκτιμώμενη.
Το DI λειτουργεί αναθέτοντας παρουσίες υπηρεσίας στον κατασκευαστή του στοιχείου, που προσδιορίζεται από τύπους TypeScript. Επίσης, οι λέξεις-κλειδιά public
αντιστοιχίζει αυτόματα παραμέτρους στο this
, οπότε δεν χρειάζεται πλέον να πληκτρολογούμε αυτές τις βαρετές γραμμές this.homeService = homeService
.
Dashboard
Clicks since last visit: {{homeService.counter}} Click!
Σύνταξη προτύπου Angular, σίγουρα αρκετά κομψό. Μου αρέσει η συντόμευση [()]
, η οποία λειτουργεί σαν σύνδεση 2 κατευθύνσεων δεδομένων, αλλά κάτω από την κουκούλα, στην πραγματικότητα είναι ένα χαρακτηριστικό δεσμευτικό + συμβάν. Όπως υπαγορεύεται από τον κύκλο ζωής των υπηρεσιών μας, homeService.counter
θα κάνει επανεκκίνηση κάθε φορά που απομακρυνόμαστε από το /home
, αλλά το appService.username
παραμένει και είναι προσβάσιμο από οπουδήποτε.
Αντιδρώ
import { observable } from 'mobx' export class HomeStore { @observable counter = 0 increment = () => { this.counter++ } }
Με το MobX, πρέπει να προσθέσουμε τον διακοσμητή @observable
σε οποιαδήποτε ιδιοκτησία που θέλουμε να κάνουμε αισθητή.
@observer export class Home extends React.Component { homeStore: HomeStore componentWillMount() { this.homeStore = new HomeStore() } render() { return } }
Για να διαχειριστούμε σωστά τον κύκλο ζωής, πρέπει να κάνουμε λίγο περισσότερη δουλειά από ότι στο γωνιακό παράδειγμα. Τυλίγουμε το HomeComponent
μέσα σε ένα Provider
, το οποίο λαμβάνει μια νέα παρουσία του HomeStore
σε κάθε συγκρότημα.
interface HomeComponentProps { appStore?: AppStore, homeStore?: HomeStore } @inject('appStore', 'homeStore') @observer export class HomeComponent extends React.Component { render() { const { homeStore, appStore } = this.props return Dashboard
Clicks since last visit: {homeStore.counter} Click! } }
HomeComponent
χρησιμοποιεί τον διακοσμητή @observer
για να ακούσετε αλλαγές στις ιδιότητες @observable
.
Ο μηχανισμός κάτω από την κουκούλα σε αυτό είναι πολύ δροσερός, οπότε ας το εξετάσουμε εν συντομία εδώ. Ο διακοσμητής @observable
παρακάμπτει μια ιδιότητα σε ένα αντικείμενο με το getter και το setter, επιτρέποντάς σας να παρακολουθείτε κλήσεις. Όταν η συνάρτηση απόδοσης ενός επαυξημένου στοιχείου ονομάζεται @observador
, οι ιδιότητες λήψης καλούνται και κρατούν μια αναφορά στο στοιχείο κλήσης.
Στη συνέχεια, όταν καλείται ο ρυθμιστής και αλλάξει η τιμή, καλούνται οι συναρτήσεις απόδοσης των στοιχείων που χρησιμοποίησαν την ιδιότητα στην τελευταία απόδοση. Τώρα τα δεδομένα σχετικά με το ποιες ιδιότητες χρησιμοποιούνται όπου ενημερώνεται και ολόκληρος ο κύκλος μπορεί να ξεκινήσει από την αρχή.
Ένας πολύ απλός μηχανισμός, και επίσης αρκετά αποδοτικός. Πιο αναλυτική εξήγηση εδώ .
Ο διακοσμητής @inyectar 'se utiliza para inyectar instancias
appStore y
homeStore en los accesorios de
HomeComponent . En este punto, cada una de esas tiendas tiene un ciclo de vida diferente.
appStore es el mismo durante la vida de la aplicación, pero
homeStore` δημιουργείται πρόσφατα σε κάθε πλοήγηση στη διαδρομή '/ home'.
Το πλεονέκτημα αυτού είναι ότι δεν είναι απαραίτητο να καθαρίσετε τις ιδιότητες με μη αυτόματο τρόπο, όπως συμβαίνει όταν όλα τα καταστήματα είναι καθολικά, κάτι που είναι επώδυνο εάν η διαδρομή είναι κάποια σελίδα 'λεπτομέρειας' που περιέχει εντελώς διαφορετικά δεδομένα κάθε φορά.
Περίληψη: Ως διαχείριση κύκλου ζωής προμηθευτή, ένα εγγενές χαρακτηριστικό του Angular's DI, είναι φυσικά απλούστερο να το επιτύχετε εκεί. Η έκδοση React είναι επίσης χρησιμοποιήσιμη, αλλά περιλαμβάνει πολύ περισσότερο boilerplate.
Αντιδρώ
Ας ξεκινήσουμε με το React σε αυτό, έχετε μια πιο άμεση λύση.
import { observable, computed, action } from 'mobx' export class HomeStore { import { observable, computed, action } from 'mobx' export class HomeStore { @observable counter = 0 increment = () => { this.counter++ } @computed get counterMessage() { console.log('recompute counterMessage!') return `${this.counter} ${this.counter === 1 ? 'click' : 'clicks'} since last visit` } }
Έχουμε λοιπόν μια υπολογισμένη ιδιότητα που συνδέεται με counter
και επιστρέφει ένα σωστά πληθυντικό μήνυμα. Το αποτέλεσμα του counterMessage
αποθηκεύεται προσωρινά και υπολογίζεται εκ νέου μόνο όταν counter
αλλαγές.
{homeStore.counterMessage} Click!
Στη συνέχεια, αναφέρουμε την ιδιότητα (και τη μέθοδο increment
) του προτύπου JSX. Το πεδίο εισαγωγής ελέγχεται δεσμεύοντας μια τιμή και επιτρέπει τη μέθοδο 'appStore' για τον χειρισμό του συμβάντος χρήστη.
Γωνιώδης
Για να επιτύχουμε το ίδιο αποτέλεσμα στο Angular, πρέπει να είμαστε λίγο πιο εφευρετικοί.
import { Injectable } from '@angular/core' import { BehaviorSubject } from 'rxjs/BehaviorSubject' @Injectable() export class HomeService { message = 'Welcome to home page' counterSubject = new BehaviorSubject(0) // Computed property can serve as basis for further computed properties counterMessage = new BehaviorSubject('') constructor() { // Manually subscribe to each subject that couterMessage depends on this.counterSubject.subscribe(this.recomputeCounterMessage) } // Needs to have bound this private recomputeCounterMessage = (x) => { console.log('recompute counterMessage!') this.counterMessage.next(`${x} ${x === 1 ? 'click' : 'clicks'} since last visit`) } increment() { this.counterSubject.next(this.counterSubject.getValue() + 1) } }
Πρέπει να ορίσουμε όλες τις τιμές που χρησιμεύουν ως βάση για μια υπολογιζόμενη ιδιότητα ως BehaviorSubject
. Η ίδια η υπολογισμένη ιδιότητα είναι επίσης μια BehaviorSubject
, επειδή οποιαδήποτε υπολογιζόμενη ιδιότητα μπορεί να χρησιμεύσει ως είσοδος σε μια άλλη υπολογισμένη ιδιότητα.
Φυσικά, RxJS
μπορώ πολύ περισσότερο παρά μόνο αυτό, αλλά αυτό θα ήταν ένα θέμα για ένα εντελώς διαφορετικό άρθρο. Το μικρό μειονέκτημα είναι ότι αυτή η ασήμαντη χρήση του RxJS για υπολογισμένες ιδιότητες είναι λίγο πιο ρητή από το αντιδραστικό παράδειγμα και πρέπει να διαχειριστείτε τις συνδρομές χειροκίνητα (όπως εδώ στον κατασκευαστή).
{ async} Click!
Σημειώστε πώς μπορούμε να αναφέρουμε το θέμα RxJS με το |asíncrona
. Αυτό είναι ένα ωραίο άγγιγμα, πολύ μικρότερο από το να χρειάζεται να εγγραφείτε στα συστατικά του. Το συστατικό input
καθοδηγείται από την οδηγία [(ngModel)]
. Παρόλο που φαίνεται περίεργο, είναι στην πραγματικότητα αρκετά κομψό. Ακριβώς μια συντακτική ζάχαρη για δεσμευτικά δεδομένα αξίας σε appService.username
και την τιμή αυτόματης εκχώρησης του συμβάντος εισαγωγής χρήστη.
Περίληψη: Οι υπολογισμένες ιδιότητες είναι ευκολότερο να εφαρμοστούν στο React / MobX από το Angular / RxJS, αλλά το RxJS θα μπορούσε να προσφέρει μερικές πιο χρήσιμες δυνατότητες FRP, οι οποίες θα μπορούσαν να εκτιμηθούν αργότερα.
Για να δείξουμε πώς συσσωρεύονται τα πρότυπα, πρόκειται να χρησιμοποιήσουμε το στοιχείο μηνυμάτων που εμφανίζει μια λίστα μηνυμάτων.
Γωνιώδης
@Component({ selector: 'app-posts', templateUrl: './posts.component.html', styleUrls: ['./posts.component.css'], providers: [ PostsService ] }) export class PostsComponent implements OnInit { constructor( public postsService: PostsService, public appService: AppService ) { } ngOnInit() { this.postsService.initializePosts() } }
Αυτό το στοιχείο συνδέει μόνο HTML, CSS και υπηρεσίες που έχουν εισαχθεί και καλεί επίσης τη λειτουργία για τη φόρτωση των μηνυμάτων API κατά την προετοιμασία. AppService
είναι ένα ημι-σφάλμα που ορίζεται στη λειτουργική μονάδα, ενώ PostsService
είναι παροδικό, με μια νέα παρουσία που δημιουργείται σε κάθε φορά που δημιουργείται το στοιχείο. Το CSS που αναφέρεται από αυτό το στοιχείο καλύπτει αυτό το στοιχείο, πράγμα που σημαίνει ότι το περιεχόμενο δεν μπορεί να επηρεάσει τίποτα εκτός του στοιχείου.
add Hello {{appService.username}}
{{post.title}} {{post.name}} {{post.message}}
Στο πρότυπο HTML, αναφερόμαστε κυρίως σε στοιχεία γωνιακού υλικού. Για να είναι διαθέσιμα, ήταν απαραίτητο να συμπεριληφθούν στις εισαγωγές app.module
(βλέπε παραπάνω). Η οδηγία *ngFor
χρησιμοποιείται για την επανάληψη του στοιχείου md-card
για κάθε θέση.
Τοπικό CSS:
.mat-card { margin-bottom: 1rem; }
Το τοπικό CSS αυξάνει μόνο μία από τις τάξεις που υπάρχουν στο στοιχείο md-card
.
Παγκόσμιο CSS:
.float-right { float: right; }
Αυτή η τάξη ορίζεται στο καθολικό αρχείο style.css
έτσι ώστε να είναι διαθέσιμο για όλα τα εξαρτήματα. Μπορεί να αναφέρεται στην τυπική μορφή, class =' float-right '
.
Μεταγλωττισμένο CSS:
.float-right { float: right; } .mat-card[_ngcontent-c1] { margin-bottom: 1rem; }
Στο μεταγλωττισμένο CSS, μπορούμε να δούμε ότι το τοπικό CSS έχει οριοθετηθεί στο στοιχείο που αποδόθηκε από τον επιλογέα χαρακτηριστικών [_ngcontent-c1]
. Κάθε στοιχείο γωνιακής απόδοσης έχει δημιουργηθεί μια κλάση όπως αυτή για σκοπούς κάλυψης CSS.
Το πλεονέκτημα αυτού του μηχανισμού είναι ότι μπορούμε να κάνουμε αναφορά σε τάξεις κανονικά και το πεδίο χειρισμού «κάτω από την κουκούλα».
Αντιδρώ
import * as style from './posts.css' import * as appStyle from '../app.css' @observer export class Posts extends React.Component { postsStore: PostsStore componentWillMount() { this.postsStore = new PostsStore() this.postsStore.initializePosts() } render() { return } }
Στο React, πάλι, πρέπει να χρησιμοποιήσουμε το Provider
για να κάνετε την εξάρτηση PostsStore 'παροδική'. Εισάγουμε επίσης στυλ CSS, που ονομάζονται style
και appStyle
, για να μπορείτε να χρησιμοποιήσετε τις κλάσεις αυτών των αρχείων CSS στο JSX.
interface PostsComponentProps { appStore?: AppStore, postsStore?: PostsStore } @inject('appStore', 'postsStore') @observer export class PostsComponent extends React.Component { render() { const { postsStore, appStore } = this.props return Hello {appStore.username}
{postsStore.posts.map(post => {post.message} )} } }
Φυσικά, το JSX αισθάνεται πολύ περισσότερο JavaScript-y από τα πρότυπα HTML της Angular, το οποίο μπορεί να είναι καλό ή κακό ανάλογα με τις προτιμήσεις σας. Αντί για την οδηγία *ngFor
, χρησιμοποιούμε την κατασκευή map
για να επαναλάβετε τις δημοσιεύσεις.
Τώρα το Angular μπορεί να είναι το πλαίσιο που προωθεί το TypeScript περισσότερο, αλλά είναι στην πραγματικότητα JSX όπου το TypScript λάμπει πραγματικά. Με την προσθήκη μονάδων CSS (που εισάγονται παραπάνω), μετατρέπει πραγματικά το πρότυπο κωδικοποίησης σε κώδικα zen. Όλα ελέγχονται. Στοιχεία, χαρακτηριστικά, ακόμη και κλάσεις CSS (appStyle.floatRight
και style.messageCard
, δείτε παρακάτω). Και φυσικά, η λεπτή φύση του JSX ενθαρρύνει τον διαχωρισμό σε εξαρτήματα και κομμάτια λίγο περισσότερο από τα πρότυπα του Angular.
Τοπικό CSS:
.messageCard { margin-bottom: 1rem; }
Παγκόσμιο CSS:
.floatRight { float: right; }
Μεταγλωττισμένο CSS:
.floatRight__qItBM { float: right; } .messageCard__1Dt_9 { margin-bottom: 1rem; }
Όπως μπορείτε να δείτε, το Loader for CSS Modules επιδιορθώνει κάθε κλάση CSS με ένα τυχαίο postfix, το οποίο εγγυάται τη μοναδικότητα. Ένας απλός τρόπος αποφυγής συγκρούσεων. Τα μαθήματα αναφέρονται στη συνέχεια μέσω των εισαγόμενων αντικειμένων από το πακέτο Ιστού. Ένα πιθανό μειονέκτημα σε αυτό μπορεί να είναι ότι δεν μπορείτε να δημιουργήσετε ένα CSS με μια τάξη και να το αυξήσετε, όπως κάναμε στο γωνιακό παράδειγμα. Από την άλλη πλευρά, αυτό μπορεί πραγματικά να είναι καλό, επειδή σας αναγκάζει να ενσωματώσετε σωστά τα στυλ.
Περίληψη: Προσωπικά, μου αρέσει το JSX λίγο καλύτερα από τα πρότυπα Angular, κυρίως λόγω ολοκλήρωσης κώδικα και τύπου υποστήριξης ελέγχου. Αυτό είναι πραγματικά ένα χαρακτηριστικό δολοφονίας. Το Angular έχει τώρα τον μεταγλωττιστή AOT, ο οποίος μπορεί να ανιχνεύσει και κάποια πράγματα, η ολοκλήρωση κώδικα λειτουργεί και για περίπου τα μισά πράγματα εκεί, αλλά δεν είναι σχεδόν τόσο πλήρης όσο το JSX / TypeScript.
Αποφασίσαμε λοιπόν να χρησιμοποιήσουμε το GraphQL για να αποθηκεύσουμε δεδομένα για αυτήν την εφαρμογή. Ένας από τους ευκολότερους τρόπους για να δημιουργήσετε το GraphQL backend είναι να χρησιμοποιήσετε κάποια BaaS, όπως το Graphcool. Έτσι κάναμε. Βασικά, απλά ορίζετε μοντέλα και χαρακτηριστικά και το CRUD σας είναι καλό.
Κοινός κώδικας
Καθώς κάποιος από τον κώδικα που σχετίζεται με το GraphQL είναι 100% ο ίδιος και για τις δύο υλοποιήσεις, δεν τον επαναλαμβάνουμε δύο φορές:
const PostsQuery = gql` query PostsQuery { allPosts(orderBy: createdAt_DESC, first: 5) { id, name, title, message } } `
Το GraphQL είναι μια γλώσσα ερωτήματος που στοχεύει στην παροχή ενός πλουσιότερου συνόλου λειτουργικότητας σε σύγκριση με τα κλασικά τελικά σημεία RESTful. Θα αναλύσουμε αυτό το συγκεκριμένο ερώτημα.
PostsQuery
είναι απλώς ένα όνομα για αυτό το ερώτημα για μεταγενέστερη αναφορά, μπορείτε να ονομάσετε οτιδήποτε.allPosts
είναι το πιο σημαντικό μέρος - αναφέρεται στη συνάρτηση ερωτήματος όλων των εγγραφών με το μοτίβο «Post». Αυτό το όνομα δημιουργήθηκε από το Graphcool.orderBy
και first
είναι παράμετροι της συνάρτησης allPosts
createdAt
είναι ένα από τα Post
χαρακτηριστικά μοντέλου. first: 5
σημαίνει ότι θα επιστρέψει μόνο τα πρώτα 5 αποτελέσματα του ερωτήματος.id
, name
, title
, και message
είναι τα χαρακτηριστικά του μοντέλου Post
ότι θέλουμε να συμπεριληφθούμε στο αποτέλεσμα. Άλλα χαρακτηριστικά θα φιλτραριστούν.Όπως μπορείτε ήδη να δείτε, είναι αρκετά ισχυρό. Ρίξε μια ματιά στο αυτή η σελίδα για να εξοικειωθείτε με τα ερωτήματα GraphQL.
interface Post { id: string name: string title: string message: string } interface PostsQueryResult { allPosts: Array }
Ναι, ως καλοί πολίτες του TypeScript, δημιουργούμε διεπαφές για αποτελέσματα GraphQL.
Γωνιώδης
@Injectable() export class PostsService { posts = [] constructor(private apollo: Apollo) { } initializePosts() { this.apollo.query({ query: PostsQuery, fetchPolicy: 'network-only' }).subscribe(({ data }) => { this.posts = data.allPosts }) } }
Το ερώτημα GraphQL είναι ένα παρατηρήσιμο RxJS και εγγραφούμε σε αυτό. Λειτουργεί λίγο σαν μια υπόσχεση, αλλά δεν ισχύει, οπότε δεν έχουμε καμία τύχη με το async/await
. Φυσικά υπάρχουν ακόμα υπόσχομαι , αλλά δεν φαίνεται να είναι η γωνιακή διαδρομή ούτως ή άλλως. Ορίσαμε fetchPolicy: 'network-only'
γιατί σε αυτήν την περίπτωση, δεν θέλουμε να αποθηκεύσουμε προσωρινά τα δεδομένα, αλλά να κάνουμε ανάκτηση κάθε φορά.
Αντιδρώ
export class PostsStore { appStore: AppStore @observable posts: Array = [] constructor() { this.appStore = AppStore.getInstance() } async initializePosts() { const result = await this.appStore.apolloClient.query({ query: PostsQuery, fetchPolicy: 'network-only' }) this.posts = result.data.allPosts } }
Η έκδοση React είναι σχεδόν πανομοιότυπη, αλλά ως apolloClient
Εδώ χρησιμοποιεί υποσχέσεις, μπορούμε να επωφεληθούμε από τη σύνταξη async / await
Υπάρχουν άλλες προσεγγίσεις στο React που μόνο 'γράφουν' ερωτήματα GraphQL στοιχεία υψηλότερης τάξης , αλλά το βρήκα να αναμιγνύει το επίπεδο δεδομένων και την παρουσίαση για να πάει πάρα πολύ.
Συνοψίζοντας: Οι ιδέες της εγγραφής RxJS vs async / wait είναι πραγματικά οι ίδιες.
Κοινός κώδικας
Για άλλη μια φορά, κάποιος σχετικός κώδικας GraphQL:
const AddPostMutation = gql` mutation AddPostMutation($name: String!, $title: String!, $message: String!) { createPost( name: $name, title: $title, message: $message ) { id } } `
Ο σκοπός των μεταλλάξεων είναι η δημιουργία ή ενημέρωση εγγραφών. Επομένως, είναι ωφέλιμο να δηλώσετε ορισμένες μεταβλητές με τη μετάλλαξη, καθώς είναι ο τρόπος με τον οποίο διαβιβάζονται τα δεδομένα σε αυτήν. Έχουμε λοιπόν τις μεταβλητές name
, title
και message
, γραμμένα ως String
, τα οποία πρέπει να συμπληρώνουμε κάθε φορά που ονομάζουμε αυτήν τη μετάλλαξη. Η συνάρτηση createPost
, πάλι, ορίζεται από το Graphcool. Προσδιορίζουμε ότι τα κλειδιά του μοντέλου Post
θα έχει τιμές των μεταβλητών μετάλλαξης εξόδου, και επίσης ότι θέλουμε το id
του νεοδημιουργημένου Post αποστέλλεται σε αντάλλαγμα.
Γωνιώδης
@Injectable() export class FormService { constructor( private apollo: Apollo, private router: Router, private appService: AppService ) { } addPost(value) { this.apollo.mutate({ mutation: AddPostMutation, variables: { name: this.appService.username, title: value.title, message: value.message } }).subscribe(({ data }) => { this.router.navigate(['/posts']) }, (error) => { console.log('there was an error sending the query', error) }) } }
Όταν καλούμε apollo.mutate
, πρέπει να παρέχουμε τη μετάλλαξη που καλούμε και τις μεταβλητές επίσης. Έχουμε το αποτέλεσμα στη συνάρτηση επανάκλησης subscribe
και χρησιμοποιούμε το εγχυμένο 'rotator' για να επιστρέψουμε στη λίστα αλληλογραφίας.
Αντιδρώ
export class FormStore { constructor() { this.appStore = AppStore.getInstance() this.routerStore = RouterStore.getInstance() this.postFormState = new PostFormState() } submit = async () => { await this.postFormState.form.validate() if (this.postFormState.form.error) return const result = await this.appStore.apolloClient.mutate( { mutation: AddPostMutation, variables: { name: this.appStore.username, title: this.postFormState.title.value, message: this.postFormState.message.value } } ) this.goBack() } goBack = () => { this.routerStore.history.push('/posts') } }
Πολύ παρόμοια με τα παραπάνω, με τη διαφορά της πιο «χειροκίνητης» έγχυσης εξάρτησης και της χρήσης async/await
.
Συνοψίζοντας: Και πάλι, δεν υπάρχει μεγάλη διαφορά εδώ. εγγραφείτε vs async / αναμονή είναι βασικά το μόνο που διαφέρει.
Θέλουμε να επιτύχουμε τους ακόλουθους στόχους με τις φόρμες σε αυτήν την εφαρμογή:
Αντιδρώ
export const check = (validator, message, options) => (value) => (!validator(value, options) && message) export const checkRequired = (msg: string) => check(nonEmpty, msg) export class PostFormState { title = new FieldState('').validators( checkRequired('Title is required'), check(isLength, 'Title must be at least 4 characters long.', { min: 4 }), check(isLength, 'Title cannot be more than 24 characters long.', { max: 24 }), ) message = new FieldState('').validators( checkRequired('Message cannot be blank.'), check(isLength, 'Message is too short, minimum is 50 characters.', { min: 50 }), check(isLength, 'Message is too long, maximum is 1000 characters.', { max: 1000 }), ) form = new FormState({ title: this.title, message: this.message }) }
Επομένως, το βιβλιοπωλείο φορμάτ Λειτουργεί ως εξής: για κάθε πεδίο στη φόρμα σας, ορίστε ένα FieldState
. Η παράμετρος που πέρασε είναι η αρχική τιμή. Το validators
παίρνει μια συνάρτηση, η οποία επιστρέφει 'false' όταν η τιμή είναι έγκυρη και ένα μήνυμα επικύρωσης όταν η τιμή δεν είναι έγκυρη. Με τις συναρτήσεις check' y
checkRequired`, όλα φαίνονται πολύ δηλωτικά.
Για να έχετε την επικύρωση ολόκληρης της φόρμας, είναι χρήσιμο να τυλίξετε επίσης αυτά τα πεδία με μια παρουσία FormState
, η οποία στη συνέχεια παρέχει την προστιθέμενη ισχύ.
@inject('appStore', 'formStore') @observer export class FormComponent extends React.Component { render() { const { appStore, formStore } = this.props const { postFormState } = formStore return Create a new post
You are now posting as {appStore.username}
Η παρουσία FormState
παρέχει ιδιότητες value
, onChange
και error
, τα οποία μπορούν εύκολα να χρησιμοποιηθούν με οποιοδήποτε στοιχείο front-end.
} }
Πότε form.hasError
είναι true
, διατηρούμε απενεργοποιημένο το κουμπί. Το κουμπί Υποβολή υποβάλλει τη φόρμα στη μετάλλαξη GraphQL που παρουσιάζεται παραπάνω.
Γωνιώδης
Στο Angular, πρόκειται να χρησιμοποιήσουμε FormService
και FormBuilder
, που αποτελούν μέρη του πακέτου @angular/forms
.
@Component({ selector: 'app-form', templateUrl: './form.component.html', providers: [ FormService ] }) export class FormComponent { postForm: FormGroup validationMessages = { 'title': { 'required': 'Title is required.', 'minlength': 'Title must be at least 4 characters long.', 'maxlength': 'Title cannot be more than 24 characters long.' }, 'message': { 'required': 'Message cannot be blank.', 'minlength': 'Message is too short, minimum is 50 characters', 'maxlength': 'Message is too long, maximum is 1000 characters' } }
Αρχικά, ας καθορίσουμε τα μηνύματα επικύρωσης.
constructor( private router: Router, private formService: FormService, public appService: AppService, private fb: FormBuilder, ) { this.createForm() } createForm() { this.postForm = this.fb.group({ title: ['', [Validators.required, Validators.minLength(4), Validators.maxLength(24)] ], message: ['', [Validators.required, Validators.minLength(50), Validators.maxLength(1000)] ], }) }
Χρησιμοποιώντας το FormBuilder
, είναι πολύ εύκολο να δημιουργήσετε τη δομή της φόρμας, ακόμη πιο συνοπτικά από το παράδειγμα του React.
get validationErrors() { const errors = {} Object.keys(this.postForm.controls).forEach(key => { errors[key] = '' const control = this.postForm.controls[key] if (control && !control.valid) { const messages = this.validationMessages[key] Object.keys(control.errors).forEach(error => { errors[key] += messages[error] + ' ' }) } }) return errors }
Για να λάβουμε δεσμευτικά μηνύματα επικύρωσης στο σωστό μέρος, πρέπει να κάνουμε κάποια επεξεργασία. Αυτός ο κωδικός έχει ληφθεί από την επίσημη τεκμηρίωση, με μερικές μικρές αλλαγές. Βασικά, στο FormService, τα πεδία διατηρούν αναφορά μόνο σε ενεργά σφάλματα, τα οποία προσδιορίζονται με το όνομα επικυρωτή, οπότε πρέπει να αντιστοιχίσουμε μη αυτόματα τα απαιτούμενα μηνύματα με τα πεδία που επηρεάζονται. Αυτό δεν είναι εντελώς αναστάτωση. Για παράδειγμα, προσφέρεται ευκολότερα στη διεθνοποίηση.
onSubmit({ value, valid }) { if (!valid) { return } this.formService.addPost(value) } onCancel() { this.router.navigate(['/posts']) } }
Για άλλη μια φορά, όταν η φόρμα είναι έγκυρη, τα δεδομένα μπορούν να σταλούν στη μετάλλαξη GraphQL.
Create a new post
You are now posting as {{appService.username}}
{{validationErrors['title']}}
{{validationErrors['message']}}
Cancel Submit
Το πιο σημαντικό πράγμα είναι να αναφερθείτε στο formGroup που δημιουργήσαμε με το FormBuilder, το οποίο είναι η ανάθεση [formGroup] = 'postForm'
. Τα πεδία της φόρμας συνδέονται με το μοντέλο φόρμας μέσω της ιδιότητας formControlName
. Και πάλι, απενεργοποιούμε το κουμπί 'Υποβολή' όταν η φόρμα δεν είναι έγκυρη. Πρέπει επίσης να προσθέσουμε τον βρώμικο έλεγχο, γιατί εδώ, η μη βρώμικη φόρμα μπορεί να είναι άκυρη. Θέλουμε να είναι 'ενεργοποιημένη' η αρχική κατάσταση του κουμπιού.
Συνοψίζοντας: Αυτή η προσέγγιση για τις φόρμες στο React και το Angular είναι αρκετά διαφορετική στο μέτωπο επικύρωσης και προτύπων. Η γωνιακή προσέγγιση περιλαμβάνει λίγο περισσότερο «μαγικό» παρά άμεση σύνδεση, αλλά από την άλλη πλευρά, είναι πιο περιεκτική.
Ένα ακόμη πράγμα. Η παραγωγή μειώνει τα μεγέθη πακέτων JS, με τις προεπιλεγμένες ρυθμίσεις της γεννήτριας εφαρμογής: κυρίως Tree Shaking in React και AOT build in Angular.
Λοιπόν, δεν υπάρχει μεγάλη έκπληξη εδώ. Η γωνιακή ήταν πάντα η πιο ογκώδης.
Όταν χρησιμοποιείτε το gzip, τα μεγέθη μειώνονται στα 275kb και 127kb αντίστοιχα.
Απλώς λάβετε υπόψη ότι αυτές είναι βασικά όλες οι βιβλιοθήκες προμηθευτών. Το ποσό του πραγματικού κώδικα εφαρμογής είναι ελάχιστο συγκριτικά, κάτι που δεν συμβαίνει σε μια πραγματική εφαρμογή. Εκεί, ο λόγος πιθανότατα θα ήταν πιο κοντά στο 1: 2 από το 1: 4. Επίσης, όταν αρχίζετε να συμπεριλαμβάνετε πολλές βιβλιοθήκες τρίτων με το React, το μέγεθος του πακέτου τείνει επίσης να αυξάνεται αρκετά γρήγορα.
Φαίνεται λοιπόν ότι δεν καταφέραμε (ξανά!) Να δώσουμε μια σαφή απάντηση σχετικά με το αν το Angular ή το React είναι καλύτερο για την ανάπτυξη ιστού.
Αποδεικνύεται ότι οι ροές εργασίας ανάπτυξης στο React και το Angular μπορεί να είναι πολύ παρόμοιες, ανάλογα με τις βιβλιοθήκες με τις οποίες επιλέγουμε να χρησιμοποιήσουμε το React. Επομένως, είναι κυρίως θέμα προσωπικής προτίμησης.
Αν σας αρέσουν οι έτοιμες στοίβες, η ισχυρή εξάρτηση εξάρτησης και το σχέδιο να χρησιμοποιήσετε κάποια καλούδια από το RxJS, επιλέξτε Angular.
Εάν θέλετε να παίξετε παιχνίδια και να δημιουργήσετε τη δική σας στοίβα, όπως η απλότητα του JSX και προτιμάτε απλούστερες υπολογιστικές ιδιότητες, επιλέξτε React / MobX.
Για άλλη μια φορά, μπορείτε να λάβετε τον πλήρη πηγαίο κώδικα της εφαρμογής από αυτό το άρθρο εδώ Υ εδώ .
Ή, αν προτιμάτε μεγαλύτερα παραδείγματα και από το RealWorld:
Ο προγραμματισμός με το React / MobX είναι στην πραγματικότητα πιο παρόμοιος με το Angular από το React / Redux. Υπάρχουν κάποιες αξιοσημείωτες διαφορές στα πρότυπα και τον χειρισμό της εξάρτησης, αλλά έχουν το ίδιο παράδειγμα μεταβλητή / δέσμευση δεδομένων .
Αντιδράστε / Redux με το παράδειγμά του αμετάβλητο / μονοκατευθυντικό είναι ένα εντελώς διαφορετικό θηρίο.
Μην ξεγελιέστε από το μικρό αποτύπωμα της βιβλιοθήκης Redux. Μπορεί να είναι μικρό, αλλά πάντως είναι ένα πλαίσιο. Οι περισσότερες από τις βέλτιστες πρακτικές της Redux σήμερα επικεντρώνονται στη χρήση βιβλιοθηκών συμβατών με redux, όπως Redux Saga για ασύγχρονη αναζήτηση κώδικα και δεδομένων, Φόρμα Redux για τη διαχείριση εντύπων, Επιλέξτε ξανά για απομνημονευτές επιλογείς (υπολογισμένες τιμές Redux). Υ Ανασυνθέτω , μεταξύ άλλων, για μια καλύτερη διαχείριση του κύκλου ζωής. Επίσης, υπάρχει μια αλλαγή στην κοινότητα Redux από Αμετάβλητο.js προς το Ράμδα ή lodash / fp , τα οποία λειτουργούν με απλά αντικείμενα JS αντί να τα μετατρέπουν.
Ένα καλό παράδειγμα του σύγχρονου Redux είναι το γνωστό Αντιδράστε το Boilerplate . Είναι μια τρομερή στοίβα ανάπτυξης, αλλά αν το ρίξετε μια ματιά, είναι πραγματικά πολύ, πολύ διαφορετικό από οτιδήποτε έχουμε δει σε αυτήν την ανάρτηση μέχρι τώρα.
Αισθάνομαι ότι η Angular παίρνει λίγο άδικη μεταχείριση από το πιο φωνητικό μέρος της κοινότητας JavaScript. Πολλοί άνθρωποι που εκφράζουν τη δυσαρέσκειά τους πιθανώς δεν εκτιμούν την τεράστια αλλαγή που συνέβη μεταξύ του παλαιού AngularJS και του σημερινού Angular. Κατά τη γνώμη μου, είναι ένα πολύ καθαρό και παραγωγικό πλαίσιο που θα έπαιρνε τον κόσμο από καταιγίδα αν είχε εμφανιστεί 1-2 χρόνια νωρίτερα.
Ωστόσο, η Angular κερδίζει μια σταθερή βάση, ειδικά στον εταιρικό κόσμο, με μεγάλες ομάδες και ανάγκες για τυποποίηση και μακροπρόθεσμη υποστήριξη. Ή με άλλα λόγια, το Angular είναι ο τρόπος με τον οποίο οι μηχανικοί της Google πιστεύουν ότι πρέπει να γίνει η ανάπτυξη ιστού, εάν αυτό ισχύει ακόμα.
Όσον αφορά το MobX, ισχύει παρόμοια αξιολόγηση. Πραγματικά δροσερό, αλλά δεν εκτιμάται πολύ.
Κατώτατη γραμμή: πριν επιλέξετε μεταξύ React και Angular, πρώτα επιλέξτε το πρότυπο προγραμματισμού σας.
μεταβλητή / δέσμευση δεδομένων ή αμετάβλητο / μονοκατευθυντικό , αυτό φαίνεται να είναι το πραγματικό πρόβλημα.