溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

基于RethinkDB +React Native怎么開發(fā)Web應(yīng)用程序

發(fā)布時(shí)間:2021-12-24 16:11:46 來源:億速云 閱讀:144 作者:iii 欄目:編程語言

這篇文章主要講解了“基于RethinkDB +React Native怎么開發(fā)Web應(yīng)用程序”,文中的講解內(nèi)容簡(jiǎn)單清晰,易于學(xué)習(xí)與理解,下面請(qǐng)大家跟著小編的思路慢慢深入,一起來研究和學(xué)習(xí)“基于RethinkDB +React Native怎么開發(fā)Web應(yīng)用程序”吧!

簡(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í)快照。

基于RethinkDB +React Native怎么開發(fā)Web應(yīng)用程序

下面,讓我們首先來分析一下手機(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 (  <View style={styles.container}>  <Main />  </View>  );  }  }  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 (  <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);

下面來分析一下上面代碼。首先,導(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&mdash;&mdash;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í),它可能是兩件事中之一&mdash;&mdash;或者當(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 (  <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函數(shù)負(fù)責(zé)返回整個(gè)應(yīng)用程序的UI部分:

render(){  ...  }

在render函數(shù)中,要建立包含應(yīng)用程序的標(biāo)題的標(biāo)題和一個(gè)按鈕用于打開模態(tài)對(duì)話框來分享新聞項(xiàng)的按鈕。

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

對(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 &&  <View style={styles.body}>  <ListView initialListSize={1} dataSource={this.state.news} style={styles.news} renderRow={this.renderNews.bind(this)}></ListView>  </View>  }

接下來是定義分享新聞的模態(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ù)。

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

接下來,為組件設(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。

基于RethinkDB +React Native怎么開發(fā)Web應(yīng)用程序

現(xiàn)在來創(chuàng)建要在其中保存新聞條目的表。單擊Add Table按鈕,命名為news_items,然后單擊Create Table。

基于RethinkDB +React Native怎么開發(fā)Web應(yīng)用程序

安裝依賴性

您可以導(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)證。這里是億速云,小編將為大家推送更多相關(guān)知識(shí)點(diǎn)的文章,歡迎關(guān)注!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI