Полный стек НЛП с React: Ionic против Cordova против React Native

Опубликовано: 2022-03-11

Примерно за 15 лет, прошедших с тех пор, как Apple выпустила первый iPhone, ландшафт разработки программного обеспечения сильно изменился. По мере того, как смартфоны получают широкое распространение и продолжают расширять свои уникальные возможности, пользователи все чаще предпочитают получать доступ к программным службам через мобильные устройства, а не через настольные компьютеры или ноутбуки. Смартфоны предлагают такие функции, как геолокация, биометрическая аутентификация и датчик движения, многие из которых настольные платформы только сейчас начинают копировать. В некоторых демографических группах смартфон или аналогичное мобильное устройство является основным средством потребления программного обеспечения, полностью минуя компьютеры.

Компании заметили этот сдвиг и существенно усилили его. Мобильные приложения больше не являются чем-то второстепенным. Приложения, начиная от Robinhood, финансовой брокерской компании, и заканчивая Instagram, компанией социальных сетей, и Uber, компанией по вызову такси, принимают стратегию развития, ориентированную на мобильные устройства. Если есть настольное приложение, оно часто предлагается в качестве дополнения к мобильному приложению, а не в качестве основного направления.

Для разработчиков полного стека адаптация к этим изменяющимся тенденциям имеет решающее значение. К счастью, существует множество зрелых и хорошо поддерживаемых технологий, которые помогают веб-разработчикам применять свои навыки в мобильной разработке. Сегодня мы рассмотрим три такие технологии: Cordova, Ionic и React Native. Мы будем использовать React.js, один из самых популярных фреймворков для веб-разработки, в качестве нашей основной технологии разработки. Хотя мы сосредоточимся на разработке приложения для iPhone, это кроссплатформенные технологии, которые можно будет кросс-компилировать для платформы Android.

Что мы построим сегодня

Мы создадим приложение, которое использует обработку естественного языка (NLP) для обработки и курирования каналов Twitter. Приложение позволит пользователю выбрать набор дескрипторов Twitter, получить самые последние обновления с помощью API Twitter и классифицировать твиты на основе настроения и темы. Затем пользователь сможет просматривать твиты в зависимости от настроения или темы.

Бэкэнд

Прежде чем мы построим переднюю часть, мы хотим построить заднюю часть. На данный момент мы сохраним простоту серверной части — мы будем использовать базовый готовый анализ настроений и тегирование частей речи, а также небольшую очистку данных для решения проблем, связанных с набором данных. Мы будем использовать библиотеку NLP с открытым исходным кодом под названием TextBlob и передавать результат через Flask.

Анализ настроений, маркировка частей речи и НЛП: краткое руководство

Если вы раньше не работали с приложениями для анализа естественного языка, эти термины могут быть вам чужды. НЛП — это общий термин для технологий, которые анализируют и обрабатывают данные естественного человеческого языка. Хотя это широкая тема, существует множество проблем, общих для всех технологий, работающих в этой области. Например, человеческий язык, в отличие от языка программирования или числовых данных, имеет тенденцию быть слабо структурированным из-за разрешающей природы грамматики человеческого языка. Кроме того, человеческий язык имеет тенденцию быть чрезвычайно контекстуальным, и фраза, произнесенная или написанная в одном контексте, может не быть переведена в другой контекст. Наконец, помимо структуры и контекста, язык чрезвычайно сложен. Слова дальше в абзаце могут изменить значение предложения в начале абзаца. Словарь можно изобрести, переопределить или изменить. Все эти сложности затрудняют перекрестное применение методов анализа данных.

Анализ настроений — это подраздел НЛП, который фокусируется на понимании эмоциональности отрывка на естественном языке. В то время как человеческие эмоции по своей природе субъективны, и поэтому их трудно определить технически, анализ настроений — это область, которая имеет огромные коммерческие перспективы. Некоторые приложения анализа настроений включают классификацию обзоров продуктов для выявления положительных и отрицательных оценок различных функций, определение настроения электронного письма или речи и группировку текстов песен по настроению. Если вам нужно более подробное объяснение анализа тональности, вы можете прочитать мою статью о создании приложения на основе анализа тональности здесь.

Тегирование части речи или тегирование POS — это совсем другое подполе. Целью POS-тегов является определение части речи данного слова в предложении с использованием грамматической и контекстуальной информации. Выявление этой связи — гораздо более сложная задача, чем кажется на первый взгляд — слово может иметь очень разные части речи в зависимости от контекста и структуры предложения, а правила не всегда понятны даже людям. К счастью, сегодня многие готовые модели предоставляют мощные и универсальные модели, интегрированные с большинством основных языков программирования. Если вы хотите узнать больше, вы можете прочитать мою статью о тегах POS здесь.

Flask, TextBlob и Tweepy

Для нашей серверной части NLP мы будем использовать Flask, TextBlob и Tweepy. Мы будем использовать Flask для создания небольшого легкого сервера, TextBlob для обработки естественного языка и Tweepy для получения твитов из Twitter API. Прежде чем приступить к кодированию, вам также нужно получить ключ разработчика из Twitter, чтобы вы могли получать твиты.

Мы можем написать гораздо более сложную серверную часть и использовать более сложные технологии НЛП, но для наших сегодняшних целей мы сохраним серверную часть настолько простой, насколько это возможно.

Серверный код

Теперь мы готовы начать кодирование. Запустите свой любимый редактор Python и терминал, и вперед!

Во-первых, мы хотим установить необходимые пакеты.

 pip install flask flask-cors textblob tweepy python -m textblob.download_corpora

Теперь давайте напишем код для нашей функциональности.

Откройте новый скрипт Python, назовите его server.py и импортируйте необходимые библиотеки:

 import tweepy from textblob import TextBlob from collections import defaultdict

Давайте теперь напишем несколько вспомогательных функций:

 # 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

