作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.
王尚伦的头像

Shanglun Wang

Sean是一个充满激情的通晓多种语言的人:一个全栈向导、系统管理员和数据科学家. He’s also developed market intelligence software.

Previously At

CB Insights
Share

从苹果发布第一款iPhone到现在大约15年了, 软件开发环境发生了巨大的变化. 随着智能手机的广泛采用和独特功能的持续增长, 用户越来越喜欢通过移动设备而不是台式机或笔记本电脑访问软件服务. Smartphones offer features such as geolocation, biometric authentication, and motion sensing, 许多桌面平台现在才开始效仿. In some demographics, 智能手机或类似的移动设备是软件消费的主要手段, 完全绕过计算机.

公司已经注意到这种转变,并在主要方面加强了这种转变. Mobile apps are no longer an afterthought. 应用范围从罗宾汉, 金融经纪公司, to Instagram, a social media company, to Uber, a ride-hailing company, are adopting a mobile-first development strategy. 如果有桌面应用程序, it is often offered as a complement to the mobile app, 而不是主要的焦点.

对于全栈开发人员来说,适应这些变化趋势是至关重要的. Fortunately, 有许多成熟且支持良好的技术可以帮助web开发人员将他们的技能应用到移动开发中. 今天,我们将探讨三种这样的技术:Cordova、Ionic和React Native. We will be using React.js, 前端web开发最流行的框架之一, 作为我们的核心开发技术. 而我们将专注于开发iPhone应用程序, 这些都是跨平台技术,可以在Android平台上进行交叉编译.

What We Will Build Today

我们将构建一个使用自然语言处理(NLP)来处理和管理Twitter提要的应用程序. 该应用程序将允许用户选择一组Twitter句柄, pull the most recent updates using a Twitter API, and categorize the tweets based on sentiment and topic. 然后,用户将能够根据情绪或主题查看推文.

Back End

在构建前端之前,我们需要构建后端. We will keep the back end simple for now - we’ll use basic, 现成的情感分析和词性标注, 还有一些数据清理来处理特定于数据集的问题. 我们将使用一个名为TextBlob的开源NLP库,并通过Flask提供结果.

情感分析、词性标注和自然语言处理:快速入门

如果您以前没有使用过自然语言分析应用程序, these terms may be very foreign to you. NLP是分析和处理自然人类语言数据的技术的总称. While this is a broad topic, 对于处理该领域的所有技术来说,有许多共同的挑战. For example, human language, unlike programming language or numerical data, 由于人类语言语法的宽容性质,往往结构松散. Additionally, human language tends to be extremely contextual, 在一种语境中说出或写下的短语可能无法翻译成另一种语境. 最后,抛开结构和语境不谈,语言是极其复杂的. 段落后面的单词可能会改变段落开头句子的意思. Vocabulary can be invented, redefined, or changed. 所有这些复杂性使得数据分析技术难以交叉应用.

情感分析是NLP的一个子领域,专注于理解自然语言段落的情感. While human emotion is inherently subjective, and therefore difficult to pin down technologically, 情感分析是一个具有巨大商业前景的分支领域. 情感分析的一些应用包括对产品评论进行分类,以识别各种特征的积极和消极评价, 检测电子邮件或演讲的情绪,并按情绪分组歌词. 如果你正在寻找情感分析的更深入的解释, 你可以阅读我关于构建基于情感分析的应用程序的文章 here.

词性标注或词性标注是一个非常不同的子领域. 词性标注的目标是利用语法和上下文信息识别句子中给定单词的词性. 识别这种关系比最初看到的要困难得多——一个单词可以根据上下文和句子结构有非常不同的词性, and the rules aren’t always clear even to humans. Fortunately, 如今,许多现成的模型提供了与大多数主要编程语言集成的强大且通用的模型. 如果您想了解更多信息,可以阅读我关于POS标记的文章 here.

Flask, TextBlob, and Tweepy

对于NLP后端,我们将使用Flask、TextBlob和Tweepy. 我们将使用Flask构建一个小的, lightweight server, TextBlob to run our natural language processing, and Tweepy to get tweets from the Twitter API. Before you start coding, 您还需要从Twitter获得开发人员密钥,以便检索tweet.

We can write a much more sophisticated back end and 使用更复杂的NLP技术,但对于我们今天的目的,我们将使后端尽可能简单.

Back-end Code

现在,我们准备开始编码. 启动您最喜欢的Python编辑器和终端,让我们开始吧!

First, we will want to install the requisite packages.

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

Now, let’s write the code for our functionality.

Open up a new Python script, call it server.py, and import the requisite libraries:

import tweepy

从textblob导入textblob
from collections import defaultdict

Let’s now write some helper functions:

# simple,用一个保护子句对一列数字求平均值,以避免被零除
def mean(lst):
    return sum(lst)/len(lst) if len(lst) > 0 else 0

#调用textblob情感分析API和名词短语API,并将其作为字典返回
def get_sentiment_and_np(句子):
    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)
    }

#使用tweepy API从用户的时间轴中获取最近的50篇文章
#如果文本被截断,我们将希望获得全文, 我们还会删除转发,因为它们不是那个特定账户的推文.
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(处理,count = 50)
    tweets = []
    for tl_item in tl:
        如果'retweeted_status'在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和HTTPS有时被认为是名词短语,所以我们把它过滤掉.
我们也试着跳过非常简短的名词短语,以避免某些误报
#如果这是一个商业应用,我们会想要一个更复杂的过滤策略.
def good_noun_phrase (noun_phrase):
    Noun_phrase_list =名词短语.split(' ')
    for np in noun_phrase_list:
        if np in {'http', 'http'} or len(np) < 3:
            return False
    return True

Now that we have the helper functions written, 我们可以用几个简单的函数把所有的东西放在一起:

