Reactを使用したフルスタックNLP:Ionic vs Cordova vs React Native

公開: 2022-03-11

Appleが最初のiPhoneをリリースしてから約15年で、ソフトウェア開発の状況は劇的に変化しました。 スマートフォンが広く採用され、独自の機能が増え続けるにつれて、ユーザーはデスクトップやラップトップではなくモバイルデバイスを介してソフトウェアサービスにアクセスすることをますます好むようになっています。 スマートフォンは、ジオロケーション、生体認証、モーションセンシングなどの機能を提供しますが、その多くはデスクトッププラットフォームがコピーを開始したばかりです。 一部の人口統計では、スマートフォンまたは同様のモバイルデバイスがソフトウェア消費の主要な手段であり、コンピューターを完全にバイパスします。

企業はこの変化に気づき、主要な方法でそれを強化しました。 モバイルアプリはもはや後付けではありません。 金融仲介会社のロビンフッドからソーシャルメディア会社のInstagram、配車サービス会社のUberまで、さまざまなアプリケーションがモバイルファーストの開発戦略を採用しています。 デスクトップアプリケーションがある場合、それは主な焦点ではなく、モバイルアプリを補完するものとして提供されることがよくあります。

フルスタック開発者にとって、これらの変化する傾向に適応することは非常に重要です。 幸いなことに、Web開発者が自分のスキルをモバイル開発に適用するのに役立つ、成熟した十分にサポートされたテクノロジーが数多くあります。 今日は、Cordova、Ionic、ReactNativeの3つのテクノロジーについて説明します。 コア開発テクノロジーとして、フロントエンドWeb開発で最も人気のあるフレームワークの1つであるReact.jsを使用します。 iPhoneアプリケーションの開発に焦点を当てますが、これらはクロスプラットフォームテクノロジーであり、Androidプラットフォームにクロスコンパイルできます。

今日構築するもの

自然言語処理(NLP)を使用してTwitterフィードを処理およびキュレートするアプリケーションを構築します。 このアプリケーションを使用すると、ユーザーは一連のTwitterハンドルを選択し、Twitter APIを使用して最新の更新をプルし、感情とトピックに基づいてツイートを分類できます。 その後、ユーザーは感情やトピックに基づいてツイートを表示できるようになります。

バックエンド

フロントエンドを構築する前に、バックエンドを構築する必要があります。 今のところ、バックエンドはシンプルに保ちます。基本的な既製の感情分析と品詞のタグ付けに加えて、データセット固有の問題に対処するためのデータクリーニングを少し使用します。 TextBlobと呼ばれるオープンソースのNLPライブラリを使用し、Flaskを介して結果を提供します。

感情分析、品詞タグ付け、およびNLP:クイック入門書

これまで自然言語分析アプリケーションを使用したことがない場合、これらの用語は非常に異質なものである可能性があります。 NLPは、自然な人間の言語データを分析および処理するテクノロジーの総称です。 これは幅広いトピックですが、この分野に取り組むすべてのテクノロジーに共通する多くの課題があります。 たとえば、人間の言語は、プログラミング言語や数値データとは異なり、人間の言語の文法の寛容な性質のために、大まかに構造化される傾向があります。 さらに、人間の言語は非常に文脈的である傾向があり、ある文脈で発声または書かれたフレーズは別の文脈に翻訳されない場合があります。 最後に、構造と文脈はさておき、言語は非常に複雑です。 段落のさらに下の単語は、段落の最初の文の意味を変える可能性があります。 語彙は、発明、再定義、または変更することができます。 これらの複雑さはすべて、データ分析手法を相互適用することを困難にします。

感情分析は、自然言語の一節の感情を理解することに焦点を当てたNLPのサブフィールドです。 人間の感情は本質的に主観的なものであるため、技術的に特定することは困難ですが、感情分析は、商業的に非常に有望なサブフィールドです。 感情分析の一部のアプリケーションには、さまざまな機能の肯定的評価と否定的評価を識別するための製品レビューの分類、電子メールまたはスピーチのムードの検出、およびムードによる歌詞のグループ化が含まれます。 感情分析のより詳細な説明を探している場合は、ここで感情分析ベースのアプリケーションの構築に関する私の記事を読むことができます。