Теперь, когда у нас написаны вспомогательные функции, мы можем собрать все вместе с помощью пары простых функций:

 # 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

Теперь вы можете запустить функцию download_analyze_tweets для списка дескрипторов, за которыми вы хотите следить, и вы должны увидеть результаты.

Я запустил следующий код:

 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)

Выполнение этого дало следующие результаты. Результаты, очевидно, зависят от времени, поэтому, если вы видите что-то похожее, вы на правильном пути.

 [{'account': '@spacex', 'tweet': 'Falcon 9… [{'account': '@nasa', 'tweet': 'Our Mars rove… {'Falcon': [{'account': '@spacex', 'tweet': 'Falc….

Теперь мы можем создать сервер Flask, что довольно просто. Создайте пустой файл с именем server.py и напишите следующий код:

 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)

Запустите сервер, и теперь вы сможете отправить почтовый запрос на сервер, используя HTTP-клиент по вашему выбору. Передайте {accounts: ["@NASA", "@SpaceX"]} в качестве аргумента json, и вы должны увидеть, что API возвращает что-то похожее на то, что было возвращено в коде анализа Twitter.

Теперь, когда у нас есть сервер, мы готовы написать интерфейс. Из-за небольшого нюанса в работе с сетью через эмулятор телефона я рекомендую вам где-нибудь развернуть свой API. В противном случае вы захотите определить, работает ли ваше приложение на эмуляторе, и отправлять запросы на <Your Computer IP>:5000 вместо localhost:5000 , когда оно находится в эмуляторе. Если вы развернете код, вы можете просто отправить запрос на этот URL-адрес.

Существует множество вариантов развертывания сервера. В качестве бесплатного простого сервера отладки с минимальной настройкой я рекомендую что-то вроде PythonAnywhere, который должен иметь возможность запускать этот сервер из коробки.

Теперь, когда мы закодировали внутренний сервер, давайте посмотрим на внешний интерфейс. Начнем с одного из самых удобных вариантов для веб-разработчика: Cordova.

Реализация Apache Cordova

Кордова Праймер

Apache Cordova — это программная технология, помогающая веб-разработчикам ориентироваться на мобильные платформы. Используя возможности веб-браузера, реализованные на платформах смартфонов, Cordova заключает код веб-приложения в собственный контейнер приложения для создания приложения. Однако Cordova — это не просто модный веб-браузер. Через Cordova API веб-разработчики могут получить доступ ко многим функциям смартфона, таким как автономная поддержка, службы определения местоположения и камера на устройстве.

Для нашего приложения мы напишем приложение, используя React.js в качестве основы JS и React-Bootstrap в качестве основы CSS. Поскольку Bootstrap — адаптивная среда CSS, она уже поддерживает работу на небольших экранах. Как только приложение будет написано, мы скомпилируем его в веб-приложение с помощью Cordova.

Настройка приложения

Мы начнем с уникальной настройки приложения Cordova React. В статье на Medium разработчик Шубхам Патил объясняет, что мы делаем. По сути, мы настраиваем среду разработки React с использованием интерфейса командной строки React, а затем среду разработки Cordova с использованием интерфейса командной строки Cordova, прежде чем окончательно объединить их.

Для начала выполните следующие две команды в папке с кодом:

 cordova create TwitterCurationCordova create-react-app twittercurationreact

После завершения настройки мы хотим переместить содержимое папки public и src приложения React в приложение Cordova. Затем в package.json скопируйте скрипты, список браузеров и зависимости из проекта React. Также добавьте "homepage": "./" в корень package.json, чтобы обеспечить совместимость с Cordova.

После слияния package.json мы захотим изменить файл public/index.html для работы с Cordova. Откройте файл и скопируйте метатеги с www/index.html, а также скрипт в конце тега body при загрузке Cordova.js.

Затем измените файл src/index.js, чтобы определить, работает ли он в Cordova. Если он работает в Cordova, нам нужно запустить код рендеринга в обработчике события deviceready. Если он работает в обычном браузере, просто сразу визуализируйте.

 const renderReactDom = () => { ReactDOM.render( <React.StrictMode> <App /> </React.StrictMode>, document.getElementById('root') ); } if (window.cordova) { document.addEventListener('deviceready', () => { renderReactDom(); }, false); } else { renderReactDom(); }

Наконец, нам нужно настроить конвейер развертывания. Добавьте приведенное ниже определение в файл config.xml:

<hook type="before_prepare" src="hooks/prebuild.js" />

И поместите следующий скрипт в 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); }); }); }); };

Это выполняет сборку React и помещает папку сборки в соответствующее место перед запуском сборки Cordova, тем самым автоматизируя процесс развертывания.

Теперь мы можем попробовать запустить наше приложение. Запустите в командной строке следующее:

 npm install rimraf npm install npm run start

Вы должны увидеть, что приложение React настроено и работает в браузере. Добавьте Кордову сейчас:

 cordova platform add iOS cordova run iOS

И вы должны увидеть приложение React, работающее в эмуляторе.

Настройка маршрутизатора и пакетов

Чтобы настроить некоторую инфраструктуру, необходимую для создания приложения, давайте начнем с установки необходимых пакетов:

 npm install react-bootstrap react-router react-router-dom

Теперь мы настроим маршрутизацию, и при этом мы также настроим простой объект глобального состояния, который будет общим для всех компонентов. В рабочем приложении мы будем использовать систему управления состоянием, такую ​​как Redux или MobX, но пока мы не будем усложнять ее. Перейдите в App.js и настройте маршрут следующим образом:

 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> }

С этим определением маршрута мы представили два маршрута, которые нам нужно будет реализовать: ввод и отображение. Обратите внимание, что переменная curatedTweets передается в Display, а переменная setCuratedTweets — во Input. Это означает, что компонент ввода сможет вызвать функцию для установки переменной curatedTweets , а Display получит переменную для отображения.

