Full-stack NLP cu React: Ionic vs Cordova vs React Native
Publicat: 2022-03-11În cei aproximativ 15 ani de când Apple a lansat primul iPhone, peisajul dezvoltării software s-a schimbat dramatic. Pe măsură ce smartphone-urile capătă o adoptare pe scară largă și continuă să crească în capabilități unice, utilizatorii preferă din ce în ce mai mult să acceseze serviciile software prin dispozitive mobile, mai degrabă decât desktop-uri sau laptop-uri. Smartphone-urile oferă funcții precum geolocalizarea, autentificarea biometrică și detectarea mișcării, multe dintre care platformele desktop abia acum încep să le copieze. În unele categorii demografice, un smartphone sau un dispozitiv mobil similar este mijlocul principal de consum de software, ocolind computerele cu totul.
Companiile au observat această schimbare și au consolidat-o în moduri majore. Aplicațiile mobile nu mai sunt o idee ulterioară. Aplicații, de la Robinhood, o companie de brokeraj financiar, până la Instagram, o companie de social media, la Uber, o companie de transport, adoptă o strategie de dezvoltare pe mobil. Dacă există o aplicație desktop, aceasta este adesea oferită ca o completare a aplicației mobile, mai degrabă decât ca obiectiv principal.
Pentru dezvoltatorii full-stack, adaptarea la aceste tendințe în schimbare este crucială. Din fericire, există multe tehnologii mature și bine susținute disponibile pentru a ajuta dezvoltatorii web să își aplice abilitățile în dezvoltarea mobilă. Astăzi, vom explora trei astfel de tehnologii: Cordova, Ionic și React Native. Vom folosi React.js, unul dintre cele mai populare cadre pentru dezvoltarea web front-end, ca tehnologie de dezvoltare de bază. Deși ne vom concentra pe dezvoltarea unei aplicații pentru iPhone, acestea sunt tehnologii multiplatforme și vor fi compilabile încrucișat cu platforma Android.
Ce vom construi astăzi
Vom construi o aplicație care utilizează procesarea limbajului natural (NLP) pentru a procesa și a gestiona fluxurile Twitter. Aplicația va permite utilizatorului să selecteze un set de mânere Twitter, să extragă cele mai recente actualizări folosind un API Twitter și să clasifice tweet-urile în funcție de sentiment și subiect. Utilizatorul va putea apoi să vadă tweet-urile în funcție de sentiment sau subiect.
Back End
Înainte de a construi front-end-ul, vom dori să construim back-end-ul. Vom păstra back-end-ul simplu deocamdată - vom folosi o analiză de bază a sentimentelor și etichetarea parțială a vorbirii, împreună cu puțină curățare a datelor pentru a rezolva problemele specifice setului de date. Vom folosi o bibliotecă NLP open-source numită TextBlob și vom servi rezultatul prin Flask.
Analiza sentimentelor, etichetarea unei părți din vorbire și NLP: o inițiere rapidă
Dacă nu ați mai lucrat cu aplicații de analiză a limbajului natural înainte, acești termeni s-ar putea să vă fie foarte străini. NLP este un termen umbrelă pentru tehnologiile care analizează și procesează datele din limbajul uman natural. Deși acesta este un subiect larg, există multe provocări care sunt comune tuturor tehnologiilor care abordează acest domeniu. De exemplu, limbajul uman, spre deosebire de limbajul de programare sau de datele numerice, tinde să fie slab structurat din cauza naturii permisive a gramaticii limbajului uman. În plus, limbajul uman tinde să fie extrem de contextual, iar o expresie rostită sau scrisă într-un context poate să nu se traducă în alt context. În cele din urmă, lăsând deoparte structura și contextul, limbajul este extrem de complex. Cuvintele mai jos într-un paragraf ar putea schimba sensul unei propoziții de la începutul paragrafului. Vocabularul poate fi inventat, redefinit sau schimbat. Toate aceste complexități fac tehnicile de analiză a datelor dificil de aplicat încrucișat.
Analiza sentimentelor este un subdomeniu al NLP care se concentrează pe înțelegerea emoționalității unui pasaj de limbaj natural. În timp ce emoția umană este în mod inerent subiectivă și, prin urmare, dificil de identificat din punct de vedere tehnologic, analiza sentimentelor este un subdomeniu care are o imensă promisiune comercială. Unele aplicații ale analizei sentimentelor includ clasificarea recenziilor produselor pentru a identifica evaluarea pozitivă și negativă a diferitelor caracteristici, detectarea stării de spirit a unui e-mail sau a unui discurs și gruparea versurilor melodiilor în funcție de starea de spirit. Dacă sunteți în căutarea unei explicații mai aprofundate despre Analiza sentimentelor, puteți citi articolul meu despre construirea unei aplicații bazate pe Analiza sentimentelor aici.
Etichetarea parțială a vorbirii sau etichetarea POS este un subcâmp foarte diferit. Scopul etichetării POS este de a identifica partea de vorbire a unui anumit cuvânt dintr-o propoziție folosind informații gramaticale și contextuale. Identificarea acestei relații este o sarcină mult mai dificilă decât se vede inițial - un cuvânt poate avea părți de vorbire foarte diferite în funcție de context și de structura propoziției, iar regulile nu sunt întotdeauna clare nici măcar pentru oameni. Din fericire, multe modele disponibile astăzi oferă modele puternice și versatile integrate cu majoritatea limbajelor de programare majore. Dacă doriți să aflați mai multe, puteți citi articolul meu despre etichetarea POS aici.
Flask, TextBlob și Tweepy
Pentru back-end-ul nostru NLP, vom folosi Flask, TextBlob și Tweepy. Vom folosi Flask pentru a construi un server mic și ușor, TextBlob pentru a rula procesarea limbajului nostru natural și Tweepy pentru a obține tweet-uri din API-ul Twitter. Înainte de a începe să codificați, veți dori, de asemenea, să obțineți o cheie de dezvoltator de la Twitter, astfel încât să puteți prelua tweet-urile.
Putem scrie un back-end mult mai sofisticat și folosi tehnologii NLP mai complexe, dar pentru scopurile noastre de astăzi, vom păstra back-end-ul cât mai simplu posibil.
Cod back-end
Acum suntem gata să începem codificarea. Porniți editorul și terminalul dvs. Python preferat și haideți!
În primul rând, vom dori să instalăm pachetele necesare.
pip install flask flask-cors textblob tweepy python -m textblob.download_corpora
Acum, să scriem codul pentru funcționalitatea noastră.
Deschideți un nou script Python, numiți-l server.py și importați bibliotecile necesare:
import tweepy from textblob import TextBlob from collections import defaultdict
Să scriem acum câteva funcții de ajutor:
# simple, average a list of numbers with a guard clause to avoid division by zero def mean(lst): return sum(lst)/len(lst) if len(lst) > 0 else 0 # call the textblob sentiment analysis API and noun phrases API and return it as a dict def get_sentiment_and_np(sentence): blob = TextBlob(sentence) return{ 'sentiment': mean([s.sentiment.polarity for s in blob.sentences if s.sentiment.polarity != 0.0]), 'noun_phrases': list(blob.noun_phrases) } # use the tweepy API to get the last 50 posts from a user's timeline # We will want to get the full text if the text is truncated, and we will also remove retweets since they're not tweets by that particular account. def get_tweets(handle): auth = tweepy.OAuthHandler('YOUR_DEVELOPER_KEY') auth.set_access_token('YOUR_DEVELOPER_SECRET_KEY') api = tweepy.API(auth) tl = api.user_timeline(handle, count=50) tweets = [] for tl_item in tl: if 'retweeted_status' in tl_item._json: Continue # this is a retweet if tl_item._json['truncated']: status = api.get_status(tl_item._json['id'], tweet_mode='extended') # get full text tweets.append(status._json['full_text']) else: tweets.append(tl_item._json['text']) return tweets # http and https are sometimes recognized as noun phrases, so we filter it out. # We also try to skip noun phrases with very short words to avoid certain false positives # If this were a commercial app, we would want a more sophisticated filtering strategy. def good_noun_phrase(noun_phrase): noun_phrase_list = noun_phrase.split(' ') for np in noun_phrase_list: if np in {'http', 'https'} or len(np) < 3: return False return True
Acum că avem funcțiile de ajutor scrise, putem pune totul împreună cu câteva funcții simple:
# reshapes the tagged tweets into dictionaries that can be easily consumed by the front-end app def group_tweets(processed_tweets): # Sort it by sentiment sentiment_sorted = sorted(processed_tweets, key=lambda x: x['data']['sentiment']) # collect tweets by noun phrases. One tweet can be present in the list of more than one noun phrase, obviously. tweets_by_np = defaultdict(list) for pt in processed_tweets: for np in pt['data']['noun_phrases']: tweets_by_np[np].append(pt) grouped_by_np = {np.title(): tweets for np, tweets in tweets_by_np.items() if len(tweets) > 1 and good_noun_phrase(np)} return sentiment_sorted, grouped_by_np # download, filter, and analyze the tweets def download_analyze_tweets(accounts): processed_tweets = [] for account in accounts: for tweet in get_tweets(account): processed_tweet = ' '.join([i for i in tweet.split(' ') if not i.startswith('@')]) res = get_sentiment_and_np(processed_tweet) processed_tweets.append({ 'account': account, 'tweet': tweet, 'data': res }) sentiment_sorted, grouped_by_np = group_tweets(processed_tweets) return processed_tweets, sentiment_sorted, grouped_by_np
Acum puteți rula funcția download_analyze_tweets
pe o listă de mânere pe care doriți să le urmați și ar trebui să vedeți rezultatele.
Am rulat următorul cod:
if __name__ == '__main__': accounts = ['@spacex', '@nasa'] processed_tweets, sentiment_sorted, grouped_by_np = download_analyze_tweets(accounts) print(processed_tweets) print(sentiment_sorted) print(grouped_by_np)
Executarea acestui lucru a produs următoarele rezultate. Rezultatele depind, evident, de timp, așa că dacă vezi ceva similar, ești pe drumul cel bun.
[{'account': '@spacex', 'tweet': 'Falcon 9… [{'account': '@nasa', 'tweet': 'Our Mars rove… {'Falcon': [{'account': '@spacex', 'tweet': 'Falc….
Acum putem construi serverul Flask, care este destul de simplu. Creați un fișier gol numit server.py și scrieți următorul cod:
from flask import Flask, request, jsonify from twitter import download_analyze_tweets from flask_cors import CORS app = Flask(__name__) CORS(app) @app.route('/get_tweets', methods=['POST']) def get_tweets(): accounts = request.json['accounts'] processed_tweets, sentiment_sorted, grouped_by_np = download_analyze_tweets(accounts) return jsonify({ 'processedTweets': processed_tweets, 'sentimentSorted': sentiment_sorted, 'groupedByNp': grouped_by_np }) if __name__ == '__main__': app.run(debug=True)
Rulați serverul și acum ar trebui să puteți trimite o cerere de postare către server folosind un client HTTP la alegere. Introduceți {accounts: [“@NASA”, “@SpaceX”]} ca argument json și ar trebui să vedeți că API-ul returnează ceva similar cu ceea ce a fost returnat în codul de analiză Twitter.
Acum că avem un server, suntem gata să codificăm front-end-ul. Din cauza unei mici nuanțe în rețeaua printr-un emulator de telefon, vă recomand să implementați API-ul undeva. În caz contrar, veți dori să detectați dacă aplicația dvs. rulează pe un emulator și să trimiteți cereri către <Your Computer IP>:5000
în loc de localhost:5000
când este într-un emulator. Dacă implementați codul, puteți pur și simplu să emiteți cererea la adresa URL respectivă.
Există multe opțiuni de implementare a serverului. Pentru un server de depanare gratuit, simplu, cu o configurare minimă necesară, recomand ceva de genul PythonAnywhere, care ar trebui să poată rula acest server din cutie.
Acum că am codificat serverul back-end, să ne uităm la front-end. Vom începe cu una dintre cele mai convenabile opțiuni pentru un dezvoltator web: Cordova.
Implementare Apache Cordova
Cordova Primer
Apache Cordova este o tehnologie software care ajută dezvoltatorii web să vizeze platformele mobile. Profitând de capacitățile browserului web implementate pe platformele smartphone, Cordova împachetează codul aplicației web într-un container de aplicații native pentru a crea o aplicație. Cu toate acestea, Cordova nu este un simplu browser web de lux. Prin intermediul API-ului Cordova, dezvoltatorii web pot accesa multe funcții specifice smartphone-ului, cum ar fi suport offline, servicii de localizare și cameră pe dispozitiv.
Pentru aplicația noastră, vom scrie o aplicație folosind React.js ca cadru JS și React-Bootstrap ca cadru CSS. Deoarece Bootstrap este un cadru CSS receptiv, are deja suport pentru rularea pe ecrane mai mici. Odată ce aplicația este scrisă, o vom compila într-o aplicație web folosind Cordova.
Configurarea aplicației
Vom începe prin a face ceva unic pentru a configura aplicația Cordova React. Într-un articol Medium , dezvoltatorul Shubham Patil explică ce facem. În esență, creăm un mediu de dezvoltare React folosind React CLI, apoi un mediu de dezvoltare Cordova folosind Cordova CLI, înainte de a fuziona cele două.
Pentru a începe, rulați următoarele două comenzi în folderul de cod:
cordova create TwitterCurationCordova create-react-app twittercurationreact
Odată finalizată configurarea, vom dori să mutăm conținutul dosarului public și src al aplicației React în aplicația Cordova. Apoi, în package.json, copiați scripturile, lista de browser și dependențele din proiectul React. De asemenea, adăugați "homepage": "./"
la rădăcina pachetului.json pentru a activa compatibilitatea cu Cordova.
Odată ce pachetul.json este îmbinat, vom dori să schimbăm fișierul public/index.html pentru a funcționa cu Cordova. Deschideți fișierul și copiați metaetichetele de pe www/index.html, precum și scriptul de la sfârșitul etichetei body atunci când este încărcat Cordova.js.
Apoi, modificați fișierul src/index.js pentru a detecta dacă rulează pe Cordova. Dacă rulează pe Cordova, vom dori să rulăm codul de randare în handlerul de evenimente deviceready. Dacă rulează într-un browser obișnuit, redați imediat.
const renderReactDom = () => { ReactDOM.render( <React.StrictMode> <App /> </React.StrictMode>, document.getElementById('root') ); } if (window.cordova) { document.addEventListener('deviceready', () => { renderReactDom(); }, false); } else { renderReactDom(); }
În cele din urmă, trebuie să ne configuram conducta de implementare. Adăugați definiția de mai jos în fișierul config.xml:
<hook type="before_prepare" src="hooks/prebuild.js" />
Și puneți următorul script în prebuild.js:
const path = require('path'); const { exec } = require('child_process'); const fs = require('fs'); const rimraf = require('rimraf'); function renameOutputFolder(buildFolderPath, outputFolderPath) { return new Promise((resolve, reject) => { fs.rename(buildFolderPath, outputFolderPath, (err) => { if (err) { reject(err); } else { resolve('Successfully built!'); } }); }); } function execPostReactBuild(buildFolderPath, outputFolderPath) { return new Promise((resolve, reject) => { if (fs.existsSync(buildFolderPath)) { if (fs.existsSync(outputFolderPath)) { rimraf(outputFolderPath, (err) => { if (err) { reject(err); return; } renameOutputFolder(buildFolderPath, outputFolderPath) .then(val => resolve(val)) .catch(e => reject(e)); }); } else { renameOutputFolder(buildFolderPath, outputFolderPath) .then(val => resolve(val)) .catch(e => reject(e)); } } else { reject(new Error('build folder does not exist')); } }); } module.exports = () => { const projectPath = path.resolve(process.cwd(), './node_modules/.bin/react-scripts'); return new Promise((resolve, reject) => { exec(`${projectPath} build`, (error) => { if (error) { console.error(error); reject(error); return; } execPostReactBuild(path.resolve(__dirname, '../build/'), path.join(__dirname, '../www/')) .then((s) => { console.log(s); resolve(s); }) .catch((e) => { console.error(e); reject(e); }); }); }); };
Aceasta execută construcția React și plasează folderul de compilare în locul potrivit înainte de începerea construcției Cordova, automatizând astfel procesul de implementare.
Acum, putem încerca să rulăm aplicația noastră. Rulați următoarele în linia de comandă:
npm install rimraf npm install npm run start
Ar trebui să vedeți aplicația React configurată și rulând în browser. Adaugă Cordova acum:
cordova platform add iOS cordova run iOS
Și ar trebui să vedeți aplicația React rulând în emulator.
Configurare router și pachete
Pentru a configura o parte din infrastructura de care avem nevoie pentru a construi aplicația, să începem prin a instala pachetele necesare:
npm install react-bootstrap react-router react-router-dom
Acum vom configura rutarea și, în timp ce facem acest lucru, vom configura și un simplu obiect de stare globală care va fi partajat de toate componentele. Într-o aplicație de producție, vom dori să folosim un sistem de management de stat precum Redux sau MobX, dar o vom menține simplu deocamdată. Accesați App.js și configurați ruta astfel:
import { BrowserRouter as Router, Redirect, Route, } from "react-router-dom"; function App() { const [curatedTweets, setCuratedTweets] = useState(); return <Router> <Route path="/" exact render={() => <Input setCuratedTweets={setCuratedTweets} />} /> <Route path="/display" render={() => <Display curatedTweets={curatedTweets} />} /> <Route path="*" exact render={() => <Redirect to="/" />} /> </Router> }
Cu această definire a rutei, am introdus două rute pe care va trebui să le implementăm: Input și Display. Observați că variabila curatedTweets
este transmisă la Display, iar variabila setCuratedTweets
este transmisă la Input. Aceasta înseamnă că componenta de intrare va putea apela funcția pentru a seta variabila curatedTweets
, iar Display va afișa variabila.
Pentru a începe codificarea componentelor, să creăm un folder sub /src numit /src/components. Sub /src/components, creați un alt folder numit /src/components/input și două fișiere dedesubt: input.js și input.css. Faceți același lucru pentru componenta Display - creați /src/components/display și sub: display.js și display.css.
Sub acestea, să creăm componente stub, așa:
import React from 'react'; import 'input.css' const Input = () => <div>Input</div>; export default Input
Și același lucru pentru Display:
import React from 'react'; import display.css' const Display = () => <div>Display</div>; export default Display
Cu aceasta, wireframingul nostru este complet și aplicația ar trebui să ruleze. Să codificăm acum pagina de intrare.
Pagina de intrare
Plan de ansamblu
Înainte de a scrie codul, să ne gândim la ce vrem să facă pagina noastră de intrare. Evident, vom dori o modalitate prin care utilizatorii să introducă și să editeze mânerele Twitter de pe care doresc să le extragă. De asemenea, dorim ca utilizatorii să poată indica că au terminat. Când utilizatorii indică că au terminat, vom dori să extragem tweet-urile curate din API-ul nostru de curatare Python și, în sfârșit, să navigăm la componenta Display.
Acum că știm ce vrem să facă componenta noastră, suntem gata să codificăm.
Configurarea fișierului
Să începem prin a importa bibliotecile withRouter
Router cu Router pentru a avea acces la funcționalitatea de navigare, Componentele React Bootstrap de care avem nevoie, așa:
import React, {useState} from 'react'; import {withRouter} from 'react-router-dom'; import {ListGroup, Button, Form, Container, Row, Col} from 'react-bootstrap'; import './input.css';
Acum, să definim funcția stub pentru Input. Știm că Input primește funcția setCuratedTweets
și, de asemenea, dorim să îi oferim posibilitatea de a naviga la ruta de afișare după ce setează tweet-urile organizate din API-ul nostru Python. Prin urmare, vom dori să luăm din setCuratedTweets
de recuzităTweets și istoric (pentru navigare).
const Input = ({setCuratedTweets, history}) => { return <div>Input</div> }
Pentru a-i oferi acces API-ului istoric, îl vom împacheta cu withRouter
în declarația de export de la sfârșitul fișierului:
export default withRouter(Input);
Containere de date
Să setăm containerele de date folosind React Hooks. Am importat deja cârligul useState
, astfel încât să putem adăuga următorul cod în corpul componentei Input:
const [handles, setHandles] = useState([]); const [handleText, setHandleText] = useState('');
Acest lucru creează containerul și modificatorii pentru mânere, care vor deține lista de mânere din care utilizatorul dorește să le extragă și handleText
, care va reține conținutul casetei de text pe care utilizatorul o folosește pentru a introduce mânerul.
Acum, să codificăm componentele UI.
Componentele UI
Componentele UI vor fi destul de simple. Vom avea un rând Bootstrap care conține caseta de text de intrare împreună cu două butoane, unul pentru a adăuga conținutul casei de intrare curente la lista de mânere și unul pentru a extrage din API. Vom avea un alt rând Bootstrap care afișează lista de mânere pe care utilizatorul dorește să le tragă folosind grupul de liste Bootstrap. În cod, arată așa:
return ( <Container className="tl-container"> <Row> <Col> <Form.Control type="text" value={handleText} onChange={changeHandler} placeholder="Enter handle to pull" /> </Col> </Row> <Row className='input-row'> <Col> <Button variant="primary" onClick={getPull}>Pull</Button> {' '} <Button variant="success" onClick={onAddClicked}>Add</Button> </Col> </Row> <Row> <Col> <ListGroup className="handles-lg"> {handles.map((x, i) => <ListGroup.Item key={i}> {x} <span onClick={groupItemClickedBuilder(i)} className="delete-btn-span"> <Button variant="danger" size="sm"> delete </Button> </span> </ListGroup.Item>)} </ListGroup> </Col> </Row> </Container> );
Pe lângă componenta UI, vom dori să implementăm cei trei handlere de evenimente UI care se ocupă de modificările datelor. Managerul de evenimente getPull
, care apelează API-ul, va fi implementat în secțiunea următoare.
// set the handleText to current event value const textChangeHandler = (e) => { e.preventDefault(); setHandleText(e.target.value); } // Add handleText to handles, and then empty the handleText const onAddClicked = (e) => { e.preventDefault(); const newHandles = [...handles, handleText]; setHandles(newHandles); setHandleText(''); } // Remove the clicked handle from the list const groupItemClickedBuilder = (idx) => (e) => { e.preventDefault(); const newHandles = [...handles]; newHandles.splice(idx, 1); setHandles(newHandles); }
Acum suntem gata să implementăm apelul API.
Apel API
Pentru apelul API, dorim să luăm mânerele pe care vrem să le extragem, să le trimitem la API-ul Python într-o solicitare POST și să punem rezultatul JSON rezultat în variabila curatedTweets
. Apoi, dacă totul merge bine, vrem să navigăm programatic la ruta /display. În caz contrar, vom înregistra eroarea în consolă, astfel încât să putem depana mai ușor.
În modul cod, arată astfel:
const pullAPI = (e) => { e.preventDefault(); fetch('http://prismatic.pythonanywhere.com/get_tweets', { method: 'POST', mode: 'cors', headers: { 'Accept': 'application/json', 'Content-Type': 'application/json', }, body: JSON.stringify({ accounts: handles }) }).then(r=>r.json()).then(j => { setCuratedTweets(j); history.push('/display'); }) .catch(e => { console.log(e); }) }
Și cu asta, ar trebui să fim gata de plecare. Simțiți-vă liber să rulați aplicația, să adăugați câteva puncte și să trimiteți o solicitare către API.
Acum suntem gata să codificăm pagina de sentimente.
Sentiment Sorted Mode
Deoarece API-ul Python sortează deja tweet-urile după sentiment, odată ce avem rezultatul din API-ul Python, pagina de sentiment nu este de fapt prea dificilă.
Plan de ansamblu
Vom dori o interfață de listă pentru a afișa tweet-urile. De asemenea, vom dori ca câteva componente de navigare să treacă la modul de grupare a subiectelor și să se întoarcă la pagina Intrare.
Pentru a începe, să definim subcomponenta modului SentimentDisplay în fișierul display.js.
Componenta SentimentDisplay
SentimentDisplay va prelua obiectul curatedTweets
și va afișa tweet-urile sortate în funcție de sentimente într-o listă. Cu ajutorul lui React-Bootstrap, componenta este destul de simplă:
const SentimentDisplay = ({curatedTweets}) => { return <ListGroup> {curatedTweets.sentimentSorted.map((x, i) => <ListGroup.Item key={i}> <div className="account-div">{x.account}:</div> <span className="tweet-span">{x.tweet}</span> <span className="sentiments-span">({x.data.sentiment.toPrecision(2)})</span> </ListGroup.Item> )} </ListGroup> }
În timp ce suntem la asta, să adăugăm și un pic de stil. Introduceți următoarele în display.css și importați-l:
.account-div { font-size: 12px; font-weight: 600; } .tweet-span { font-size: 11px; font-weight: 500; } .sentiments-span { font-size: 10px; } .tl-container { margin-top: 10px; } .disp-row { margin-top: 5px; }
Acum putem arăta componenta SentimentDisplay. Schimbați funcția de Display
astfel:
const Display = ({curatedTweets}) => { return <SentimentDisplay curatedTweets={curatedTweets} /> };
Să profităm și de această ocazie pentru a codifica componentele de navigație. Vom dori două butoane - butonul „Înapoi la editare” și Modul Grup de subiecte.
Putem implementa aceste butoane într-un rând Bootstrap separat chiar deasupra componentei SentimentDisplay, astfel:
Return <Container className="tl-container"> <Row> <Col> <Link to="/"><Button variant='primary'>Back</Button></Link> {' '} <Button variant='success'>View by Topic</Button> </Col> </Row> <Row className="disp-row"> <Col> <SentimentDisplay curatedTweets={curatedTweets} /> </Col> </Row> </Container>
Rulați aplicația și trageți tweet-urile de la câteva mânere. Arata destul de ingenios!
Modul de grupare a subiectelor
Acum, dorim să implementăm modul de grupare a subiectelor. Este puțin mai complex decât SentimentDisplay, dar din nou, unele componente Bootstrap foarte utile ne ajută enorm.
Plan de ansamblu
Vom obține toate sintagmele nominale și le vom afișa ca o listă de acordeon. Vom reda apoi tweet-urile care conțin sintagmele nominale odată ce lista acordeonului este extinsă.
Implementarea trecerii la modul de grupare a subiectelor
În primul rând, să implementăm logica pentru a comuta de la modul Sentiment la modul de grupare a subiectelor. Să începem prin a crea mai întâi componenta stub:
const TopicDisplay = () => { return <div>Topic Display</div> }
Și setați o logică pentru a crea un mod de afișare. În Componenta principală de afișare, adăugați următoarele linii pentru a crea logica pentru care componenta este afișată.
// controls the display mode. Remember to import {useState} from 'react' const [displayType, setDisplayType] = useState('Sentiment'); // Switch the Display Mode const toggleDisplayType = () => { setDisplayType(displayType === 'Sentiment' ? 'Topic': 'Sentiment'); } // determines the text on the mode switch button const switchStr = displayType === 'Sentiment'? 'View by Topic': 'View by Sentiment'
Și schimbați JSX-ul la următorul pentru a adăuga logica:
Return <Container className="tl-container"> <Row> <Col> <Link to="/"><Button variant='primary'>Back</Button></Link> {' '} <Button variant='success' onClick={toggleDisplayType}>{switchStr}</Button> </Col> </Row> <Row className="disp-row"> <Col> { displayType === 'Sentiment'? <SentimentDisplay curatedTweets={curatedTweets} />: <TopicDisplay curatedTweets={curatedTweets} /> } </Col> </Row> </Container>
Acum, ar trebui să vedeți stub-ul de afișare a grupului de subiecte când comutați.
Componenta TopicDisplay
Acum, suntem gata să codificăm componenta TopicDisplay
. După cum sa discutat mai devreme, va folosi lista de acordeon Bootstrap. Implementarea este de fapt destul de simplă:
const TopicDisplay = ({curatedTweets}) => { return <Accordion> {Object.keys(curatedTweets.groupedByNp).map((x, i) => <Card key={i}> <Card.Header> <Accordion.Toggle as={Button} variant="link" eventKey={i}> {x} ({curatedTweets.groupedByNp[x].length}) </Accordion.Toggle> </Card.Header> <Accordion.Collapse eventKey={i}> <Card.Body> <ListGroup> {curatedTweets.groupedByNp[x].map((y, i2) => <ListGroup.Item key={i2}> <div className="account-div">{y.account}:</div> <span className="tweet-span">{y.tweet}</span> <span className="sentiments-span">({y.data.sentiment.toPrecision(2)})</span> </ListGroup.Item> )} </ListGroup> </Card.Body> </Accordion.Collapse> </Card> )} </Accordion> }
Rulați aplicația și ar trebui să vedeți Afișarea subiectului.
Acum aplicația este terminată și suntem gata să construim aplicația pentru emulator.
Rularea aplicației în emulator
Cordova facilitează rularea aplicației în emulator. Pur și simplu rulați:
cordova platform add ios # if you haven't done so already cordova run ios
Și ar trebui să vedeți aplicația în emulator. Deoarece Bootstrap este o aplicație web receptivă, aplicația web se adaptează la lățimea unui iPhone și totul arată destul de frumos.
Cu aplicația Cordova terminată, să ne uităm acum la implementarea Ionic.
Implementare Ionic-React
Grund ionic
Ionic este o bibliotecă de componente web și un set de instrumente CLI care facilitează construirea de aplicații hibride. Inițial, Ionic a fost construit pe deasupra AngularJS și Cordova, dar de atunci și-au lansat componentele în React.js și au început să susțină Capacitor, o platformă similară Cordova. Ceea ce îl diferențiază pe Ionic este că, deși utilizați componente web, componentele se simt foarte asemănătoare cu o interfață mobilă nativă. În plus, aspectul componentelor Ionic se adaptează automat la sistemul de operare pe care rulează, ceea ce ajută din nou aplicația să arate și să se simtă mai nativă și mai naturală. În cele din urmă, deși acest lucru nu intră în domeniul de aplicare al articolului nostru, Ionic oferă, de asemenea, câteva instrumente de construcție care fac implementarea aplicației dvs. puțin mai ușoară.
Pentru aplicația noastră, vom folosi componentele React ale lui Ionic pentru a construi interfața de utilizare în timp ce folosim o parte din logica JavaScript pe care am construit-o în timpul secțiunii Cordova.
Configurarea aplicației
În primul rând, vom dori să instalăm instrumentele Ionic. Deci, să rulăm următoarele:
npm install -g @Ionic/cli native-run cordova-res
Și după ce s-a terminat instalarea, să mergem la folderul de proiect. Acum, folosim Ionic CLI pentru a crea noul nostru folder de proiect:
ionic start twitter-curation-Ionic blank --type=react --capacitor
Urmărește cum se întâmplă magia și acum intră în dosar cu:
cd twitter-curation-Ionic
Și rulați aplicația goală cu:
ionic serve
Aplicația noastră este astfel configurată și gata de funcționare. Să definim câteva rute.
Înainte de a trece mai departe, veți observa că Ionic a început proiectul folosind TypeScript. Deși nu îmi ies din cale să folosesc TypeScript, are câteva caracteristici foarte frumoase și îl vom folosi pentru această implementare.
Configurare router
Pentru această implementare, vom folosi trei rute - input, sentimentDisplay
și topicDisplay
. Facem acest lucru pentru că dorim să profităm de funcțiile de tranziție și navigare oferite de Ionic și pentru că folosim componente Ionic, iar listele de acordeon nu sunt pre-ambalate cu Ionic. Putem implementa propriile noastre, desigur, dar pentru acest tutorial, vom rămâne cu componentele ionice furnizate.
Dacă navigați la App.tsx, ar trebui să vedeți rutele de bază deja definite.
Pagina de intrare
Plan de ansamblu
Vom folosi multă logică și cod similar cu implementarea Bootstrap, cu câteva diferențe cheie. În primul rând, vom folosi TypeScript, ceea ce înseamnă că vom avea adnotări de tip pentru codul nostru, pe care le veți vedea în secțiunea următoare. În al doilea rând, vom folosi componente Ionic, care sunt foarte asemănătoare ca stil cu Bootstrap, dar vor fi sensibile la sistemul de operare în stil. În cele din urmă, vom naviga dinamic folosind API-ul istoric ca în versiunea Bootstrap, dar accesând istoricul ușor diferit datorită implementării Routerului Ionic.
Configurare
Să începem prin a configura componenta de intrare cu o componentă stub. Creați un folder sub paginile numite input și creați sub acesta un fișier numit Input.tsx. În interiorul acelui fișier, introduceți următorul cod pentru a crea o componentă React. Observați că, deoarece folosim TypeScript, este puțin diferit.
import React, {useState} from 'react'; const Input : React.FC = () => { return <div>Input</div>; } export default Input;
Și schimbați componenta din App.tsx la:
const App: React.FC = () => ( <IonApp> <IonReactRouter> <IonRouterOutlet> <Route path="/input" component={Input} exact={true} /> <Route exact path="/" render={() => <Redirect to="/input" />} /> </IonRouterOutlet> </IonReactRouter> </IonApp> );
Acum, când reîmprospătați aplicația, ar trebui să vedeți componenta Input stub.
Containere de date
Să creăm acum containerele de date. Dorim containerele pentru mânerele Twitter introduse, precum și conținutul curent al casetei de introducere. Deoarece folosim TypeScript, va trebui să adăugăm adnotările de tip la invocarea useState
în funcția componentă:
const Input : React.FC = () => { const [text, setText] = useState<string>(''); const [accounts, setAccounts] = useState<Array<string>>([]); return <div>Input</div>; }
De asemenea, vom dori un container de date care să dețină valorile returnate din API. Deoarece conținutul acestuia trebuie partajat cu celelalte rute, le definim la nivelul App.tsx. Importați useState
din React în fișierul App.tsx și modificați funcția containerului aplicației la următoarea:
const App: React.FC = () => { const [curatedTweets, setCuratedTweets] = useState<CuratedTweets>({} as CuratedTweets); return ( <IonApp> <IonReactRouter> <IonRouterOutlet> <Route path="/input" component={Input} exact={true} /> <Route exact path="/" render={() => <Redirect to="/input" />} /> </IonRouterOutlet> </IonReactRouter> </IonApp> ); }
În acest moment, dacă utilizați un editor cu evidențiere de sintaxă, cum ar fi Visual Studio Code, ar trebui să vedeți că CuratedTweets se aprinde. Acest lucru se datorează faptului că fișierul nu știe cum arată interfața CuratedTweets. Să definim asta acum. Creați un folder sub src numit interfețe și creați în el un fișier numit CuratedTweets.tsx. În fișier, definiți interfața CuratedTweets după cum urmează:
interface TweetRecordData { noun_phrases: Array<string>, sentiment: number } export interface TweetRecord { account: string, data: TweetRecordData, tweet: string } export default interface CuratedTweets { groupedByNp: Record<string, Array<TweetRecord>>, processedTweets: Array<TweetRecord>, sentimentSorted: Array<TweetRecord> }
Acum, aplicația știe despre structura datelor returnate API. Importați interfața CuratedTweets în App.tsx. Ar trebui să vedeți compilarea App.tsx fără nicio problemă acum.
Trebuie să mai facem câteva lucruri aici. Trebuie să trecem funcția setCuratedTweets
în componenta Input și să facem componenta Input conștientă de această funcție.
În App.tsx, modificați ruta de intrare astfel:
<Route path="/input" render={() => <Input setCuratedTweets={setCuratedTweets}/>} exact={true} />
Acum, ar trebui să vedeți semnul editorului în jos altceva - Input nu știe despre noul suport care i-a fost transmis, așa că vom dori să-l definim în Input.tsx.
First, import the CuratedTweets interface, then define the ContainerProps interface like so:
interface ContainerProps { setCuratedTweets: React.Dispatch<React.SetStateAction<CuratedTweets>> }
And finally, change the Input component definition like so:
const Input : React.FC<ContainerProps> = ({setCuratedTweets}) => { const [text, setText] = useState<string>(''); const [accounts, setAccounts] = useState<Array<string>>([]); return <div>Input</div>; }
We are done defining the data containers, and now, onto building the UI components.

UI Components
For the UI component, we will want to build an input component and a list display component. Ionic provides some simple containers for these.
Let's start by importing the library components we'll be using:
import { IonInput, IonItem, IonList, IonButton, IonGrid, IonRow, IonCol } from '@Ionic/react';
Now, we can replace the stub component with the IonInput
, wrapped in an IonGrid:
return <IonGrid> <IonRow> <IonCol> <IonInput value={text} placeholder="Enter accounts to pull from" onIonChange={e => setText(e.detail.value!)} /> </IonCol> </IonRow> </IonGrid>
Notice that the event listener is onIonChange
instead of onChange
. Otherwise, it should look very familiar.
When you open the app in your browser, it may not look like the Bootstrap app. However, if you set your browser to emulator mode, the UI will make more sense. It will look even better once you deploy it on mobile, so look forward to it.
Now, let's add some buttons. We will want an “Add to list” button and a “Pull API” button. For that, we can use IonButton. Change the size of the input's IonCol to 8 and add the following two buttons with columns:
<IonCol size="8"> <IonInput value={text} placeholder="Enter accounts to pull from" onIonChange={e => setText(e.detail.value!)} /> </IonCol> <IonCol size="2"> <IonButton style={{float: 'right'}} color="primary" size="small" onClick={onAddClicked}>Add</IonButton> </IonCol> <IonCol size="2"> <IonButton style={{float: 'right'}} color="success" size="small" onClick={onPullClicked}>Pull</IonButton> </IonCol>
Since we're writing the buttons, let's write the event handlers as well.
The handler to add a Twitter handle to the list is simple:
const onAddClicked = () => { if (text === undefined || text.length === 0) { return; } const newAccounts: Array<string> = [...accounts, text]; setAccounts(newAccounts); setText(''); }
We will implement the API call in the next section, so let's just put a stub function for onPullClicked
:
const onPullClicked = () => {}
Now, we need to write the component for displaying the list of handles that has been inputted by the user. For that, we will use IonList, put into a new IonRow:
<IonRow> <IonCol> <IonList> {accounts.map((x:string, i:number) => <IonItem key={i}> <IonGrid> <IonRow> <IonCol size="8" style={{paddingTop: '12px'}}>{x}</IonCol> <IonCol><IonButton style={{float: 'right'}} color="danger" size="small" onClick={deleteClickedBuilder(i)}>Delete</IonButton></IonCol> </IonRow> </IonGrid> </IonItem>)} </IonList> </IonCol> </IonRow>
Each list item is displaying the handle and a delete button in its very own IonGrid. For this code to compile, we will want to implement the deleteClickedHandler
as well. It should be very familiar from the previous section but with TypeScript annotations.
const deleteClickedBuilder = (idx: number) => () => { const newAccounts: Array<string> = [...accounts]; newAccounts.splice(idx, 1); setAccounts(newAccounts); }
Save this, and you should see the Input page with all the UI components implemented. We can add handles, delete handles, and click the button to invoke the API.
As a final exercise, let's move the in-line styles to CSS. Create a file in the input folder called input.css and import it in the Input.tsx file. Then, add the following styles:
.input-button { float: right; } .handle-display { padding-top: 12px; }
Now, add className="input-button”
on all of the IonButtons and className=”handle-display”
on the handle list item IonCol that is displaying the intended Twitter handle. Save the file, and you should see everything looking quite good.
API Call
The code to pull the API is very familiar from the previous section, with one exception - we have to get access to the history component to be able to dynamically change routes. We will do this using the withHistory
hook.
We first import the hook:
import { useHistory } from 'react-router';
And then implement the handler in the input component:
const history = useHistory(); const switchToDisplay = () => { history.push('/display'); } const onPullClicked = () => { fetch('http://prismatic.pythonanywhere.com/get_tweets', { method: 'POST', mode: 'cors', headers: { 'Accept': 'application/json', 'Content-Type': 'application/json', }, body: JSON.stringify({ accounts }) }).then(r=>r.json()).then(j => { setCuratedTweets(j); switchToDisplay(); }) .catch(e => { console.log(e); }) }
Adăugarea unui antet
Our Input page looks quite nice, but it looks a little bare due to Ionic's mobile-centric styling. To make the UI look more natural, Ionic provides a header feature that lets us provide a more natural user experience. When running on mobile, the header will also simulate the native OS's mobile platform, which makes the user experience even more natural.
Change your component import to:
import { IonPage, IonHeader, IonToolbar, IonTitle, IonContent, IonInput, IonItem, IonList, IonButton, IonGrid, IonRow, IonCol } from '@Ionic/react';
And now wrap the UI in an Ionic page with a header, like so:
return <IonPage> <IonHeader> <IonToolbar> <IonTitle>Twitter Curation App</IonTitle> </IonToolbar> </IonHeader> <IonContent> <IonHeader collapse="condense"> <IonToolbar> <IonTitle size="large">Twitter Curation App</IonTitle> </IonToolbar> </IonHeader> <IonGrid> <IonRow> <IonCol size="8"> <IonInput value={text} placeholder="Enter accounts to pull from" onIonChange={e => setText(e.detail.value!)} /> </IonCol> <IonCol size="2"> <IonButton className="input-button" color="primary" size="small" onClick={onAddClicked}>Add</IonButton> </IonCol> <IonCol size="2"> <IonButton className="input-button" color="success" size="small" onClick={onPullClicked}>Pull</IonButton> </IonCol> </IonRow> <IonRow> <IonCol> <IonList> {accounts.map((x:string, i:number) => <IonItem key={i}> <IonGrid> <IonRow> <IonCol size="8" className="handle-display">{x}</IonCol> <IonCol><IonButton className="input-button" color="danger" size="small" onClick={deleteClickedBuilder(i)}>Delete</IonButton></IonCol> </IonRow> </IonGrid> </IonItem>)} </IonList> </IonCol> </IonRow> </IonGrid> </IonContent> </IonPage>
Acum arată bine!
Pagina Sortată de Sentimente
Plan de ansamblu
Pagina sortată de sentiment va fi în mare măsură similară cu pagina sortată de sentiment din pagina Bootstrap, dar folosind TypeScript și Ionic. De asemenea, vom implementa Topic Display ca o rută separată pentru a profita de funcțiile de navigare ale lui Ionic în timp ce rulăm pe mobil, așa că va trebui să oferim acestei pagini posibilitatea de a naviga și la ruta Topic Display.
Configurare rută
Să începem prin a crea un folder nou numit sentimentsorted și un fișier numit SentimentSorted.tsx dedesubt. Exportați o componentă stub astfel:
import React from 'react'; const SentimentSorted: React.FC = () => { return <div>Sentiment Sorted</div> } export default SentimentSorted;
Și în App.tsx, importați componenta:
import SentimentSorted from './pages/sentimentsorted/SentimentSorted';
Și adăugați traseul:
<Route path="/display" render={() => <SentimentSorted curatedTweets={curatedTweets} />} />
Veți primi o eroare TypeScript care spune că SentimentSorted
nu se așteaptă la recuzita curatedTweets, așa că să ne ocupăm de asta acum în secțiunea următoare.
Componentele UI
Să începem prin a defini elementele de recuzită ale containerului. La fel ca componenta de intrare:
import CuratedTweets from '../../interfaces/CuratedTweets'; interface ContainerProps { curatedTweets: CuratedTweets }
Și acum, schimbați afișajul stub:
const SentimentSorted: React.FC<ContainerProps> = ({curatedTweets}) => { return <div>Sentiment Sorted</div> }
Și totul ar trebui să fie compilat.
Display-ul este foarte simplu, este doar un IonList cu componente de afișare:
return <IonGrid> <IonRow> <IonCol> <IonList> {(curatedTweets.sentimentSorted).map((x, i) => <IonItem key={i}> <IonLabel className="ion-text-wrap"> <h2>{x.account}:</h2> <h3>{x.tweet}</h3> <p>({x.data.sentiment.toPrecision(2)})</p> </IonLabel> </IonItem>)} </IonList> </IonCol> </IonRow> </IonGrid>
Dacă salvați și extrageți niște tweet-uri folosind componenta de intrare, ar trebui să vedeți tweet-urile afișate într-o listă.
Acum, să adăugăm butoanele de navigare. Adăugați la IonGrid:
<IonRow> <IonCol> <IonButton color='primary' onClick={switchToInput}>Back</IonButton> <IonButton color="success" onClick={toggleDisplayType}>{switchStr}</IonButton> </IonCol> </IonRow>
switchToInput
este foarte ușor de implementat cu API-ul istoric:
const switchToInput = () => { history.goBack(); }
Și ToggleDisplayType
ar trebui să fie și el familiar:
const toggleDisplayType = () => { setDisplayType(displayType === 'Sentiment' ? 'Topic': 'Sentiment'); } const switchStr = displayType === 'Sentiment'? 'View by Topic': 'View by Sentiment'
Acum avem implementată componenta SentimentDisplay
. Acum, înainte de a implementa Pagina de afișare a subiectului, trebuie să implementăm componenta care afișează toate subiectele. Vom face asta în secțiunea următoare.
Componenta Grupuri de subiecte
Să adăugăm o opțiune de afișare a listei de subiecte și să o afișăm condiționat. Pentru a face asta, trebuie să spargem lista de afișare a sentimentelor. Redenumiți SentimentDisplay în Display și să dezvăluim lista de afișare a sentimentelor:
interface SentimentDisplayProps { sentimentSorted: Array<TweetRecord> } const SentimentDisplay: React.FC<SentimentDisplayProps> = ({sentimentSorted}) => { return <IonList> {(sentimentSorted || []).map((x, i) => <IonItem key={i}> <IonLabel className="ion-text-wrap"> <h2>{x.account}:</h2> <h3>{x.tweet}</h3> <p>({x.data.sentiment.toPrecision(2)})</p> </IonLabel> </IonItem>)} </IonList> }
Observați cum folosim una dintre definițiile clasei din interfața CuratedTweets. Asta pentru că aceste componente nu au nevoie de întregul obiect CuratedTweets, ci doar de un subset. Lista de subiecte este foarte asemănătoare:
interface TopicDisplayProps { groupedByNP: Record<string, Array<TweetRecord>> } const TopicDisplay: React.FC<TopicDisplayProps> = ({groupedByNP}) => { return <IonList> {Object.keys(groupedByNP || {}).map((x, i) => <IonItem key={i} routerLink={`/topicDisplay/${encodeURIComponent(x)}`}> <IonLabel className="ion-text-wrap"> <h2>{x} ({groupedByNP[x].length})</h2> </IonLabel> </IonItem>)} </IonList> }
Și acum, afișarea condiționată este ușor de configurat în Componenta de afișare:
return ( <IonGrid> <IonRow> <IonCol> <IonButton color='primary' onClick={switchToInput}>Back</IonButton> <IonButton color="success" onClick={toggleDisplayType}>{switchStr}</IonButton> </IonCol> </IonRow> { displayType === 'Sentiment'? <SentimentDisplay sentimentSorted={curatedTweets.sentimentSorted} /> : <TopicDisplay groupedByNP={curatedTweets.groupedByNp} /> } </IonGrid> );
Asigurați-vă că modificați exportul implicit și acum suntem gata să implementăm Pagina de afișare a subiectului.
Pagina de afișare a subiectului
Plan de ansamblu
Pagina de afișare a subiectului este o afișare de listă similară cu afișarea sentimentelor, dar vom căuta subiectul în cauză din parametrul rută.
Configurare rută
Dacă ai ajuns atât de departe, ar trebui să știi deja ce să faci. Să creăm un folder de pagină numit topicdisplay și un TopicDisplay.tsx, să scriem o componentă stub și să o importăm în pagina App.tsx. Acum, să setăm rutele:
<Route path="/topicDisplay/:topic" render={() => <TopicDisplay curatedTweets={curatedTweets} /> } />
Acum suntem gata să implementăm componenta UI.
Componentele UI
Mai întâi, să creăm definiția ContainerProps
:
interface ContainerProps { curatedTweets: CuratedTweets } const TopicDisplay: React.FC<ContainerProps> = ({curatedTweets}) => { Return <div>Topic Display</div> }
Acum, va trebui să recuperăm subiectul din numele căii URL. Pentru a face asta, vom folosi API-ul istoric. Deci, să importăm useHistory
, să instanțiem API-ul istoric și să extragem subiectul din calea. În timp ce suntem la asta, să implementăm și funcționalitatea de comutare înapoi:
const TopicDisplay: React.FC<ContainerProps> = ({curatedTweets}) => { const history = useHistory(); const switchToDisplay = () => { history.goBack(); } const topic = history.location.pathname.split('/')[2]; const tweets = (curatedTweets.groupedByNp || {})[topic];
Acum că avem tweet-urile cu acel subiect, afișarea este de fapt destul de simplă:
return ( <IonGrid> <IonRow> <IonCol> <IonButton color='primary' onClick={switchToDisplay}>Back</IonButton> </IonCol> </IonRow> <IonRow> <IonCol> <IonList> {(tweets || []).map((x, i) => <IonItem key={i}> <IonLabel className="ion-text-wrap"> <h2>{x.account}:</h2> <h3>{x.tweet}</h3> <p>({x.data.sentiment.toPrecision(2)})</p> </IonLabel> </IonItem>)} </IonList> </IonCol> </IonRow> </IonGrid> );
Salvează și fugi, iar lucrurile ar trebui să arate bine.
Rularea aplicației în emulator
Pentru a rula aplicația în emulator, pur și simplu rulăm câteva comenzi ionice pentru a adăuga platforma mobilă și a copia codul, similar cu modul în care am configurat lucrurile cu Cordova.
ionic build # builds the app ionic cap add ios # adds iOS as one of the platforms, only have to run once ionic cap copy # copy the build over ionic cap sync # only need to run this if you added native plugins ionic cap open ios # run the iOS emulator
Și ar trebui să vedeți apariția aplicației.
React Native Implementation
React Native Primer
React Native adoptă o abordare foarte diferită de abordările bazate pe web din secțiunile anterioare. React Native redă codul dvs. React ca componente native. Acest lucru vine cu mai multe avantaje. În primul rând, integrarea cu sistemul de operare de bază este mult mai profundă, ceea ce permite dezvoltatorului să profite de noile funcții ale smartphone-ului și de caracteristicile specifice sistemului de operare care ar putea să nu fie disponibile prin Cordova/Capacitor. În al doilea rând, deoarece nu există un motor de randare bazat pe web în mijloc, o aplicație React Native este în general mai rapidă decât una scrisă folosind Cordova. În cele din urmă, deoarece React Native permite integrarea componentelor native, dezvoltatorii pot exercita un control mult mai fin asupra aplicației lor.
Pentru aplicația noastră, vom folosi logica din secțiunile anterioare și vom folosi o bibliotecă de componente React Native numită NativeBase pentru a codifica interfața noastră de utilizare.
Configurarea aplicației
În primul rând, veți dori să instalați toate componentele necesare ale React Native urmând instrucțiunile de aici.
Odată ce React Native este instalat, să începem proiectul:
react-native init TwitterCurationRN
Lăsați scriptul de configurare să ruleze și, în cele din urmă, folderul ar trebui creat. Cd în folder și rulați react-native run-ios și ar trebui să vedeți pop-up emulatorul cu aplicația exemplu.
Vom dori să instalăm și NativeBase, deoarece aceasta este biblioteca noastră de componente. Pentru a face asta, rulăm:
npm install --save native-base react-native link
De asemenea, dorim să instalăm navigatorul de stivă React Native. Să fugim:
npm install --save @react-navigation/stack @react-navigation/native
Și
react-native link cd ios pod-install cd
Pentru a finaliza conectarea și instalarea pluginurilor native.
Configurare router
Pentru rutare, vom folosi navigatorul de stivă pe care l-am instalat la pasul de mai sus.
Importați componentele routerului:
import { NavigationContainer } from '@react-navigation/native'; import {createStackNavigator} from '@react-navigation/stack';
Și acum, creăm un navigator de stivă:
const Stack = createStackNavigator();
Modificați conținutul componentei aplicației pentru a utiliza navigatorul de stivă:
const App = () => { return ( <> <StatusBar bar /> <NavigationContainer> <Stack.Navigator initialRouteName="Entry"> <Stack.Screen name="Entry" component={Entry} /> </Stack.Navigator> </NavigationContainer> </> ); };
În acest moment, veți primi o eroare deoarece Entry nu este încă definită. Să definim un element stub doar pentru a-l face fericit.
Creați un folder de componente în proiectul dvs., creați un fișier numit Entry.jsx și adăugați o componentă stub astfel:
import React, {useState} from 'react'; import { Text } from 'native-base'; export default Entry = ({navigation}) => { return <Text>Entry</Text>; // All text must be wrapped in a <Text /> component or <TextView /> if you're not using NativeBase. }
Importați componenta Entry în aplicația dvs. și ar trebui să fie construită.
Acum suntem gata să codificăm pagina de intrare.
Pagina de intrare
Plan de ansamblu
Vom implementa o pagină care este foarte asemănătoare cu cea implementată mai sus, dar folosind componente NativeBase. Majoritatea API-urilor JavaScript și React pe care le-am folosit, cum ar fi hooks și fetch, sunt încă disponibile.
Singura diferență va fi modul în care lucrăm cu API-ul de navigare, pe care îl veți vedea mai târziu.
Componentele UI
Componentele NativeBase suplimentare pe care le vom folosi sunt Container, Content, Input, List, ListItem și Button. Toate acestea au analogi în Ionic și Bootstrap React, iar constructorii NativeBase l-au făcut foarte intuitiv pentru oamenii familiarizați cu aceste biblioteci. Pur și simplu importați astfel:
import { Container, Content, Input, Item, Button, List, ListItem, Text } from 'native-base';
Iar componenta este:
return <Container> <Content> <Item regular> <Input placeholder='Input Handles Here' onChangeText={inputChange} value={input} /> <Button primary onPress={onAddClicked}><Text> Add </Text></Button> <Text> </Text> <Button success onPress={onPullClicked}><Text> Pull </Text></Button> </Item> <Item> <List style={{width: '100%'}}> {handles.map((item) => <ListItem key={item.key}> <Text>{item.key}</Text> </ListItem>)} </List> </Item> </Content> </Container>
Și acum, să implementăm gestionatorii de stare și evenimente:
const [input, changeInput] = useState(''); const [handles, changeHandles] = useState([]); const inputChange = (text) => { changeInput(text); } const onAddClicked = () => { const newHandles = [...handles, {key: input}]; changeHandles(newHandles); changeInput(''); }
Și, în sfârșit, apelul API:
const onPullClicked = () => { fetch('http://prismatic.pythonanywhere.com/get_tweets', { method: 'POST', mode: 'cors', headers: { 'Accept': 'application/json', 'Content-Type': 'application/json', }, body: JSON.stringify({ accounts: handles.map(x => x.key) }) }).then(r=>r.json()).then(data => { navigation.navigate('SentimentDisplay', { data }); }) .catch(e => { console.log(e); }) }
Observați că această implementare este aceeași ca și în implementarea NativeBase, cu excepția faptului că navigăm într-un mod diferit. Navigatorul de stivă trece în componentele sale o propă numită „navigație” și care poate fi folosită pentru a naviga între rute cu .navigate
. Pe lângă simpla navigare, puteți, de asemenea, să transmiteți date către componenta țintă. Vom folosi acest mecanism pentru a transmite datele.
Pentru a face compilarea aplicației, trebuie să-l facem pe Entry
conștient de componenta de navigare. Pentru a face asta, va trebui să schimbăm declarația funcției componente:
export default Entry = ({navigation}) => {
Acum salvați și ar trebui să vedeți pagina.
Pagina Sortată de Sentimente
Plan de ansamblu
Vom implementa pagina de sentiment la fel ca în secțiunile anterioare, dar vom stila pagina puțin diferit și, de asemenea, vom folosi biblioteca de navigare în mod diferit pentru a obține valoarea returnată a apelului API.
Deoarece React Native nu are CSS, va trebui fie să definim un obiect StyleSheet, fie pur și simplu să codificăm stilurile în linie. Deoarece vom împărtăși o parte din stilul între componente, să creăm o foaie de stil globală. Vom face asta după configurarea rutei.
De asemenea, deoarece StackNavigator
are un buton de navigare Înapoi încorporat, nu va fi nevoie să implementăm propriul nostru buton Înapoi.
Configurare rută
Definirea rutei în StackNavigator
este foarte simplă. Pur și simplu creăm unul nou numit Stack Screen și îi dăm componenta, la fel ca routerul React.
<NavigationContainer> <Stack.Navigator initialRouteName="Entry"> <Stack.Screen name="Entry" component={Entry} /> <Stack.Screen name="SentimentDisplay" component={SentimentDisplay} /> </Stack.Navigator> </NavigationContainer>
Pentru ca acest lucru să funcționeze, va trebui desigur să creăm o componentă stub în components/SentimentDisplay.js:
import React from 'react'; import {Text} from 'native-base'; const SentimentDisplay = () => { return <Text>Sentiment Display</Text>; } export default SentimentDisplay;
Și importați-l:
import SentimentDisplay from './components/SentimentDisplay';
Acum suntem gata să creăm foaia de stil globală.
Foaie de stil globală
Mai întâi, creați un fișier numit globalStyles.js. Apoi, importați componenta StyleSheet din React Native și definiți stilurile:
import {StyleSheet} from 'react-native'; export default StyleSheet.create({ tweet: {paddingTop: 5}, accountName: {fontWeight: '600'}, })
Și suntem gata să codificăm interfața de utilizare.
Componentele UI
Componenta UI este destul de familiară, cu excepția modului în care lucrăm cu rutele. Vom dori să folosim navigarea și traseul special de recuzită StackNavigator
pentru a obține starea curentă a aplicației și pentru a naviga la afișarea subiectului în cazul în care utilizatorul dorește să vadă acea pagină.
Modificați definiția componentei pentru a accesa elementele de recuzită de navigare:
const SentimentDisplay = ({route, navigation}) => {
Și acum, implementăm starea aplicației citire și funcționalitatea de navigare:
const {params: {data}} = route; const viewByTopicClicked = () => { navigation.navigate('TopicDisplay', { data }); }
Importați stilurile globale:
import globalStyles from './globalStyles';
Si componentele:
import { View } from 'react-native'; import {List, Item, Content, ListItem, Container, Text, Button} from 'native-base';
Și, în sfârșit, componentele:
return <Container> <Content> <Item> <Button primary onPress={viewByTopicClicked}><Text>View By Topic</Text></Button> </Item> <Item> <List style={{width: '100%'}}> {data.sentimentSorted.map((item, i) => <ListItem key={i}> <View style={globalStyles.listItem}> <Text style={globalStyles.accountName}>{item.account}:</Text> <Text style={globalStyles.tweet}>{item.tweet} ({item.data.sentiment.toPrecision(2)})</Text> </View> </ListItem>)} </List> </Item> </Content> </Container>;
Salvați și încercați să extrageți câteva tweet-uri și ar trebui să vedeți afișarea sentimentului. Acum, pe pagina de grupare a subiectelor.
Pagina de grupare a subiectelor
Plan de ansamblu
Afișarea subiectului este din nou foarte asemănătoare. Vom folosi un generator de handler pentru a construi o funcție de navigare pentru a naviga la pagina de afișare pentru un anumit articol de subiect și vom defini, de asemenea, o foaie de stil care este specifică acestei pagini.
Un lucru nou pe care îl vom face este implementarea unui TouchableOpacity, care este o componentă specifică React Native care funcționează la fel ca un buton.
Configurare rută
Definiția rutei este aceeași ca înainte:
<Stack.Navigator initialRouteName="Entry"> <Stack.Screen name="Entry" component={Entry} /> <Stack.Screen name="SentimentDisplay" component={SentimentDisplay} /> <Stack.Screen name="TopicDisplay" component={TopicDisplay} /> </Stack.Navigator>
Componentele componentei stub/TopicDisplay.js:
import React from 'react'; import {Text} from 'native-base'; const TopicDisplay = () => { return <Text>Topic Display</Text>; } export default TopicDisplay;
Și importați-l:
import TopicDisplay from './components/TopicDisplay;
Componentele UI
Multe dintre acestea vor părea foarte familiare. Importați funcțiile bibliotecii:
import { View, TouchableOpacity, StyleSheet } from 'react-native'; import {List, Item, Content, ListItem, Container, Text} from 'native-base';
Importați stilurile globale:
import globalStyles from './globalStyles';
Definiți stilurile personalizate:
const styles = StyleSheet.create({ topicName: {fontWeight: '600'}, })
Definiți recuzita de navigare:
export default TopicDisplay = ({route, navigation}) => {
Definiți manipulatorii de date și acțiuni. Observați că folosim un constructor de handler, o funcție care returnează o funcție:
const {params: {data}} = route; const specificItemPressedHandlerBuilder = (topic) => () => { navigation.navigate('TopicDisplayItem', { data, topic }); }
Și acum, componentele. Observați că folosim un TouchableOpacity, care poate avea un handler onPress
. Am fi putut folosi și TouchableTransparency, dar animația de clic și menținere a TouchableOpacity a fost mai potrivită pentru aplicația noastră.
return <Container> <Content> <Item> <List style={{width: '100%'}}> {Object.keys(data.groupedByNp).map((topic, i) => <ListItem key={i}> <View style={globalStyles.listItem}> <TouchableOpacity onPress={specificItemPressedHandlerBuilder(topic)}> <Text style={styles.topicName}>{topic}</Text> </TouchableOpacity> </View> </ListItem>)} </List> </Item> </Content> </Container>;
Și asta ar trebui să o facă. Salvați și încercați aplicația!
Acum, pe pagina de articol de afișare a subiectului.
Subiect Afișează pagina articol
Plan de ansamblu
Pagina articolului de afișare a subiectului este foarte asemănătoare și toate idiosincraziile sunt luate în considerare în celelalte secțiuni, așa că ar trebui să fie ușor de mers de aici.
Configurare rută
Vom adăuga definiția rutei:
<Stack.Screen name="TopicDisplayItem" component={TopicDisplayItem} />
Adăugați importul:
import TopicDisplayItem from './components/TopicDisplayItem';
Și creați componenta stub. În loc de doar o componentă goală, să importam și componentele NativeBase pe care le vom folosi și să definim elementele de recuzită a rutei:
import React from 'react'; import {View} from 'react-native'; import {List, Item, Content, ListItem, Container, Text} from 'native-base'; import globalStyles from './globalStyles'; const TopicDisplayItem = ({route}) => { const {params: {data, topic}} = route; return <Text>Topic Display Item</Text>; } export default TopicDisplayItem;
Componentele UI
Componenta UI este destul de simplă. L-am mai văzut și nu implementăm cu adevărat nicio logică personalizată. Deci, hai să mergem la asta! Respiră adânc…
return <Container> <Content> <Item> <List style={{width: '100%'}}> {data.groupedByNp[topic].map((item, i) => <ListItem key={i}> <View style={globalStyles.listItem}> <Text style={globalStyles.accountName}>{item.account}:</Text> <Text style={globalStyles.tweet}>{item.tweet} ({item.data.sentiment.toPrecision(2)})</Text> </View> </ListItem>)} </List> </Item> </Content> </Container>;
Salvați, și ar trebui să fim gata de plecare! Acum suntem gata să rulăm aplicația în emulator... așteptați, nu-i așa?
Rularea aplicației
Ei bine, din moment ce lucrați cu React Native, rulați deja aplicația în emulator, așa că această parte este deja îngrijită. Acesta este unul dintre lucrurile grozave despre mediul de dezvoltare React Native.
Uf! Cu asta, am terminat cu partea de codificare a acestui articol. Să vedem ce am învățat despre tehnologii.
Compararea tehnologiilor
Cordova: argumente pro și contra
Cel mai bun lucru despre Cordova este viteza absolută cu care un dezvoltator web priceput poate codifica ceva funcțional și rezonabil de prezentabil. Abilitățile de dezvoltare web și experiența se transferă cu ușurință deoarece, la urma urmei, codificați o aplicație web. Procesul de dezvoltare este rapid și simplu, iar accesarea API-ului Cordova este simplă și intuitivă.
Dezavantajele utilizării Cordova în mod direct provin în principal din dependența excesivă de componentele web. Utilizatorii au ajuns să se aștepte la o anumită experiență de utilizator și design de interfață atunci când folosesc aplicații mobile, iar atunci când o aplicație se simte ca un site mobil, experiența poate fi puțin tulburătoare. În plus, majoritatea funcțiilor încorporate în aplicații, cum ar fi animațiile de tranziție și utilitarele de navigare, trebuie implementate manual.
Ionic: argumente pro și contra
Cea mai bună parte a Ionic a fost câte funcții centrate pe mobil am primit „gratuit”. Codând la fel cum aș fi codificat o aplicație web, am reușit să construiesc o aplicație care pare mult mai prietenoasă cu dispozitivele mobile decât pur și simplu folosind Cordova și React-Bootstrap. A existat animație de navigare, butoane cu stiluri native și multe opțiuni de interfață cu utilizatorul care au făcut experiența utilizatorului foarte lină.
Dezavantajul utilizării Ionic a fost parțial cauzat de punctele sale forte. În primul rând, uneori era greu de imaginat cum se va comporta aplicația în diferite medii. Doar pentru că aplicația arăta într-un fel nu înseamnă că aceeași plasare a interfeței de utilizare ar arăta la fel în alt mediu. În al doilea rând, Ionic se află deasupra multor piese de tehnologii de bază, iar accesul la unele dintre componente s-a dovedit dificil. În cele din urmă, acest lucru este specific lui Ionic-React, dar deoarece Ionic a fost construit pentru prima dată pentru Angular, multe caracteristici Ionic-React păreau să aibă mai puțină documentație și suport. Cu toate acestea, echipa Ionic pare foarte atentă la nevoile dezvoltatorilor React și oferă rapid noi funcții.
React Native: argumente pro și contra
React Native a avut o experiență de utilizator foarte simplă în curs de dezvoltare pe mobil. Prin conectarea directă la emulator, nu era un mister cum va arăta aplicația. Interfața de depanare bazată pe web a fost extrem de utilă în aplicarea încrucișată a tehnicilor de depanare din lumea aplicațiilor web, iar ecosistemul este destul de robust.
Dezavantajul lui React Native vine din apropierea sa de interfața nativă. Multe biblioteci bazate pe DOM nu au putut fi folosite, ceea ce a însemnat nevoia de a învăța noi biblioteci și cele mai bune practici. Fără beneficiul CSS, stilarea aplicației a fost oarecum mai puțin intuitivă. În cele din urmă, cu multe componente noi de învățat (de exemplu, Vizualizare în loc de div, componenta Text care înfășoară totul, Butoane vs. TouchableOpacity vs. TouchableTransparency etc.), există o curbă de învățare la început dacă cineva intră în Reacționează lumea nativă cu puține cunoștințe anterioare despre mecanică.
Când să folosiți fiecare tehnologie
Deoarece Cordova, Ionic și React Native au toate avantaje și dezavantaje foarte puternice, fiecare tehnologie are un context în care s-ar bucura de cea mai bună productivitate și performanță.
Dacă aveți deja o aplicație existentă, care este pe internet, cu o identitate de marcă puternică în jurul designului UI și al aspectului general, cea mai bună opțiune ar fi Cordova, care vă oferă acces la funcțiile native ale smartphone-ului, permițându-vă în același timp să reutilizați cea mai mare parte a componentele web și păstrați-vă identitatea mărcii în acest proces. Pentru aplicații relativ simple care utilizează un cadru receptiv, este posibil să puteți construi o aplicație mobilă cu foarte puține modificări necesare. Cu toate acestea, aplicația dvs. va arăta mai puțin ca o aplicație și mai mult ca o pagină web, iar unele dintre componentele pe care oamenii le așteaptă de la o aplicație mobilă vor fi codificate separat. Prin urmare, recomand Cordova în cazurile în care vă aflați într-un proiect web care porta aplicația pe mobil.
Dacă începeți să codificați o nouă aplicație cu o filozofie de aplicație mai întâi, dar setul de abilități al echipei dvs. este în primul rând în dezvoltarea web, vă recomand Ionic. Biblioteca lui Ionic vă permite să scrieți rapid cod care arată și se simte aproape de componentele native, permițându-vă în același timp să vă aplicați abilitățile și instinctele ca dezvoltator web. Am descoperit că cele mai bune practici de dezvoltare web se aplicau ușor încrucișat dezvoltării cu Ionic, iar stilizarea cu CSS a funcționat fără probleme. În plus, versiunea mobilă a site-ului arată mult mai nativă decât un site web codificat folosind un cadru CSS receptiv. Cu toate acestea, la câțiva pași de-a lungul drumului, am constatat că integrarea React-Ionic-Native API necesită unele ajustări manuale, care s-ar putea dovedi consumatoare de timp. Prin urmare, recomand Ionic în cazurile în care aplicația dvs. este dezvoltată de la zero și doriți să partajați o cantitate semnificativă de cod între o aplicație web capabilă de mobil și o aplicație mobilă.
Dacă codificați o nouă aplicație cu o bază de cod nativă implementată, poate doriți să încercați React Native. Chiar dacă nu utilizați cod nativ, ar putea fi și cea mai bună opțiune în cazurile în care sunteți deja familiarizat cu React Native sau când preocuparea dvs. principală este aplicația mobilă, mai degrabă decât o aplicație hibridă. După ce mi-am concentrat majoritatea eforturilor de dezvoltare front-end pe dezvoltarea web, am constatat inițial că începerea cu React Native a avut o curbă de învățare mai mare decât Ionic sau Cordova, din cauza diferenței de organizare a componentelor și convenții de codare. Cu toate acestea, odată ce aceste nuanțe sunt învățate, experiența de codare este destul de lină, mai ales cu ajutorul unei biblioteci de componente precum NativeBase. Având în vedere calitatea mediului de dezvoltare și controlul asupra aplicației, dacă front-end-ul proiectului dumneavoastră este în primul rând o aplicație mobilă, aș recomanda React Native ca instrument de alegere.
Subiecte viitoare
Unul dintre subiectele pe care nu am avut timp să le explorez a fost ușurința de a accesa API-urile native, cum ar fi camera, geolocalizarea sau autentificarea biometrică. Unul dintre marile beneficii ale dezvoltării mobile este accesibilitatea unui ecosistem bogat API care, în general, nu este accesibil în browser.
În articolele viitoare, aș dori să explorez ușurința dezvoltării acestor aplicații native activate cu API folosind diverse tehnologii multiplatforme.
Concluzie
Astăzi, am implementat o aplicație de curatare Twitter folosind trei tehnologii diferite de dezvoltare mobilă multiplatformă. Sper că acest lucru v-a dat o idee bună despre cum este fiecare tehnologie și v-a inspirat să vă dezvoltați propria aplicație bazată pe React.
Multumesc pentru lectura!