#将标记的推文重塑为前端应用程序可以轻松使用的字典
def group_tweets(processed_tweets):
    # Sort it by sentiment
    Sentiment_sorted = sorted(processed_tweets, key=lambda x: x['data']['sentiment'])

    #通过名词短语收集推文. 显然,一条tweet可以出现在多个名词短语列表中.
    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:
        对于get_tweets(account)中的tweet:
            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 在您想要遵循的句柄列表中,您应该看到结果.

I ran the following code:

if __name__ == '__main__':
    账户= ['@spacex', '@nasa']
    Processed_tweets, sentiment_sorted, grouped_by_np = download_analyze_tweets(帐户)
    print(processed_tweets)
    print(sentiment_sorted)
    print(grouped_by_np)

Executing this produced the following results. 结果显然与时间有关,所以如果你看到类似的东西,你就在正确的轨道上.

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

现在我们可以构建Flask服务器,这非常简单. 创建一个名为server的空文件.并编写以下代码:

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(帐户)
    return jsonify({
        'processedTweets': processed_tweets,
        'sentimentSorted': sentiment_sorted,
        'groupedByNp': grouped_by_np
    })

if __name__ == '__main__':
    app.run(debug=True)

Run the server, 现在,您应该能够使用您选择的HTTP客户端向服务器发送post请求. Pass in {accounts: [“@NASA”, " @SpaceX "]}作为json参数, 您应该会看到API返回类似于Twitter分析代码返回的内容.

现在我们有了服务器,我们准备编写前端代码. 因为在电话模拟器上联网的一个细微差别, I recommend that you deploy your API somewhere. 否则,您将需要检测您的应用程序是否在模拟器上运行,并将请求发送到 :5000 instead of localhost:5000 when it is in an emulator. 如果部署了代码,则可以简单地向该URL发出请求.

There are many options to deploy the server. For a free, simple debug server with minimal setup required, I recommend something like PythonAnywhere, which should be able to run this server out of the box.

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

Apache Cordova实现

Cordova Primer

Apache Cordova是一种帮助web开发人员瞄准移动平台的软件技术. 通过利用在智能手机平台上实现的web浏览器功能, Cordova将web应用程序代码封装到本地应用程序容器中,以创建应用程序. Cordova isn’t simply a fancy web browser, however. Through the Cordova API, Web开发人员可以访问许多智能手机特有的功能,比如离线支持, location services, and on-device camera.

对于我们的应用程序,我们将使用React编写一个应用程序.js框架和React-Bootstrap作为CSS框架. Because Bootstrap is a responsive CSS framework, it already has support for running on smaller screens. 一旦应用程序编写完成,我们将使用Cordova将其编译为web应用程序.

Configuring the App

我们将首先做一些独特的事情来设置Cordova React应用程序. In a Medium article, developer Shubham Patil explains what we’re doing. Essentially, 我们正在使用React CLI设置React开发环境, 然后是使用Cordova CLI的Cordova开发环境, 在最终合并两者之前.

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

cordova create TwitterCurationCordova

create-react-app twittercurationreact

Once the setup is done, 我们要把React应用的public和src文件夹的内容移到Cordova应用中. Then, in the package.从React项目中复制脚本、浏览器列表和依赖项. Also add "homepage": "./" at the root of the package.json to enable compatibility with Cordova.

Once the package.json is merged, we will want to change the public/index.html文件与Cordova一起工作. Open up the file and copy the meta tags from www/index.在Cordova . html以及脚本正文标签的末尾.js is loaded.

Next, change the src/index.js file to detect if it’s running on Cordova. 如果它在Cordova上运行,我们将希望在deviceready事件处理程序中运行呈现代码. 如果它在普通浏览器中运行,只需立即渲染即可.

const renderReactDom = () => {
  ReactDOM.render(
    
      
    ,
    document.getElementById('root')
  );
}

if (window.cordova) {
  document.addEventListener('deviceready', () => {
    renderReactDom();
  }, false);
} else {
  renderReactDom();
}

Finally, we need to set up our deployment pipeline. Add the below definition into the config.xml file:

And put the following script into prebuild.js:

const path = require('path');
const { exec } = require('child_process');
const fs = require('fs');
Const rimraf = require('rimraf');

函数renameOutputFolder(buildFolderPath, outputFolderPath) {
    return new Promise((resolve, reject) => {
        fs.rename(buildFolderPath, outputFolderPath, (err) => {
            if (err) {
                reject(err);
            } else {
                resolve('Successfully built!');
            }
        });
    });
}

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构建开始之前将构建文件夹放入适当的位置, thus automating the deployment process.

现在,我们可以试着运行我们的应用程序. Run the following in the command line:

npm install rimraf
npm install
npm run start

你应该会看到React应用程序已经设置好并在浏览器中运行. Add Cordova now:

cordova platform add iOS

cordova run iOS

And you should see the React app running in the emulator.

Router and Packages Setup

来建立一些基础设施,我们需要构建应用程序, let’s start by installing the requisite packages:

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

现在我们将设置路由, and while doing so, 我们还将设置一个简单的全局状态对象,它将被所有组件共享. In a production application, 我们希望使用像Redux或MobX这样的状态管理系统, 但我们现在将保持简单. Go to App.Js并配置路由如下:

import {
  BrowserRouter as Router,
  Redirect,
  Route,
} from "react-router-dom";

function App() {
  const [curatedTweets, setCuratedTweets] = useState();
  return 
       } />
       } />
       } />
  
}

With this route definition, 我们已经介绍了需要实现的两条路由:Input和Display. Notice that the curatedTweets variable is being passed to Display, and the setCuratedTweets 变量被传递给Input. 这意味着输入组件将能够调用函数来设置 curatedTweets variable, and Display will get the variable to display.

为了开始编写组件,让我们在/src下创建一个名为/src/components的文件夹. Under /src/components, 创建另一个名为/src/components/input的文件夹,并在下面创建两个文件:input.js and input.css. 做同样的显示组件-创建/src/组件/显示和下面:显示.js and display.css.

