读取25分钟

具有React的全堆栈NLP:离子VS Cordova VS React Native

肖恩是一个充满激情的Polyglot:一个全堆栈的向导,Sysadmin和数据科学家。他'S还开发了市场智能软件。

自苹果发布的大约15年以来,软件开发景观发生了巨大变化。由于智能手机获得广泛的采用并继续以独特的能力增长,用户越来越喜欢通过移动设备而不是台式机或笔记本电脑访问软件服务。智能手机提供诸如地理定位,生物认证和运动感应等功能,其中许多桌面平台现在只开始复制。在一些人口统计数据中,智能手机或类似的移动设备是软件消耗的主要手段,完全绕过计算机。

公司已经注意到这一转变并以主要的方式加强了它。移动应用程序不再是事后的想法。申请从罗宾时代,金融经纪公司,一个社交媒体公司到Uber,一家乘车公司,乘车公司,乘车公司,正在采用移动第一开发战略。如果有桌面应用程序,则通常作为移动应用程序的补充,而不是主要焦点。

对于全堆叠开发人员来说,适应这些转移趋势至关重要。幸运的是,有许多成熟和支持良好的技术可用于帮助Web开发人员将其技能应用于移动开发。今天,我们将探索三种这样的技术:Cordova,Ionic和Native Native。我们将使用React.js是前端Web开发最受欢迎的框架之一,作为我们的核心开发技术。虽然我们将专注于开发iPhone应用程序,但这些是跨平台技术,并将交叉编译到Android平台。

我们今天将建立什么

我们将构建一个应用程序,该应用程序使用自然语言处理(NLP)来处理和策划Twitter源。应用程序将允许用户选择一组Twitter句柄,使用Twitter API提取最新的更新,并根据情绪和主题对推文进行分类。然后,用户将能够基于情绪或主题查看推文。

后端

在我们构建前端之前,我们将想要构建后端。现在我们将保持后端简单 - 我们将使用基本,现成的情绪分析和词性标记,以及一些数据清理,以处理特定于数据集的问题。我们将使用一个名为TextBlob的开源NLP库,并为烧瓶提供结果。

情绪分析,语音份额和NLP:快速底漆

如果您之前没有使用自然语言分析应用程序,这些术语可能对您来说非常外国。 NLP是一种用于分析和处理自然人语言数据的技术的伞术语。虽然这是一个广泛的主题,但有许多挑战对于解决这一领域的所有技术都是共同的。例如,由于人类语言语法的允许性质,人类语言与编程语言或数值数据不同,往往是松散的结构。另外,人类语言往往是非常语境的,并且在一个上下文中发出或写入的短语可能不会转化为另一个背景。最后,除了结构和背景之外,语言非常复杂。在段落中进一步下降的单词可以在段落开始时改变句子的含义。词汇可以发明,重新定义或改变。所有这些复杂性使数据分析技术难以交叉。

情感分析是NLP的子场,专注于了解自然语言通道的情绪。虽然人类的情绪本质上是主观的,因此难以解决技术,情绪分析是具有巨大商业承诺的子场。情绪分析的一些应用包括分类产品审查,以识别各种功能的正面和负面评估,通过心情检测电子邮件的情绪或致辞和分组歌曲歌词。如果您正在寻找更深入的情感分析解释,您可以阅读我的文章建立基于情感分析的应用程序 这里.

词语份额标记或POS标记是一个非常不同的子场。 POS标记的目标是使用语法和上下文信息识别给定单词中给定单词的一部分。识别这种关系是一个比最初遇到眼睛更加艰巨的任务 - 一个词可以基于上下文和句子的结构具有非常不同的语音部分,并且规则甚至对人类并不总是清晰。幸运的是,今天许多现成的型号提供了与大多数主要编程语言集成的强大和多功能的模型。如果您想了解更多信息,您可以阅读POS标记的文章 这里.

烧瓶,教科博和旋转

对于我们的NLP后端,我们将使用烧瓶,文本丛和Tweepy。我们将使用Flask构建一个小型轻量级服务器,TextBlob来运行我们的自然语言处理,然后从Twitter API获得推文。在开始编码之前,您还需要从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

