使用React.js和appbase.io构建类似Twitter的Search Feed

This is Part — II of a series on building Twitter like live search feeds with different Javascript frameworks. You can check out the Part-I published on scotch.io where we used jQuery.

这是关于构建Twitter的系列文章的第二部分,该Twitter像使用不同Javascript框架的实时搜索供稿一样。 您可以查看在scotch.io发布的使用jQuery的第I部分

Ever tried Twitter Live Search and wondered how it works? On the surface, you search for a #hashtag or a keyword and Twitter shows you a live feed of results, with new tweets appearing continuously after the initial search results were rendered completely!

是否曾经尝试过Twitter Live Search,并想知道它如何工作? 从表面上看,您搜索一个#hashtag或关键字,Twitter显示给您实时的结果提要,在完全呈现初始搜索结果之后,新的tweets不断出现!

Image: Live twitter search for #GameOfThrones

图片:实时Twitter搜索#GameOfThrones

How is Twitter able to return a dynamic result feed for any searched keyword? One way to implement a Twitter Search style live feed is to return the original search results from a database (SQL, ElasticSearch, Mongo etc.) and then have a separate feed component using a realtime channel (think socket.io).

Twitter如何为搜索到的任何关键字返回动态结果供稿? 实施Twitter搜索样式实时供稿的一种方法是从数据库(SQL,ElasticSearch,Mongo等)返回原始搜索结果,然后使用实时通道(如socket.io)拥有单独的供稿组件。

Image: Architecture for a naive realtime feed

图片:天真实时提要的体系结构

We can’t know for sure how Twitter internally implements the realtime feed, but the approach described above might be easy to get started but requires scaling two different system components, the database and the feed logic. It can also suffer from data consistency issues because of constant syncing that is required by this arranged. Performance wise, it would do okay with a O(MxN) time complexity (where M=data insertions into the DB per batch, N=channels with at least one active subscriber).

我们无法确定Twitter如何在内部实现实时供稿,但是上述方法可能很容易上手,但需要扩展两个不同的系统组件,即数据库和供稿逻辑。 由于此安排所需的持续同步,它也可能遭受数据一致性问题的困扰。 在性能方面,这样做的时间复杂度为O(MxN) (其中M =每批向DB中插入数据的数量, N =至少有一个活动订户的通道)是可以的。

In this post, we will describe a scalable approach to building realtime feeds using Meetup’s RSVPs as the data source. We will store this data in appbase.io, which acts as a database (thanks to Elasticsearch) with a realtime feeds layer. For the backend, we will use a Node.JS worker to fetch Meetup’s RSVPs and insert them into an appbase.io app. On the frontend, we will use React.JS to create the feed UI and query appbase.io in realtime with different keyword filters.

在本文中,我们将介绍一种可扩展的方法,该方法使用Meetup的RSVP作为数据源来构建实时提要。 我们会将这些数据存储在appbase.io中,该数据库充当具有实时feed层的数据库(由于Elasticsearch)。 对于后端,我们将使用Node.JS工作程序来获取Meetup的RSVP并将其插入到appbase.io应用程序中。 在前端,我们将使用React.JS创建提要UI并使用不同的关键字过滤器实时查询appbase.io。

输入meetupblast! ( Enter meetupblast! )

Meetupblast shows a live feed of meetup RSVPs searchable by cities and topics. Like Twitter Live Search, it returns a dynamic feed of RSVP data by the selected cities and topics. It’s a great way to find cool meetups other people are attending in your city.

Meetupblast显示了可以按城市和主题搜索的Meetup RSVP的实时提要。 像Twitter Live Search一样,它返回选定城市和主题的RSVP数据动态提要。 这是找到其他人正在您城市参加的酷聚会的好方法。