品詞タグ付け、またはPOSタグ付けは、非常に異なるサブフィールドです。 品詞タグ付けの目的は、文法的および文脈的情報を使用して、文中の特定の単語の品詞を識別することです。 この関係を特定することは、最初に目にするよりもはるかに難しい作業です。単語は、文脈や文の構造に基づいて非常に異なる品詞を持つ可能性があり、ルールは人間にとってさえ常に明確であるとは限りません。 幸いなことに、今日の多くの既製のモデルは、ほとんどの主要なプログラミング言語と統合された強力で用途の広いモデルを提供します。 詳細については、POSタグ付けに関する私の記事をここで読むことができます。

Flask、TextBlob、およびTweepy

NLPバックエンドには、Flask、TextBlob、およびTweepyを使用します。 Flaskを使用して小型で軽量のサーバーを構築し、TextBlobを使用して自然言語処理を実行し、Tweepyを使用してTwitterAPIからツイートを取得します。 コーディングを開始する前に、Twitterから開発者キーを取得して、ツイートを取得できるようにすることもできます。

より洗練されたバックエンドを記述し、より複雑なNLPテクノロジーを使用することもできますが、今日の目的のために、バックエンドを可能な限りシンプルに保ちます。

バックエンドコード

これで、コーディングを開始する準備が整いました。 お気に入りの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クライアントを使用してサーバーにPOSTリクエストを送信できるようになります。 {accounts:[“ @NASA”、“ @SpaceX”]}をjson引数として渡すと、APIがTwitter分析コードで返されたものと同様のものを返すことがわかります。

サーバーができたので、フロントエンドをコーディングする準備が整いました。 電話エミュレータを介したネットワーキングには微妙な違いがあるため、APIをどこかにデプロイすることをお勧めします。 それ以外の場合は、アプリがエミュレーターで実行されているかどうかを検出し、エミュレーター内にある場合はlocalhost:5000 5000ではなく<Your Computer IP>:5000にリクエストを送信する必要があります。 コードをデプロイする場合は、そのURLにリクエストを発行するだけです。

サーバーをデプロイするための多くのオプションがあります。 最小限のセットアップで済む無料のシンプルなデバッグサーバーには、このサーバーをすぐに実行できるPythonAnywhereのようなものをお勧めします。

バックエンドサーバーをコーディングしたので、フロントエンドを見てみましょう。 Web開発者にとって最も便利なオプションの1つであるCordovaから始めます。

ApacheCordovaの実装

コルドバ入門書

Apache Cordovaは、Web開発者がモバイルプラットフォームをターゲットにするのに役立つソフトウェアテクノロジーです。 Cordovaは、スマートフォンプラットフォームに実装されているWebブラウザー機能を利用して、Webアプリケーションコードをネイティブアプリケーションコンテナーにラップし、アプリケーションを作成します。 ただし、Cordovaは単なる派手なWebブラウザではありません。 Cordova APIを介して、Web開発者は、オフラインサポート、位置情報サービス、オンデバイスカメラなどのスマートフォン固有の多くの機能にアクセスできます。

このアプリケーションでは、JSフレームワークとしてReact.jsを使用し、CSSフレームワークとしてReact-Bootstrapを使用してアプリケーションを作成します。 BootstrapはレスポンシブCSSフレームワークであるため、小さな画面での実行をすでにサポートしています。 アプリケーションが作成されたら、Cordovaを使用してWebアプリケーションにコンパイルします。

アプリの構成

まず、CordovaReactアプリをセットアップするために独自のことを行います。 Mediumの記事で、開発者のShubhamPatilが私たちが行っていることを説明しています。 基本的に、React CLIを使用してReact開発環境をセットアップし、次にCordova CLIを使用してCordova開発環境をセットアップしてから、最終的に2つをマージします。