You can now run the function download_analyze_tweets on a list of handles that you want to follow, and you should see the results.

我运行了以下代码:

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

现在我们可以构建烧瓶服务器,这很简单。创建一个名为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请求。传递{帐户:[“@nasa”,“@spacex”]}作为JSON参数,您应该看到API返回类似于Twitter分析代码中返回的内容。

Now that we have a server, we are ready to code the front end. Because of a small nuance in networking over a phone emulator, I recommend that you deploy your API somewhere. Otherwise, you will want to detect whether your app is running on an emulator and send requests to <Your Computer IP>:5000 instead of localhost:5000 when it is in an emulator. If you deploy the code, you can simply issue the request to that URL.

部署服务器有许多选项。对于一个具有最小设置所需的免费的简单调试服务器,我推荐类似PythonAnywhere的内容,这应该能够在框中运行此服务器。

现在我们已经编写了后端服务器,让我们看看前端。我们将从Web开发人员的最方便的选项之一开始:Cordova。

Apache Cordova实现

科尔多瓦底漆

Apache Cordova是一种帮助Web开发人员目标移动平台的软件技术。通过利用在智能手机平台上实现的Web浏览器功能,Cordova将Web应用程序代码包装到本机应用程序容器中以创建应用程序。然而,Cordova不仅仅是一个花哨的网络浏览器。通过Cordova API,Web开发人员可以访问许多智能手机特定功能,例如离线支持,位置服务和设备上相机。

对于我们的应用程序,我们将使用React.js作为JS Framework和React-Bootstrap编写一个应用程序作为CSS框架。因为Bootstrap是一个响应的CSS框架,所以它已经支持在较小的屏幕上运行。写入应用程序后,我们将使用Cordova将其编译为Web应用程序。

配置应用程序

我们将首先做一些独特的东西来设置Cordova React应用程序。在一个 中等的 文章,开发人员Shubham Patil解释了我们正在做的事情。从本质上讲,我们正在使用React CLI建立React开发环境,然后使用Cordova CLI进行Cordova开发环境,然后在最后合并两者之前。

要启动,请在代码文件夹中运行以下两个命令:

cordova create TwitterCurationCordova

create-react-app twittercurationreact

Once the setup is done, we will want to move the contents of the React app’s public and src folder to the Cordova app. Then, in the package.json, copy over the scripts, browser list, and dependencies from the React project. Also add "homepage": "./" at the root of the package.json to enable compatibility with Cordova.

一旦包装,我们将想改变公共/ index.html文件以与Cordova一起使用。打开文件并在加载Cordova.js时从www / index.html复制元标记以及正文标记末尾的脚本。

接下来,更改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 Build启动之前将Build Folder放入相应的位置,从而自动化部署过程。

现在,我们可以尝试运行我们的应用程序。在命令行中运行以下内容:

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

With this route definition, we have introduced two routes that we will need to implement: Input and Display. Notice that the curatedTweets variable is being passed to Display, and the setCuratedTweets variable is being passed to Input. This means the input component will be able to call the function to set the curatedTweets variable, and Display will get the variable to display.

要开始编码组件,让我们创建一个名为/ src /组件的文件夹。在/ src /组件下,创建一个名为/ src / component /输入的另一个文件夹和下面的两个文件:input.js和input.css。为显示组件执行相同的操作 - Create / SRC / Component / 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

通过,我们的线框是完整的,应用程序应该运行。现在让我们编写输入页面。

输入页面

大白图计划

在我们编写代码之前,让我们考虑我们想要的输入页面。显然,我们将希望用户输入和编辑他们想要拉动的推特处理方式。我们还希望用户能够指示他们已经完成了。当用户表明完成后,我们将希望从Python策季API中拉出策致推文,最后导航到显示组件。

现在我们知道我们希望组件要做什么,我们已准备好代码。

设置文件

Let’s start by importing React Router library’s withRouter to get access to navigation functionality, the React Bootstrap Components we need, like so:

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

