简介
熟悉如何React Native + Redux 等全家桶技术来开发IM类App。
技术栈:
- React Native, React Navigation,
- Redux, Redux Saga, Redux Persist,
- Immutable
- reselect
- Fetch
- LeanCloud服务
创建 React Native项目
首先安装Node,NPM,React Native Cli等。
npm install -g react-native
react-native init OneIM
创建成功后,验证是否能正确运行(以andorid为例)
首先启动android模拟器,然后打包运行项目。
cd OneIM
react-native run-android
正确的话,脚本会自动构建出android测试包,安装到模拟器上,并运行显示欢迎页面。
注意,如果出现连接不上服务的错误, 在应用的设置菜单里输入正确的dev server和port信息。
项目目录结构
项目目录结构一个有两种:
- 按代码类型划分
- 按功能划分
个人推荐按功能划分目录结构,这样符合高内聚低耦合的设计思想。以后,随着项目的不断膨胀,
修改某一个功能时不用频繁的切换目录,而且,如果某个功能比较复杂,也可以在这个功能目录里按照类型划分子目录。
所以,最终项目的目录结构如下:
配置LeanCloud服务
首先,创建LeanCloud帐号,获取APP ID及 APP KEY的信息。
然后,安装LeanCloud的SDK
npm install --save leancloud-storage leancloud-realtime
RN默认使用jest进行测试。编写测试代码,验证LeanCloud是否能够正确访问。
import AV from 'leancloud-storage';
import {AV_APP_ID as appId, AV_APP_KEY as appKey} from '../../app/constants';
describe('leancloud test suit',() => {
it('save user test', async () => {
AV.init({appId, appKey});
let user = new AV.User();
user.setUsername('user1');
user.setPassword('testpass');
const result = await user.signUp();
console.log(result);
})
});
测试代码通过,表示已经成功设置好LeanCloud服务。
可以设置环境变量DEBUG=leancloud*, 开启调试日志,SDK会把网络请求、错误消息等信息输出到 IDE 的日志窗口。
快速实现新用户注册及登录功能
React Navigation
页面导航或路由是一个应用的基本功能,RN本身提供了非常好用的导航组件react-navigation。
React Navigation提供三种方式的导航:
- TabNavigator, 提供页签效果导航
- StackNavigator, 导航栏效果导航
- DrawerNavigator,抽屉效果,左侧滑出
我们先用StackNavigator方式快速实现用户注册、登录及登录成功后跳转到主页的功能。
npm install --save react-navigation
配置好三个导航页面
const StackNav = StackNavigator({
Login: {screen: Login},
SignUp: {screen: SignUp},
Home: {screen: ChatList}
});
导航跳转时只需要
this.props.navigation.navigate('SignUp')
创建三个页面
为了统一风格,组件页面文件命名采用首字母大写的驼峰方式,其它文件采用首字母小写的驼峰方式。
三个页面:
- Login - 登录页
- SignUp - 注册页
- ChatList - 会话列表主页
setup AV sdk:
import App from "./App";
import AV from 'leancloud-storage';
import {AV_APP_ID as appId, AV_APP_KEY as appKey} from './constants';
function setup() {
//init leancloud sdk
AV.initialize(appId,appKey);
global.AV = AV;
return App;
}
export default setup;
Login:
class Login extends Component {
static navigationOptions = {
title: 'Login',
};
constructor(props) {
super(props);
this.state = {
userName: null,
password: null,
sending: false,
};
}
_onLogin = () => {
const {userName,password} = this.state;
if (!userName || !password) {
toastUtils.showShort('用户名或密码不能为空。');
return;
}
const user = new global.AV.User();
user.setUsername(userName);
user.setPassword(password);
this.setState({sending:true});
user.logIn().then((result) => {
toastUtils.showShort('登录成功。');
this.props.navigation.navigate('Home');
this.setState({sending:false});
},(err) => {
toastUtils.showShort(`登录失败,${err}`);
this.setState({sending:false});
});
};
_onSignUp = () => {
this.props.navigation.navigate('SignUp');
};
render() {
return (
<View>
<TextInput placeholder='输入用户名' onChangeText={(userName) => {
this.setState({userName})
}} value={this.state.userName}/>
<TextInput secureTextEntry={true} placeholder='输入密码' onChangeText={(password) => {
this.setState({password})
}} value={this.state.password}/>
<TouchableOpacity >
<Button onPress={this._onLogin} title={`登录`} disabled={this.state.sending} />
</TouchableOpacity>
<TouchableOpacity onPress={this._onSignUp}>
<Text >注册新账户</Text>
</TouchableOpacity>
</View>
);
}
}
SignUp:
class SignUp extends Component {
static navigationOptions = {
title: 'SignUp',
};
constructor(props) {
super(props)
this.state = {
userName: null,
password: null,
};
}
_onSignUp = () => {
const {userName,password} = this.state;
if (!userName || !password) {
toastUtils.showShort('用户名或密码不能为空。');
return;
}
const user = new global.AV.User();
user.setUsername(userName);
user.setPassword(password);
user.signUp().then((result) => {
toastUtils.showShort('注册成功。');
this.props.navigation.navigate('Login');
},(err) => {
toastUtils.showShort(`注册失败。${err}`);
})
};
render() {
return (
<View>
<TextInput placeholder='输入用户名' onChangeText={(userName) => {
this.setState({userName})
}} value={this.state.userName}/>
<TextInput secureTextEntry={true} placeholder='输入密码' onChangeText={(password) => {
this.setState({password})
}} value={this.state.password}/>
<TouchableOpacity >
<Button onPress={this._onSignUp} title={`注册`}/>
</TouchableOpacity>
</View>
);
}
}
ChatList:
const ChatList = () => (
<View>
<Text>Chat List page.</Text>
</View>
);
打包运行项目
我只在android平台上测试过,ios直接参考官方文档。
cd android
gradlew assembleRelease
如何管理项目状态
从上面这个简单的例子可以看出,对于不复杂的功能可以直接在react中使用state方式,开发起来很快,也比较简单。
但对于稍大型项目,这种state维护在组件中的方式会让组件变得越来越难维护。
接下来,我们要迎来React最重要的伙伴Redux家族。通过Redux,我们可以以更加优雅的方式将应用的状态与显示组件彻底分离开来。
最后
代码已经上传到github,后面会不定期更新。