開始するには、コードフォルダーで次の2つのコマンドを実行します。

 cordova create TwitterCurationCordova create-react-app twittercurationreact

セットアップが完了したら、Reactアプリのpublicフォルダーとsrcフォルダーの内容をCordovaアプリに移動します。 次に、package.jsonで、Reactプロジェクトからスクリプト、ブラウザーリスト、および依存関係をコピーします。 また、package.jsonのルートに"homepage": "./"を追加して、Cordovaとの互換性を有効にします。

package.jsonがマージされたら、public/index.htmlファイルをCordovaで動作するように変更します。 ファイルを開き、www / index.htmlからメタタグをコピーし、Cordova.jsがロードされたときにbodyタグの最後にあるスクリプトをコピーします。

次に、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を追加します:

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

このルート定義を使用して、実装する必要のある2つのルート(入力と表示)を導入しました。 curatedTweets変数がDisplayに渡され、 setCuratedTweets変数がInputに渡されていることに注意してください。 これは、入力コンポーネントが関数を呼び出してcuratedTweets変数を設定できることを意味し、Displayは変数を表示するようになります。

コンポーネントのコーディングを開始するには、/srcの下に/src/componentsというフォルダーを作成しましょう。 / src / componentsの下に、/ src / components / inputという別のフォルダーと、その下にinput.jsとinput.cssという2つのファイルを作成します。 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ハンドルを入力および編集する方法が必要になります。 また、ユーザーが完了したことを示すことができるようにする必要があります。 ユーザーが完了したことを示したら、PythonキュレーションAPIからキュレートされたツイートを取得し、最後に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アクセスを提供するために、ファイルの最後にあるexportステートメントでwithRouterでラップします。

 export default withRouter(Input);

データコンテナ

Reactフックを使用してデータコンテナを設定しましょう。 すでにuseStateフックをインポートしているので、次のコードをInputコンポーネントの本体に追加できます。

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

これにより、ユーザーがプルしたいハンドルのリストを保持するハンドルのコンテナーと修飾子、およびユーザーがハンドルを入力するために使用するテキストボックスのコンテンツを保持するhandleTextが作成されます。

それでは、UIコンポーネントをコーディングしましょう。

UIコンポーネント

UIコンポーネントはかなりシンプルになります。 入力テキストボックスと2つのボタンを含む1つのブートストラップ行があります。1つは現在の入力ボックスのコンテンツをハンドルのリストに追加するためのもので、もう1つはAPIからプルするためのものです。 ユーザーがブートストラップリストグループを使用してプルしたいハンドルのリストを表示する別のブートストラップ行があります。 コードでは、次のようになります。

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

UIコンポーネントに加えて、データの変更を処理する3つのUIイベントハンドラーを実装する必要があります。 APIを呼び出すgetPullイベントハンドラーは、次のセクションで実装されます。

 // 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呼び出しでは、プルするハンドルを取得し、それをPOSTリクエストでPython APIに送信し、結果の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の結果を取得すると、感情ページは実際にはそれほど難しくありません。

全体像の計画

ツイートを表示するためのリストインターフェイスが必要になります。 また、いくつかのナビゲーションコンポーネントをトピックグループ化モードに切り替えて、入力ページに戻る必要があります。

まず、display.jsファイルでSentimentDisplayモードのサブコンポーネントを定義しましょう。

SentimentDisplayコンポーネント

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

また、この機会にナビゲーションコンポーネントをコーディングしてみましょう。 「編集に戻る」ボタンとトピックグループモードの2つのボタンが必要になります。

これらのボタンは、次のように、SentimentDisplayコンポーネントのすぐ上にある別のBootstrap行に実装できます。

 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コンポーネントをコーディングする準備が整いました。 前に説明したように、ブートストラップアコーディオンリストを活用します。 実装は実際にはかなり単純です。

 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はレスポンシブWebアプリであるため、WebアプリはiPhoneの幅に適応し、すべてが非常に見栄えがします。

Cordovaアプリが完成したら、Ionicの実装を見てみましょう。