Now, let’s define the stub function for Input. We know that Input gets the setCuratedTweets function, and we also want to give it the ability to navigate to the display route after it sets the curated tweets from our Python API. Therefore, we will want to take from the props setCuratedTweets and history (for navigation).

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

To give it the history API access, we will wrap it with withRouter in the export statement at the end of the file:

export default withRouter(Input);

数据容器

Let’s set up the data containers using React Hooks. We already imported the useState hook so we can add the following code to the Input component’s body:

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

This creates the container and modifiers for handles, which will hold the list of handles that the user wishes to pull from, and the handleText, which will hold the content of the textbox that the user uses to input the handle.

现在,让我们来电ui组件。

UI组件

UI组件将相当简单。我们将拥有一个引导行,其中包含输入文本框以及两个按钮,一个用于将当前输入框内容添加到句柄列表中,并且可以从API中拉动。我们将拥有另一个引导行,该行显示用户希望使用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>
); 

In addition to the UI component, we will want to implement the three UI event handlers that handle data changes. The getPull event handler, which calls the API, will be implemented in the next section.

// 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呼叫

For the API call, we want to take the handles that we want to pull, send that over to the Python API in a POST request, and put the resulting JSON result into the curatedTweets variable. Then, if everything goes well, we want to programmatically navigate to the /display route. Otherwise, we will log the error to the console so we can debug more easily.

在代码模式下,它看起来像这样:

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文件中定义SendimentDisplay模式子组件。

SentimingDisplay组件

The SentimentDisplay will take the curatedTweets object and display the sentiment-sorted tweets in a list. With the help of React-Bootstrap, the component is quite simple:

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

We can now show the SentimentDisplay component. Change the Display function like so:

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

我们还借此机会来编写导航组件。我们需要两个按钮 - “返回编辑”按钮和主题组模式。

我们可以在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有点复杂,但是,一些非常方便的引导组件可以非常帮助我们。

大白图计划

我们将获得所有名词短语并将其显示为手风琴清单。然后,我们将在展开手风琴列表后渲染包含名词短语的推文。

实现转换到主题分组模式

首先,让我们实现从情绪模式切换到主题分组模式的逻辑。让我们首先创建存根组件:

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>

现在,您应该在切换时看到主题组显示存根。

主题显示组件

Now, we are ready to code the TopicDisplay component. As discussed before, it will leverage the Bootstrap Accordion List. The implementation is actually fairly simple:

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 platform add ios # if you haven’t done so already
cordova run ios

你应该在仿真器中看到应用程序。由于Bootstrap是一个响应式Web应用程序,Web应用程序适应iPhone的宽度,一切都看起来相当不错。

随着Cordova应用程序完成,我们现在看看离子实施。

离子反应实施

离子底漆

离子是一个Web组件库和CLI工具包,使构建混合应用更容易。原来,离子建在AngularJS和Cordova之上,但它们已经在React.JS中释放了它们的组件并开始支持电容器,一个类似于Cordova的平台。离子分开的是什么,即使您使用的Web组件,组件也会感觉与本机移动接口非常相似。此外,离子组件的外观和感觉自动适应它运行的操作系统,这再次有助于应用程序外观和感觉更自然。最后,虽然这是我们文章的范围之外,IONIC还提供了几种构建工具,使得部署应用程序有点更轻松。

对于我们的应用程序,我们将使用IONIC的React组件来构建UI,同时利用我们在Cordova部分中构建的一些JavaScript逻辑。

配置应用程序

首先,我们将想要安装离子工具。所以让我们运行以下内容:

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开始该项目。虽然我不走出我的方式使用打字签,但它有一些非常好的功能,我们将使用它来实现此实施。

路由器设置

For this implementation, we will use three routes - input, sentimentDisplay, and topicDisplay. We do this because we want to take advantage of the transition and navigation features provided by Ionic and because we’re using Ionic components, and accordion lists do not come prepackaged with Ionic. We can implement our own, of course, but for this tutorial, we will stay with the provided Ionic components.