Чтобы начать программировать компоненты, давайте создадим в /src папку с именем /src/components. В /src/components создайте еще одну папку с именем /src/components/input и два файла под ней: input.js и input.css. Сделайте то же самое для компонента Display — создайте /src/components/display и ниже: display.js и display.css.

Под ними давайте создадим компоненты-заглушки, например:

 import React from 'react'; import 'input.css' const Input = () => <div>Input</div>; export default Input

И то же самое для дисплея:

 import React from 'react'; import display.css' const Display = () => <div>Display</div>; export default Display

На этом наш каркас завершен, и приложение должно работать. Давайте теперь закодируем страницу ввода.

Входная страница

Общий план

Прежде чем мы напишем код, давайте подумаем о том, что мы хотим, чтобы наша страница ввода делала. Очевидно, нам нужен способ, с помощью которого пользователи могли бы вводить и редактировать дескрипторы Twitter, из которых они хотят получать данные. Мы также хотим, чтобы пользователи могли указать, что они сделали. Когда пользователи укажут, что они закончили, мы захотим извлечь кураторские твиты из нашего API курации Python и, наконец, перейти к компоненту Display.

Теперь, когда мы знаем, что должен делать наш компонент, мы готовы к написанию кода.

Настройка файла

Давайте начнем с импорта библиотеки React Router withRouter , чтобы получить доступ к функциям навигации, нужным нам компонентам React Bootstrap, например:

 import React, {useState} from 'react'; import {withRouter} from 'react-router-dom'; import {ListGroup, Button, Form, Container, Row, Col} from 'react-bootstrap'; import './input.css';

Теперь давайте определим функцию-заглушку для ввода. Мы знаем, что Input получает функцию setCuratedTweets , и мы также хотим дать ему возможность переходить к маршруту отображения после того, как он установит кураторские твиты из нашего Python API. Поэтому мы захотим взять из реквизита setCuratedTweets и историю (для навигации).

 const Input = ({setCuratedTweets, history}) => { return <div>Input</div> }

Чтобы предоставить ему доступ к API истории, мы обернем его withRouter в операторе экспорта в конце файла:

 export default withRouter(Input);

Контейнеры данных

Давайте настроим контейнеры данных, используя React Hooks. Мы уже импортировали хук useState , поэтому можем добавить следующий код в тело компонента Input:

 const [handles, setHandles] = useState([]); const [handleText, setHandleText] = useState('');

Это создает контейнер и модификаторы для дескрипторов, которые будут содержать список дескрипторов, из которых пользователь хочет извлечь данные, и handleText , который будет содержать содержимое текстового поля, которое пользователь использует для ввода дескриптора.

Теперь давайте напишем компоненты пользовательского интерфейса.

Компоненты пользовательского интерфейса

Компоненты пользовательского интерфейса будут довольно простыми. У нас будет одна строка Bootstrap, которая содержит текстовое поле ввода вместе с двумя кнопками, одну для добавления текущего содержимого поля ввода в список дескрипторов и одну для извлечения из API. У нас будет еще одна строка Bootstrap, которая отображает список дескрипторов, которые пользователь хочет получить, используя группу списка Bootstrap. В коде это выглядит так:

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

В дополнение к компоненту пользовательского интерфейса нам потребуется реализовать три обработчика событий пользовательского интерфейса, которые обрабатывают изменения данных. Обработчик события getPull , который вызывает API, будет реализован в следующем разделе.

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

Теперь мы готовы реализовать вызов API.

Вызов API

Для вызова API мы хотим взять дескрипторы, которые мы хотим получить, отправить их API Python в запросе POST и поместить полученный результат JSON в переменную curatedTweets . Затем, если все пойдет хорошо, мы хотим программно перейти к маршруту /display. В противном случае мы зарегистрируем ошибку в консоли, чтобы упростить отладку.

В кодовом режиме это выглядит так:

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

И с этим мы должны быть готовы идти. Не стесняйтесь запускать приложение, добавлять пару дескрипторов и отправлять запрос в API.

Теперь мы готовы закодировать страницу настроений.

Режим сортировки настроений

Поскольку Python API уже сортирует твиты по настроению, как только мы получим результат от Python API, страница настроения на самом деле не так уж сложна.

Общий план

Нам нужен интерфейс списка для отображения твитов. Нам также нужно, чтобы пара навигационных компонентов переключалась в режим группировки тем и возвращалась на страницу ввода.

Для начала давайте определим подкомпонент режима SentimentDisplay в файле display.js.

Компонент отображения настроений

SentimentDisplay возьмет объект curatedTweets и отобразит отсортированные по настроению твиты в списке. С помощью React-Bootstrap компонент довольно прост:

 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> }

Пока мы этим занимаемся, давайте также добавим немного стиля. Поместите следующее в display.css и импортируйте его:

 .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; }

Теперь мы можем показать компонент SentimentDisplay. Измените функцию Display следующим образом:

 const Display = ({curatedTweets}) => { return <SentimentDisplay curatedTweets={curatedTweets} /> };

Давайте также воспользуемся этой возможностью, чтобы запрограммировать навигационные компоненты. Нам понадобятся две кнопки — кнопка «Назад к редактированию» и режим группы тем.

Мы можем реализовать эти кнопки в отдельной строке Bootstrap прямо над компонентом SentimentDisplay, например:

 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>

Запустите приложение и вытащите твиты с пары ручек. Выглядит довольно прикольно!

Режим группировки тем

Теперь мы хотим реализовать режим группировки тем. Это немного сложнее, чем SentimentDisplay, но опять же, некоторые очень удобные компоненты Bootstrap очень нам помогают.

Общий план

Мы получим все фразы с существительными и отобразим их в виде аккордеонного списка. Затем мы будем отображать твиты, содержащие словосочетания существительных, как только список аккордеона будет расширен.

Реализация переключения в режим группировки тем

