我是一个react-native的初学者,在学习完react-native的一些基本内容,比如,页面布局,列表渲染,事件处理,网络请求,路由跳转页面等等之后,我想做一个实战app来综合应用所学的知识。下面是我要实现的app,一个简单的github的app.
上图是在genemotion模拟器的效果,本文将从0到1讲解这个github APP的实现过程,希望对react-native的初学者有帮助,一些开发中遇到的坑我会和大家分享。前提是你已经搭建好rn的开发环境,我是使用window来开发rn。下面正式开始撸码姿势~
首先打开一个目录,初始化一个项目,这里我的项目名是github,这可能会花比较长的时间,请耐心等待。。
当你的项目初始化完成后,进入github项目目录,输入react-native run-android,启动项目(启动项目之前先开启的模拟器)。同样,输入这个命令后你需要等待一分钟,这个时候你会看到一个 js server的窗口自动启动
这里给大家介绍一个坑,在开发过程中,当你的server窗口未关闭时,你npm第三方模块是不成功的,或者你在启动server后在项目目录中增加图片,然后试图在代码中引用你新添加的图片时,js server也是会报错的。这个时候你需要先关闭js server窗口,再进行安装模块或者导入图片,再重新react-native run-android。
当你js server启动成功,而且你的项目也build成功之后你会看到
这时候,看看你的模拟器,你会看到一个react-antive的初始应用。如下图。
打开你模拟器上的menu菜单,开启热更新,这样你更改你的代码,就可以实时再你的模拟器上看到效果。
下面,我将简单的介绍一下这个初始代码,如果你已经掌握了rn的基础,请直接跳过这里。
左边是项目目录,你先在只需要关注index.android.js和index.ios.js两个文件,因为这是项目的入口文件。现在打开你的index.anddroid.js文件
import React, { Component } from 'react';
import {
AppRegistry,
StyleSheet,
Text,
View
} from 'react-native';
export default class github extends Component {
render() {
return (
Welcome to React Native!
To get started, edit index.android.js
Double tap R on your keyboard to reload,{'\n'}
Shake or press menu button for dev menu
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
welcome: {
fontSize: 20,
textAlign: 'center',
margin: 10,
},
instructions: {
textAlign: 'center',
color: '#333333',
marginBottom: 5,
},
});
AppRegistry.registerComponent('github', () => github);
可以看到react-native的写法和react是一样的,组件仍然使用state作为组件的状态管理,用props接受参数,仍然是使用jsx语法,不同点主要有:①你使用的不再是html的标签而是使用rn给你提供的原生组件,比如 AppRegistry
模块则是用来告知React Native哪一个组件被注册为整个应用的根容器。AppRegistry.registerComponent('github', () => github)这里用引号括起来的'HelloWorldApp'必须和你init创建的项目名一致。
现在我们不想把所有代码冗杂在index.android.js入口文件里面,我希望在入口文件里面import我们的主页组件。先添加一个js目录。
现在把你的index.android.js改成
import React, { Component } from 'react';
import {
AppRegistry,
StyleSheet,
Text,
View
} from 'react-native';
import HomePage from './js/pages/HomePage';
export default class github extends Component {
render() {
return (
);
}
}
AppRegistry.registerComponent('github', () => github);
现在我们需要写我们的HomePage组件(页面)了,这个页面包含一个底部导航栏,点击导航的图标,可以切换至不同的组件。lets do it..
import React, { Component } from 'react';
import {
StyleSheet,
Text,
View,
Image
} from 'react-native';
import TabNavigator from 'react-native-tab-navigator'
export default class HomePage extends Component {
constructor(props){
super(props);
this.state = {
selectedTab :'popular'
}
}
render() {
return (
}
renderSelectedIcon={() =>
}
onPress={() => this.setState({selectedTab: 'popular'})}
>
{/*选项卡对应的页面*/}
}
renderSelectedIcon={() =>
}
onPress={() => this.setState({selectedTab: 'trending'})}
>
}
renderSelectedIcon={() =>
}
onPress={() => this.setState({selectedTab: 'favorite'})}
>
}
renderSelectedIcon={() =>
}
onPress={() => this.setState({selectedTab: 'my'})}
>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1
},
icon: {
width:26,
height:26
}
});
zh
这里,我们用到了一个第三方组件
react-native-tab-navigator,注意当
TabNavigator.Item 的onpress添加点击点击事件时,依赖于HomePage组件的一个selectedTab状态。这里这个组件的使用方式挺简单的,但是需要考虑一点的是每个tab对应一个组件,这些组件是一次性加载,还是当对应的item被点击时才惰性加载?当切换tab时,已经加浏览过的tab页面是会保存原来的状态,还是重新加载?当然react-native-tab-navigator已经帮我们做了这些性能优化的问题。不过,你可以考虑,如果让你实现这个组件,你会怎么实现?还要说明一点是Image图片的style样式的
tintColor是改变图片的颜色。
现在我希望当我点击底部导航最热图标时,能够显示PopularPage页面。所以在js/pages目录下新建PopularPage.js组件。并且在HomePage页面里面导入该组件,并替换我们前面的示意view.如下图所示修改HomePage
接下来,让我们完成PopularPage页面。
我们的PopularPage页面包含头部和内容部分,如图,我们要实现的效果图如下
考虑到页面头部在很多页面都要使用到,所以我要把它做成一个可配置的组件。在js/components下面新建NavigationBar.js,对于NavigationBar的中间的标题和左边的元素,右边的元素都应该作为配置项,通过props获得的。
所以,Navigation Bar的参考代码如下
import React, {Component, PropTypes} from 'react';
import {
StyleSheet,
Text,
View,
StatusBar,
Platform
} from 'react-native';
//会包含状态栏,还有顶部导航栏
export default class NavigationBar extends Component{
static propTypes = {
rightButton: PropTypes.element,
leftButton: PropTypes.element
}
render(){
return
{/*顶部导航栏*/}
{this.props.leftButton}
{this.props.title}
{this.props.rightButton}
;
}
}
const styles = StyleSheet.create({
container: {
backgroundColor:'#63B8FF',
padding:5
},
statusBar:{
height:Platform.OS === 'ios' ? 20 : 0
},
navBar:{
flexDirection:'row',
justifyContent:'space-between',
alignItems:'center'
},
titleWrapper:{
flexDirection:'column',
justifyContent:'center',
alignItems:'center',
position:'absolute',
left:40,
right:40,
bottom:0
},
title:{
fontSize:16,
color:'#FFF'
},
leftBtnStyle:{
width:24,
height:24
},
rightBtn:{
flexDirection:'row',
alignItems:'center',
paddingRight:8
}
});
现在我们在PopularPage里面引入我们刚刚写的NavigationBar头部,并传入参数,标题是一个文本,右边元素是两个图标。Popular Page代码如下
import React, {Component} from 'react';
import {
StyleSheet,
Text,
View,
TouchableOpacity,
Image
} from 'react-native';
import ScrollableTabView from "react-native-scrollable-tab-view";
import NavigationBar from '../components/NavigationBar';
export default class PopularPage extends Component {
constructor(props){
super(props);
}
getNavRightBtn = ()=>{
return
;
}
render() {
return (
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1
}
});
这个时候看看你效果,头部组件是不是出现了~~~~~~~~~~~接下让我们实现下面的内容部分,这又是个tab。我们需要
react-native-scrollable-tab-view这个第三方组件。这个用法也很简单。
当然每个tab页面,我封装成另一个组件PopularTab。内容的类别我用组件的一个languages状态保存,每个tab页面是一个滚动的列表用来展示从github服务器上获得的数据,
我用另一个PopularTab组件封装,现在这个组件只显示一句话。
import React, {Component} from 'react';
import {
StyleSheet,
Text,
View,
TouchableOpacity,
Image
} from 'react-native';
import ScrollableTabView from "react-native-scrollable-tab-view";
import NavigationBar from '../components/NavigationBar';
export default class PopularPage extends Component {
constructor(props){
super(props);
this.state = {
languages: ['ios','android','js','react']
};
}
getNavRightBtn = ()=>{
return
;
}
render() {
return (
{
this.state.languages.map((item,i)=>{
return ;
})
}
);
}
}
class PopularTab extends Component{
static defaultProps = {
tabLabel: 'IOS',
}
render(){
return(
{`这是${this.props.tabLabel}的内容`}
)
}
}
const styles = StyleSheet.create({
container: {
flex: 1
}
});
然后,你会得到如下效果~
//加载数据
fetch(`https://api.github.com/search/repositories?q=${this.props.tabLabel}&sort=stars`)
.then(response => response.json()) //服务器响应response对象,继续变成json对象
.then(json => {
//数据返回成功后执行回调函数
}).catch((error) => {
console.error(error);
}).done();
你可以在chrome浏览器中测试上述代码~新版的chrome是支持fetch API的。数据一共有192078条,而且每一项数据是一个json对象,观察json数据都返回了哪些信息。
ScrollView和ListView/FlatList应该如何选择?ScrollView会简单粗暴地把所有子元素一次性全部渲染出来。其原理浅显易懂,使用上自然也最简单。然而这样简单的渲染逻辑自然带来了性能上的不足。想象一下你有一个特别长的列表需要显示,可能有好几屏的高度。创建和渲染那些屏幕以外的JS组件和原生视图,显然对于渲染性能和内存占用都是一种极大的拖累和浪费。
这就是为什么我们还有专门的ListView组件。ListView会惰性渲染子元素,只在它们将要出现在屏幕中时开始渲染。这种惰性渲染逻辑要复杂很多,因而API在使用上也更为繁琐。除非你要渲染的数据特别少,否则你都应该尽量使用ListView,哪怕它们用起来更麻烦。
回到项目中来,现在改造我们的PopularTab组件,因为 PopularTab组件是为了渲染github项目数据的.<ListView dataSource={this.state.dataSource} renderRow={this.renderRow} />现在,我的 PopularTab代码如下
class PopularTab extends Component{
static defaultProps = {
tabLabel: 'IOS',
}
constructor(props){
super(props);
this.state = {
dataSource : new ListView.DataSource({rowHasChanged:(r1,r2) => r1 !== r2}), //是一个优化,节省无用的UI渲染
};
}
//渲染ListView的每一行
renderRow = (obj)=>{
return ({obj.name} );
}
//加载数据
loadData = ()=>{
this.setState({isLoading:true});
//请求网络
fetch(`https://api.github.com/search/repositories?q=${this.props.tabLabel}&sort=stars`)
.then(response => response.json()) //服务器响应response对象,继续变成json对象
.then(json => {
console.log(json);
//更新dataSource
this.setState({
dataSource:this.state.dataSource.cloneWithRows(json.items)
});
}).catch((error) => {
console.error(error);
}).done();
}
render(){
return(
);
}
componentDidMount = ()=>{
this.loadData();
}
}
在我们异步请求数据后回调函数中,更新dataSource,触发组件的render行为。
现在我们可以看到PopularPage页面能够渲染我们返回的数据了。但是,不幸的是,显示的很丑,而且在数据没有返回前,没有提示用户正在加载数据,所以体验很差,不过别急我们一个个击破~~~~~
}
/>
很显然,你需要给PopularTab组件加一个loading状态,当数据返回前为true,数据返回后设置为false。此时再看看效果吧,我这里就不贴图了
import React, {Component} from 'react';
import {
StyleSheet,
Text,
View,
Image,
TouchableOpacity
} from 'react-native';
export default class ProjectRow extends Component{
static defaultProps = {
item: {}
}
render(){
var item = this.props.item;
return
{item.full_name}
{item.description}
作者:
星:
{item.stargazers_count}
}
}
const styles = StyleSheet.create({
container: {
flex:1,
backgroundColor:'#FFF',
flexDirection:'column',
padding:10,
marginLeft:5,
marginRight:5,
marginVertical:5,
borderColor:'#dddddd',
borderWidth:0.5,
borderRadius:2,
shadowColor:'gray',
shadowOffset:{width:0.5,height:0.5},
shadowRadius:1, //阴影半径
shadowOpacity:0.4,
elevation:2 //Android 投影
},
title:{
fontSize:16,
marginBottom:2,
color:'#212121'
},
description:{
fontSize:14,
marginBottom:2,
color:'#757575'
},
bottom:{
flexDirection:'row',
alignItems:'center',
justifyContent:'space-between'
},
bottomTextWrapper:{
flexDirection:'row',
alignItems:'center'
}
});
这里的UI你可以发挥你自己的艺术细胞进行改写~~~~然后,我们原来每一个项目只是以Text的形式渲染项目名称,现在我希望渲染标题,作者,star,图片等等信息,也就是每一项以ProjectRow组件的方式渲染~
//渲染ListView的每一行
renderRow = (obj)=>{
return ;
}