如果您导航到App.tsx,则应查看已定义的基本路由。

输入页面

大白图计划

我们将使用许多类似的逻辑和代码作为引导实现,具有几个关键差异。首先,我们将使用类型签字,这意味着我们将为我们的代码提供型注释,您将在下一节中看到。其次,我们将使用离子组件,其风格非常相似,但在其造型中将是OS敏感的。最后,我们将在引导版本中使用历史证明API动态导航,但由于离子路由器实现,稍微不同地访问历史。

配置

首先通过使用存根组件设置输入组件来开始。在名为INPUT的页面下创建一个文件夹,并在该文件下创建一个名为INPUT.TSX的文件。在该文件中,请放置以下代码以创建React组件。请注意,因为我们正在使用类型签,它有点不同。

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

现在,当您刷新应用程序时,您应该看到输入的存根组件。

数据容器

Let’s create the data containers now. We want the containers for inputted Twitter handles as well as the current contents of the input box. Because we’re using TypeScript, we’ll need to add the type annotations to our useState invocation in the component function:

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

We will also want a data container to hold the return values from the API. Because the content of that needs to be shared with the other routes, we define them at the App.tsx level. Import useState from React in the App.tsx file and change the app container function to the below:

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代码,则应查看CutatedTweets亮起。这是因为文件不知道CutatedTweets接口的样子。让我们定义它。在SRC下创建一个名为interfaces的文件夹,并在它中创建一个名为cutatedtweets.tsx的文件。在文件中,定义CuredTweets界面,如下所示:

 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中导入CutatedTweets接口。您应该看到app.tsx编译,现在没有问题。

We need to do a couple more things here. We need to pass the setCuratedTweets function into the Input component and make the Input component aware of this function.

在app.tsx中,修改输入路由如下:

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

现在,您应该看到编辑器标志下别的东西 - 输入不知道关于新的道具传递给它,所以我们将想在Input.tsx中定义它。

首先,导入CutatedTweets界面,然后定义容器Props接口,如下所示:

interface ContainerProps {
    setCuratedTweets: React.Dispatch<React.SetStateAction<CuratedTweets>>
}

最后,更改输入组件定义,如下所示:

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

我们完成了定义数据容器,现在在构建UI组件上。

UI组件

对于UI组件,我们将想要构建输入组件和列表显示组件。离子为这些提供一些简单的容器。

让我们首先导入我们使用的库组件:

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.

在浏览器中打开应用程序时,它可能看起来不像引导应用程序。但是,如果将浏览器设置为仿真器模式,则UI将更有意义。在移动设备上部署移动时,它会看起来更好,所以期待它。

现在,让我们添加一些按钮。我们希望“添加到列表”按钮和“拉动API”按钮。为此,我们可以使用Ionbutton。将输入的IONCOL的大小更改为8,然后添加以下两个按钮:

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

自从我们编写按钮以来,我们也会写下事件处理程序。

将Twitter句柄添加到列表中的处理程序很简单:

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 = () => {}

现在,我们需要编写组件以显示用户输入的句柄列表。为此,我们将使用Ionlist,进入新的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);
}

保存此项,您应该看到已实现的所有UI组件的输入页。我们可以添加句柄,删除句柄,然后单击按钮以调用API。

作为最终的练习,让我们将符合线条样式移动到CSS。在名为input.css的输入文件夹中创建文件并在Input.TSX文件中导入它。然后,添加以下样式:

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

我们首先导入钩子:

import { useHistory } from 'react-router';

然后在输入组件中实现处理程序:

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

}

添加标题

我们的输入页面看起来相当不错,但由于离子的移动中心造型,它看起来有点赤裸裸。为了使UI看起来更自然,IONIC提供了一个标题功能,让我们提供更自然的用户体验。在移动设备上运行时,标题还将模拟本机OS的移动平台,这使得用户体验更加自然。

将组件导入更改为:

import { IonPage, IonHeader, IonToolbar, IonTitle, IonContent, IonInput, IonItem, IonList, IonButton, IonGrid, IonRow, IonCol } from '@Ionic/react';