Во-первых, давайте реализуем логику для переключения из режима тональности в режим группировки тем. Давайте начнем с создания компонента-заглушки:

 const TopicDisplay = () => { return <div>Topic Display</div> }

И установите некоторую логику, чтобы создать режим для его отображения. В главном компоненте отображения добавьте следующие строки, чтобы создать логику, для которой будет отображаться компонент.

 // 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'

И измените JSX на следующее, чтобы добавить логику:

 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>

Теперь при переключении вы должны увидеть заглушку отображения группы тем.

Компонент TopicDisplay

Теперь мы готовы закодировать компонент TopicDisplay . Как обсуждалось ранее, он будет использовать аккордеонный список Bootstrap. Реализация на самом деле довольно проста:

 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> }

Запустите приложение, и вы должны увидеть отображение темы.

Теперь приложение готово, и мы готовы создать приложение для эмулятора.

Запуск приложения в эмуляторе

Cordova позволяет очень легко запускать приложение в эмуляторе. Просто запустите:

 cordova platform add ios # if you haven't done so already cordova run ios

И вы должны увидеть приложение в эмуляторе. Поскольку Bootstrap — адаптивное веб-приложение, веб-приложение адаптируется к ширине iPhone, и все выглядит довольно хорошо.

Когда приложение Cordova готово, давайте теперь посмотрим на реализацию Ionic.

Реализация Ionic-React

Ионная грунтовка

Ionic — это библиотека веб-компонентов и набор инструментов CLI, которые упрощают создание гибридных приложений. Первоначально Ionic был построен на основе AngularJS и Cordova, но с тех пор они выпустили свои компоненты в React.js и начали поддерживать Capacitor, платформу, похожую на Cordova. Что отличает Ionic, так это то, что даже если вы используете веб-компоненты, они очень похожи на родной мобильный интерфейс. Кроме того, внешний вид компонентов Ionic автоматически адаптируется к операционной системе, в которой они работают, что снова помогает приложению выглядеть и работать более естественно и естественно. Наконец, хотя это выходит за рамки нашей статьи, Ionic также предоставляет несколько инструментов сборки, которые немного упрощают развертывание вашего приложения.

Для нашего приложения мы будем использовать компоненты Ionic React для создания пользовательского интерфейса, используя при этом некоторую логику JavaScript, которую мы создали в разделе Cordova.

Настройка приложения

Во-первых, мы хотим установить инструменты Ionic. Итак, давайте запустим следующее:

 npm install -g @Ionic/cli native-run cordova-res

И после завершения установки переходим в папку проекта. Теперь мы используем Ionic CLI для создания нашей новой папки проекта:

 ionic start twitter-curation-Ionic blank --type=react --capacitor

Смотрите, как происходит волшебство, а теперь зайдите в папку с:

 cd twitter-curation-Ionic

И запустите пустое приложение с помощью:

 ionic serve

Итак, наше приложение настроено и готово к работе. Определим несколько маршрутов.

Прежде чем мы двинемся дальше, вы заметите, что Ionic начал проект с использованием TypeScript. Хотя я не стараюсь изо всех сил использовать TypeScript, у него есть несколько очень хороших функций, и мы будем использовать его для этой реализации.

Настройка маршрутизатора

Для этой реализации мы будем использовать три маршрута — input, topicDisplay sentimentDisplay Мы делаем это, потому что хотим воспользоваться функциями перехода и навигации, предоставляемыми Ionic, а также потому, что мы используем компоненты Ionic, а списки аккордеонов не поставляются в комплекте с Ionic. Конечно, мы можем реализовать свои собственные, но для этого урока мы останемся с предоставленными компонентами Ionic.

Если вы перейдете к App.tsx, вы должны увидеть уже определенные основные маршруты.

Входная страница

Общий план

Мы будем использовать ту же логику и код, что и реализация Bootstrap, с некоторыми ключевыми отличиями. Во-первых, мы будем использовать TypeScript, что означает, что у нас будут аннотации типов для нашего кода, которые вы увидите в следующем разделе. Во-вторых, мы будем использовать компоненты Ionic, которые очень похожи по стилю на Bootstrap, но стили будут зависеть от ОС. Наконец, мы будем динамически перемещаться с использованием API истории, как в версии Bootstrap, но доступ к истории будет немного другим из-за реализации Ionic Router.

Настройка

Давайте начнем с настройки компонента ввода с помощью компонента-заглушки. Создайте папку под страницами с именем input и создайте под ней файл с именем Input.tsx. В этот файл поместите следующий код для создания компонента React. Обратите внимание: поскольку мы используем TypeScript, он немного отличается.

 import React, {useState} from 'react'; const Input : React.FC = () => { return <div>Input</div>; } export default Input;

И измените компонент в App.tsx на:

 const App: React.FC = () => ( <IonApp> <IonReactRouter> <IonRouterOutlet> <Route path="/input" component={Input} exact={true} /> <Route exact path="/" render={() => <Redirect to="/input" />} /> </IonRouterOutlet> </IonReactRouter> </IonApp> );

Теперь, когда вы обновите приложение, вы должны увидеть компонент-заглушку ввода.

Контейнеры данных

Теперь создадим контейнеры данных. Нам нужны контейнеры для введенных дескрипторов Twitter, а также текущее содержимое поля ввода. Поскольку мы используем TypeScript, нам нужно добавить аннотации типа к нашему useState в функции компонента:

 const Input : React.FC = () => { const [text, setText] = useState<string>(''); const [accounts, setAccounts] = useState<Array<string>>([]); return <div>Input</div>; }

Нам также понадобится контейнер данных для хранения значений, возвращаемых API. Поскольку его содержимое необходимо использовать совместно с другими маршрутами, мы определяем их на уровне App.tsx. Импортируйте useState из React в файл App.tsx и измените функцию контейнера приложения на следующую:

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

