本文来自Differential Blog,不过文中示例代码有不少bug,有些是版本问题,有些是npm包的问题,测试修改过后的Github示例代码在此:https://github.com/loongmxbt/meteor-react-native-basic 。下面是正文部分。
Parse最近宣布停止服务,许多公司会寻求它的替代品。这次Parse的关门会让许多人不会首选BaaS产品,转而倾向于自行实现后端,比如说使用Meteor。
我们来谈谈如何将一个React Native的App连接到Meteor App(作为服务端)。这篇教程假设你已经安装好了React Native和Meteor,并且能成功运行。如果你还没有配置好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
启动应用,试一下添加删除帖子功能吧!
创建 React Native App
在一个新的终端窗口输入以下命令:
react-native init RNApp && cd RNApp
注意:此处React Native新版本用的是babel6
,可是有些依赖的库并不是这个版本,就会导致红屏出错,所以解决方案就是把这个所以babel删了,升级依赖。
- 先删除依赖包
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了,尽管它并没做什么事。
我们可以点击Increment和Decrement两个按钮,可以看到控制台中的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的思想理念来构造你的组件结构。
在下一篇文章中,我们会讲解如何将React Native应用连接到Meteor的用户系统。
当然,目前这个Repo还有一点小问题,就是实时性只体现在RNApp -> Meteor App这里,如果在Meteor App中修改,RNApp需要手动刷新,这里可能与node-ddp-client这个包的observe有关,有待进一步的挖掘。