现在用标题将UI包装在离子页面中,如下所示:

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>

现在看起来不错!

情绪排序页面

大白图计划

情绪排序页面将与来自引导页面的情绪排序页面大致类似,但使用类型签字和离子。我们还将将主题显示为单独的路由,以利用IONIC的导航功能在移动设备上运行时,因此我们需要将此页面提供导航到主题显示路由的功能。

路线设置

让我们首先创建一个名为Senmimensort的新文件夹,并在下面的名为SentimingOrted.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} />} />

You will get a TypeScript error saying that SentimentSorted is not expecting the curatedTweets props, so let’s take care of that now in the next section.

UI组件

让我们首先定义容器的道具。很像输入组件:

import CuratedTweets from '../../interfaces/CuratedTweets';

interface ContainerProps {
    curatedTweets: CuratedTweets
}

现在,更改存根显示:

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

一切都应该编制。

显示屏非常简单,它只是一个带有显示组件的电离员:

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>

The switchToInput is very easy to implement with the history API:

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

ToggleDisplayType should be familiar as well:

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

const switchStr = displayType === 'Sentiment'? 'View by Topic': 'View by Sentiment'

Now we have the SentimentDisplay component implemented. Now, before we implement the Topic Display Page, we need to implement the component that displays all the topics. We will do that in the next section.

主题组组件

让我们添加主题列表显示选项并有条件地显示它。为此,我们需要打破情绪显示列表。重命名要显示的SentimentDisplay,让我们突破情绪显示列表:

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

请注意,我们如何使用CutatedTweets接口中的类定义之一。那是因为这些组件不需要整个CutatedTweets对象,而是只需要一个子集。主题列表非常相似:

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的页面文件夹和一个主题Display.TSX,写一个存根组件,并将其导入app.tsx页面。现在,让我们设置路线:

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

现在我们已准备好实现UI组件。

UI组件

First, let’s create the ContainerProps definition:

interface ContainerProps {
    curatedTweets: CuratedTweets
}

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

Now, we will need to retrieve the topic from the URL path name. To do that, we will be using the history API. So let’s import useHistory, instantiate the history API, and pull the topic from the pathname. While we’re at it, let’s also implement the switch back functionality:

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 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从上一节的基于Web的方法采用非常不同的方法。 React Native渲染您的React Code作为本机组件。这有几个优点。首先,与底层操作系统的集成更深,这允许开发人员利用Cordova /电容可能无法使用的新智能手机功能和特定于OS特定功能。其次,由于中间没有基于Web的渲染引擎,因此React Native应用程序通常比使用Cordova写的更快。最后,由于React Native允许集成本机组件,因此开发人员可以对其应用进行更精细的控制。

对于我们的应用程序,我们将使用上一节中的逻辑,并使用名为nativeBase的React Native组件库来编码我们的UI。

配置应用程序

首先,您将想在说明之后安装所有必需的React本机的组件 这里.

安装原生物后,让我们开始项目:

react-native init TwitterCurationRN

让设置脚本运行,最终,应创建该文件夹。 CD进入文件夹并运行React-Native Run-IOS,您应该看到使用示例应用程序弹出仿真器。

我们将希望安装NativeBase,因为这是我们的组件库。要做到这一点,我们运行:

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

我们还想安装React Native Stack Navigator。让我们跑:

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

更改应用程序组件的内容以使用Stack Navigator:

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

此时,您将收到错误,因为尚未定义条目。让我们定义一个存根元素只是为了让它开心。

在项目中创建一个组件文件夹,创建一个名为条目的文件。将文件组件添加如下:

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组件。我们使用的大多数JavaScript和React API,例如钩子和获取,都仍然可用。

一个差异将是我们与导航API合作的方式,您将稍后看到。

UI组件

我们将使用的其他NativeBase组件是容器,内容,输入,列表,listItem和按钮。这些都有离子和自举反应中的类似物,而不是本地人的建造者对熟悉这些库的人来说已经非常直观。简单地导入如此:

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