(http://appbaseio-apps.github.io/meetupblast-react/)

http://appbaseio-apps.github.io/meetupblast-react/

You can try the live demo and access the code on github here or continue reading to see how we built this.

您可以尝试的现场演示,并访问在GitHub上的代码在这里或继续阅读,看看我们如何建立这个。

关键成分 (Key Ingredients)

The meetupblast recipe can be broken into two key pieces: the backend worker and the user interface.

metupblast配方可以分为两个关键部分:后端工作程序和用户界面。

  1. Backend Worker
    • We use Meetup’s RSVP stream endpoint to get realtime RSVPs.
    • We then store this data in appbase.io, which provides us a convenient way to query both historical data and realtime feeds — we call it the streaming DB ;)

    后端工作者
    • 我们使用Meetup的RSVP流端点来获取实时RSVP
    • 然后,我们将这些数据存储在appbase.io中 ,这为我们提供了一种方便的方式来查询历史数据和实时提要-我们将其称为流式数据库;)
  2. User Interface
    • Querying appbase.io for Meetup RSVP feeds by cities and topics.
    • The UI / UX logic is entirely written in a React.JS frontend. And we use typeahead for displaying the search results, it’s a convenience library for building search interfaces from Twitter.

    用户界面
    • 通过城市和主题查询appbase.io以获取Meetup RSVP提要。
    • UI / UX逻辑完全用React.JS前端编写。 我们使用typeahead来显示搜索结果,它是一个方便的库,用于从Twitter构建搜索界面。

深潜 ( Deep Dive )

Now that we know what meetupblast does, let’s get to the thick of how the app works.

现在我们知道metupblast的功能,让我们深入了解该应用程序的工作方式。

后端工作者 (Backend Worker)

Our backend worker is a simple Node.JS code that keeps running forever on a DigitalOcean droplet.

我们的后端工作人员是一个简单的Node.JS代码,该代码可以在DigitalOcean Droplet上永久运行。

The worker consumes meetup RSVP data from their APIs

工作人员从其API消耗Meetup RSVP数据

http.get(meetup_url, function(res) {
     res.on('data', function(chunk) { // called on new RSVPs
         var data = JSON.parse(chunk);
         meetup_data.push(data); // capture RSVPs in an array
     });
});

and then index each RSVP into appbase.io.

然后将每个RSVP索引到appbase.io中。

appbaseRef.index({
  type: DATA_TABLE,            // collection to store the data into
  body: meetup_data[counter]
}).on('data', function(res) {
  console.log("successfully indexed one meetup RSVP");
})

Meetup provides us a JSON object for every RSVP. We then write this data into appbase.io as soon as it arrives. appbase.io is built as a streaming layer on ElasticSearch, and provides a live query interface for streaming JSON results.

Meetup为每个RSVP提供了一个JSON对象。 然后,我们会在数据到达后立即将其写入appbase.io。 appbase.io被构建为ElasticSearch上的流传输层,并提供实时查询接口以流传输JSON结果。

An RSVP JSON data looks like this:

RSVP JSON数据如下所示:

"visibility": "public",
"response": "yes",
"guests": 0,
"member": {
    "member_id": 185034988,
    "photo": "http://photos1.meetupstatic.com/photos/member/d/0/0/0/thumb_248333248.jpeg",
    "member_name": "Wilma"
},
"rsvp_id": 1566804180,
"mtime": 1440266230993,
"event": {
    "event_name": "Wednesday Badminton @ Flying Dragon",
    "event_id": "224809211",
    "time": 1440630000000,
    "event_url": "http://www.meetup.com/Richmond-Hill-Friends/events/224809211/"
},
"group": {
    "group_topics": [
    {
        "urlkey": "social",
        "topic_name": "Social"
    },
    {
        "urlkey": "board-games",
        "topic_name": "Board Games"
    },
    {
        "urlkey": "movie-nights",
        "topic_name": "Movie Nights"
    }
    ],
"group_city": "Richmond Hill",
"group_country": "ca",
"group_id": 1676043,
"group_name": "Richmond Hill Friends",
"group_lon": -79.4,
"group_urlname": "Richmond-Hill-Friends",
"group_state": "ON",
"group_lat": 43.84
}

用户界面 ( User Interface )

The User Interface is a small frontend that queries appbase.io for realtime meetups based on specific cities and tags and displays them in a neat list.

用户界面是一个小型前端,可以根据特定的城市标签向 appbase.io查询实时聚会,并在整洁的列表中显示它们。