Under those, let’s create stub components, like so:

import React from ‘react’;
import ‘input.css’

const Input = () => 
Input
; export default Input

And the same for Display:

import React from ‘react’;
import display.css’

const Display = () => 
Display
; export default Display

这样,我们的线框图就完成了,应用程序应该运行了. 现在让我们编写Input页面.

Input Page

Big-picture Plan

在编写代码之前,让我们考虑一下Input页面要做什么. Obviously, 我们希望用户能够输入和编辑他们想要提取的Twitter句柄. 我们还希望用户能够表明他们已经完成了. When the users indicate they are done, 我们想要从我们的Python策展API中拉出策展的推文,最后导航到Display组件.

既然知道了组件要做什么,就可以开始编写代码了.

Setting Up the File

Let’s start by importing React Router library’s 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';

Now, let’s define the stub function for Input. We know that Input gets the setCuratedTweets function, 我们还想让它能够在从我们的Python API设置tweet后导航到显示路由. Therefore, we will want to take from the props setCuratedTweets and history (for navigation).

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

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

导出默认withouter(输入);

Data Containers

Let’s set up the data containers using React Hooks. We already imported the useState 这样我们就可以把下面的代码添加到Input组件的主体中:

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

This creates the container and modifiers for handles, 哪个将保存用户希望从中拉出的句柄列表, and the handleText,它将保存用户用于输入句柄的文本框的内容.

Now, let’s code up the UI components.

UI Components

The UI components will be fairly simple. 我们将有一个Bootstrap行,其中包含输入文本框以及两个按钮, 一个用于将当前输入框内容添加到句柄列表, and one to pull from the API. 我们将有另一个Bootstrap行,显示用户希望使用Bootstrap列表组拉出的句柄列表. In code, it looks like so:

return (
    
        
            
                
            
        
        
            
                
                {' '}
                
            
        
        
            
                
                    {handles.map((x, i) => 
                        {x}
                        
                        
                        
                    )}
                
            
        
    
); 

除了UI组件之外, 我们需要实现三个处理数据更改的UI事件处理程序. The getPull 事件处理程序,它调用API,将在下一节中实现.

// set the handleText to current event value
const textChangeHandler = (e) => {
    e.preventDefault();
    setHandleText(e.target.value);
}

//添加handleText到句柄,然后清空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);
}

Now, we’re ready to implement the API call.

API Call

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. 然后,如果一切顺利,我们希望以编程方式导航到/display路由. 否则,我们将把错误记录到控制台,以便更容易地进行调试.

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

const pullAPI = (e) => {
    e.preventDefault();
    fetch('http://prismatic.pythonanywhere.com/get_tweets', {
        method: 'POST',
            mode: 'cors',
            headers: {
            'Accept': 'application/json',
            “内容类型”:“application / json”,
            },
            body: JSON.stringify({
            accounts: handles
            })
        }).then(r=>r.json()).then(j => {
        setCuratedTweets(j);
            history.push('/display');
        })
    .catch(e => {
        console.log(e);
        })
}

And with that, we should be good to go. 您可以随意运行应用程序,添加几个句柄,然后向API发送请求.

Now, we’re ready to code the sentiment page.

Sentiment Sorted Mode

因为Python API已经根据情绪对tweet进行了排序, once we have the result from the Python API, the sentiment page is actually not too difficult.

Big-picture Plan

We will want a list interface to show the tweets. 我们还需要一些导航组件来切换到主题分组模式并返回到Input页面.

首先,让我们在显示中定义SentimentDisplay模式子组件.js file.

SentimentDisplay Component

SentimentDisplay将会 curatedTweets object and display the sentiment-sorted tweets in a list. 在React-Bootstrap的帮助下,这个组件非常简单:

const SentimentDisplay = ({curatedTweets}) => {
    return 
      {curatedTweets.sentimentSorted.map((x, i) =>
            
                
{x.account}:
{x.tweet} ({x.data.sentiment.toPrecision(2)})
)}
}

While we’re at it, let’s also add some styling. 将以下内容显示出来.css and import it:

 .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组件上方单独的Bootstrap行中实现这些按钮, like so:

Return 
    
        
            
            {' '}
           
        
    
    
        
            
        
    

Run the app and pull the tweets from a couple of handles. Looks pretty nifty!

Topic Grouping Mode

Now, we want to implement the Topic Grouping Mode. It’s a bit more complex than the SentimentDisplay, but again, some very handy Bootstrap components help us out immensely.

Big-picture Plan

我们将获得所有的名词短语,并将它们显示为一个手风琴列表. 展开手风琴列表后,我们将呈现包含名词短语的tweet.

Implementing Switching to Topic Grouping Mode

首先,让我们实现从情感模式切换到主题分组模式的逻辑. Let’s start by creating the stub component first:

const TopicDisplay = () => {
    return 
Topic Display
}

And set some logic to create a mode to display it. In the main Display Component, 添加以下行来创建显示组件的逻辑.

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

And change the JSX to the following to add the logic:

Return 
    
        
            
            {' '}
            
        
    
    
        
                {
                    displayType === 'Sentiment'?
                    :
                    
                }
        
    

现在,您应该在切换时看到Topic Group Display存根.

The TopicDisplay Component

Now, we are ready to code the TopicDisplay component. 如前所述,它将利用Bootstrap手风琴列表. The implementation is actually fairly simple:

const TopicDisplay = ({curatedTweets}) => {
    return 
        {Object.keys(curatedTweets.groupedByNp).map((x, i) =>
            
                
                    
                        {x} ({curatedTweets.groupedByNp[x].length})
                    
                
                
                    
                        
                            {curatedTweets.groupedByNp[x].map((y, i2) =>
                                
                                    
{y.account}:
{y.tweet} ({y.data.sentiment.toPrecision(2)})
)}
)}
}