На этом этапе, если вы используете редактор с подсветкой синтаксиса, такой как Visual Studio Code, вы должны увидеть загорание CuratedTweets. Это потому, что файл не знает, как выглядит интерфейс CuratedTweets. Давайте определим это сейчас. Создайте в src папку с именем interfaces и создайте в ней файл с именем CuratedTweets.tsx. В файле определите интерфейс CuratedTweets следующим образом:

 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> }

Теперь приложение знает о структуре возвращаемых данных API. Импортируйте интерфейс CuratedTweets в App.tsx. Теперь вы должны увидеть, что App.tsx компилируется без проблем.

Здесь нам нужно сделать еще пару вещей. Нам нужно передать функцию setCuratedTweets в компонент ввода и сообщить компоненту ввода об этой функции.

В App.tsx измените входной маршрут следующим образом:

 <Route path="/input" render={() => <Input setCuratedTweets={setCuratedTweets}/>} exact={true} />

Теперь вы должны увидеть, что редактор помечает что-то еще — Input не знает о новой передаче, поэтому мы хотим определить ее в 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

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

Добавление заголовка

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>

Теперь это выглядит красиво!

Страница с сортировкой настроений

Общий план

Страница с сортировкой по тональности будет во многом похожа на страницу с сортировкой по тональности на странице Bootstrap, но с использованием TypeScript и Ionic. Мы также реализуем Topic Display как отдельный маршрут, чтобы воспользоваться преимуществами навигационных функций Ionic при работе на мобильных устройствах, поэтому нам нужно будет дать этой странице возможность также переходить к маршруту Topic Display.

Настройка маршрута

Давайте начнем с создания новой папки с именем сортировка по настроению и файла с именем сортировка по настроению.tsx под ней. Экспортируйте компонент-заглушку следующим образом:

 import React from 'react'; const SentimentSorted: React.FC = () => { return <div>Sentiment Sorted</div> } export default SentimentSorted;

И в App.tsx импортируйте компонент:

 import SentimentSorted from './pages/sentimentsorted/SentimentSorted';

И добавьте маршрут:

 <Route path="/display" render={() => <SentimentSorted curatedTweets={curatedTweets} />} />

Вы получите сообщение об ошибке TypeScript, говорящее о том, что SentimentSorted не ожидает реквизита curatedTweets, поэтому давайте позаботимся об этом сейчас в следующем разделе.

Компоненты пользовательского интерфейса

Начнем с определения свойств контейнера. Очень похоже на компонент ввода:

 import CuratedTweets from '../../interfaces/CuratedTweets'; interface ContainerProps { curatedTweets: CuratedTweets }

А теперь измените отображение заглушки:

 const SentimentSorted: React.FC<ContainerProps> = ({curatedTweets}) => { return <div>Sentiment Sorted</div> }

И все должно компилироваться.

Отображение очень простое, это просто IonList с компонентами отображения:

 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>

Если вы сохраните и извлечете некоторые твиты с помощью компонента ввода, вы должны увидеть твиты, отображаемые в списке.

Теперь давайте добавим кнопки навигации. Добавьте в IonGrid:

 <IonRow> <IonCol> <IonButton color='primary' onClick={switchToInput}>Back</IonButton> <IonButton color="success" onClick={toggleDisplayType}>{switchStr}</IonButton> </IonCol> </IonRow>

switchToInput очень легко реализовать с помощью API истории:

 const switchToInput = () => { history.goBack(); }

И ToggleDisplayType должен быть вам знаком:

 const toggleDisplayType = () => { setDisplayType(displayType === 'Sentiment' ? 'Topic': 'Sentiment'); } const switchStr = displayType === 'Sentiment'? 'View by Topic': 'View by Sentiment'

Теперь у нас реализован компонент SentimentDisplay . Теперь, прежде чем мы реализуем страницу отображения тем, нам нужно реализовать компонент, который отображает все темы. Мы сделаем это в следующем разделе.

Компонент тематических групп

Добавим параметр отображения списка тем и будем отображать его условно. Для этого нам нужно разбить список отображения настроений. Переименуйте SentimentDisplay в Display, и давайте разобьем список отображения настроений:

 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> }

Обратите внимание, как мы используем одно из определений класса в интерфейсе CuratedTweets. Это потому, что этим компонентам нужен не весь объект CuratedTweets, а только его подмножество. Список тем очень похож:

 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> }

И теперь условное отображение легко настроить в Display Component:

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

Обязательно измените экспорт по умолчанию, и теперь мы готовы реализовать страницу отображения темы.

Страница отображения темы

Общий план

Страница отображения темы представляет собой отображение списка, аналогичное отображению настроений, но мы будем искать рассматриваемую тему по параметру маршрута.

Настройка маршрута

Если вы зашли так далеко, вы уже должны знать, что делать. Давайте создадим папку страницы с именем TopicDisplay и TopicDisplay.tsx, напишем компонент-заглушку и импортируем его на страницу App.tsx. Теперь настроим маршруты:

 <Route path="/topicDisplay/:topic" render={() => <TopicDisplay curatedTweets={curatedTweets} /> } />

Теперь мы готовы реализовать компонент пользовательского интерфейса.

Компоненты пользовательского интерфейса

Во-первых, давайте создадим определение ContainerProps :

 interface ContainerProps { curatedTweets: CuratedTweets } const TopicDisplay: React.FC<ContainerProps> = ({curatedTweets}) => { Return <div>Topic Display</div> }

Теперь нам нужно будет получить тему из имени пути URL. Для этого мы будем использовать API истории. Итак, давайте импортируем useHistory , создадим экземпляр API истории и вытащим тему из пути. Пока мы этим занимаемся, давайте также реализуем функцию обратного переключения:

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

Теперь, когда у нас есть твиты с этой конкретной темой, отобразить их на самом деле довольно просто:

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

Сохраните и запустите, и все должно выглядеть хорошо.

