這篇文章主要講解了“基于RethinkDB +React Native怎么開發(fā)Web應(yīng)用程序”,文中的講解內(nèi)容簡(jiǎn)單清晰,易于學(xué)習(xí)與理解,下面請(qǐng)大家跟著小編的思路慢慢深入,一起來研究和學(xué)習(xí)“基于RethinkDB +React Native怎么開發(fā)Web應(yīng)用程序”吧!
創(chuàng)新互聯(lián)公司-專業(yè)網(wǎng)站定制、快速模板網(wǎng)站建設(shè)、高性價(jià)比古雷港網(wǎng)站開發(fā)、企業(yè)建站全套包干低至880元,成熟完善的模板庫,直接使用。一站式古雷港網(wǎng)站制作公司更省心,省錢,快速模板網(wǎng)站建設(shè)找我們,業(yè)務(wù)覆蓋古雷港地區(qū)。費(fèi)用合理售后完善,十余年實(shí)體公司更值得信賴。
簡(jiǎn)介
一個(gè)實(shí)時(shí)應(yīng)用程序能夠使用戶***時(shí)間了解他想了解的信息。用戶不必不停地刷新用戶界面來獲取***的消息更新,應(yīng)用程序的服務(wù)器端會(huì)自動(dòng)更新客戶端應(yīng)用的。在本文中,我們將使用時(shí)下流行的RethinkDB +React Native框架開發(fā)一個(gè)真正實(shí)時(shí)的移動(dòng)Web應(yīng)用程序。
下圖給出示例工程的運(yùn)行時(shí)快照。
下面,讓我們首先來分析一下手機(jī)應(yīng)用程序的編碼情況,然后再來討論服務(wù)器端組件相關(guān)編程,其中將使用到Node、Express、Socket.io和RethinkDB等技術(shù)。
安裝依賴性
從你克隆下來的工程中導(dǎo)航到NewsShare目錄下,然后執(zhí)行命令npm install來安裝一下下面這些工程依賴項(xiàng):
1.react-native:這是React Native(本機(jī))框架。
2.lodash:用于管理新聞項(xiàng)數(shù)組,以便通過票數(shù)來限制和排序該數(shù)組。
3.react-native-modalbox:用于創(chuàng)建模態(tài)對(duì)話框來共享一則新聞。
4.react-native-button:React Native模態(tài)對(duì)話框依賴于它,用于創(chuàng)建按鈕。
5.react-native-vector-icons:用于使用流行圖標(biāo)集,如FontAwesome和Ionicons等來創(chuàng)建圖標(biāo)。這主要用于為投票按鈕創(chuàng)建圖標(biāo)。
6.socket.io-client:Socket.io的客戶端組件,它是一個(gè)實(shí)時(shí)應(yīng)用程序框架。
鏈接圖標(biāo)
安裝依賴關(guān)系后,還需要一個(gè)額外步驟就是使圖標(biāo)正常工作,即要將它們鏈接到應(yīng)用程序。這是通過使用rnpm——React Native的軟件包管理器實(shí)現(xiàn)的。
我們要使用npm來安裝 rnpm,格式如下:
npm install rnpm -g
然后,就可以執(zhí)行NewsSharer目錄下的rnpm link命令來鏈接圖標(biāo)了。
開發(fā)移動(dòng)客戶端程序
下面給出的是文件index.android.js的內(nèi)容:
import React, { Component } from 'react'; import { AppRegistry, StyleSheet, View } from 'react-native'; import Main from './components/Main'; class NewsSharer extends Component { render() { return (); } } const styles = StyleSheet.create({ container: { flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: '#F5FCFF', } }); AppRegistry.registerComponent('NewsSharer', () => NewsSharer);
該文件是Android應(yīng)用程序的入口點(diǎn)文件。如果你想把它部署到iOS上,那么你可以把上述代碼復(fù)制到文件index.ios.js中。
此文件的主要任務(wù)是導(dǎo)入Main組件,組件是應(yīng)用程序的核心所在。當(dāng)您導(dǎo)入組件而不是重復(fù)地為每個(gè)平臺(tái)編碼時(shí),這可以大大減少編程的重復(fù)性。
編寫主應(yīng)用程序組件
在路徑components/Main.js下創(chuàng)建文件Main.js,內(nèi)容如下:
import React, { Component } from 'react'; import { AppRegistry, StyleSheet, Text, View, TextInput, TouchableHighlight, Linking, ListView } from 'react-native'; import Button from 'react-native-button'; import Modal from 'react-native-modalbox'; import Icon from 'react-native-vector-icons/Octicons'; import "../UserAgent"; import io from 'socket.io-client/socket.io'; import _ from 'lodash'; var base_url = 'http://YOUR_DOMAIN_NAME_OR_IP_ADDRESS:3000'; export default class Main extends Component { constructor(props){ super(props); this.socket = io(base_url, { transports: ['websocket'] }); this.state = { is_modal_open: false, news_title: '', news_url: '', news_items_datasource: new ListView.DataSource({ rowHasChanged: (row1, row2) => row1 !== row2, }), is_news_loaded: false, news: {}, news_items: [] }; } getNewsItems(){ fetch(base_url + '/news') .then((response) => { return response.json(); }) .then((news_items) => { this.setState({ 'news_items': news_items }); var news_datasource = this.state.news_items_datasource.cloneWithRows(news_items); this.setState({ 'news': news_datasource, 'is_news_loaded': true }); return news_items; }) .catch((error) => { alert('Error occured while fetching news items'); }); } componentWillMount(){ this.socket.on('news_updated', (data) => { var news_items = this.state.news_items; if(data.old_val === null){ news_items.push(data.new_val); }else{ _.map(news_items, function(row, index){ if(row.id == data.new_val.id){ news_items[index].upvotes = data.new_val.upvotes; } }); } this.updateUI(news_items); }); } updateUI(news_items){ var ordered_news_items = _.orderBy(news_items, 'upvotes', 'desc'); var limited_news_items = _.slice(ordered_news_items, 0, 30); var news_datasource = this.state.news_items_datasource.cloneWithRows(limited_news_items); this.setState({ 'news': news_datasource, 'is_news_loaded': true, 'is_modal_open': false, 'news_items': limited_news_items }); } componentDidMount(){ this.getNewsItems(); } upvoteNewsItem(id, upvotes){ fetch(base_url + '/upvote-newsitem', { method: 'POST', headers: { 'Accept': 'application/json', 'Content-Type': 'application/json', }, body: JSON.stringify({ news_id: id, upvotes: upvotes + 1 }) }) .catch((err) => { alert('Error occured while trying to upvote'); }); } openModal(){ this.setState({ is_modal_open: true }); } closeModal(){ this.setState({ is_modal_open: false }); } shareNews(){ fetch(base_url + '/save-newsitem', { method: 'POST', headers: { 'Accept': 'application/json', 'Content-Type': 'application/json', }, body: JSON.stringify({ news_title: this.state.news_title, news_url: this.state.news_url, }) }) .then((response) => { alert('News was shared!'); this.setState({ news_title: '', news_url: '' }); }) .catch((err) => { alert('Error occured while sharing news'); }); } openPage(url){ Linking.canOpenURL(url).then(supported => { if(supported){ Linking.openURL(url); } }); } renderNews(news){ return (); } render(){ return ( {news.upvotes} {news.title} ); } } const styles = StyleSheet.create({ container: { flex: 1, alignSelf: 'stretch', backgroundColor: '#F5FCFF', }, header: { flex: 1, backgroundColor: '#3B3738', flexDirection: 'row' }, app_title: { flex: 7, padding: 10 }, header_text: { fontSize: 20, color: '#FFF', fontWeight: 'bold' }, header_button_container: { flex: 3 }, body: { flex: 19 }, btn: { backgroundColor: "#0***5D1", color: "white", margin: 10 }, modal: { height: 300 }, modal_header: { margin: 20, }, modal_body: { alignItems: 'center' }, input_row: { padding: 20 }, modal_header_text: { fontSize: 18, fontWeight: 'bold' }, share_btn: { width: 100 }, news_item: { paddingLeft: 10, paddingRight: 10, paddingTop: 15, paddingBottom: 15, marginBottom: 5, borderBottomWidth: 1, borderBottomColor: '#ccc', flex: 1, flexDirection: 'row' }, news_item_text: { color: '#575757', fontSize: 18 }, upvote: { flex: 2, paddingRight: 15, paddingLeft: 5, alignItems: 'center' }, news_title: { flex: 18, justifyContent: 'center' }, upvote_text: { fontSize: 18, fontWeight: 'bold' } }); AppRegistry.registerComponent('Main', () => Main); { this.state.is_news_loaded && News Sharer } Share News this.setState({news_title: text})} value={this.state.news_title} placeholder="Title" /> this.setState({news_url: text})} value={this.state.news_url} placeholder="URL" keyboardType="url" />
下面來分析一下上面代碼。首先,導(dǎo)入編程中所需要的內(nèi)置的React Native及第三方組件:
import React, { Component } from 'react'; import { AppRegistry, StyleSheet, Text, View, TextInput, TouchableHighlight, Linking, ListView } from 'react-native'; import Button from 'react-native-button'; import Modal from 'react-native-modalbox'; import Icon from 'react-native-vector-icons/Octicons'; import "../UserAgent"; import io from 'socket.io-client/socket.io'; import _ from 'lodash';
注意,你使用如下方式導(dǎo)入了自己開發(fā)的另外文件中的代碼:
import "../UserAgent";
這是你在根目錄NewsSharer下看到的UserAgent.js文件。它包含的代碼用于設(shè)置用戶代理為react-native——Socket.io需要這樣做,或者它會(huì)假設(shè)程序運(yùn)行于瀏覽器環(huán)境中。
window.navigator.userAgent = 'react-native';
接下來,確定應(yīng)用程序要請(qǐng)求的基URL。如果您要進(jìn)行本地測(cè)試,這可能是您的計(jì)算機(jī)的內(nèi)部IP地址。為了使這能夠工作,你必須確保你的手機(jī)或平板電腦連接到與您的計(jì)算機(jī)位于同一網(wǎng)絡(luò)。
var base_url = 'http://YOUR_DOMAIN_NAME_OR_IP_ADDRESS:3000';
接下來,在構(gòu)造函數(shù)中,初始化套接字連接:
this.socket = io(base_url, { transports: ['websocket'] });
然后,設(shè)置應(yīng)用程序的默認(rèn)狀態(tài):
this.state = { is_modal_open: false, //for showing/hiding the modal news_title: '', //default value for news title text field news_url: '', //default value for news url text field //initialize a datasource for the news items news_items_datasource: new ListView.DataSource({ rowHasChanged: (row1, row2) => row1 !== row2, }), //for showing/hiding the news items is_news_loaded: false, news: {}, //the news items datasource news_items: [] //the news items };
此函數(shù)的功能是使用內(nèi)置的fetch方法從服務(wù)器端取回新聞項(xiàng)目。它向news路由發(fā)出GET請(qǐng)求,然后從響應(yīng)中提取news_items對(duì)象。這個(gè)對(duì)象用于稍后創(chuàng)建客戶端ListView組件所需的新聞數(shù)據(jù)源。一旦創(chuàng)建,它便使用新聞數(shù)據(jù)源更新狀態(tài);這樣一來,用戶界面新聞項(xiàng)內(nèi)容也可以得到相應(yīng)的更新。
getNewsItems(){ fetch(base_url + '/news') .then((response) => { return response.json(); }) .then((news_items) => { this.setState({ 'news_items': news_items }); var news_datasource = this.state.news_items_datasource.cloneWithRows(news_items); this.setState({ 'news': news_datasource, 'is_news_loaded': true }); return news_items; }) .catch((error) => { alert('Error occured while fetching news items'); }); }
下面的ComponentWillMount方法是React的生命周期方法之一。這允許您可以在初始化渲染發(fā)生前執(zhí)行你自己的定制代碼。也正是在此處,你監(jiān)聽Socket.io的服務(wù)器組件發(fā)出的news_updated事件;而當(dāng)此事件發(fā)生時(shí),它可能是兩件事中之一——或者當(dāng)用戶共享新聞項(xiàng)時(shí)或者當(dāng)他們對(duì)現(xiàn)有新聞項(xiàng)投贊成票時(shí)。
值得注意的是,當(dāng)出現(xiàn)新的新聞項(xiàng)時(shí)RethinkDB的changefeed將為old_val返回一個(gè)null值。這也正是我們區(qū)分上面兩種可能性的辦法。如果用戶共享一個(gè)新聞項(xiàng),那么將其推到news_items數(shù)組中;否則,查找投贊成票的新聞項(xiàng)并更新其贊成票計(jì)數(shù)?,F(xiàn)在,您可以更新用戶界面來反映所做的更改了。
componentWillMount(){ this.socket.on('news_updated', (data) => { var news_items = this.state.news_items; if(data.old_val === null){ //a new news item is shared //push the new item to the news_items array news_items.push(data.new_val); }else{ //an existing news item is upvoted //find the news item that was upvoted and update its upvote count _.map(news_items, function(row, index){ if(row.id == data.new_val.id){ news_items[index].upvotes = data.new_val.upvotes; } }); } //update the UI to reflect the changes this.updateUI(news_items); }); }
接下來,UpdateUI函數(shù)使用贊成票數(shù)按照從高到低訂閱新聞項(xiàng)。一旦排序,便提取最前面的30條新聞,同時(shí)更新狀態(tài)。
updateUI(news_items){ var ordered_news_items = _.orderBy(news_items, 'upvotes', 'desc'); var limited_news_items = _.slice(ordered_news_items, 0, 30); var news_datasource = this.state.news_items_datasource.cloneWithRows(limited_news_items); this.setState({ 'news': news_datasource, 'is_news_loaded': true, 'is_modal_open': false, 'news_items': limited_news_items }); }
下面要介紹的ComponentDidMount方法是另一個(gè)React生命周期方法,此方法在初始渲染之后調(diào)用。在此方法中,我們實(shí)現(xiàn)從服務(wù)器端獲取新聞項(xiàng)。
【注】,如果你想在安裝組件之前發(fā)出請(qǐng)求的話,也可以從componentWillMount方法中從服務(wù)器端獲取新聞項(xiàng)。
componentDidMount(){ this.getNewsItems(); }
接下來的upvoteNewsItem方法將向服務(wù)器端發(fā)出一個(gè)投贊成票新聞項(xiàng)請(qǐng)求:
upvoteNewsItem(id, upvotes){ fetch(base_url + '/upvote-newsitem', { method: 'POST', headers: { 'Accept': 'application/json', 'Content-Type': 'application/json', }, body: JSON.stringify({ news_id: id, upvotes: upvotes + 1 }) }) .catch((err) => { alert('Error occured while trying to upvote'); }); }
接下來,openModal和closeModal方法分別負(fù)責(zé)顯示與隱藏共享新聞內(nèi)容的模態(tài)對(duì)話框。
openModal(){ this.setState({ is_modal_open: true }); } closeModal(){ this.setState({ is_modal_open: false }); }
繼續(xù)往下來,shareNews函數(shù)用于發(fā)送請(qǐng)求來創(chuàng)建一條新聞項(xiàng):
shareNews(){ fetch(base_url + '/save-newsitem', { method: 'POST', headers: { 'Accept': 'application/json', 'Content-Type': 'application/json', }, body: JSON.stringify({ news_title: this.state.news_title, news_url: this.state.news_url, }) }) .then((response) => { alert('News was shared!'); this.setState({ news_title: '', news_url: '' }); }) .catch((err) => { alert('Error occured while sharing news'); }); }
再往下,openPage函數(shù)用于在瀏覽器中打開新聞項(xiàng)對(duì)應(yīng)的URL:
openPage(url){ Linking.canOpenURL(url).then(supported => { if(supported){ Linking.openURL(url); } }); }
接下來,RenderNews函數(shù)將針對(duì)每個(gè)新聞項(xiàng)返回UI。這個(gè)方法中還負(fù)責(zé)顯示“upvote”按鈕、贊成票數(shù)和新聞標(biāo)題。其中,新聞標(biāo)題被封裝在一個(gè)TouchableHighlight組件。這允許我們通過執(zhí)行openPage函數(shù)來打開對(duì)應(yīng)的URL。對(duì)于贊成票數(shù),也要這樣做。
【注意】該代碼使用了TouchableHighlight組件而不是Button組件,因?yàn)锽utton組件不能內(nèi)含View或Text組件。
renderNews(news){ return (); } {news.upvotes} {news.title}
再往下,render函數(shù)負(fù)責(zé)返回整個(gè)應(yīng)用程序的UI部分:
render(){ ... }
在render函數(shù)中,要建立包含應(yīng)用程序的標(biāo)題的標(biāo)題和一個(gè)按鈕用于打開模態(tài)對(duì)話框來分享新聞項(xiàng)的按鈕。
News Sharer
對(duì)于body部分,使用ListView組件來渲染新聞項(xiàng)。它有三個(gè)必需的參數(shù):initialListSize,dataSource和renderRow。其中,InitialListSize被設(shè)置為1;這樣一來,ListView就能夠針對(duì)內(nèi)容部分的每一個(gè)幀逐行渲染。如果你想一次顯示所有行的話,你還可以把這值修改得更大些。dataSource對(duì)應(yīng)于新聞項(xiàng),renderRow函數(shù)用于渲染每一行中的新聞項(xiàng)。
{ this.state.is_news_loaded &&}
接下來是定義分享新聞的模態(tài)對(duì)話框。此對(duì)話框中使用了兩個(gè)文本字段分別用于輸入標(biāo)題和新聞URL,還有一個(gè)按鈕用于將新聞提交到服務(wù)器。文本字段使用了TextInput組件實(shí)現(xiàn)。由于沒有使用標(biāo)簽控件,所以需要在TextInput組件中輸入占位符文本來提示用戶要輸入的內(nèi)容。
這兩個(gè)文本字段都有一個(gè)onChangeText方法,在文本值更新時(shí)使用。keyboardType的Url用于新聞URL的文本字段;這樣的話,它將打開設(shè)備的鍵盤,實(shí)現(xiàn)輸入U(xiǎn)RL的優(yōu)化支持。用戶不必手動(dòng)輸入內(nèi)容,可以使用拷貝和粘貼。文本字段的下方是用于共享新聞的按鈕。按鈕的點(diǎn)擊將調(diào)用先前定義的shareNews函數(shù)。
Share News this.setState({news_title: text})} value={this.state.news_title} placeholder="Title" /> this.setState({news_url: text})} value={this.state.news_url} placeholder="URL" keyboardType="url" />
接下來,為組件設(shè)置樣式:
const styles = StyleSheet.create({ container: { flex: 1, alignSelf: 'stretch', backgroundColor: '#F5FCFF', }, header: { flex: 1, backgroundColor: '#3B3738', flexDirection: 'row' }, app_title: { flex: 7, padding: 10 }, header_text: { fontSize: 20, color: '#FFF', fontWeight: 'bold' }, header_button_container: { flex: 3 }, body: { flex: 19 }, btn: { backgroundColor: "#0***5D1", color: "white", margin: 10 }, modal: { height: 300 }, modal_header: { margin: 20, }, modal_body: { alignItems: 'center' }, input_row: { padding: 20 }, modal_header_text: { fontSize: 18, fontWeight: 'bold' }, share_btn: { width: 100 }, news_item: { paddingLeft: 10, paddingRight: 10, paddingTop: 15, paddingBottom: 15, marginBottom: 5, borderBottomWidth: 1, borderBottomColor: '#ccc', flex: 1, flexDirection: 'row' }, news_item_text: { color: '#575757', fontSize: 18 }, upvote: { flex: 2, paddingRight: 15, paddingLeft: 5, alignItems: 'center' }, news_title: { flex: 18, justifyContent: 'center' }, upvote_text: { fontSize: 18, fontWeight: 'bold' } });
開發(fā)服務(wù)器端組件
現(xiàn)在正是時(shí)候要移動(dòng)到的服務(wù)器組件的應(yīng)用程序,在這里,您將學(xué)習(xí)如何保存和 RethinkDB,upvote 新聞項(xiàng)目以及如何通知應(yīng)用程序在數(shù)據(jù)庫中發(fā)生了變化。
創(chuàng)建數(shù)據(jù)庫
我假定您已經(jīng)在您的計(jì)算機(jī)上安裝了RethinkDB。否則的話,請(qǐng)按照RethinkDB網(wǎng)站上的提示(https://www.rethinkdb.com/docs/install/)先行安裝吧。
安裝完畢后,您現(xiàn)在可以打開瀏覽器訪問http://localhost:8080來查看RethinkDB管理控制臺(tái)。在Tables選項(xiàng)卡上單擊,然后單擊Add Database按鈕。這將打開一個(gè)模態(tài)窗口,允許您輸入數(shù)據(jù)庫的名稱,稱之為newssharer吧,***單擊Click。
現(xiàn)在來創(chuàng)建要在其中保存新聞條目的表。單擊Add Table按鈕,命名為news_items,然后單擊Create Table。
安裝依賴性
您可以導(dǎo)航到項(xiàng)目的根目錄 (即newssharer-server.js和package.json文件所在位置),執(zhí)行npm install命令來安裝以下服務(wù)器依賴項(xiàng):
1.Express: 基于Node.js的web框架,允許您創(chuàng)建響應(yīng)特定路由的web服務(wù)器。
2.Body-parser:便于從請(qǐng)求正文中提取JSON字符串。
3.Rethinkdb:Node.js的RethinkDB客戶端。
4.socket.io:一個(gè)實(shí)時(shí)框架,當(dāng)有人分享新聞或?qū)ΜF(xiàn)有新聞投贊成票時(shí)允許您連接到所有的客戶端。
服務(wù)端編程
文件newssharer-server.js的代碼如下:
var r = require('rethinkdb'); var express = require('express'); var app = express(); var server = require('http').createServer(app); var io = require('socket.io')(server); var bodyParser = require('body-parser'); app.use(bodyParser.json()); var connection; r.connect({host: 'localhost', port: 28015}, function(err, conn) { if(err) throw err; connection = conn; r.db('newssharer').table('news_items') .orderBy({index: r.desc('upvotes')}) .changes() .run(connection, function(err, cursor){ if (err) throw err; io.sockets.on('connection', function(socket){ cursor.each(function(err, row){ if(err) throw err; io.sockets.emit('news_updated', row); }); }); }); }); app.get('/create-table', function(req, res){ r.db('newssharer').table('news_items').indexCreate('upvotes').run(connection, function(err, result){ console.log('boom'); res.send('ok') }); }); app.get('/fill', function(req, res){ r.db('newssharer').table('news_items').insert([ { title: 'A Conversation About Fantasy User Interfaces', url: 'https://www.subtraction.com/2016/06/02/a-conversation-about-fantasy-user-interfaces/', upvotes: 30 }, { title: 'Apple Cloud Services Outage', url: 'https://www.apple.com/support/systemstatus/', upvotes: 20 } ]).run(connection, function(err, result){ if (err) throw err; res.send('news_items table was filled!'); }); }); app.get('/news', function(req, res){ res.header("Content-Type", "application/json"); r.db('newssharer').table('news_items') .orderBy({index: r.desc('upvotes')}) .limit(30) .run(connection, function(err, cursor) { if (err) throw err; cursor.toArray(function(err, result) { if (err) throw err; res.send(result); }); }); }); app.post('/save-newsitem', function(req, res){ var news_title = req.body.news_title; var news_url = req.body.news_url; r.db('newssharer').table('news_items').insert([ { 'title': news_title, 'url': news_url, 'upvotes': 100 }, ]).run(connection, function(err, result){ if (err) throw err; res.send('ok'); }); }); app.post('/upvote-newsitem', function(req, res){ var id = req.body.news_id; var upvote_count = req.body.upvotes; r.db('newssharer').table('news_items') .filter(r.row('id').eq(id)) .update({upvotes: upvote_count}) .run(connection, function(err, result) { if (err) throw err; res.send('ok'); }); }); app.get('/test/upvote', function(req, res){ var id = '144f7d7d-d580-42b3-8704-8372e9b2a17c'; var upvote_count = 350; r.db('newssharer').table('news_items') .filter(r.row('id').eq(id)) .update({upvotes: upvote_count}) .run(connection, function(err, result) { if (err) throw err; res.send('ok'); }); }); app.get('/test/save-newsitem', function(req, res){ r.db('newssharer').table('news_items').insert([ { 'title': 'banana', 'url': 'http://banana.com', 'upvotes': 190, 'downvotes': 0 }, ]).run(connection, function(err, result){ if(err) throw err; res.send('ok'); }); }); server.listen(3000);
在上面的代碼中,您首先導(dǎo)入依賴項(xiàng):
var r = require('rethinkdb'); var express = require('express'); var app = express(); var server = require('http').createServer(app); var io = require('socket.io')(server); var bodyParser = require('body-parser'); app.use(bodyParser.json());
然后,創(chuàng)建用于存儲(chǔ)當(dāng)前的RethinkDB連接的變量。
var connection;
監(jiān)聽變化
連接到RethinkDB數(shù)據(jù)庫,默認(rèn)情況下在端口28015(即創(chuàng)建連接的地址處)上運(yùn)行RethinkDB。如果你想使用不同的端口,可以將28015替換為你所使用的端口。
r.connect({host: 'localhost', port: 28015}, function(err, conn) { if(err) throw err; connection = conn; ... });
還是在數(shù)據(jù)庫連接代碼中,查詢newssharer數(shù)據(jù)庫中的表news_items,并按投票計(jì)數(shù)排序項(xiàng)目。然后,使用RethinkDB的Changefeeds功能來偵聽表(數(shù)據(jù)庫排序日志)中的更改。表中發(fā)生每一次變化(CRUD操作),都會(huì)發(fā)出此通知。
r.db('newssharer').table('news_items') .orderBy({index: r.desc('upvotes')}) .changes() .run(connection, function(err, cursor){ ... });
在run方法里面的回調(diào)函數(shù)中,初始化套接字連接并循環(huán)遍歷cursor的內(nèi)容。Cursor描述了表中所做的更改。每次發(fā)生更改時(shí),都會(huì)觸發(fā)cursor.each函數(shù)。
【注意】該函數(shù)并不包含所有的數(shù)據(jù)更改。每當(dāng)有新的更改時(shí),獲取替換以前的更改。這意味著,在任何給定時(shí)間內(nèi)只能遍歷單行。這將允許您使用socket.io來把更改發(fā)送到客戶端。
if (err) throw err; //check if there are errors and return it if any io.sockets.on('connection', function(socket){ cursor.each(function(err, row){ if(err) throw err; io.sockets.emit('news_updated', row); }); });
如果有新聞項(xiàng)被共享,那么每一行都有下列結(jié)構(gòu):
{ "old_val": null, "new_val": { "id": 1, "news_title": "Google", "news_url": "http://google.com", "upvotes": 0 } }
這就是為什么前面代碼中檢查null,因?yàn)橐粋€(gè)新建的新聞項(xiàng)不會(huì)有一個(gè)old_val。
如果用戶對(duì)一個(gè)新聞項(xiàng)投贊成票:
{ "old_val": { "id": 1, "news_title": "Google", "news_url": "http://google.com", "upvotes": 0 } "new_val": { "id": 1, "news_title": "Google", "news_url": "http://google.com", "upvotes": 1 } }
那么,將返回該行中的對(duì)應(yīng)于舊值和新值的完整結(jié)構(gòu)。這意味著,你可以在一個(gè)客戶端更新多個(gè)字段,并且可以把所有這些變化發(fā)送給其他相連接的客戶端。借助于RethinkDB的changfeeds特性,基于RethinkDB開發(fā)實(shí)時(shí)應(yīng)用變得特別簡(jiǎn)單。
對(duì)Upvotes字段添加索引
下面這個(gè)路由把一個(gè)索引添加到upvotes字段上:
app.get('/add-index', function(req, res){ r.db('newssharer').table('news_items').indexCreate('upvotes').run(connection, function(err, result){ res.send('ok') }); });
上述創(chuàng)建索引操作對(duì)于orderBy功能來說是必需的,因?yàn)樗枰闩判虻淖侄斡幸粋€(gè)索引。
.orderBy({index: r.desc('upvotes')})
當(dāng)服務(wù)器運(yùn)行時(shí),在測(cè)試應(yīng)用前請(qǐng)確保在你的瀏覽器中打開網(wǎng)址http://localhost:3000/add-index。注意,上面這個(gè)路由僅需要調(diào)用一次。
添加空新聞項(xiàng)
下面這個(gè)路由將在news_items表中插入一個(gè)空的入口。實(shí)際上這是用于測(cè)試目的的一個(gè)可選功能;這樣一來,在不需要使用程序添加的情況下你會(huì)立即看到表中出現(xiàn)一個(gè)新項(xiàng)。
app.get('/fill', function(req, res){ r.db('newssharer').table('news_items').insert([ { title: 'A Conversation About Fantasy User Interfaces', url: 'https://www.subtraction.com/2016/06/02/a-conversation-about-fantasy-user-interfaces/', upvotes: 30 }, { title: 'Apple Cloud Services Outage', url: 'https://www.apple.com/support/systemstatus/', upvotes: 20 } ]).run(connection, function(err, result){ if (err) throw err; res.send('news_items table was filled!'); }); });
返回新聞項(xiàng)
下面的路由將返回新聞項(xiàng):
app.get('/news', function(req, res){ res.header("Content-Type", "application/json"); r.db('newssharer').table('news_items') .orderBy({index: r.desc('upvotes')}) .limit(30) .run(connection, function(err, cursor) { if (err) throw err; cursor.toArray(function(err, result) { if (err) throw err; res.send(result); }); }); });
注意到,該新聞項(xiàng)按照贊成票數(shù)從高到低的順序排序,并且限定為最多30條。另外,這里沒有使用cursor.each來遍歷新聞項(xiàng),而是使用cursor.toArray并通過如下結(jié)構(gòu)把新聞項(xiàng)轉(zhuǎn)換成一個(gè)數(shù)組:
[ { "title": "A Conversation About Fantasy User Interfaces", "url": "https://www.subtraction.com/2016/06/02/a-conversation-about-fantasy-user-interfaces/", "upvotes": 30 }, { "title": "Apple Cloud Services Outage", "url": "https://www.apple.com/support/systemstatus/", "upvotes": 20 } ]
新建與保存新聞項(xiàng)
下面的路由實(shí)現(xiàn)新建與保存新聞項(xiàng)功能:
app.post('/save-newsitem', function(req, res){ var news_title = req.body.news_title; var news_url = req.body.news_url; r.db('newssharer').table('news_items').insert([ { 'title': news_title, 'url': news_url, 'upvotes': 100 }, ]).run(connection, function(err, result){ if (err) throw err; res.send('ok'); }); });
當(dāng)一個(gè)用戶共享應(yīng)用程序中的新聞項(xiàng)時(shí)將調(diào)用上面的路由。它接收來自于請(qǐng)求正文(Request Body)的新聞標(biāo)題和URL數(shù)據(jù)。最初的贊成票數(shù)設(shè)置為100,但您可以選擇另一個(gè)數(shù)字。
對(duì)新聞項(xiàng)投贊成票
下面的路由實(shí)現(xiàn)對(duì)新聞項(xiàng)投贊成票功能:
app.post('/upvote-newsitem', function(req, res){ var id = req.body.news_id; var upvote_count = req.body.upvotes; r.db('newssharer').table('news_items') .filter(r.row('id').eq(id)) .update({upvotes: upvote_count}) .run(connection, function(err, result) { if (err) throw err; res.send('ok'); }); });
當(dāng)用戶在程序中對(duì)新聞項(xiàng)投贊成票時(shí)將調(diào)用上面的路由函數(shù)。該函數(shù)使用新聞項(xiàng)的ID來取回新聞數(shù)據(jù)并更新之。
【注意】你已經(jīng)在應(yīng)用程序中把upvotes值加1了;因此,也就已經(jīng)提供了請(qǐng)求主體(Request Body)內(nèi)的數(shù)據(jù)。
測(cè)試保存與投贊成票功能
至此,我們已經(jīng)建立了好幾個(gè)路由?,F(xiàn)在,不妨來測(cè)試一下保存功能與投贊成票功能。實(shí)現(xiàn)這一測(cè)試的***時(shí)機(jī)是當(dāng)應(yīng)用程序已經(jīng)運(yùn)行于移動(dòng)設(shè)備上時(shí)。如此一來,你便能夠看到UI更新情況。我們將在下一節(jié)討論如何運(yùn)行程序的問題。
下面的路由實(shí)現(xiàn)測(cè)試保存功能:
app.get('/test/save-newsitem', function(req, res){ r.db('newssharer').table('news_items').insert([ { 'title': 'banana', 'url': 'http://banana.com', 'upvotes': 190, 'downvotes': 0 }, ]).run(connection, function(err, result){ if(err) throw err; res.send('ok'); }); });
下面的路由實(shí)現(xiàn)對(duì)新聞項(xiàng)投贊成票的測(cè)試功能。記住一定要使用已有新聞項(xiàng)的ID來取代程序中的ID才能使測(cè)試正常進(jìn)行:
app.get('/test/upvote', function(req, res){ var id = '144f7d7d-d580-42b3-8704-8372e9b2a17c'; var upvote_count = 350; r.db('newssharer').table('news_items') .filter(r.row('id').eq(id)) .update({upvotes: upvote_count}) .run(connection, function(err, result) { if (err) throw err; res.send('ok'); }); });
運(yùn)行服務(wù)器端程序
到此,我假定RethinkDB一直運(yùn)行于后臺(tái)中。如果還沒有運(yùn)行起來,請(qǐng)先運(yùn)行它。一旦RethinkDB運(yùn)行后,你就可以在項(xiàng)目根目錄下執(zhí)行命令node newssharer-server.js來運(yùn)行程序的服務(wù)器端組件了。
感謝各位的閱讀,以上就是“基于RethinkDB +React Native怎么開發(fā)Web應(yīng)用程序”的內(nèi)容了,經(jīng)過本文的學(xué)習(xí)后,相信大家對(duì)基于RethinkDB +React Native怎么開發(fā)Web應(yīng)用程序這一問題有了更深刻的體會(huì),具體使用情況還需要大家實(shí)踐驗(yàn)證。這里是創(chuàng)新互聯(lián),小編將為大家推送更多相關(guān)知識(shí)點(diǎn)的文章,歡迎關(guān)注!