Run the app, and you should see the Topic Display.

现在应用程序完成了,我们准备为模拟器构建应用程序.

在模拟器中运行应用程序

Cordova makes it very easy to run the app in the emulator. Simply run:

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

And you should see the app in the emulator. Because Bootstrap is a responsive web app, the web app adapts to the width of an iPhone, 一切看起来都很好.

Cordova应用完成后,现在让我们看看Ionic的实现.

Ionic-React Implementation

Ionic Primer

Ionic是一个web组件库和CLI工具包,可以更轻松地构建混合应用程序. Originally, Ionic was built on top of AngularJS and Cordova, but they have since released their components in React.并开始支持类似Cordova的平台Capacitor. 让Ionic与众不同的是,即使你使用的是web组件, 这些组件感觉非常类似于本机移动界面. Additionally, Ionic组件的外观和感觉会自动适应它运行的操作系统, 这再次帮助应用程序的外观和感觉更原生和自然. Finally, while this is outside of the scope of our article, Ionic还提供了几个构建工具,使部署应用程序更容易一些.

For our application, 我们将使用Ionic的React组件来构建UI,同时利用我们在Cordova部分构建的一些JavaScript逻辑.

Configuring the App

First, we will want to install the Ionic tools. So let’s run the following:

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

安装完成后,让我们进入项目文件夹. 现在,我们使用Ionic CLI创建我们的新项目文件夹:

离子开始微博-策展-离子空白-类型=反应-电容器

Watch the magic happen, and now go into the folder with:

cd twitter-curation-Ionic

And run the blank app with:

ionic serve

Our app is thus set up and ready to go. Let’s define some routes.

在我们继续之前,你会注意到Ionic是使用TypeScript启动项目的. While I don’t go out of my way to use TypeScript, 它有一些非常好的功能, and we will be using it for this implementation.

Router Setup

For this implementation, we will use three routes - input, sentimentDisplay, and topicDisplay. 我们这样做是因为我们想利用Ionic提供的过渡和导航功能,因为我们正在使用Ionic组件, 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.

If you navigate to the App.tsx, you should see the basic routes already defined.

Input Page

Big-picture Plan

我们将使用许多与Bootstrap实现类似的逻辑和代码, with a few key differences. First, we will use TypeScript, which means we will have type annotations for our code, which you will see in the next section. Second, 我们将使用离子成分, 它们在风格上与Bootstrap非常相似,但在样式上对操作系统敏感. Lastly, 我们将像在Bootstrap版本中那样使用历史API动态导航,但由于Ionic Router的实现,访问历史的方式略有不同.

Setting Up

让我们从使用存根组件设置输入组件开始. 在名为input的页面下创建一个文件夹,并在该文件夹下创建一个名为input的文件.tsx. 在该文件中,放入以下代码来创建一个React组件. 注意,因为我们使用的是TypeScript,所以有一点不同.

import React, {useState} from 'react';

const Input : React.FC = () => {
    return 
Input
; } export default Input;

并在App中更改组件.tsx to:

const App: React.FC = () => (
  
    
      
        
         } />
      
    
  
);

现在,当你刷新应用程序时,你应该会看到Input存根组件.

Data Containers

Let’s create the data containers now. 我们需要输入Twitter句柄的容器以及输入框的当前内容. 因为我们使用的是TypeScript,所以我们需要把类型注解添加到 useState invocation in the component function:

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

我们还需要一个数据容器来保存来自API的返回值. 因为它的内容需要与其他路由共享,所以我们在应用中定义它们.tsx level. Import useState from React in the App.TSX文件,并将app容器函数修改如下:

const App: React.FC = () => {
  const [curatedTweets, setCuratedTweets] = useState({} as CuratedTweets);
  return (
  
    
      
        
         } />
      
    
  
  );
}

At this point, 如果你使用的是像Visual Studio Code这样带有语法高亮的编辑器, you should see the CuratedTweets light up. 这是因为文件不知道CuratedTweets界面是什么样子的. Let’s define that now. 在src下创建一个名为interfaces的文件夹,并在其中创建一个名为CuratedTweets的文件.tsx. 在文件中,定义CuratedTweets接口如下:

 interface TweetRecordData {
    noun_phrases: Array,
    sentiment: number
}

导出接口TweetRecord {
    account: string,
    data: TweetRecordData,
    tweet: string
}

export default interface CuratedTweets {
    groupedByNp: Record>,
    processedTweets: Array,
    sentimentSorted: Array
}

现在应用程序知道了API返回数据的结构. Import the CuratedTweets interface in App.tsx. You should see the App.TSX编译现在没有问题.

We need to do a couple more things here. We need to pass the setCuratedTweets 函数放到Input组件中,并让Input组件知道这个函数.

In the App.tsx, modify the Input route like so:

 } exact={true} />

Now, 你应该看到编辑器标记了一些其他的东西——Input不知道传递给它的新道具,所以我们要在Input中定义它.tsx.

首先,导入CuratedTweets接口,然后像这样定义ContainerProps接口:

interface ContainerProps {
    setCuratedTweets: React.Dispatch>
}

And finally, change the Input component definition like so:

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

我们已经完成了数据容器的定义,现在开始构建UI组件.

UI Components

对于UI组件,我们需要构建一个输入组件和一个列表显示组件. Ionic provides some simple containers for these.

让我们从导入将要使用的库组件开始:

从“@Ionic/react”导入{IonInput, IonItem, IonList, IonButton, ionrid, IonRow, IonCol};

Now, we can replace the stub component with the IonInput, wrapped in an IonGrid:

return 
    
        
             setText(e.detail.value!)} />
      
    

注意,事件侦听器是 onIonChange instead of onChange. Otherwise, it should look very familiar.