Ionic-Reactの実装

イオンプライマー

Ionicは、ハイブリッドアプリケーションの構築を容易にするWebコンポーネントライブラリおよびCLIツールキットです。 当初、IonicはAngularJSとCordovaの上に構築されていましたが、その後React.jsでコンポーネントをリリースし、Cordovaと同様のプラットフォームであるCapacitorのサポートを開始しました。 Ionicを際立たせているのは、Webコンポーネントを使用している場合でも、コンポーネントがネイティブのモバイルインターフェイスに非常に似ていることです。 さらに、Ionicコンポーネントのルックアンドフィールは、それが実行されているオペレーティングシステムに自動的に適応します。これにより、アプリケーションのルックアンドフィールがよりネイティブで自然になります。 最後に、これは記事の範囲外ですが、Ionicには、アプリケーションのデプロイを少し簡単にするいくつかのビルドツールも用意されています。

このアプリケーションでは、IonicのReactコンポーネントを使用して、Cordovaセクションで作成したJavaScriptロジックの一部を活用しながらUIを作成します。

アプリの構成

まず、Ionicツールをインストールします。 それでは、以下を実行してみましょう。

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

インストールが完了したら、プロジェクトフォルダに移動しましょう。 次に、IonicCLIを使用して新しいプロジェクトフォルダーを作成します。

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

魔法が起こるのを見て、今度は次のようにフォルダに移動します。

 cd twitter-curation-Ionic

そして、次のコマンドで空のアプリを実行します。

 ionic serve

これでアプリがセットアップされ、準備が整いました。 いくつかのルートを定義しましょう。

先に進む前に、IonicがTypeScriptを使用してプロジェクトを開始したことに気付くでしょう。 TypeScriptを使用するのに邪魔になることはありませんが、いくつかの非常に優れた機能があり、この実装に使用します。

ルーターのセットアップ

この実装では、input、 sentimentDisplaytopicDisplayの3つのルートを使用します。 これは、Ionicが提供するトランジションおよびナビゲーション機能を利用したいため、またIonicコンポーネントを使用しており、アコーディオンリストにはIonicがあらかじめパッケージ化されていないためです。 もちろん、独自の実装も可能ですが、このチュートリアルでは、提供されているIonicコンポーネントを使用します。

App.tsxに移動すると、すでに定義されている基本ルートが表示されます。

入力ページ

全体像の計画

Bootstrapの実装と同様のロジックとコードを多数使用しますが、いくつかの重要な違いがあります。 まず、TypeScriptを使用します。これは、コードに型注釈を付けることを意味します。これについては、次のセクションで説明します。 次に、Bootstrapとスタイルが非常に似ているが、スタイルがOSに依存するIonicコンポーネントを使用します。 最後に、Bootstrapバージョンのように履歴APIを使用して動的にナビゲートしますが、Ionicルーターの実装により、履歴へのアクセスは少し異なります。

セットアップ

スタブコンポーネントを使用して入力コンポーネントを設定することから始めましょう。 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レベルで定義します。 App.tsxファイルのReactからuseStateをインポートし、アプリコンテナ関数を次のように変更します。

 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リターンデータの構造を認識します。 App.tsxにCuratedTweetsインターフェースをインポートします。 App.tsxが問題なくコンパイルされるのを確認できます。

ここでさらにいくつかのことを行う必要があります。 setCuratedTweets関数をInputコンポーネントに渡し、Inputコンポーネントにこの関数を認識させる必要があります。

App.tsxで、次のように入力ルートを変更します。

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

これで、エディターが別のフラグを下に表示するはずです。Inputは、渡された新しいpropを認識しないため、Input.tsxで定義する必要があります。

まず、CuratedTweetsインターフェースをインポートしてから、ContainerPropsインターフェースを次のように定義します。

 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コンポーネント

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を使用します。 また、モバイルでの実行中にIonicのナビゲーション機能を利用するために、トピック表示を別のルートとして実装するため、このページにもトピック表示ルートに移動できるようにする必要があります。

ルート設定