Notice that this implementation is the same as in the NativeBase implementation, except we are navigating in a different way. The stack navigator passes into its components a prop called “navigation,” and that can be used to navigate between routes with .navigate. In addition to simply navigating, you can also pass data to the target component. We will use this mechanism to pass the data.

To make the app compile, we still need to make Entry aware of the navigation component. To do that, we will need to change the component function declaration:

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

现在保存,您应该看到页面。

情绪排序页面

大白图计划

我们将实现类似于上一节的情绪页面,但我们将稍微造成一些不同的页面,我们也将使用不同的导航库以不同地获取API调用返回值。

由于React Native没有CSS,因此我们将需要定义样式表对象或简单地代码行样式。因为我们将共享跨组件的一些样式,所以让我们创建一个全局样式表。路线设置后我们会这样做。

Also, because StackNavigator has an in-built Back navigation button, we won’t need to implement our own Back button.

路线设置

Route definition in StackNavigator is very simple. We simply create a new one named Stack Screen and give it the component, much like the React router.

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

要进行这项工作,我们当然需要在Components / SentIngdDisplay.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导入并定义样式:

import {StyleSheet} from 'react-native';

export default StyleSheet.create({
    tweet: {paddingTop: 5},
    accountName: {fontWeight: '600'},
})

我们已准备好代码UI。

UI组件

The UI component is pretty familiar, with the exception of how we work with the routes. We will want to use StackNavigator’s special props navigation and route to get the current application state and to navigate to the topic display should the user want to see that page.

更改组件定义以访问导航道具:

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

保存并尝试提出一些推文,您应该看到情绪显示。现在进入主题分组页面。

主题分组页面

大白图计划

主题显示再次非常相似。我们将使用Handler Builder构建导航功能以导航到特定主题项的显示页面,我们还将定义特定于此页面的样式表。

我们将要做的一件新事物正在实现一种触摸缺陷,这是一个响应本机制特定组件,其功能与按钮相同。

路线设置