Запуск приложения в эмуляторе

Чтобы запустить приложение в эмуляторе, мы просто запускаем несколько команд Ionic, чтобы добавить мобильную платформу и скопировать код, аналогично тому, как мы настраиваем все в 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

И вы должны увидеть, как появится приложение.

Реагировать на нативную реализацию

Реагировать на родное руководство

React Native использует совершенно другой подход, чем веб-подходы, описанные в предыдущих разделах. React Native отображает ваш код React как нативные компоненты. Это дает несколько преимуществ. Во-первых, гораздо более глубокая интеграция с базовой операционной системой, что позволяет разработчику использовать преимущества новых функций смартфонов и особенностей ОС, которые могут быть недоступны через Cordova/Capacitor. Во-вторых, поскольку в середине нет веб-движка рендеринга, приложение React Native обычно работает быстрее, чем приложение, написанное с использованием Cordova. Наконец, поскольку React Native позволяет интегрировать нативные компоненты, разработчики могут более точно контролировать свое приложение.

Для нашего приложения мы будем использовать логику из предыдущих разделов и использовать библиотеку компонентов React Native под названием NativeBase для кодирования нашего пользовательского интерфейса.

Настройка приложения

Во-первых, вам нужно установить все необходимые компоненты React Native, следуя приведенным здесь инструкциям.

После установки React Native запустим проект:

 react-native init TwitterCurationRN

Пусть сценарий установки запустится, и в конечном итоге папка должна быть создана. Перейдите в папку и запустите react-native run-ios, и вы должны увидеть всплывающее окно эмулятора с примером приложения.

Мы также хотим установить NativeBase, так как это наша библиотека компонентов. Для этого запускаем:

 npm install --save native-base react-native link

Мы также хотим установить навигатор стека React Native. Давайте работать:

 npm install --save @react-navigation/stack @react-navigation/native

А также

 react-native link cd ios pod-install cd

Завершить привязку и установку нативных плагинов.

Настройка маршрутизатора

Для маршрутизации мы будем использовать навигатор стека, который мы установили на предыдущем шаге.

Импортируйте компоненты маршрутизатора:

 import { NavigationContainer } from '@react-navigation/native'; import {createStackNavigator} from '@react-navigation/stack';

А теперь создадим навигатор стека:

 const Stack = createStackNavigator();

Измените содержимое компонента App, чтобы использовать навигатор стека:

 const App = () => { return ( <> <StatusBar bar /> <NavigationContainer> <Stack.Navigator initialRouteName="Entry"> <Stack.Screen name="Entry" component={Entry} /> </Stack.Navigator> </NavigationContainer> </> ); };

На этом этапе вы получите сообщение об ошибке, потому что запись еще не определена. Давайте определим элемент-заглушку, чтобы сделать его счастливым.

Создайте папку компонентов в своем проекте, создайте файл с именем Entry.jsx и добавьте компонент-заглушку следующим образом:

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

Импортируйте компонент Entry в свое приложение, и он должен собраться.

Теперь мы готовы закодировать страницу ввода.

Входная страница

Общий план

Мы будем реализовывать страницу, очень похожую на страницу, реализованную выше, но с использованием компонентов NativeBase. Большинство используемых нами API-интерфейсов JavaScript и React, таких как хуки и выборка, по-прежнему доступны.

Единственным отличием будет то, как мы работаем с навигационным API, с которым вы познакомитесь позже.

Компоненты пользовательского интерфейса

Дополнительные компоненты NativeBase, которые мы будем использовать, — это Container, Content, Input, List, ListItem и Button. Все они имеют аналоги в Ionic и Bootstrap React, и разработчики NativeBase сделали его очень интуитивно понятным для людей, знакомых с этими библиотеками. Просто импортируйте так:

 import { Container, Content, Input, Item, Button, List, ListItem, Text } from 'native-base';

И компонент:

 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>

А теперь давайте реализуем обработчики состояний и событий:

 const [input, changeInput] = useState(''); const [handles, changeHandles] = useState([]); const inputChange = (text) => { changeInput(text); } const onAddClicked = () => { const newHandles = [...handles, {key: input}]; changeHandles(newHandles); changeInput(''); }

И, наконец, вызов 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); }) }

Обратите внимание, что эта реализация такая же, как и в реализации NativeBase, за исключением того, что мы перемещаемся по-другому. Навигатор стека передает своим компонентам реквизит под названием «навигация», который можно использовать для навигации между маршрутами с помощью .navigate . Помимо простой навигации, вы также можете передавать данные целевому компоненту. Мы будем использовать этот механизм для передачи данных.