まず、sentimentsortedという名前の新しいフォルダーと、その下にSentimentSorted.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} />} />

SentimentSortedがcuratedTweetsの小道具を期待していないというTypeScriptエラーが表示されるので、次のセクションでそれを処理しましょう。

UIコンポーネント

コンテナの小道具を定義することから始めましょう。 入力コンポーネントとよく似ています。

 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は、historyAPIを使用して非常に簡単に実装できます。

 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インターフェースでクラス定義の1つをどのように使用しているかに注目してください。 これは、これらのコンポーネントが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> }

そして今、条件付き表示は表示コンポーネントで簡単に設定できます。

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

これで、UIコンポーネントを実装する準備が整いました。

UIコンポーネント

まず、 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> );

保存して実行すると、状況は良好に見えるはずです。

エミュレーターでアプリを実行する

エミュレーターでアプリを実行するには、Cordovaで設定した場合と同様に、いくつかのIonicコマンドを実行してモバイルプラットフォームを追加し、コードをコピーします。

 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

そして、アプリが表示されるはずです。

ReactNativeの実装

React Native Primer

React Nativeは、前のセクションのWebベースのアプローチとは非常に異なるアプローチを採用しています。 React Nativeは、Reactコードをネイティブコンポーネントとしてレンダリングします。 これにはいくつかの利点があります。 まず、基盤となるオペレーティングシステムとの統合がはるかに深くなっているため、開発者は、Cordova/Capacitorでは利用できない可能性のある新しいスマートフォン機能やOS固有の機能を利用できます。 次に、中央にWebベースのレンダリングエンジンがないため、React Nativeアプリは、通常、Cordovaを使用して作成されたアプリよりも高速です。 最後に、React Nativeではネイティブコンポーネントの統合が可能であるため、開発者はアプリケーションをよりきめ細かく制御できます。

このアプリケーションでは、前のセクションのロジックを使用し、NativeBaseと呼ばれるReactNativeコンポーネントライブラリを使用してUIをコーディングします。

アプリの構成

まず、ここの手順に従って、ReactNativeの必要なすべてのコンポーネントをインストールする必要があります。

React Nativeがインストールされたら、プロジェクトを開始しましょう。

 react-native init TwitterCurationRN

セットアップスクリプトを実行すると、最終的にフォルダが作成されます。 フォルダにCdしてreact-nativerun-iosを実行すると、サンプルアプリでエミュレータがポップアップ表示されます。

コンポーネントライブラリであるNativeBaseもインストールする必要があります。 そのために、次のコマンドを実行します。

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

また、ReactNativeスタックナビゲーターをインストールします。 実行してみましょう:

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

スタックナビゲーターを使用するようにアプリコンポーネントのコンテンツを変更します。

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

アプリにエントリコンポーネントをインポートすると、ビルドされます。

これで、入力ページをコーディングする準備が整いました。

入力ページ

全体像の計画

上記で実装したページと非常によく似ていますが、NativeBaseコンポーネントを使用するページを実装します。 フックやフェッチなど、使用したJavaScriptAPIとReactAPIのほとんどは、引き続き利用できます。

1つの違いは、ナビゲーションAPIの操作方法です。これについては後で説明します。

UIコンポーネント

使用する追加のNativeBaseコンポーネントは、Container、Content、Input、List、ListItem、およびButtonです。 これらはすべてIonicとBootstrapReactに類似したものがあり、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実装と同じであることに注意してください。 スタックナビゲーターは、そのコンポーネントに「navigation」と呼ばれる小道具を渡します。これは、 .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>

これを機能させるには、もちろん、components/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という名前のファイルを作成します。 次に、React NativeからStyleSheetコンポーネントをインポートし、スタイルを定義します。

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

これで、UIをコーディングする準備が整いました。

UIコンポーネント

ルートの操作方法を除いて、UIコンポーネントは非常によく知られています。 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>;

保存してツイートをプルしてみると、感情が表示されます。 次に、トピックのグループ化ページに移動します。

トピックのグループ化ページ

全体像の計画