Image: Building the live feed interface with appbase.io, typeahead.js and react.js

图片:使用appbase.io,typeahead.js和react.js构建实时Feed界面

The final app directory structure will look as follows:

最终的应用程序目录结构如下所示:

Project Structure

The codebase can be accessed at the meetupblast github repo. Or you can follow the remaining tutorial to see how we build it step by step.

可以在metupblast github repo上访问该代码库。 或者,您可以按照剩余的教程了解我们如何逐步构建它。

步骤0:初始项目设置 ( Step 0: Initial Project Setup )

We use bower_components for managing library dependencies for bootstrap, appbase-js and typeahead.js. If you haven’t used it before, it’s a neat package manager for the web.

我们使用bower_components来管理引导程序,appbase-js和typeahead.js的库依赖关系。 如果您以前从未使用过它,那么它是Web上一个整洁的软件包管理器。

bower init   /* follow the interactive setup to create bower.json */
bowerinstall --save bootstrap
bower install --save appbase-js
bower install --save typeahead.js

We will use browserify and reactify, two node_modules along with gulp, a streaming build for transpiling all the React .jsx files we will soon be adding to our codebase into javascript.

我们将使用browserify和reactify,两个node_modules和gulp,以及用于将所有React .jsx文件进行编译的流式构建,我们将很快将其添加到我们的代码库中成为javascript。

