从0到1写一个react native的app(上)

xiaoxiao2021-02-28  98

我是一个react-native的初学者,在学习完react-native的一些基本内容,比如,页面布局,列表渲染,事件处理,网络请求,路由跳转页面等等之后,我想做一个实战app来综合应用所学的知识。下面是我要实现的app,一个简单的github的app.

上图是在genemotion模拟器的效果,本文将从0到1讲解这个github APP的实现过程,希望对react-native的初学者有帮助,一些开发中遇到的坑我会和大家分享。前提是你已经搭建好rn的开发环境,我是使用window来开发rn。下面正式开始撸码姿势~

1.初始化项目

首先打开一个目录,初始化一个项目,这里我的项目名是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 ( <View style={styles.container}> <Text style={styles.welcome}> Welcome to React Native! </Text> <Text style={styles.instructions}> To get started, edit index.android.js </Text> <Text style={styles.instructions}> Double tap R on your keyboard to reload,{'\n'} Shake or press menu button for dev menu </Text> </View> ); } } 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给你提供的原生组件,比如<Button/> <Image/>等等。②你在定义样式的时候是通过StyleSheet.create的形式创建一个styles样式对象,然后再组件里面通过style属性引入样式即可。AppRegistry模块则是用来告知React Native哪一个组件被注册为整个应用的根容器。AppRegistry.registerComponent('github', () => github)这里用引号括起来的'HelloWorldApp'必须和你init创建的项目名一致。

2.添加底部导航

现在我们不想把所有代码冗杂在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 ( <HomePage/> ); } } 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 ( <View style={styles.container}> <TabNavigator> <TabNavigator.Item selected={this.state.selectedTab === 'popular'} title="最热" selectedTitleStyle={{color: '#63B8FF'}} renderIcon={() => <Image style={styles.icon} source={require('../../res/images/ic_popular.png')}/>} renderSelectedIcon={() => <Image style={[styles.icon,,{tintColor:'#63B8FF'}]} source={require('../../res/images/ic_popular.png')}/>} onPress={() => this.setState({selectedTab: 'popular'})} > {/*选项卡对应的页面*/} <View style={{backgroundColor:'pink',flex:1}}></View> </TabNavigator.Item> <TabNavigator.Item selected={this.state.selectedTab === 'trending'} title="趋势" selectedTitleStyle={{color: '#63B8FF'}} renderIcon={() => <Image style={styles.icon} source={require('../../res/images/ic_trending.png')}/>} renderSelectedIcon={() => <Image style={[styles.icon,{tintColor:'#63B8FF'}]} source={require('../../res/images/ic_trending.png')}/>} onPress={() => this.setState({selectedTab: 'trending'})} > <View style={{backgroundColor:'lightblue',flex:1}}></View> </TabNavigator.Item> <TabNavigator.Item selected={this.state.selectedTab === 'favorite'} title="收藏" selectedTitleStyle={{color: '#63B8FF'}} renderIcon={() => <Image style={styles.icon} source={require('../../res/images/ic_favorite.png')}/>} renderSelectedIcon={() => <Image style={[styles.icon,{tintColor:'#63B8FF'}]} source={require('../../res/images/ic_favorite.png')}/>} onPress={() => this.setState({selectedTab: 'favorite'})} > <View style={{backgroundColor:'green',flex:1}}></View> </TabNavigator.Item> <TabNavigator.Item selected={this.state.selectedTab === 'my'} title="我的" selectedTitleStyle={{color: '#63B8FF'}} renderIcon={() => <Image style={styles.icon} source={require('../../res/images/ic_my.png')}/>} renderSelectedIcon={() => <Image style={[styles.icon,{tintColor:'#63B8FF'}]} source={require('../../res/images/ic_my.png')}/>} onPress={() => this.setState({selectedTab: 'my'})} > <View style={{backgroundColor:'white',flex:1}}></View> </TabNavigator.Item> </TabNavigator> </View> ); } } 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是改变图片的颜色。

最终实现的效果如下:

3.写PopularPage页面

  现在我希望当我点击底部导航最热图标时,能够显示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 <View style={styles.container}> <View style={styles.statusBar}> <StatusBar hidden={false} barStyle="light-content"/> </View> {/*顶部导航栏*/} <View style={styles.navBar}> <View style={styles.leftBtnStyle}> {this.props.leftButton} </View> <View style={styles.titleWrapper}> <Text style={styles.title}>{this.props.title}</Text> </View> <View style={styles.rightBtn}> {this.props.rightButton} </View> </View> </View>; } } 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 <View style={{flexDirection:'row',alignItems:'center'}}> <TouchableOpacity activeOpacity={0.7}> <Image source={require('../../res/images/ic_search_white_48pt.png')} style={{width:24,height:24}}/> </TouchableOpacity> <TouchableOpacity activeOpacity={0.7}> <Image source={require('../../res/images/ic_more_vert_white_48pt.png')} style={{width:24,height:24}}/> </TouchableOpacity> </View>; } render() { return ( <View style={styles.container}> <NavigationBar title="热门" rightButton={this.getNavRightBtn()}/> </View> ); } } 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 <View style={{flexDirection:'row',alignItems:'center'}}> <TouchableOpacity activeOpacity={0.7}> <Image source={require('../../res/images/ic_search_white_48pt.png')} style={{width:24,height:24}}/> </TouchableOpacity> <TouchableOpacity activeOpacity={0.7}> <Image source={require('../../res/images/ic_more_vert_white_48pt.png')} style={{width:24,height:24}}/> </TouchableOpacity> </View>; } render() { return ( <View style={styles.container}> <NavigationBar title="热门" rightButton={this.getNavRightBtn()}/> <ScrollableTabView tabBarBackgroundColor="#63B8FF" tabBarActiveTextColor="#FFF" tabBarInactiveTextColor="#F5FFFA" tabBarUnderlineStyle={{backgroundColor:"#E7E7E7",height:2}}> { this.state.languages.map((item,i)=>{ return <PopularTab key={`tab${i}`} tabLabel={item}/> ; }) } </ScrollableTabView> </View> ); } } class PopularTab extends Component{ static defaultProps = { tabLabel: 'IOS', } render(){ return( <View> <Text>{`这是${this.props.tabLabel}的内容`}</Text> </View> ) } } const styles = StyleSheet.create({ container: { flex: 1 } });然后,你会得到如下效果~