トピックの表示も非常によく似ています。 ハンドラービルダーを使用して、特定のトピックアイテムの表示ページに移動するためのナビゲーション関数を構築します。また、このページに固有のスタイルシートを定義します。

私たちが行う新しいことの1つは、TouchableOpacityを実装することです。これは、ボタンのように機能するReactNative固有のコンポーネントです。

ルート設定

ルート定義は以前と同じです。

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

スタブコンポーネントcomponents/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;

UIコンポーネント

これの多くは非常によく知られているように見えます。 ライブラリ関数をインポートします。

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

そして今、コンポーネント。 onPressハンドラーを持つことができるTouchableOpacityを使用していることに注意してください。 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;

UIコンポーネント

UIコンポーネントは非常にシンプルです。 以前に見たことがありますが、実際にはカスタムロジックを実装していません。 だから、それのために行きましょう! 深呼吸する…

 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を使用しているので、既にエミュレーターでアプリを実行しているので、この部分はすでに処理されています。 これは、ReactNativeの開発環境の優れた点の1つです。

ふぅ! これで、この記事のコーディング部分は終わりです。 私たちがテクノロジーについて学んだことを見てみましょう。

テクノロジーの比較

コルドバ:長所と短所

Cordovaの最も優れている点は、熟練したWeb開発者が機能的で合理的に見栄えのするものをコーディングできるスピードです。 結局のところ、Webアプリをコーディングしているので、Web開発のスキルと経験は簡単に移行できます。 開発プロセスは迅速かつシンプルであり、CordovaAPIへのアクセスもシンプルで直感的です。

Cordovaを直接使用することの欠点は、主にWebコンポーネントへの過度の依存にあります。 ユーザーは、モバイルアプリを使用するときに特定のユーザーエクスペリエンスとインターフェイスデザインを期待するようになりました。アプリケーションがモバイルサイトのように感じられる場合、エクスペリエンスは少し不快になる可能性があります。 さらに、トランジショナルアニメーションやナビゲーションユーティリティなど、アプリに組み込まれている機能のほとんどは手動で実装する必要があります。

Ionic:長所と短所

Ionicの最も優れた点は、「無料」で入手したモバイル中心の機能の数です。 Webアプリケーションをコーディングするのと同じようにコーディングすることで、CordovaとReact-Bootstrapを使用するよりもはるかにモバイルフレンドリーに見えるアプリを構築することができました。 ナビゲーションアニメーション、ネイティブなスタイルのボタン、およびユーザーエクスペリエンスを非常にスムーズにする多くのユーザーインターフェイスオプションがありました。

Ionicを使用することの欠点は、その長所が部分的に原因でした。 まず、さまざまな環境でアプリがどのように動作するかを想像するのが難しい場合がありました。 アプリが一方向に見えたからといって、同じUI配置が別の環境でも同じように見えるとは限りません。 第二に、Ionicは多くの基盤となるテクノロジーの上に位置しており、一部のコンポーネントにアクセスするのは困難であることがわかりました。 最後に、これはIonic-Reactに固有ですが、Ionicは最初にAngular用に構築されたため、多くのIonic-React機能のドキュメントとサポートが少ないようです。 ただし、Ionicチームは、React開発者のニーズに非常に注意を払っているようで、新しい機能を迅速に提供します。

React Native:長所と短所

React Nativeは、モバイルでの開発が非常にスムーズなユーザーエクスペリエンスでした。 エミュレーターに直接接続することで、アプリケーションがどのように見えるかは不思議ではありませんでした。 Webベースのデバッガーインターフェイスは、Webアプリケーションの世界からのデバッグ手法を相互適用するのに非常に役立ち、エコシステムは非常に堅牢です。

React Nativeの欠点は、ネイティブインターフェイスに近いことです。 多くのDOMベースのライブラリは使用できませんでした。つまり、新しいライブラリとベストプラクティスを学ぶ必要がありました。 CSSの利点がなければ、アプリケーションのスタイリングはやや直感的ではありませんでした。 最後に、学習する多くの新しいコンポーネント(たとえば、divの代わりに表示、すべてをラップするテキストコンポーネント、ボタンとTouchableOpacityとTouchableTransparencyなど)を使用すると、誰かがメカニックの予備知識がほとんどないネイティブワールドに反応します。