npm init   /* follow the interactive setup to create package.json */
npm install --save browserify
npm install --save reactify
npm install --save gulp (if you don't already have it)

If you haven’t used gulp before or would like to learn more about how the transpiling process works in greater depth, checkout this spot on tutorial by Tyler McGinnis on the topic.

如果您以前从未使用过gulp,或者想了解更多有关转码过程如何更深入地工作的信息,请在Tyler McGinnis关于该主题的教程中查看该点。

Next, we will configure the gulpfile to read all our React files.

接下来,我们将配置gulpfile来读取我们所有的React文件。

var browserify = require('browserify');
var gulp = require('gulp');
var source = require("vinyl-source-stream");
var reactify = require('reactify');

gulp.task('browserify', function() {
    var b = browserify({
        entries: ['src/app.js'],
        debug: true
    });
    b.transform(reactify); // use the reactify transform
    return b.bundle()
        .pipe(source('main.js'))
        .pipe(gulp.dest('./dist'));
});

gulp.task('watch', function() {
  gulp.watch('src/*.js', ['browserify']);
  gulp.watch('src/*.jsx', ['browserify']);
});

gulp.task('default', ['watch', 'browserify']);

A gulp file typically has one or more build tasks. We define the transpiling task browserify, which reactifies all the .jsx files starting from src/app.js into a single dist/main.js file. We will run the gulp build in the next step. This is how the project tree should look at this point —  files at step 0.

gulp文件通常具有一个或多个构建任务。 我们定义transpiling任务browserify,这reactifies全部来自SRC启动.jsx文件/ app.js成一个单一的DIST / main.js文件。 我们将在下一步中运行gulp构建。 这就是项目树在此时的样子- 步骤0的文件

步骤1:初始化项目文件 ( Step 1: Initializing Project Files )

Next, we will initialize the project files, assets folder and start writing the index.html.

接下来,我们将初始化项目文件,assets文件夹并开始编写index.html。

touch index.html
mkdir assets && touch assets/style.css
mkdir src && cd src
touch app.js request.js helper.js
touch container.jsx filterContainer.jsx tag.jsx user.jsx userImg.jsx

We recommend taking the stylesheet file style.css and paste it as is into assets/style.css file.

我们建议采用样式表文件style.css并将其原样粘贴到asset / style.css文件中。

Next, we will use our project setup files and call them from index.html.

接下来,我们将使用我们的项目设置文件,并从index.html调用它们。

<!DOCTYPE html>
<html>
    <head>
        <title>Meetup</title>
        <link rel="stylesheet" href="bower_components/bootstrap/dist/css/bootstrap.min.css">
        <link rel="stylesheet" type="text/css" href="assets/style.css" />
        <script src="bower_components/jquery/dist/jquery.min.js"></script>
        <script src="bower_components/appbase-js/browser/appbase.js"></script>
        <script src="bower_components/bootstrap/dist/js/bootstrap.min.js"></script>
        <script type="text/javascript" src="bower_components/typeahead.js/dist/typeahead.bundle.js"></script>
        <meta name="viewport" content="width=device-width, initial-scale=1">
    </head>
    <body class="container">
        <a href="https://github.com/appbaseio-apps/meetupblast-react"><img style="position: absolute; top: 0; right: 0; border: 0;z-index:15" src="https://camo.githubusercontent.com/652c5b9acfaddf3a9c326fa6bde407b87f7be0f4/68747470733a2f2f73332e616d617a6f6e6177732e636f6d2f6769746875622f726962626f6e732f666f726b6d655f72696768745f6f72616e67655f6666373630302e706e67" alt="Fork me on GitHub" data-canonical-src="https://s3.amazonaws.com/github/ribbons/forkme_right_orange_ff7600.png"></a>
        <div id="container"></div>
        <script type="text/javascript" src="src/helper.js"></script>
        <script type="text/javascript" src="src/request.js"></script>
        <script type="text/javascript">
            var REQUEST = new meetup_request(null);
        </script>
        <script src="dist/main.js"></script>
    </body>
</html>

Now that we have initialized all the project files, let’s transpile our non-existent .jsx files and run the app.

现在,我们已经初始化了所有项目文件,让我们转换不存在的.jsx文件并运行该应用程序。

gulp browserify
/* If you see any missing node modules like'vinyl-source-stream', add them with an npm install command */
npm install --save vinyl-source-stream

You should now see a dist/ folder generated in the project root with a file called main.js (as expected from our gulp build process).

现在,您应该在项目根目录中看到一个dist /文件夹,其中包含一个名为main.js的文件(正如我们的gulp构建过程所期望的那样)。

Let’s run the app at this point using

现在让我们使用

python -m SimpleHTTPServer 3000

Image: Step 1, a blank screen page created using our project setup

图片:步骤1,使用我们的项目设置创建的空白屏幕页面

Your project tree at this point should look like this — project tree at step 1.

此时,您的项目树应该看起来像这样- 步骤1中的项目树

第2步:使用React编写UI组件 ( Step 2: Writing UI Components with React )

Our project is setup with all the necessary files, we have a good handle on the React transpiling process using gulp. Now’s the time to dive into the src/ codebase.

我们的项目已经安装了所有必需的文件,我们对使用gulp的React编译过程有了很好的了解。 现在是时候深入src /代码库了。

var React = require('react');
var ReactDOM = require('react-dom');

var Container = require('./container.jsx'); 
ReactDOM.render(
    <div>   
        <Container></Container>
    </div>
    , document.getElementById('container'));

app.js is the entry point. In case you missed it, we used this file path in our gulp browserify build process too.

app.js是入口点。 如果您错过了它,我们也在gulp browserify构建过程中使用了此文件路径。

helper.js is a helper file, we recommend you get it from here and paste it as is.

helper.js是一个帮助程序文件,我们建议您从此处获取它并直接粘贴。

It requires container.jsx file, which we declare inside the app.js file.

它需要container.jsx文件,我们在app.js文件中声明了该文件。

Container.jsx is where we define the main app container. It contains:

Container.jsx是我们定义主应用程序容器的地方。 它包含了:

  1. filterContainer.jsx contains the city and topic filter components. It fires the RSVP feed based on current cities and topics selected.
    a. tag.jsx defines the actual filter component, and the checkboxing / removing methods.

    filterContainer.jsx包含城市和主题过滤器组件。 它根据当前选择的城市和主题触发RSVP feed。
    一个。 tag.jsx定义实际的过滤器组件以及复选框/删除方法。
  2. user.jsx displays the single user's RSVP feed UI.
    a. userImg.jsx displays the default image if user's pic can't be found.

    user.jsx显示单个用户的RSVP feed UI。
    一个。 如果找不到用户的图片,则userImg.jsx将显示默认图像。

Before delving into the React components, here’s a small primer on them. React components are the defining units of code organization (like classes in Java) in React. A component can inherit another component, have child components and can be published on the interwebs. A component is always defined with a React.createClass({specObject}) invocation, where the specObject should have a mandatory render() method. It can also contain other methods as well. The official docs are a good reference to whet your appetite about a component’s spec and lifecycle.

在深入研究React组件之前,这里有一个关于它们的小入门。 React组件是React中代码组织(如Java中的类)的定义单元。 一个组件可以继承另一个组件,具有子组件,并且可以在互连网上发布。 组件总是通过React.createClass( {specObject} 调用来定义的,其中specObject应该具有强制的render()方法。 它还可以包含其他方法。 官方文档是您提高对组件规格和生命周期的胃口的好参考。

If you have never created a React component based app before, we recommend checking out this status feed app tutorial.

如果您以前从未创建过基于React组件的应用程序,建议您查看此状态供稿应用程序教程

Enough said, let’s take a look at out codebase.

够了,让我们看一下代码库。

var React = require('react');
var FilterContainer = require('./filterContainer.jsx');
var User = require('./User.jsx');

var Container = React.createClass({
    getInitialState: function() {
        return {
            users: [],
            CITY_LIST: [],
            TOPIC_LIST: []
        };
    },
    componentDidMount: function() {
        var $this = this;
        this.make_responsive();
        $('.meetup-record-holder').on('scroll', function() {
            if ($(this).scrollTop() + $(this).innerHeight() >= this.scrollHeight) {
                var stream_on = REQUEST.PAGINATION($this.state.CITY_LIST, $this.state.TOPIC_LIST);
                stream_on.done(function(res) {
                    $this.on_get_data(res, true);
                }).fail('error', function(err) {});
            }
        });
    },
    make_responsive: function() {
        function size_set() {
            var window_height = $(window).height() - 15;
            $('.meetup-record-holder').css('height', window_height);
        };
        size_set();
        $(window).resize(function() {
            size_set();
        });
    },
    on_get_data: function(res, append) {
        var $this = this;
        //responseStream.stop();
        if (res.hasOwnProperty('hits')) {
            var record_array = res.hits.hits;
            if (append) {
                var arr = $this.state.users;
                var new_array = $.merge(arr, record_array);
                $this.setState({
                    users: new_array
                });
            } else {
                record_array = record_array.reverse();
                $this.setState({
                    users: record_array
                });
            }
        } else {
            var arr = $this.state.users;
            arr.unshift(res);
            $this.setState({
                users: arr
            });
        }
    },
    set_list: function(method, list) {
        if(method == 'city') {
            this.setState({
                CITY_LIST: list
            });
        }
        else {
            this.setState({
                TOPIC_LIST: list
            });   
        }
    },
    render: function() {
        var $this = this;
        return (
                <div className="row meetup-container">
                    <FilterContainer key='1' 
                        on_get_data={this.on_get_data}
                        CITY_LIST={this.state.CITY_LIST}
                        TOPIC_LIST={this.state.TOPIC_LIST}
                        set_list={this.set_list}
                        >
                    </FilterContainer>
                    <div className="meetup-record-holder" id="meetup-record-holder">
                        <div className="container full_row" id="record-container">
                            {this.state.users.map(function(single_user1, i){
                                var single_user = single_user1._source;
                                return (
                                    <User
                                        key={i}
                                        index={i}
                                        name={single_user.member.member_name}
                                        img={single_user.member.photo}
                                        event_name={single_user.event.event_name}
                                        group_city={single_user.group.group_city}
                                        group_topics={single_user.group.group_topics}
                                        event_url={single_user.event.event_url}
                                        TOPIC_LIST={$this.state.TOPIC_LIST}
                                     ></User>
                                );
                            })}
                        </div>        
                    </div>
                </div>
        );
    }
});

module.exports = Container;

Container is the main component. It responsible for keeping the state of three variables:

容器是主要组成部分。 它负责保持三个变量的状态:

  1. users — an array of RSVP feeds that are being streamed by appbase.io

    用户-appbase.io正在流式传输的一组RSVP feed
  2. CITY_LIST — an array of current city selection for filtering which feeds need to be streamed

    CITY_LIST —当前城市选择的数组,用于过滤需要流送的提要
  3. TOPIC_LIST — an array of current topic selection for filtering feeds by topics.

    TOPIC_LIST —当前主题选择的数组,用于按主题过滤供稿。

If we wanted to include other UI elements to filter feeds by (for instance, dates of meetups), we would similarly use another variable keep it’s state in the container.

如果我们想包括其他UI元素来过滤提要(例如,聚会的日期),我们将类似地使用另一个变量将其状态保存在容器中。

The container is divided into two sub-components:

容器分为两个子组件:

  1. FilterContainer component which creates the UI widget and manages the interaction flow to update the cities and topic, and

    FilterContainer组件,它创建UI小部件并管理交互流以更新城市和主题,以及
  2. User component is responsible for displaying the individual feed element’s UI.

    用户组件负责显示单个提要元素的UI。
var React = require('react');
var Tag = require('./tag.jsx');

var FilterContainer = React.createClass({
    componentWillMount: function() {
        this.fire_response();
    },
    fire_response: function() {
        var $this = this;
        streamingClient = REQUEST.GET_STREAMING_CLIENT();
        var stream_on = REQUEST.FIRE_FILTER(this.props.CITY_LIST, this.props.TOPIC_LIST);
        stream_on.on('data', function(res) {
            $this.props.on_get_data(res);
            $this.stream_start();
        }).on('error', function(err) {});
    },
    stream_start: function() {
        var $this = this;
        streamingClient = REQUEST.GET_STREAMING_CLIENT();
        var stream_on = REQUEST.STREAM_START(this.props.CITY_LIST, this.props.TOPIC_LIST);
        stream_on.on('data', function(res) {
            $this.props.on_get_data(res, true);
        }).on('error', function(err) {});
    },
    set_list: function(method, list) {
        this.props.set_list(method, list);
        this.fire_response();
    },
    render: function() {
        return (
                <div className="meetup-filter-container">
                    <Tag key="0" type="city"
                        set_list={this.set_list}
                        list={this.props.CITY_LIST}
                        fire_response={this.fire_response}></Tag>
                    <Tag key="1" type="topic"
                        set_list={this.set_list}
                        list={this.props.TOPIC_LIST}
                        fire_response={this.fire_response}></Tag>
                </div>
        )
    }
});


module.exports = FilterContainer;

In the FilterContainer, we initialize the RSVP feed stream via the fire_response method. It contains the Tag component to reuse the UI elements for building the city and topic list views.

FilterContainer中 ,我们通过fire_response方法初始化RSVP提要流。 它包含Tag组件,可重复使用UI元素来构建城市和主题列表视图。

Image: City and Topic UI elements built with FilterContainer and Tag components

图片:使用FilterContainer和Tag组件构建的City和Topic UI元素

You can copy and paste the Tag component’s code from here.

您可以从此处复制并粘贴Tag组件的代码。

Let’s take a look at the User component.

让我们看一下User组件。

var React = require('react');

var UserImg = require('./userImg.jsx');

//User component
var User = React.createClass({
    HIGHLIGHT_TAGS: function(group_topics) {
        var highlight_tags = [];
        var group_topics = group_topics;
        var highlight = this.props.TOPIC_LIST;

        if (highlight.length) {
            for (i = 0; i < group_topics.length; i++) {
                for (var j = 0; j < highlight.length; j++) {
                    if (highlight[j] == group_topics[i])
                        group_topics.splice(i, 1);
                }
            }
            for (i = 0; i < highlight.length; i++) {
                highlight_tags.push(highlight[i]);
            }
        }

        var lower = group_topics.length < 3 ? group_topics.length : 3;
        for (i = 0; i < lower; i++) {
            highlight_tags.push(group_topics[i]['topic_name']);
        }
        return highlight_tags;
    },
    render: function() {
        var highlight_tags = this.HIGHLIGHT_TAGS(this.props.group_topics);

        return (
                <a className="full_row single-record single_record_for_clone"
                    href={this.props.event_url}
                    target="_blank"
                    key="1">
                    <div className="img-container">
                        <UserImg key={this.props.event_url} src={this.props.img} />
                    </div>
                    <div className="text-container full_row">
                        <div className="text-head text-overflow full_row">
                            <span className="text-head-info text-overflow">
                                {this.props.name} is going to {this.props.event_name}
                            </span>
                            <span className="text-head-city">{this.props.group_city}</span>
                        </div>
                        <div className="text-description text-overflow full_row">
                            <ul className="highlight_tags">
                                {
                                    highlight_tags.map(function(tag,i){
                                        return (<li key={i}>{tag}</li>)
                                    })
                                }
                            </ul>
                        </div>
                    </div>
                </a>
        )
    }
});


module.exports = User;

A lot of the code here is in making sure we give it the proper styling layout. By now, it should be clear how components in React. They are not very different from the abstractions offered by Object Oriented Programming languages like Java.

这里的许多代码都是在确保我们为其提供正确的样式布局。 到现在为止,应该清楚React中的组件如何。 它们与Java之类的面向对象编程语言所提供的抽象没有太大区别。

This component uses one sub-component called UserImg. It’s a very simple component that uses a default puppy image when a user’s image URL in the RSVP JSON doesn’t resolve.

该组件使用一个名为UserImg的子组件。 这是一个非常简单的组件,当RSVP JSON中的用户图像URL无法解析时,它将使用默认的小狗图像。

var React = require('react');

var UserImg = React.createClass({
    componentDidMount: function() {
        var self = this;
        this.img = new Image();
        var defaultSrc = 'http://www.avidog.com/wp-content/uploads/2015/01/BellaHead082712_11-50x65.jpg';
        this.img.onerror = function() {
            if (self.isMounted()) {
                self.setState({
                    src: defaultSrc
                });
            }
        };
        this.img.src = this.state.src;
    },
    getInitialState: function() {
        return {
            src: this.props.src
        };
    },
    render: function() {
        return <img src={this.state.src} />;
    }
});
module.exports = UserImg;

This is the entirety of our React code: We should have app.js, container.jsx, filterContainer.jsx, tag.jsx, user.jsx and userImg.jsx files.

这是我们整个React代码的整体:我们应该有app.js,container.jsx,filterContainer.jsx,tag.jsx,user.jsx和userImg.jsx文件。

We are missing one last important file before we get to see the working demo, request.js. We use the appbase-js lib here for defining live queries on the RSVP feed data. We recommend getting it as is from here to get to the functional demo.

在看到有效的演示request.js之前,我们缺少最后一个重要文件。 我们在此处使用appbase-js库来定义对RSVP feed数据的实时查询。 我们建议从此处直接获取它以进行功能演示。

Let’s run the app now.

让我们现在运行该应用程序。

gulp browserify
python -m SimpleHTTPServer 3000

Image: Step 2, complete UI rendered with React

图片:第2步,使用React渲染完整的UI

The project tree at this step should resemble the final project. You can also see the live demo at appbaseio-apps.github.io/meetupblast-react.

此步骤中的项目树应类似于最终项目 。 您还可以在appbaseio-apps.github.io/meetupblast-react上观看实时演示。

实时查询如何工作 (How Live Queries Work)

Earlier, we skipped an important part of explaining how the live queries work. Let’s take a look at it here.

之前,我们跳过了解释实时查询如何工作的重要部分。 让我们在这里看看。

There are three important queries happening in the UI:

UI中发生了三个重要的查询:

  1. Streaming RSVP feeds live filtered by the user selected cities and topics.

    流式RSVP提要通过用户选择的城市和主题进行实时过滤。
  2. Generating the list of cities and topics.

    生成城市和主题列表。
  3. Paginating to show historical data when user scrolls down.

    当用户向下滚动时分页显示历史数据。

Let’s see how to do the first one.

让我们看看如何做第一个。

// connecting to the app using our unique credentials. We will use these since the backend worker is configured to update this app, but you can change this by creating your own app from appbase.io dashboard.
var appbaseRef = new Appbase({
    appname: "meetup2",
    url: "https://qz4ZD8xq1:a0edfc7f-5611-46f6-8fe1-d4db234631f3@scalr.api.appbase.io"
})
// Now let's get the initial feed data
var response = appbaseRef.search({
    type: "meetup",
    size: 25,
    body: {
      query: {
        match_all: {}
      }
    }
})
response.on("data", function(data) {
    console.log("25 results matching everything", data.hits.hits) 
    // all initial data is returned in an array called hits.hits. You can browse the data object to see other interesting meta data like the time taken for the query.
});

When you run the above snippet in the console, you should see an array of size 25. Now, this is great but how do we continuously stream new RSVP feeds as they are indexed in the backend. Here’s how:

当您在控制台中运行上述代码片段时,您应该看到一个大小为25的数组。现在,这很好,但是如何在后端对新的RSVP feed进行索引时,如何连续地对其进行流式处理。 这是如何做:

// Let's subscribe to new data updates. searchStream() works exactly like search() except it only returns new data matching the query is indexed or modified.
var streamResponse = appbaseRef.searchStream({
    type: "meetup",
    body: {
      query: {
        match_all: {}
      }
    }
})
streamResponse.on("data", function(sdata) {
    console.log("I am a new data", sdata._source)
    // all new data is returned as JSON data in streams
})

Image: Browser console output when running the above script

图片:运行上述脚本时的浏览器控制台输出

We now know how live queries work with appbase.io APIs. The interesting part here is the JSON query object defined in the search() and searchStream() methods. We can change this query object to only showing RSVPs that match a white list of cities and/or topics. Anything that can be specified with Elasticsearch’s Query DSL can be queried live with appbase.io. This is exactly what request.js does. It also creates a list of top 1000 cities and topics at the start of the app based on aggregating all the RSVPs indexed in the last month. See https://github.com/appbaseio-apps/meetupblast-react/blob/master/src/request.js#L17. You can read more about appbase’s APIs here or check out a whole host of interesting live querying based apps at appbase.io’s community page.

现在,我们知道实时查询如何与appbase.io API一起使用。 这里有趣的部分是在search()和searchStream()方法中定义的JSON 查询对象。 我们可以将此查询对象更改为仅显示与城市和/或主题的白名单匹配的RSVP。 可以使用appbase.io实时查询Elasticsearch的Query DSL中可以指定的任何内容。 这正是request.js所做的。 它还基于汇总上个月索引的所有RSVP,在应用程序的开头创建了前1000个城市和主题的列表。 参见https://github.com/appbaseio-apps/meetupblast-react/blob/master/src/request.js#L17 。 您可以在此处阅读有关appbase API的更多信息或在appbase.io的社区页面上查看大量有趣的基于实时查询的应用程序。

摘要 ( Summary )

This is Part-II of a N-Part series where we show how to build a Twitter like Live Search Feed using appbase.io and React.JS.

这是N部分系列的第二部分,其中我们展示了如何使用appbase.ioReact.JS构建像Live Search Feed这样的Twitter。

  1. We started out with understanding the feed datastructure,

    我们从了解Feed数据结构开始,
  2. We then looked at the backend worker, where we fetched data from meetup’s streaming endpoint and indexed it into appbase.io,

    然后,我们查看了后端工作程序,该工作程序是从metup的流式传输端点中获取数据并将其索引到appbase.io中的,
  3. Finally, we looked at building the live feed user interface using React Components. We covered the gulp build system for transpiling React in step 0, linked everything together in step 1 and wrote React Components for the UI in step 2.

    最后,我们研究了使用React Components构建实时供稿用户界面。 我们在步骤0中介绍了用于编译React的gulp构建系统,在步骤1中将所有内容链接在一起,并在步骤2中为UI编写了React Components。

Live demo and final code repository links for further hacking.

实时演示最终代码存储库链接,用于进一步的黑客攻击。

翻译自: https://scotch.io/tutorials/build-a-twitter-like-search-feed-with-react-js-and-appbase-io

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值