路由定义与以前相同:

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

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}) => {

定义数据和操作处理程序。请注意,我们正在使用Handler Builder,返回函数的函数:

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

和 now, the components. Notice that we’re using a TouchableOpacity, which can have an onPress handler. We could have used TouchableTransparency as well, but TouchableOpacity’s click-and-hold animation was better suited for our application.

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,您已经在仿真器中运行了应用程序,因此这部分已被关心。这是关于对原生的发展环境做出反应的好东西之一。

哇!有了,我们完成了本文的编码部分。让我们看看我们对技术了解的了解。

比较技术

科尔多瓦:优点和缺点

关于Cordova的最佳速度是熟练的Web开发人员可以编码功能和合理呈现的纯粹速度。 Web开发技能和经验轻松转移,因为毕竟,您正在编写Web应用程序。开发过程快速且简单,并访问Cordova API简单而直观。

使用Cordova的缺点主要来自对Web组件的过度依赖性。用户在使用移动应用程序时预计特定的用户体验和接口设计,当应用程序感觉像移动网站时,体验可能有点震动。此外,必须手动实现内置于应用中的大多数内置的功能,例如过渡动画和导航实用程序。

离子:优点

离子的最佳部分是我获得的“免费”以摩托车为中心的特征。通过编码,就像我将编码一个Web应用程序,我能够构建一个应用程序,这些应用程序看起来比简单地使用Cordova和React-Bootstrap更多的移动友好。有导航动画,具有本机样式的按钮,以及许多用户界面选项,使用户体验非常平滑。

使用离子率的缺点是由其强度的部分引起的。首先,有时难以想象该应用程序如何在各种环境中表现。仅仅因为应用程序看起来有一种方式并不意味着相同的UI放置在另一个环境中看起来都是一样的。其次,离子坐在许多底层技术的顶部,并获得一些组件的访问难以。最后,这是特定于离子反应的,但由于首先为角度构建离子,许多离子反应特征似乎具有更少的文档和支持。然而,离子团队似乎非常关注反应开发人员的需求,并迅速提供新功能。

反应本地:优点和缺点

React Native在移动设备上具有非常顺利的用户体验。通过直接连接到仿真器,它没有谜,应用程序如何看待。基于Web的调试器界面非常有助于从Web应用程序世界交叉应用调试技术,生态系统非常强大。

React Nation的缺点来自其对本机接口的邻近。无法使用许多基于DOM的库,这意味着必须学习新的库和最佳实践。没有CSS的好处,造型申请表现不佳。最后,使用许多新组件来学习(例如,查看而不是div,文本组件包装所有内容,按钮与触摸疏透与触摸touchableTranspacity等),如果有人进入了一开始就有一些学习曲线与机械师的少数知识进行了反应本地世界。

何时使用每种技术

因为科尔科犬,离子和反应本地都具有非常强大的利弊,所以每种技术都有一个背景,它将享有最佳的生产力和性能。

如果您已经拥有一个现有的应用程序,它首先具有强大的品牌标识,周围围绕UI设计和一般的外观,您的最佳选择将是Cordova,这使您可以访问智能手机的本机功能,同时让您重复使用大部分Web组件并在过程中保留您的品牌标识。对于使用响应框架的相对简单的应用程序,您可能能够构建一个非常少量的更改所需的移动应用程序。但是,您的应用程序看起来不像应用程序,更像是一个网页,并且来自移动应用程序的一些组件将分别编码。 因此,我推荐Cordova,因为您在Web-First Project将应用程序移植到移动设备中。

如果您已开始使用App-First哲学编写新的应用程序,但您的团队技能集主要在Web开发中,我推荐离子。离子的图书馆让您快速编写代码,即靠原生组件,同时让您将技能和本能作为Web开发人员应用。我发现Web开发最佳实践随时易于使用离子开发,并使用CSS无缝地造型。此外,网站的移动版本比使用响应CSS框架编码的网站更高。然而,在沿途的某些步骤中,我发现反应离子天然API集成需要一些手动调整,这可能证明耗时。 因此,我建议在从地上开发应用程序的情况下,您希望在能够在移动的Web应用程序和移动应用程序之间共享大量代码。

如果您正在使用某些本机代码库编码新应用程序,则可能需要尝试ortent native。即使您不使用本机代码,也可能是您已经熟悉react本机的案例中的最佳选择,或者当您的主要关切是移动应用程序而不是混合应用程序时。专注于我的大部分前端开发努力在Web开发上,我最初发现,由于组成组织和编码约定的差异,React Native的入门比离子或Cordova更多的学习曲线。然而,一旦了解这些细微差别,编码体验都非常顺利,特别是在诸如NativeBase等组件库的帮助下。鉴于开发环境的质量和对应用程序的控制, 如果您的项目的前端主要是移动应用程序,我建议原始作为您选择的工具。

未来的主题

我没有时间探索的话题之一是易于访问本机API,例如相机,地理位置或生物识别身份验证。移动开发的一个很大效益之一是浏览器通常无法访问的丰富API生态系统的可访问性。

在未来的文章中,我想探索使用各种跨平台技术开发这些本机APIable的应用程序。

结论

今天,我们使用三种不同的跨平台移动开发技术实施了一个Twitter策策应用程序。我希望这给了你对每个技术的良好感觉,并激发了你开发自己的基于反应的应用程序。

谢谢你的阅读!

理解基础知识

自然语言处理(NLP)有多困难?

NLP.是一种机器学习伞的技术,包括理解和响应或报告意义,上下文,情绪和其他属性。这些技术中的许多是复杂的并且需要显着的处理能力。幸运的是,有开源库做了很多繁重的举重,使得许多工程师和应用程序可以获得NLP。

自然语言处理如何工作?

有许多不同的技术,具体取决于目标。通常,这些技术解析句子,然后查找周围单词或句子结构中的上下文提示,以导出更深入的含义。

在2020年学习是否值得它?

本文毫无疑问地展示了反应的价值及其支持框架。每个框架 - 科尔多瓦,离子和反应本地 - 具有复杂的移动应用的价值。学习反应是一种自然起点。