各テクノロジーをいつ使用するか

Cordova、Ionic、およびReact Nativeはすべて非常に強力な長所と短所があるため、各テクノロジーには、最高の生産性とパフォーマンスを享受できるコンテキストがあります。

UIデザインと一般的なルックアンドフィールを取り巻く強力なブランドアイデンティティを備えたWebファーストの既存のアプリケーションを既にお持ちの場合、最適なオプションはCordovaです。これにより、スマートフォンのネイティブ機能にアクセスしながら、ほとんどのアプリケーションを再利用できます。 Webコンポーネントを作成し、その過程でブランドアイデンティティを維持します。 レスポンシブフレームワークを使用する比較的単純なアプリケーションの場合、必要な変更がほとんどないモバイルアプリを構築できる場合があります。 ただし、アプリケーションはアプリのようではなくWebページのように見え、モバイルアプリに期待されるコンポーネントの一部は個別にコード化されます。 したがって、アプリケーションをモバイルに移植するWebファーストのプロジェクトに参加している場合は、Cordovaをお勧めします。

アプリファーストの哲学で新しいアプリケーションのコーディングを開始しているが、チームのスキルセットが主にWeb開発にある場合は、Ionicをお勧めします。 Ionicのライブラリを使用すると、ネイティブコンポーネントに近い外観と感触のコードをすばやく記述しながら、Web開発者としてのスキルと本能を適用できます。 Web開発のベストプラクティスは、Ionicを使用した開発に容易に相互適用でき、CSSを使用したスタイリングはシームレスに機能することがわかりました。 さらに、モバイル版のサイトは、レスポンシブCSSフレームワークを使用してコーディングされたWebサイトよりもはるかにネイティブに見えます。 ただし、途中のいくつかのステップで、React-Ionic-Native APIの統合には手動調整が必要であり、時間がかかることがわかりました。 したがって、アプリケーションをゼロから開発していて、モバイル対応のWebアプリケーションとモバイルアプリケーションの間で大量のコードを共有したい場合は、Ionicをお勧めします。

ネイティブコードベースが実装された新しいアプリケーションをコーディングしている場合は、ReactNativeを試してみることをお勧めします。 ネイティブコードを使用していない場合でも、React Nativeに既に精通している場合や、ハイブリッドアプリケーションではなくモバイルアプリケーションが主な関心事である場合にも最適なオプションです。 フロントエンドの開発作業のほとんどをWeb開発に集中させた後、コンポーネントの編成とコーディング規則の違いにより、React Nativeを使い始めるには、IonicやCordovaよりも多くの学習曲線があることに最初は気付きました。 ただし、これらのニュアンスを学習すると、特にNativeBaseなどのコンポーネントライブラリを使用すると、コーディングエクスペリエンスが非常にスムーズになります。 開発環境の品質とアプリケーションの制御を考えると、プロジェクトのフロントエンドが主にモバイルアプリケーションである場合は、選択するツールとしてReactNativeをお勧めします。

今後のトピック

探索する時間がなかったトピックの1つは、カメラ、ジオロケーション、生体認証などのネイティブAPIへのアクセスのしやすさでした。 モバイル開発の大きな利点の1つは、通常はブラウザーではアクセスできない豊富なAPIエコシステムにアクセスできることです。

今後の記事では、さまざまなクロスプラットフォームテクノロジを使用してこれらのネイティブAPI対応アプリケーションを開発することの容易さを探求したいと思います。

結論

本日、3つの異なるクロスプラットフォームモバイル開発テクノロジーを使用してTwitterキュレーションアプリを実装しました。 これにより、各テクノロジーがどのようなものかをよく理解し、独自のReactベースのアプリケーションを開発するきっかけになったと思います。

読んでくれてありがとう!