Meteor开发指南 — 使用Meteor作为React Native的实时后端

本文来自Differential Blog,不过文中示例代码有不少bug,有些是版本问题,有些是npm包的问题,测试修改过后的Github示例代码在此:https://github.com/loongmxbt/meteor-react-native-basic 。下面是正文部分。

Parse最近宣布停止服务,许多公司会寻求它的替代品。这次Parse的关门会让许多人不会首选BaaS产品,转而倾向于自行实现后端,比如说使用Meteor。

我们来谈谈如何将一个React Native的App连接到Meteor App(作为服务端)。这篇教程假设你已经安装好了React NativeMeteor,并且能成功运行。如果你还没有配置好React Native环境的话,可以查看React Native中文文档

你可以通过这里看到完成好的app的源代码,访问Github

创建Meteor App

首先创建一个Meteor App:

meteor create meteor-app

然后删除autopublish和insecure包,这通常是最佳实践:

cd meteor-app && meteor remove autopublish insecure

然后添加random包:

meteor add random

然后删除默认生成的三个html,css,js文件:

rm meteor-app.*

接着按照如下的目录和文件结构创建:

  • /both/posts.js
  • /client/home.html
  • /client/home.js
  • /client/index.html
  • /server/app.js

我们马上会逐一讲解这些文件。

为Meteor App增加功能

这会是一个非常简单的app,其中演示的只是如何创建连接,订阅数据和调用方法,我们所要做的是创建一个Posts集合,给它一些初始种子数据,然后发布这个集合。然后在客户端我们订阅数据,并返回一个总体文章的计数。用户可以添加或者删除文章,这是我们要使用React Native模仿的功能。这里是代码:

/both/posts.js文件中,我们创建Posts集合并且创建两个Meteor方法:

Posts = new Mongo.Collection('posts');

Meteor.methods({  
  'addPost': function() {
    Posts.insert({title: 'Post ' + Random.id()});
  },

  'deletePost': function() {
    let post = Posts.findOne();
    if (post) {
      Posts.remove({_id: post._id});
    }
  }
})

/server/app.js中,我们创建初始数据并发布Posts集合:

Meteor.startup(function() {  
  if (Posts.find().count() === 0) {
    for (i = 1; i <= 10; i++) {
      Posts.insert({title: 'Post ' + Random.id()});
    }
  }
});

Meteor.publish('posts', function() {  
  return Posts.find();
});

/client/home.html中,我们创建一个最简模板:

<template name="home">  
  <h1>Post Count: {{count}}</h1>
  <button id="increment">Increment</button>
  <button id="decrement">Decrement</button>
</template>  

/client/home.js中,我们订阅posts并创建模板helpers:

Template.home.onCreated(function() {  
  this.subscribe('posts');
});

Template.home.helpers({  
  count() {
    return Posts.find().count();
  }
});

Template.home.events({  
  'click #increment': function(e) {
    e.preventDefault();

    Meteor.call('addPost');
  },

  'click #decrement': function(e) {
    e.preventDefault();

    Meteor.call('deletePost');
  }
})

/client/index.html中,我们创建整体模板:

<head>  
  <title>meteor-app</title>
</head>

<body>  
  {{> home}}
</body>  

现在你就创建好了一个功能完备的Meteor App了,命令行输入meteor启动应用,试一下添加删除帖子功能吧!

Meteor App

创建 React Native App

在一个新的终端窗口输入以下命令:

react-native init RNApp && cd RNApp

注意:此处React Native新版本用的是babel6,可是有些依赖的库并不是这个版本,就会导致红屏出错,所以解决方案就是把这个所以babel删了,升级依赖。

  1. 先删除依赖包
rm -rf node_modules ncu -u npm install

2,修改package.json文件

"scripts": { 
    "clean:babelrc": "find ./node_modules -name react-packager -prune -o -name '.babelrc' -print | xargs rm -f", 
    "postinstall": "npm run clean:babelrc" 
}

当然,还有一个比较简单粗暴的解决方法,就是直接删除错误提示中的.babelrc文件:

rm node_modules/react-deep-force-update/.babelrc

设置 React Native App

我们在生成的app中创建和修改2个文件。

首先创建app/index.js

import React, {  
  View,
  Text,
  StyleSheet
} from 'react-native';

import Button from './button';

export default React.createClass({  
  getInitialState() {
    return {
      connected: false,
      posts: {}
    }
  },

  handleIncrement() {
    console.log('inc');
  },

  handleDecrement() {
    console.log('dec');
  },

  render() {
    let count = 10;
    return (
      <View style={styles.container}>
        <View style={styles.center}>
          <Text>Posts: {count}</Text>
          <Button text="Increment" onPress={this.handleIncrement}/>
          <Button text="Decrement" onPress={this.handleDecrement}/>
        </View>
      </View>
    );
  }
});

const styles = StyleSheet.create({  
  container: {
    flex: 1,
    justifyContent: 'center',
    backgroundColor: '#F5FCFF',
  },
  center: {
    alignItems: 'center'
  }
});

创建app/button.js

import React, {  
  View,
  Text,
  TouchableOpacity,
  StyleSheet
} from 'react-native';

export default React.createClass({  
  render() {
    let { text, onPress } = this.props;

    return (
      <TouchableOpacity style={styles.button} onPress={onPress}>
        <Text>{text}</Text>
      </TouchableOpacity>
    );
  }
});