当你在浏览器中打开应用程序时,它可能看起来不像Bootstrap应用程序. 但是,如果将浏览器设置为模拟器模式,则UI将更有意义. 一旦你将它部署到移动设备上,它会看起来更好,所以期待它吧.

Now, let’s add some buttons. 我们将需要一个“添加到列表”按钮和一个“拉API”按钮. 为此,我们可以使用IonButton. 将输入的iconcol的大小改为8,并添加以下两个带有列的按钮:


             setText(e.detail.value!)} />
        
        
            Add
        
        
           Pull
        

既然我们正在编写按钮,那么我们也来编写事件处理程序.

The handler to add a Twitter handle to the list is simple:

const onAddClicked = () => {
        如果(text === undefined ||文本.length === 0) {
            return;
        }
        const newAccounts: Array = [...accounts, text];
        setAccounts(newAccounts);
        setText('');
    }

我们将在下一节中实现API调用,所以让我们为 onPullClicked:

const onPullClicked = () => {}

Now, 我们需要编写用于显示用户输入的句柄列表的组件. For that, we will use IonList, put into a new IonRow:


    
        
            {accounts.map((x:string, i:number) => 
                
                    
                        {x}
                        Delete
                    
                
            )}
        
    

每个列表项都在自己的IonGrid中显示句柄和删除按钮. For this code to compile, we will want to implement the deleteClickedHandler as well. 它应该与上一节非常熟悉,但使用的是TypeScript注释.

const deleteClickedBuilder = (idx: number) => () => {
    const newAccounts: Array = [...accounts];
    newAccounts.splice(idx, 1);
    setAccounts(newAccounts);
}

保存后,您将看到Input页面,其中实现了所有UI组件. 我们可以添加句柄、删除句柄,并单击按钮来调用API.

As a final exercise, let’s move the in-line styles to CSS. Create a file in the input folder called input.css并导入到Input.tsx file. 然后,添加以下样式:

.input-button {
    float: right;
}

.handle-display {
    padding-top: 12px;
}

Now, add className="input-button” on all of the IonButtons and className=”handle-display” 在显示预期Twitter句柄的句柄列表项IonCol上. 保存文件,您应该会看到一切看起来都很好.

API Call

从上一节中我们非常熟悉提取API的代码, 只有一个例外——我们必须访问历史组件才能动态更改路由. 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',
            “内容类型”:“application / json”,
        },
        body: JSON.stringify({
            accounts
        })
    }).then(r=>r.json()).then(j => {
        setCuratedTweets(j);
        switchToDisplay();
    })
    .catch(e => {
        console.log(e);
    })

}

Adding a Header

我们的输入页面看起来相当不错,但是由于Ionic以移动设备为中心的样式,它看起来有点简陋. 使UI看起来更自然, Ionic提供了一个标题功能,让我们提供更自然的用户体验. When running on mobile, 头部也将模拟本机操作系统的移动平台, which makes the user experience even more natural.

将组件导入更改为:

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

现在将UI包装在Ionic页面中,并添加标题,如下所示:

return 
    
      
        Twitter Curation App
      
    
    
      
        
        Twitter Curation App
        
      
       
        
            
                 setText(e.detail.value!)} />
            
            
                Add
            

            
            Pull
            

        

        
            
                
                    {accounts.map((x:string, i:number) => 
                        
                            
                                {x}
                                Delete
                            
                        
                 )}
                
            
        
    
    
  

Now that looks nice!

Sentiment Sorted Page

Big-picture Plan

情感排序页面将与Bootstrap页面中的情感排序页面非常相似,但使用了TypeScript和Ionic. 我们还将实现主题显示作为一个单独的路线,以利用Ionic在移动设备上运行时的导航功能, 所以我们需要给这个页面导航到主题显示路线的能力,以及.

Route Setup

让我们首先创建一个名为sentimentsorted文件夹和一个名为sentimentsorted文件.tsx underneath. 像这样导出一个存根组件:

import React from 'react';

const SentimentSorted: React.FC = () => {
    return 
Sentiment Sorted
} 导出默认SentimentSorted;

And in the App.tsx, import the component:

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

And add the route:

 } />

You will get a TypeScript error saying that SentimentSorted 不期待curatedTweets道具,所以让我们在下一节中处理它.

UI Components

Let’s begin by defining the container’s props. 就像输入组件一样:

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

interface ContainerProps {
    curatedTweets: CuratedTweets
}

现在,改变存根显示:

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

And everything should compile.

显示非常简单,它只是一个带有显示组件的IonList:

return 
    
        
        
            {(curatedTweets.sentimentSorted).map((x, i) =>
            
                
                    

{x.account}:

{x.tweet}

({x.data.sentiment.toPrecision(2)})

)}

If you save and pull some tweets using the input component, you should see the tweets displayed in a list.

Now, let’s add the navigation buttons. Add to the IonGrid:


    
        Back
        {switchStr}
    

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

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

And 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, 我们需要实现显示所有主题的组件. 我们将在下一节中这样做.

Topic Groups Component

让我们添加一个主题列表显示选项,并有条件地显示它. 要做到这一点,我们需要打破情绪显示列表. 将SentimentDisplay重命名为Display,让我们打开情感显示列表:

接口SentimentDisplayProps {
    sentimentSorted: Array
}

const SentimentDisplay: React.FC = ({sentimentSorted}) => {
    return 
        {(sentimentSorted || []).map((x, i) =>
        
            
                

{x.account}:

{x.tweet}

({x.data.sentiment.toPrecision(2)})

)}
}

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

接口TopicDisplayProps {
    groupedByNP: Record>
}

const TopicDisplay: React.FC = ({groupedByNP}) => {
    return 
        {Object.keys(groupedByNP || {}).map((x, i) =>
        
            
                

{x} ({groupedByNP[x].length})

)}
}

现在,条件显示很容易在display组件中设置:

