您好,登錄后才能下訂單哦!
這篇文章主要講解了“基于RethinkDB +React Native怎么開發Web應用程序”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“基于RethinkDB +React Native怎么開發Web應用程序”吧!
簡介
一個實時應用程序能夠使用戶***時間了解他想了解的信息。用戶不必不停地刷新用戶界面來獲取***的消息更新,應用程序的服務器端會自動更新客戶端應用的。在本文中,我們將使用時下流行的RethinkDB +React Native框架開發一個真正實時的移動Web應用程序。
下圖給出示例工程的運行時快照。
下面,讓我們首先來分析一下手機應用程序的編碼情況,然后再來討論服務器端組件相關編程,其中將使用到Node、Express、Socket.io和RethinkDB等技術。
安裝依賴性
從你克隆下來的工程中導航到NewsShare目錄下,然后執行命令npm install來安裝一下下面這些工程依賴項:
1.react-native:這是React Native(本機)框架。
2.lodash:用于管理新聞項數組,以便通過票數來限制和排序該數組。
3.react-native-modalbox:用于創建模態對話框來共享一則新聞。
4.react-native-button:React Native模態對話框依賴于它,用于創建按鈕。
5.react-native-vector-icons:用于使用流行圖標集,如FontAwesome和Ionicons等來創建圖標。這主要用于為投票按鈕創建圖標。
6.socket.io-client:Socket.io的客戶端組件,它是一個實時應用程序框架。
鏈接圖標
安裝依賴關系后,還需要一個額外步驟就是使圖標正常工作,即要將它們鏈接到應用程序。這是通過使用rnpm——React Native的軟件包管理器實現的。
我們要使用npm來安裝 rnpm,格式如下:
npm install rnpm -g
然后,就可以執行NewsSharer目錄下的rnpm link命令來鏈接圖標了。
開發移動客戶端程序
下面給出的是文件index.android.js的內容:
import React, { Component } from 'react'; import { AppRegistry, StyleSheet, View } from 'react-native'; import Main from './components/Main'; class NewsSharer extends Component { render() { return ( <View style={styles.container}> <Main /> </View> ); } } const styles = StyleSheet.create({ container: { flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: '#F5FCFF', } }); AppRegistry.registerComponent('NewsSharer', () => NewsSharer);
該文件是Android應用程序的入口點文件。如果你想把它部署到iOS上,那么你可以把上述代碼復制到文件index.ios.js中。
此文件的主要任務是導入Main組件,組件是應用程序的核心所在。當您導入組件而不是重復地為每個平臺編碼時,這可以大大減少編程的重復性。
編寫主應用程序組件
在路徑components/Main.js下創建文件Main.js,內容如下:
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 ( <View style={styles.news_item}> <TouchableHighlight onPress={this.upvoteNewsItem.bind(this, news.id, news.upvotes)} underlayColor={"#E8E8E8"}> <View style={styles.upvote}> <Icon name="triangle-up" size={30} color="#666" /> <Text style={styles.upvote_text}>{news.upvotes}</Text> </View> </TouchableHighlight> <TouchableHighlight onPress={this.openPage.bind(this, news.url)} underlayColor={"#E8E8E8"}> <View style={styles.news_title}> <Text style={styles.news_item_text}>{news.title}</Text> </View> </TouchableHighlight> </View> ); } render(){ return ( <View style={styles.container}> <View style={styles.header}> <View style={styles.app_title}> <Text style={styles.header_text}>News Sharer</Text> </View> <View style={styles.header_button_container}> <Button onPress={this.openModal.bind(this)} style={styles.btn}> Share News </Button> </View> </View> { this.state.is_news_loaded && <View style={styles.body}> <ListView initialListSize={1} dataSource={this.state.news} style={styles.news} renderRow={this.renderNews.bind(this)}></ListView> </View> } <Modal isOpen={this.state.is_modal_open} style={styles.modal} position={"center"} > <View style={styles.modal_body}> <View style={styles.modal_header}> <Text style={styles.modal_header_text}>Share News</Text> </View> <View style={styles.input_row}> <TextInput style={{height: 40, borderColor: 'gray', borderWidth: 1}} onChangeText={(text) => this.setState({news_title: text})} value={this.state.news_title} placeholder="Title" /> </View> <View style={styles.input_row}> <TextInput style={{height: 40, borderColor: 'gray', borderWidth: 1}} onChangeText={(text) => this.setState({news_url: text})} value={this.state.news_url} placeholder="URL" keyboardType="url" /> </View> <View style={styles.input_row}> <Button onPress={this.shareNews.bind(this)} style={[styles.btn, styles.share_btn]}> Share </Button> </View> </View> </Modal> </View> ); } } 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);
下面來分析一下上面代碼。首先,導入編程中所需要的內置的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';
注意,你使用如下方式導入了自己開發的另外文件中的代碼:
import "../UserAgent";
這是你在根目錄NewsSharer下看到的UserAgent.js文件。它包含的代碼用于設置用戶代理為react-native——Socket.io需要這樣做,或者它會假設程序運行于瀏覽器環境中。
window.navigator.userAgent = 'react-native';
接下來,確定應用程序要請求的基URL。如果您要進行本地測試,這可能是您的計算機的內部IP地址。為了使這能夠工作,你必須確保你的手機或平板電腦連接到與您的計算機位于同一網絡。
var base_url = 'http://YOUR_DOMAIN_NAME_OR_IP_ADDRESS:3000';
接下來,在構造函數中,初始化套接字連接:
this.socket = io(base_url, { transports: ['websocket'] });
然后,設置應用程序的默認狀態:
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 };
此函數的功能是使用內置的fetch方法從服務器端取回新聞項目。它向news路由發出GET請求,然后從響應中提取news_items對象。這個對象用于稍后創建客戶端ListView組件所需的新聞數據源。一旦創建,它便使用新聞數據源更新狀態;這樣一來,用戶界面新聞項內容也可以得到相應的更新。
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的生命周期方法之一。這允許您可以在初始化渲染發生前執行你自己的定制代碼。也正是在此處,你監聽Socket.io的服務器組件發出的news_updated事件;而當此事件發生時,它可能是兩件事中之一——或者當用戶共享新聞項時或者當他們對現有新聞項投贊成票時。
值得注意的是,當出現新的新聞項時RethinkDB的changefeed將為old_val返回一個null值。這也正是我們區分上面兩種可能性的辦法。如果用戶共享一個新聞項,那么將其推到news_items數組中;否則,查找投贊成票的新聞項并更新其贊成票計數。現在,您可以更新用戶界面來反映所做的更改了。
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函數使用贊成票數按照從高到低訂閱新聞項。一旦排序,便提取最前面的30條新聞,同時更新狀態。
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方法是另一個React生命周期方法,此方法在初始渲染之后調用。在此方法中,我們實現從服務器端獲取新聞項。
【注】,如果你想在安裝組件之前發出請求的話,也可以從componentWillMount方法中從服務器端獲取新聞項。
componentDidMount(){ this.getNewsItems(); }
接下來的upvoteNewsItem方法將向服務器端發出一個投贊成票新聞項請求:
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方法分別負責顯示與隱藏共享新聞內容的模態對話框。
openModal(){ this.setState({ is_modal_open: true }); } closeModal(){ this.setState({ is_modal_open: false }); }
繼續往下來,shareNews函數用于發送請求來創建一條新聞項:
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:
openPage(url){ Linking.canOpenURL(url).then(supported => { if(supported){ Linking.openURL(url); } }); }
接下來,RenderNews函數將針對每個新聞項返回UI。這個方法中還負責顯示“upvote”按鈕、贊成票數和新聞標題。其中,新聞標題被封裝在一個TouchableHighlight組件。這允許我們通過執行openPage函數來打開對應的URL。對于贊成票數,也要這樣做。
【注意】該代碼使用了TouchableHighlight組件而不是Button組件,因為Button組件不能內含View或Text組件。
renderNews(news){ return ( <View style={styles.news_item}> <TouchableHighlight onPress={this.upvoteNewsItem.bind(this, news.id, news.upvotes)} underlayColor={"#E8E8E8"}> <View style={styles.upvote}> <Icon name="triangle-up" size={30} color="#666" /> <Text style={styles.upvote_text}>{news.upvotes}</Text> </View> </TouchableHighlight> <TouchableHighlight onPress={this.openPage.bind(this, news.url)} underlayColor={"#E8E8E8"}> <View style={styles.news_title}> <Text style={styles.news_item_text}>{news.title}</Text> </View> </TouchableHighlight> </View> ); }
再往下,render函數負責返回整個應用程序的UI部分:
render(){ ... }
在render函數中,要建立包含應用程序的標題的標題和一個按鈕用于打開模態對話框來分享新聞項的按鈕。
<View style={styles.header}> <View style={styles.app_title}> <Text style={styles.header_text}>News Sharer</Text> </View> <View style={styles.header_button_container}> <Button onPress={this.openModal.bind(this)} style={styles.btn}> Share News </Button> </View> </View>
對于body部分,使用ListView組件來渲染新聞項。它有三個必需的參數:initialListSize,dataSource和renderRow。其中,InitialListSize被設置為1;這樣一來,ListView就能夠針對內容部分的每一個幀逐行渲染。如果你想一次顯示所有行的話,你還可以把這值修改得更大些。dataSource對應于新聞項,renderRow函數用于渲染每一行中的新聞項。
{ this.state.is_news_loaded && <View style={styles.body}> <ListView initialListSize={1} dataSource={this.state.news} style={styles.news} renderRow={this.renderNews.bind(this)}></ListView> </View> }
接下來是定義分享新聞的模態對話框。此對話框中使用了兩個文本字段分別用于輸入標題和新聞URL,還有一個按鈕用于將新聞提交到服務器。文本字段使用了TextInput組件實現。由于沒有使用標簽控件,所以需要在TextInput組件中輸入占位符文本來提示用戶要輸入的內容。
這兩個文本字段都有一個onChangeText方法,在文本值更新時使用。keyboardType的Url用于新聞URL的文本字段;這樣的話,它將打開設備的鍵盤,實現輸入URL的優化支持。用戶不必手動輸入內容,可以使用拷貝和粘貼。文本字段的下方是用于共享新聞的按鈕。按鈕的點擊將調用先前定義的shareNews函數。
<Modal isOpen={this.state.is_modal_open} style={styles.modal} position={"center"} > <View style={styles.modal_body}> <View style={styles.modal_header}> <Text style={styles.modal_header_text}>Share News</Text> </View> <View style={styles.input_row}> <TextInput style={{height: 40, borderColor: 'gray', borderWidth: 1}} onChangeText={(text) => this.setState({news_title: text})} value={this.state.news_title} placeholder="Title" /> </View> <View style={styles.input_row}> <TextInput style={{height: 40, borderColor: 'gray', borderWidth: 1}} onChangeText={(text) => this.setState({news_url: text})} value={this.state.news_url} placeholder="URL" keyboardType="url" /> </View> <View style={styles.input_row}> <Button onPress={this.shareNews.bind(this)} style={[styles.btn, styles.share_btn]}> Share </Button> </View> </View> </Modal>
接下來,為組件設置樣式:
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' } });
開發服務器端組件
現在正是時候要移動到的服務器組件的應用程序,在這里,您將學習如何保存和 RethinkDB,upvote 新聞項目以及如何通知應用程序在數據庫中發生了變化。
創建數據庫
我假定您已經在您的計算機上安裝了RethinkDB。否則的話,請按照RethinkDB網站上的提示(https://www.rethinkdb.com/docs/install/)先行安裝吧。
安裝完畢后,您現在可以打開瀏覽器訪問http://localhost:8080來查看RethinkDB管理控制臺。在Tables選項卡上單擊,然后單擊Add Database按鈕。這將打開一個模態窗口,允許您輸入數據庫的名稱,稱之為newssharer吧,***單擊Click。
現在來創建要在其中保存新聞條目的表。單擊Add Table按鈕,命名為news_items,然后單擊Create Table。
安裝依賴性
您可以導航到項目的根目錄 (即newssharer-server.js和package.json文件所在位置),執行npm install命令來安裝以下服務器依賴項:
1.Express: 基于Node.js的web框架,允許您創建響應特定路由的web服務器。
2.Body-parser:便于從請求正文中提取JSON字符串。
3.Rethinkdb:Node.js的RethinkDB客戶端。
4.socket.io:一個實時框架,當有人分享新聞或對現有新聞投贊成票時允許您連接到所有的客戶端。
服務端編程
文件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);
在上面的代碼中,您首先導入依賴項:
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());
然后,創建用于存儲當前的RethinkDB連接的變量。
var connection;
監聽變化
連接到RethinkDB數據庫,默認情況下在端口28015(即創建連接的地址處)上運行RethinkDB。如果你想使用不同的端口,可以將28015替換為你所使用的端口。
r.connect({host: 'localhost', port: 28015}, function(err, conn) { if(err) throw err; connection = conn; ... });
還是在數據庫連接代碼中,查詢newssharer數據庫中的表news_items,并按投票計數排序項目。然后,使用RethinkDB的Changefeeds功能來偵聽表(數據庫排序日志)中的更改。表中發生每一次變化(CRUD操作),都會發出此通知。
r.db('newssharer').table('news_items') .orderBy({index: r.desc('upvotes')}) .changes() .run(connection, function(err, cursor){ ... });
在run方法里面的回調函數中,初始化套接字連接并循環遍歷cursor的內容。Cursor描述了表中所做的更改。每次發生更改時,都會觸發cursor.each函數。
【注意】該函數并不包含所有的數據更改。每當有新的更改時,獲取替換以前的更改。這意味著,在任何給定時間內只能遍歷單行。這將允許您使用socket.io來把更改發送到客戶端。
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); }); });
如果有新聞項被共享,那么每一行都有下列結構:
{ "old_val": null, "new_val": { "id": 1, "news_title": "Google", "news_url": "http://google.com", "upvotes": 0 } }
這就是為什么前面代碼中檢查null,因為一個新建的新聞項不會有一個old_val。
如果用戶對一個新聞項投贊成票:
{ "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 } }
那么,將返回該行中的對應于舊值和新值的完整結構。這意味著,你可以在一個客戶端更新多個字段,并且可以把所有這些變化發送給其他相連接的客戶端。借助于RethinkDB的changfeeds特性,基于RethinkDB開發實時應用變得特別簡單。
對Upvotes字段添加索引
下面這個路由把一個索引添加到upvotes字段上:
app.get('/add-index', function(req, res){ r.db('newssharer').table('news_items').indexCreate('upvotes').run(connection, function(err, result){ res.send('ok') }); });
上述創建索引操作對于orderBy功能來說是必需的,因為它需要你排序的字段有一個索引。
.orderBy({index: r.desc('upvotes')})
當服務器運行時,在測試應用前請確保在你的瀏覽器中打開網址http://localhost:3000/add-index。注意,上面這個路由僅需要調用一次。
添加空新聞項
下面這個路由將在news_items表中插入一個空的入口。實際上這是用于測試目的的一個可選功能;這樣一來,在不需要使用程序添加的情況下你會立即看到表中出現一個新項。
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); }); }); });
注意到,該新聞項按照贊成票數從高到低的順序排序,并且限定為最多30條。另外,這里沒有使用cursor.each來遍歷新聞項,而是使用cursor.toArray并通過如下結構把新聞項轉換成一個數組:
[ { "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 } ]
新建與保存新聞項
下面的路由實現新建與保存新聞項功能:
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'); }); });
當一個用戶共享應用程序中的新聞項時將調用上面的路由。它接收來自于請求正文(Request Body)的新聞標題和URL數據。最初的贊成票數設置為100,但您可以選擇另一個數字。
對新聞項投贊成票
下面的路由實現對新聞項投贊成票功能:
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'); }); });
當用戶在程序中對新聞項投贊成票時將調用上面的路由函數。該函數使用新聞項的ID來取回新聞數據并更新之。
【注意】你已經在應用程序中把upvotes值加1了;因此,也就已經提供了請求主體(Request Body)內的數據。
測試保存與投贊成票功能
至此,我們已經建立了好幾個路由。現在,不妨來測試一下保存功能與投贊成票功能。實現這一測試的***時機是當應用程序已經運行于移動設備上時。如此一來,你便能夠看到UI更新情況。我們將在下一節討論如何運行程序的問題。
下面的路由實現測試保存功能:
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'); }); });
下面的路由實現對新聞項投贊成票的測試功能。記住一定要使用已有新聞項的ID來取代程序中的ID才能使測試正常進行:
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'); }); });
運行服務器端程序
到此,我假定RethinkDB一直運行于后臺中。如果還沒有運行起來,請先運行它。一旦RethinkDB運行后,你就可以在項目根目錄下執行命令node newssharer-server.js來運行程序的服務器端組件了。
感謝各位的閱讀,以上就是“基于RethinkDB +React Native怎么開發Web應用程序”的內容了,經過本文的學習后,相信大家對基于RethinkDB +React Native怎么開發Web應用程序這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。