const styles = StyleSheet.create({  
  button: {
    flex: 1,
    backgroundColor: '#eee',
    paddingHorizontal: 20,
    paddingVertical: 10,
    marginVertical: 10
  }
});

修改index.ios.js

import React, {  
  AppRegistry,
  Component
} from 'react-native';

import App from './app';

class RNApp extends Component {  
  render() {
    return <App />;
  }
}
AppRegistry.registerComponent('RNApp', () => RNApp);  

修改index.android.js

import React, {
  AppRegistry,
  Component
} from 'react-native';

import App from './app';

class RNApp extends Component {
  render() {
    return <App />;
  }
}
AppRegistry.registerComponent('RNApp', () => RNApp);

你现在已经有了一个可以运行的React Native App了,尽管它并没做什么事。

React Native App

我们可以点击Increment和Decrement两个按钮,可以看到控制台中的log:

Console log

连接React Native应用到Meteor服务器

现在到了有趣的部分了,我们来让两个app相互间通讯,我们会使用node-ddp-client这个包,你也可以使用其他类似的扩展包。

首先我们通过npm添加扩展包:

npm install ddp --save

首先我们要导入ddp client这个库并且初始化它。由于这是个开发应用示例,所以我们只使用到默认配置。node-ddp-client的README阐述了一些其他ddp的配置。

app/index.js中:

import React, {  
  View,  
  Text,  
  StyleSheet  
} from 'react-native';

import Button from './button';

import DDPClient from 'ddp-client';  
let ddpClient = new DDPClient();

export default React.createClass({  
/*
 * Removed from snippet for brevity
 */
});

下一步我们真正连接到服务器。同样在app/index.js中:

/*
 * Removed from snippet for brevity
 */

import DDPClient from 'ddp-client';  
let ddpClient = new DDPClient();

export default React.createClass({  
  getInitialState() {
    return {
      connected: false,
      posts: {}
    }
  },

  componentDidMount() {
    ddpClient.connect((err, wasReconnect) => {
      let connected = true;
      if (err) connected = false;

      this.setState({ connected: connected });
    });
  },

  /*
   * Removed from snippet for brevity
   */
});

我们现在就连接上了!你可以基于这些做更多的事情,但这里只讲解最基本的场景。这是你开始所需要的一切。

在React Native中订阅

app/index.js中,发布你的订阅。当订阅完成后,目前我们只是更新state来获取posts数据。

/*
 * Removed from snippet for brevity
 */

export default React.createClass({  
  getInitialState() {
    return {
      connected: false,
      posts: {}
    }
  },

  componentDidMount() {
    ddpClient.connect((err, wasReconnect) => {
      let connected = true;
      if (err) connected = false;

      this.setState({ connected: connected });
      this.makeSubscription();
    });
  },

  makeSubscription() {
    ddpClient.subscribe("posts", [], () => {
      this.setState({posts: ddpClient.collections.posts});
    });
  },

  /*
   * Removed from snippet for brevity
   */

  render() {
    let count = Object.keys(this.state.posts).length;
    return (
      <View style={styles.container}>
        <View style={styles.center}>
          <Text>Posts: {count}</Text>
          <Button text="Increment" onPress={this.handleIncrement}/>
          <Button text="Decrement" onPress={this.handleDecrement}/>
        </View>
      </View>
    );
  }
});

这很棒,但是实时的东西怎么加进去呢?我们会使用一个最基本的方法来获得实时性。

app/index.js中:

/*
 * Removed from snippet for brevity
 */
componentDidMount() {  
  ddpClient.connect((err, wasReconnect) => {
    let connected = true;
    if (err) connected = false;

    this.setState({ connected: connected });
    this.makeSubscription();
    this.observePosts();
  });
},

makeSubscription() {  
  ddpClient.subscribe("posts", [], () => {
    this.setState({posts: ddpClient.collections.posts});
  });
},

// This is just extremely simple. We're replacing the entire state whenever the collection changes
observePosts() {  
  let observer = ddpClient.observe("posts");
  observer.added = (id) => {
    this.setState({posts: ddpClient.collections.posts})
  }
  observer.changed = (id, oldFields, clearedFields, newFields) => {
    this.setState({posts: ddpClient.collections.posts})
  }
  observer.removed = (id, oldValue) => {
    this.setState({posts: ddpClient.collections.posts})
  }
},
/*
 * Removed from snippet for brevity
 */

在 React Native 中调用 Meteor 方法

那么如何在React Native应用中添加和删除文章呢?

app/index.js中:

handleIncrement() {  
  ddpClient.call('addPost');
},

handleDecrement() {  
  ddpClient.call('deletePost');
},

现在你就有了一个功能完备的,简单明了的React Native作为前端,Meteor作为后端的应用。我希望这篇教程能让你开启编写React Native+Meteor混合应用的道路。你可以(应该)使用一些其他框架,来管理应用的状态,比如Redux等,并且使用React的思想理念来构造你的组件结构。

Meteor React Native

在下一篇文章中,我们会讲解如何将React Native应用连接到Meteor的用户系统。

当然,目前这个Repo还有一点小问题,就是实时性只体现在RNApp -> Meteor App这里,如果在Meteor App中修改,RNApp需要手动刷新,这里可能与node-ddp-client这个包的observe有关,有待进一步的挖掘。

Meteor全栈开发
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343

推荐阅读更多精彩内容