4.使用fecth API获取数据

   React Native提供了和web标准一致的fecth API,基于ajax,但是采用promise的写法。现在我们要从github服务器上获取当前选中selectedTab的最热项目,代码如下: //加载数据 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数据都返回了哪些信息。

5.使用List View高效渲染列表

渲染列表可以用Scroll View和ListView,两者都是滚动事件的响应者,都可以渲染长列表。使用方法可以参考rn官网文档

ScrollView和ListView/FlatList应该如何选择?ScrollView会简单粗暴地把所有子元素一次性全部渲染出来。其原理浅显易懂,使用上自然也最简单。然而这样简单的渲染逻辑自然带来了性能上的不足。想象一下你有一个特别长的列表需要显示,可能有好几屏的高度。创建和渲染那些屏幕以外的JS组件和原生视图,显然对于渲染性能和内存占用都是一种极大的拖累和浪费。

这就是为什么我们还有专门的ListView组件。ListView会惰性渲染子元素,只在它们将要出现在屏幕中时开始渲染。这种惰性渲染逻辑要复杂很多,因而API在使用上也更为繁琐。除非你要渲染的数据特别少,否则你都应该尽量使用ListView,哪怕它们用起来更麻烦。

回到项目中来,现在改造我们的PopularTab组件,因为 PopularTab组件是为了渲染github项目数据的. ListView组件的使用方式很简单,只需要传递dataSource 和 renderRow的渲染方法,renderRow的参数是DataSource的其中一项。 <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 (<Text>{obj.name}</Text>); } //加载数据 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( <View style={styles.container}> <ListView dataSource={this.state.dataSource} renderRow={this.renderRow} /> </View> ); } componentDidMount = ()=>{ this.loadData(); } }在我们异步请求数据后回调函数中,更新dataSource,触发组件的render行为。 现在我们可以看到PopularPage页面能够渲染我们返回的数据了。 但是,不幸的是,显示的很丑,而且在数据没有返回前,没有提示用户正在加载数据,所以体验很差,不过别急我们一个个击破~~~~~

6.使用RefreshControl提示正在加载信息,提高用户体验

还好,ScrollView和List View都有一个 refreshControl属性,用来指定当数据正在加载时显示的组件~而rn原生组件中给我们提供了一个叫 RefreshControl的组件, 修改PopularTab组件 <ListView dataSource={this.state.dataSource} renderRow={this.renderRow} refreshControl={ <RefreshControl refreshing={this.state.isLoading} tintColor="#63B8FF" title="正在加载..." titleColor="#63B8FF" colors={['#63B8FF']}/>} />很显然,你需要给PopularTab组件加一个loading状态,当数据返回前为true,数据返回后设置为false。此时再看看效果吧,我这里就不贴图了

7.写ProjectRow组件显示项目

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 <TouchableOpacity onPress = {this.props.onSelect} activeOpacity={0.5}> <View style={styles.container}> <Text style={styles.title}>{item.full_name}</Text> <Text style={styles.description}>{item.description}</Text> <View style={styles.bottom}> <View style={styles.bottomTextWrapper}> <Text>作者:</Text> <Image style={{width:22,height:22}} source={{uri:item.owner.avatar_url}}/> </View> <View style={styles.bottomTextWrapper}> <Text>星:</Text> <Text>{item.stargazers_count}</Text> </View> <Image source={require("../../res/images/ic_unstar_transparent.png")} style={{width:22,height:22}}/> </View> </View> </TouchableOpacity> } } 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组件的方式渲染~ 现在引入ProjectRow,并修改Popular Tab组件

//渲染ListView的每一行 renderRow = (obj)=>{ return <ProjectRow item={obj}/>; }

现在我们来看看效果~~

有木有感觉很棒呢!当然你点击项目的时候,不能看到项目详情,以及如果用户只对js感兴趣,只想看js相关的热门项目,不想看到其他的,那是不是还得增加一个订阅内容的功能,这些功能我们放到下一次~~~~~敬请期待吧,下次课我们将讲到用路由跳转页面,本地存储,以及在app里面嵌套网页,以及响应事件相关的知识点~~~~~如果你喜欢本文,就给个赞吧~

转载请注明原文地址: https://www.6miu.com/read-74990.html

最新回复(0)