return (
    
        
            
                Back
                {switchStr}
            
        
        {
            displayType === 'Sentiment'?  :
            
         }
    
);

确保更改默认导出,现在我们已经准备好实现主题显示页面.

Topic Display Page

Big-picture Plan

主题显示页面是一个类似于情感显示的列表显示, 但是我们将从route参数中寻找问题的主题.

Route Setup

如果你已经学到了这一步,你应该已经知道该怎么做了. 让我们创建一个名为topicdisplay的页面文件夹和一个topicdisplay.tsx, write a stub component, and import it into the App.tsx page. Now, let’s set up the routes:

  } />

Now we’re ready to implement the UI component.

UI Components

First, let’s create the ContainerProps definition:

interface ContainerProps {
    curatedTweets: CuratedTweets
}

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

现在,我们需要从URL路径名中检索主题. To do that, we will be using the history API. So let’s import useHistory,实例化历史API,并从路径名中提取主题. 同时,让我们来实现切换回功能:

const TopicDisplay: React.FC = ({curatedTweets}) => {
    const history = useHistory();
    const switchToDisplay = () => {
        history.goBack();
    }
    const topic = history.location.pathname.split('/')[2];
    const tweets = (curatedTweets.groupedByNp || {})[topic];

现在我们有了特定主题的tweet,显示实际上非常简单:

return (
    
        
            
                Back
            
        
        
            
                
                    {(tweets || []).map((x, i) => 
                        
                            

{x.account}:

{x.tweet}

({x.data.sentiment.toPrecision(2)})

)}
);

Save and run, and things should be looking good.

在模拟器中运行应用程序

在模拟器中运行应用程序, 我们只需运行几个Ionic命令来添加移动平台并复制代码, similar to how we set things up with Cordova.

ionic build # builds the app
ionic cap add ios #添加ios作为平台之一,只需要运行一次
ionic cap copy # copy the build over
Ionic cap sync #只需要在你添加了本地插件的情况下运行
ionic cap open ios # run the iOS emulator

你应该会看到应用出现.

React Native Implementation

React Native Primer

React Native采用了一种与前面章节中基于web的方法截然不同的方法. React Native renders your React code as native components. 这有几个优点. First, 与底层操作系统的集成要深入得多, 让开发者能够利用Cordova/Capacitor无法提供的智能手机新功能和特定于操作系统的功能. Second, 因为中间没有基于web的渲染引擎, React Native应用程序通常比使用Cordova编写的应用程序更快. Finally, 因为React Native允许本地组件的集成, 开发人员可以对他们的应用程序施加更细粒度的控制.

For our application, 我们将使用前几节中的逻辑,并使用一个名为NativeBase的React Native组件库来编写UI.

Configuring the App

First, 你需要按照说明安装React Native的所有必要组件 here.

Once React Native is installed, let’s start the project:

react-native init TwitterCurationRN

让设置脚本运行,最终,文件夹应该被创建. Cd into the folder and run react-native run-ios, 你应该会看到带有示例应用程序的模拟器弹出.

我们还需要安装NativeBase,因为它是我们的组件库. To do that, we run:

NPM install——save native-base
react-native link

We also want to install the React Native stack navigator. Let’s run:

NPM install——save @react-navigation/stack @react-navigation/native

And

react-native link
cd ios
pod-install
cd

完成本地插件的链接和安装.

Router Setup

对于路由,我们将使用在上面步骤中安装的堆栈导航器.

Import the router components:

从“@react-navigation/native”中导入{NavigationContainer};
从“@react-navigation/stack”中导入{createStackNavigator};

And now, we create a stack navigator:

const Stack = createStackNavigator();

修改App组件的内容以使用堆栈导航器:

const App = () => {
  return (
    <>
      
        
          
            
          
        
    
  );
};

此时,您将得到一个错误,因为Entry尚未定义. Let’s define a stub element just to make it happy.

在项目中创建一个组件文件夹,创建一个名为Entry的文件.jsx, and add a stub component like so:

import React, {useState} from 'react';
从'native-base'导入{Text};

export default Entry = ({navigation}) => {
    return Entry; // All text must be wrapped in a  component or  if you’re not using NativeBase.
}

在你的应用中导入Entry组件,它就应该构建了.

Now, we’re ready to code the Input page.

Input Page

Big-picture Plan

我们将实现一个与上面实现的非常相似的页面,但是使用NativeBase组件. 我们使用的大多数JavaScript和React api,比如hook和fetch,都仍然可用.

唯一的区别是我们使用导航API的方式,稍后您将看到.

UI Components

我们将使用的其他NativeBase组件是Container, Content, Input, List, ListItem, and Button. These all have analogs in Ionic and Bootstrap React, NativeBase的构建者让熟悉这些库的人非常直观. Simply import like so:

导入{容器、内容、输入

    Item, Button, List, ListItem, Text } from 'native-base';

And the component is:

return 
        
          
            
            
             
            
          
        
                
                   {handles.map((item) => 
                        {item.key}
                    )}
                
          
        
      

And now, let’s implement the state and event handlers:

const [input, changeInput] = useState('');
    const [handles, changeHandles] = useState([]);
    const inputChange = (text) => {
        changeInput(text);
    }

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

And finally, the API call:

const onPullClicked = () => {
    fetch('http://prismatic.pythonanywhere.com/get_tweets', {
        method: 'POST',
        mode: 'cors',
        headers: {
            'Accept': 'application/json',
            “内容类型”:“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实现相同, except we are navigating in a different way. 堆栈导航器将一个名为“navigation”的属性传递给它的组件,” and that can be used to navigate between routes with .navigate. 除了简单的导航之外,您还可以将数据传递给目标组件. We will use this mechanism to pass the data.

To make the app compile, we still need to make Entry 知道导航组件. 要做到这一点,我们需要修改组件的函数声明:

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

Now save, and you should see the page.

Sentiment Sorted Page

Big-picture Plan

我们将像前面几节一样实现情感页面, but we will be styling the page a little differently, 我们还将以不同的方式使用导航库来获取API调用返回值.

Because React Native doesn’t have CSS, 我们要么需要定义一个样式表对象,要么简单地将样式内联编码. 因为我们将在组件之间共享一些样式,所以让我们创建一个全局样式表. We will do that after the route setup.

Also, because StackNavigator 有一个内置的后退导航按钮,我们将不需要实现我们自己的后退按钮.

Route Setup

Route definition in StackNavigator is very simple. 我们只需创建一个名为Stack Screen的新组件,并给它提供组件,就像React路由器一样.

 
    
        
        
    

要做到这一点,我们当然需要在components/SentimentDisplay中创建一个存根组件.js:

import React from 'react';
从'native-base'导入{Text};

const SentimentDisplay = () => {
    return Sentiment Display;
}

导出默认的SentimentDisplay;

And import it:

导入SentimentDisplay./组件/ SentimentDisplay”;

Now, we’re ready to create the global stylesheet.

Global StyleSheet

First, create a file named globalStyles.js. 然后,从React Native中导入StyleSheet组件并定义样式:

import {StyleSheet} from 'react-native';

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

我们准备好编写UI了.

UI Components

除了我们如何处理路由之外,UI组件非常熟悉. We will want to use StackNavigator的特殊道具导航和路由,以获取当前应用程序状态,并在用户希望看到该页面时导航到主题显示.

修改组件定义来访问导航道具:

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

现在,我们实现应用程序的状态读取和导航功能:

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

Import the global styles:

import globalStyles from './globalStyles';

And the components:

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

And finally, the components:

return 
    
        
         
        
        
            
                {data.sentimentSorted.map((item, i) => 
                    
                    {item.account}:
                    {item.tweet} ({item.data.sentiment.toPrecision(2)})
                    
                )}
            
        
    
;

保存并尝试拉出一些tweet,您应该会看到情感显示. 现在进入主题分组页面.

Topic Grouping Page

Big-picture Plan

主题显示也是非常相似的. 我们将使用处理程序构建器来构建导航函数,以导航到特定主题项的显示页面, 我们还将定义一个特定于这个页面的样式表.

我们将要做的一件新事情是实现touchableacity, 这是一个React Native特定的组件,功能很像一个按钮.

Route Setup

Route definition is the same as before:


    
    
    

The stub component components/TopicDisplay.js:

import React from 'react';
从'native-base'导入{Text};

const TopicDisplay = () => {
    return Topic Display;
} 

export default TopicDisplay;

And import it:

import TopicDisplay from './components/TopicDisplay;

UI Components

A lot of this will look very familiar. Import the library functions:

import {
   View,
   TouchableOpacity,
   StyleSheet
 } from 'react-native';
从'native-base'导入{List, Item, Content, listtiitem, Container, Text};

Import the global styles:

import globalStyles from './globalStyles';

Define the custom styles:

const styles = StyleSheet.create({
    topicName: {font - size: '600'},
})

Define the navigation props:

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

定义数据和操作处理程序. 注意,我们正在使用一个handler生成器,一个返回一个函数的函数:

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

And now, the components. 注意到我们在使用TouchableOpacity,它可以有 onPress handler. We could have used TouchableTransparency as well, 但TouchableOpacity的点击按住动画更适合我们的应用.

return 
    
        
            
                {Object.keys(data.groupedByNp).map((topic, i) => 
                    
                    
                        {topic}
                    
                    
                )}
            
        
    
 ;

And this should do it. 保存并试用该应用程序!

Now, onto the Topic Display Item Page.

Topic Display Item Page

Big-picture Plan

主题显示项目页面非常相似,所有的特性都在其他部分得到了处理,所以从这里开始应该是一帆风顺的.

Route Setup

我们将添加路由定义:


Add the import:

导入TopicDisplayItem./组件/ TopicDisplayItem”;

And create the stub component. 而不仅仅是一个裸组件, 让我们导入将要使用的NativeBase组件,并定义路由道具:

import React from 'react';
从'react-native'中导入{View};
从'native-base'导入{List, Item, Content, listtiitem, Container, Text};
 
import globalStyles from './globalStyles';
 
const TopicDisplayItem = ({route}) => {
    const {params: {data, topic}} = route;
    return Topic Display Item;
}
 
导出默认TopicDisplayItem;

UI Components

UI组件非常简单. 我们以前见过它,我们并没有真正实现任何自定义逻辑. So, let’s just go for it! Take a deep breath…

return 
    
        
            
                {data.groupedByNp[topic].map((item, i) => 
                    
                    {item.account}:
                    {item.tweet} ({item.data.sentiment.toPrecision(2)})
                    
                )}
            
        
    
;

保存,我们就可以出发了! 现在我们已经准备好在模拟器中运行应用程序了……等等,我们不是已经准备好了吗?

Running the App

Well, since you’re working with React Native, 你已经在模拟器中运行应用了,所以这部分已经处理好了. 这是React Native开发环境的一大优点.

Whew! 至此,本文的编码部分就完成了. Let’s see what we learned about the technologies.

Comparing the Technologies

Cordova: Pros and Cons

Cordova最好的地方在于,一个熟练的web开发人员可以以极快的速度编写出功能齐全且合理美观的代码. Web开发技能和经验很容易转移,因为, after all, you are coding a web app. The development process is quick and simple, and accessing Cordova API is simple and intuitive as well.

直接使用Cordova的缺点主要来自对web组件的过度依赖. 用户在使用移动应用程序时,已经开始期待特定的用户体验和界面设计, and when an application feels like a mobile site, 这种体验可能会有点不和谐. Additionally, most of the features built into apps, such as transitional animations and navigation utilities, must be implemented manually.

Ionic: Pros and Cons

Ionic最好的部分是我“免费”获得了许多以移动为中心的功能.” By coding much like I would have coded a web application, 我能够构建一个看起来比简单地使用Cordova和React-Bootstrap更适合移动设备的应用程序. 有导航动画, 具有本地样式的按钮, 许多用户界面选项使用户体验非常流畅.

使用Ionic的缺点部分是由它的优点造成的. 首先,有时候很难想象应用程序在不同环境下的表现. 仅仅因为应用看起来是一种方式并不意味着相同的UI放置在另一种环境中看起来是一样的. Second, Ionic是建立在许多底层技术之上的, 事实证明,获得一些组件是很困难的. Lastly, 这是Ionic-React特有的, but because Ionic was first built for Angular, Ionic-React的许多特性似乎缺少文档和支持. However, Ionic团队似乎非常关注React开发人员的需求,并快速提供新功能.

React Native: Pros and Cons

React Native拥有非常流畅的手机用户开发体验. 通过直接连接到模拟器,应用程序的外观就不再神秘了. 基于web的调试器界面对于web应用程序领域的交叉应用调试技术非常有帮助, 而且这个生态系统非常强大.

React Native的缺点来自于它与本机接口的接近. Many DOM-based libraries could not be used, 这意味着必须学习新的库和最佳实践. 如果没有CSS的好处,应用程序的样式就不那么直观了. Finally, with many new components to learn (e.g.,视图而不是div,文本组件包裹一切,按钮vs. TouchableOpacity vs. TouchableTransparency, etc.), 如果一个人在进入React Native世界之前对机制知之甚少,那么在一开始就会有一些学习曲线.

When to Use Each Technology

Because Cordova, Ionic, and React Native all have very strong pros and cons, 每种技术都有一个环境,它可以在其中享受最佳的生产力和性能.

如果你已经有了一个现有的应用程序,它是web优先的,围绕着UI设计和一般的外观和感觉有很强的品牌标识, 你最好的选择是科多瓦, 哪一种方法既能让你使用智能手机的原生功能,又能让你重用大部分网络组件,并在此过程中保留你的品牌标识. 对于使用响应式框架的相对简单的应用程序, 你可能只需要做很少的修改就能开发出一个移动应用. However, 你的应用程序看起来不像一个应用程序,而更像一个网页, 人们期望从移动应用程序中获得的一些组件将被单独编码. Therefore, 如果你的项目是web优先项目,将应用程序移植到移动设备上,我推荐使用Cordova.

如果你以应用为先的理念开始编写一个新的应用程序, but your team’s skill set is primarily in web development, I recommend Ionic. Ionic的库可以让你快速编写看起来和感觉上都接近原生组件的代码,同时让你运用自己作为web开发人员的技能和直觉. 我发现web开发的最佳实践很容易交叉应用到Ionic开发中, and styling with CSS worked seamlessly. In addition, 与使用响应式CSS框架编写的网站相比,该网站的移动版本看起来更原生. However, at some steps along the way, 我发现React-Ionic-Native API集成需要一些手动调整, 这可能会很耗时. Therefore, 如果你的应用程序是从头开始开发的,并且你想在一个移动web应用程序和一个移动应用程序之间共享大量的代码,我推荐Ionic.

如果您正在编写一个新应用程序,并实现了一些本地代码库, 你可以试试React Native. Even if you’re not using native code, 在你已经熟悉React Native的情况下,它也是最好的选择, 或者当您主要关注移动应用程序而不是混合应用程序时. 将我的大部分前端开发工作集中在web开发上, 我最初发现,由于组件组织和编码约定的差异,React Native比Ionic或Cordova有更多的学习曲线. However, 一旦掌握了这些细微差别, the coding experience is quite smooth, 特别是在像NativeBase这样的组件库的帮助下. 考虑到开发环境的质量和对应用程序的控制, 如果你的项目前端主要是一个移动应用程序, I would recommend React Native as your tool of choice.

Future Topics

我没有时间探讨的一个主题是访问原生api(如camera)的便利性, geolocation, or biometric authentication. One of the great benefits of mobile development is the accessibility 一个丰富的API生态系统,通常无法在浏览器上访问.

In future articles, 我想探讨使用各种跨平台技术开发这些支持本地api的应用程序的便利性.

Conclusion

Today, 我们使用三种不同的跨平台移动开发技术实现了一款Twitter策展应用. 我希望这篇文章能让您对每种技术有一个很好的了解,并启发您开发自己的基于react的应用程序.

You can find the code associated with the tutorial on GitHub.

Understanding the basics

  • How difficult is natural language processing (NLP)?

    NLP是一种机器学习技术,包括理解、响应或报告意义, context, mood, and other attributes. 其中许多技术都很复杂,需要大量的处理能力. Fortunately, 有一些开源库可以完成大部分繁重的工作, making NLP accessible to many engineers and applications.

  • How does natural language processing work?

    根据不同的目标,有许多不同的技术. In general, 这些技巧解析句子, 然后在周围的单词或句子结构中寻找上下文线索,以获得更深层次的含义.

  • 在2020年学习React值得吗?

    本文毫无疑问地展示了React及其支持框架的价值. 每个框架——Cordova、Ionic和React Native——对复杂的移动应用都有价值. Learning React is a natural starting point.

Consult the author or an expert on this topic.
Schedule a call
王尚伦的头像
Shanglun Wang

Located in New York, NY, United States

Member since December 16, 2016

About the author

Sean是一个充满激情的通晓多种语言的人:一个全栈向导、系统管理员和数据科学家. He’s also developed market intelligence software.

Toptal作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.

Previously At

CB Insights

World-class articles, delivered weekly.

订阅意味着同意我们的 privacy policy

World-class articles, delivered weekly.

订阅意味着同意我们的 privacy policy

Toptal Developers

Join the Toptal® community.