Чтобы приложение скомпилировалось, нам все еще нужно сообщить Entry о компоненте навигации. Для этого нам нужно будет изменить объявление функции компонента:

 export default Entry = ({navigation}) => {

Теперь сохраните, и вы должны увидеть страницу.

Страница с сортировкой настроений

Общий план

Мы будем реализовывать страницу тональности так же, как и в предыдущих разделах, но мы будем немного по-другому оформлять страницу, а также будем по-другому использовать библиотеку навигации для получения возвращаемого значения вызова API.

Поскольку в React Native нет CSS, нам нужно либо определить объект StyleSheet, либо просто закодировать стили в строке. Поскольку мы будем использовать некоторые стили для всех компонентов, давайте создадим глобальную таблицу стилей. Мы сделаем это после настройки маршрута.

Кроме того, поскольку StackNavigator имеет встроенную кнопку навигации «Назад», нам не нужно реализовывать собственную кнопку «Назад».

Настройка маршрута

Определение маршрута в StackNavigator очень простое. Мы просто создаем новый с именем Stack Screen и даем ему компонент, как и маршрутизатор React.

 <NavigationContainer> <Stack.Navigator initialRouteName="Entry"> <Stack.Screen name="Entry" component={Entry} /> <Stack.Screen name="SentimentDisplay" component={SentimentDisplay} /> </Stack.Navigator> </NavigationContainer>

Чтобы это работало, нам, конечно, потребуется создать компонент-заглушку в component/SentimentDisplay.js:

 import React from 'react'; import {Text} from 'native-base'; const SentimentDisplay = () => { return <Text>Sentiment Display</Text>; } export default SentimentDisplay;

И импортируйте его:

 import SentimentDisplay from './components/SentimentDisplay';

Теперь мы готовы создать глобальную таблицу стилей.

Глобальная таблица стилей

Сначала создайте файл с именем globalStyles.js. Затем импортируйте компонент StyleSheet из React Native и определите стили:

 import {StyleSheet} from 'react-native'; export default StyleSheet.create({ tweet: {paddingTop: 5}, accountName: {fontWeight: '600'}, })

И мы готовы кодировать пользовательский интерфейс.

Компоненты пользовательского интерфейса

Компонент пользовательского интерфейса довольно знаком, за исключением того, как мы работаем с маршрутами. Мы будем использовать специальную навигацию и маршрут StackNavigator , чтобы получить текущее состояние приложения и перейти к отображению темы, если пользователь захочет увидеть эту страницу.

Измените определение компонента, чтобы получить доступ к свойствам навигации:

 const SentimentDisplay = ({route, navigation}) => {

А теперь реализуем чтение состояния приложения и функционал навигации:

 const {params: {data}} = route; const viewByTopicClicked = () => { navigation.navigate('TopicDisplay', { data }); }

Импортируйте глобальные стили:

 import globalStyles from './globalStyles';

И компоненты:

 import { View } from 'react-native'; import {List, Item, Content, ListItem, Container, Text, Button} from 'native-base';

И, наконец, компоненты:

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

Сохраните и попробуйте получить несколько твитов, и вы должны увидеть отображение тональности. Теперь на странице тематической группы.

Страница группировки тем

Общий план

Отображение темы снова очень похоже. Мы будем использовать построитель обработчиков для создания функции навигации для перехода на страницу отображения для определенного элемента темы, а также мы будем определять таблицу стилей, специфичную для этой страницы.

Одна новая вещь, которую мы будем делать, — это реализация TouchableOpacity, специального компонента React Native, который работает так же, как кнопка.

Настройка маршрута

Определение маршрута такое же, как и раньше:

 <Stack.Navigator initialRouteName="Entry"> <Stack.Screen name="Entry" component={Entry} /> <Stack.Screen name="SentimentDisplay" component={SentimentDisplay} /> <Stack.Screen name="TopicDisplay" component={TopicDisplay} /> </Stack.Navigator>

Компоненты-заглушки /TopicDisplay.js:

 import React from 'react'; import {Text} from 'native-base'; const TopicDisplay = () => { return <Text>Topic Display</Text>; } export default TopicDisplay;

И импортируйте его:

 import TopicDisplay from './components/TopicDisplay;

Компоненты пользовательского интерфейса

Многое из этого будет выглядеть очень знакомо. Импортируйте библиотечные функции:

 import { View, TouchableOpacity, StyleSheet } from 'react-native'; import {List, Item, Content, ListItem, Container, Text} from 'native-base';

Импортируйте глобальные стили:

 import globalStyles from './globalStyles';

Определите пользовательские стили:

 const styles = StyleSheet.create({ topicName: {fontWeight: '600'}, })

Определите реквизиты навигации:

 export default TopicDisplay = ({route, navigation}) => {

Определите обработчики данных и действий. Обратите внимание, что мы используем построитель обработчиков, функцию, которая возвращает функцию:

 const {params: {data}} = route; const specificItemPressedHandlerBuilder = (topic) => () => { navigation.navigate('TopicDisplayItem', { data, topic }); }

А теперь компоненты. Обратите внимание, что мы используем TouchableOpacity, который может иметь обработчик onPress . Мы могли бы также использовать TouchableTransparency, но анимация щелчка и удержания TouchableOpacity лучше подходила для нашего приложения.

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

И это должно сделать это. Сохраните и попробуйте приложение!

Теперь перейдите на страницу элемента отображения темы.

Страница элемента отображения темы

Общий план

Страница элемента отображения темы очень похожа, и обо всех особенностях позаботятся в других разделах, поэтому отсюда все должно быть гладко.

Настройка маршрута

Мы добавим определение маршрута:

 <Stack.Screen name="TopicDisplayItem" component={TopicDisplayItem} />

Добавьте импорт:

 import TopicDisplayItem from './components/TopicDisplayItem';

И создайте компонент-заглушку. Вместо простого компонента давайте также импортируем компоненты NativeBase, которые мы будем использовать, и определим параметры маршрута:

 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;

Компоненты пользовательского интерфейса

Компонент пользовательского интерфейса довольно прост. Мы видели это раньше, и на самом деле мы не реализуем какую-либо пользовательскую логику. Итак, давайте просто пойдем! Сделайте глубокий вдох…

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

Сохраните, и мы должны быть готовы идти! Теперь мы готовы запустить приложение в эмуляторе… подождите, разве мы уже не делали?

Запуск приложения

Что ж, поскольку вы работаете с React Native, вы уже запускаете приложение в эмуляторе, поэтому об этой части уже позаботились. Это одна из замечательных особенностей среды разработки React Native.

Вау! На этом мы закончили с частью этой статьи, посвященной кодированию. Давайте посмотрим, что мы узнали о технологиях.

Сравнение технологий

Кордова: плюсы и минусы

Самое лучшее в Cordova — это скорость, с которой опытный веб-разработчик может написать что-то функциональное и достаточно презентабельное. Навыки и опыт веб-разработки легко передаются, потому что, в конце концов, вы пишете веб-приложение. Процесс разработки быстрый и простой, а доступ к Cordova API также прост и интуитивно понятен.

Недостатки прямого использования Cordova в основном связаны с чрезмерной зависимостью от веб-компонентов. Пользователи ожидают особого пользовательского опыта и дизайна интерфейса при использовании мобильных приложений, и когда приложение выглядит как мобильный сайт, это может немного раздражать. Кроме того, большинство функций, встроенных в приложения, таких как переходная анимация и утилиты навигации, необходимо реализовывать вручную.

Ионный: плюсы и минусы

Лучшей частью Ionic было то, сколько мобильных функций я получил «бесплатно». Кодируя так же, как я кодировал бы веб-приложение, я смог создать приложение, которое выглядит намного более удобным для мобильных устройств, чем просто использование Cordova и React-Bootstrap. Была навигационная анимация, кнопки с родными стилями и множество опций пользовательского интерфейса, которые сделали пользовательский интерфейс очень плавным.

Недостаток использования Ionic частично был вызван его сильными сторонами. Во-первых, иногда было трудно представить, как приложение будет вести себя в различных средах. Тот факт, что приложение выглядело в одном направлении, не означает, что такое же размещение пользовательского интерфейса будет выглядеть так же в другом окружении. Во-вторых, Ionic работает поверх многих базовых технологий, и получить доступ к некоторым компонентам оказалось непросто. Наконец, это характерно для Ionic-React, но, поскольку Ionic был впервые создан для Angular, многие функции Ionic-React, казалось, имели меньше документации и поддержки. Тем не менее, команда Ionic, кажется, очень внимательно относится к потребностям разработчиков React и быстро предоставляет новые функции.

React Native: плюсы и минусы

У React Native было очень гладкое взаимодействие с пользователем при разработке на мобильных устройствах. При прямом подключении к эмулятору не было тайной, как будет выглядеть приложение. Веб-интерфейс отладчика был чрезвычайно полезен для перекрестного применения методов отладки из мира веб-приложений, а экосистема достаточно надежна.

Недостаток React Native заключается в его близости к нативному интерфейсу. Многие библиотеки на основе DOM нельзя было использовать, что означало необходимость изучения новых библиотек и лучших практик. Без преимуществ CSS стилизация приложения была несколько менее интуитивной. Наконец, со многими новыми компонентами для изучения (например, View вместо div, компонент Text, обертывающий все, кнопки vs. TouchableOpacity vs. Реагируйте на родной мир с небольшим предварительным знанием механики.

Когда использовать каждую технологию

Поскольку у Cordova, Ionic и React Native есть очень сильные плюсы и минусы, у каждой технологии есть контекст, в котором она может обеспечить максимальную производительность и производительность.

Если у вас уже есть существующее веб-приложение с сильным фирменным стилем, связанным с дизайном пользовательского интерфейса и общим внешним видом, лучшим вариантом будет Cordova, которая дает вам доступ к собственным функциям смартфона, позволяя вам повторно использовать большую часть ваших приложений. веб-компонентов и сохранить при этом индивидуальность вашего бренда. Для относительно простых приложений, использующих адаптивный фреймворк, вы можете создать мобильное приложение с минимальными изменениями. Однако ваше приложение будет выглядеть не столько как приложение, сколько как веб-страница, а некоторые компоненты, которые люди ожидают от мобильного приложения, будут написаны отдельно. Поэтому я рекомендую Cordova в тех случаях, когда вы работаете над веб-проектом, перенося приложение на мобильные устройства.

Если вы начинаете кодировать новое приложение с философией «сначала приложение», но набор навыков вашей команды в основном связан с веб-разработкой, я рекомендую Ionic. Библиотека Ionic позволяет вам быстро писать код, который выглядит и ощущается близко к нативным компонентам, позволяя вам применять свои навыки и чутье веб-разработчика. Я обнаружил, что передовой опыт веб-разработки легко применим к разработке с помощью Ionic, а стилизация с помощью CSS работает без проблем. Кроме того, мобильная версия сайта выглядит намного нативнее, чем сайт, закодированный с использованием адаптивного CSS-фреймворка. Однако на некоторых этапах я обнаружил, что интеграция React-Ionic-Native API требует некоторой ручной настройки, которая может занять много времени. Поэтому я рекомендую Ionic в тех случаях, когда ваше приложение разрабатывается с нуля, и вы хотите разделить значительный объем кода между мобильным веб-приложением и мобильным приложением.

Если вы пишете новое приложение с какой-либо собственной кодовой базой, вы можете попробовать React Native. Даже если вы не используете нативный код, это также может быть лучшим вариантом в тех случаях, когда вы уже знакомы с React Native или когда вашей основной задачей является мобильное приложение, а не гибридное приложение. Сосредоточив большую часть своих усилий по разработке внешнего интерфейса на веб-разработке, я сначала обнаружил, что для начала работы с React Native требуется больше времени для обучения, чем для Ionic или Cordova, из-за различий в организации компонентов и соглашений о кодировании. Однако, как только эти нюансы будут изучены, опыт кодирования будет довольно гладким, особенно с помощью библиотеки компонентов, такой как NativeBase. Учитывая качество среды разработки и контроль над приложением, если интерфейс вашего проекта в основном представляет собой мобильное приложение, я бы рекомендовал React Native в качестве предпочтительного инструмента.

Будущие темы

Одной из тем, на изучение которой у меня не было времени, была простота доступа к родным API, таким как камера, геолокация или биометрическая аутентификация. Одним из больших преимуществ мобильной разработки является доступность богатой экосистемы API, которая обычно недоступна в браузере.

В будущих статьях я хотел бы изучить простоту разработки этих собственных приложений с поддержкой API с использованием различных кросс-платформенных технологий.

Заключение

Сегодня мы внедрили приложение для курирования Twitter, используя три разные технологии кроссплатформенной мобильной разработки. Я надеюсь, что это дало вам хорошее представление о каждой технологии и вдохновило вас на разработку собственного приложения на основе React.

Спасибо за чтение!