从本章开始我们将一起来开发一个简单的微信精选列表应用。主要包括两个界面,主界面是微信精选列表,点击每一条信息可以进入到详细信息展示的界面。通过这个应用我们将学习如何从网络获取数据、怎样对UI组件进行布局及设置样式、以及常用UI组件的使用。
可以在React Native和React的官方网站查看到更多ReactNative及React的相关内容。对应的中文社区:React Native与React。
项目完整源码
项目的完整源码下载地址。
注意
一、教程以开放IOS应用为基础讲解。
二、涉及到的原生代码采用Objective-C语言完成。
微信精选
单条内容
可以将微信精选列表看做一个tableview,那么每一条内容就是一个cell。我们将从搭建单条内容的界面开始讲解。先看一下最终完成的样子:
这个界面主要包括文本和图片,采用文本堆叠在图片之上的布局方式。
开发React Native应用主要需要JavaScript、CSS和JSX三种技术。其中JavaScript负责业务逻辑;CSS负责UI样式和布局;JSX将UI组件封装为标签语言,使我们可以通过JSX标签构建UI。React Native的这种开发模式借鉴了传统的web开发。这样做主要的优势是可以将内容和表现分离,JSX负责内容,CSS负责表现。代码逻辑更加清晰。
JSX
相信大部分同学对于JavaScript和CSS肯定不陌生。如果没有使用React的经验可能不了解JSX是什么。那么就来说一说JSX,先来看一段代码:
<View>
<Text>
JSX,JavaScript语法扩展
</Text>
</View>
上面就是一段JSX代码。
每一个UI闭合的标签都称之为组件,<View/>
组件可以认为代表UIView
,<Text/>
组件可以认为代表UILabel
。ReactNative就是通过这些JSX组件与原生UI一一对应起来的。
JSX,本质是JavaScript的语法扩展。React在运行期将JSX转换为JavaScript代码。
JSX以<
起始,以/>
结束的闭合标签。JSX级能够解析HTML标签,也能够解析React组件。规定HTML标签必须以小写字母开头表示,React组件以大写字母开头。
JSX,在构建ReactNative应用并不是必须的,也可以直接通过JavaScript创建UI组件。但是使用JSX能够让我们的代码更加直观清晰,并且JSX支持与JavaScript混编,这也赋予其非常大的灵活性。
Flexbox弹性盒
搭建UI界面必须解决的一个问题就是屏幕适配的问题。在IOS开发中我们使用autolayout进行布局,ReactNative也为我们提供了它解决布局问题的技术——Flexbox弹性盒。Flexbox本身是CSS3的技术标准,ReactNative因为使用CSS进行布局和样式设置,所以也就顺理成章的使用Flexbox弹性盒技术。
可以通过下面的代码在组件中声明样式:
<View style = {styles.container}>
<Text style = {styles.content}>
JSX,JavaScript语法扩展
</Text>
</View>
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
});
ReactNative并没有支持全部的CSS属性,ReactNative支持的CSS属性可以看这里。关于Flexbox弹性盒详细的属性可以查看这里。
关于Flexbox的教程推荐这两篇文章:Flex 布局教程:语法篇、Flex 布局教程:实例篇。
编写单条内容界面
了解了JSX与Flexbox之后,我们就可以正式开始编写单条内容的界面。下面是完整的代码:
'use strict';
import React, { Component } from 'react';
import {
AppRegistry,
StyleSheet,
Text,
View,
Image,
} from 'react-native'
class ReactNativeLearn extends Component {
render() {
return (
<View style={styles.container}>
<Image
style={{
width: 300,
height: 200,
}}
resizeMode={"contain"}
source={{uri:'http://facebook.github.io/react/img/logo_og.png'}}
/>
<Text
style={{
color: 'black',
fontSize: 16,
fontWeight: 'normal',
fontFamily: 'Helvetica Neue',
}}>
新闻内容
</Text>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
});
AppRegistry.registerComponent('ReactNativeLearn', () => ReactNativeLearn);
启动Xcode,运行工程:
可以看到我们已经实现简单的上下布局的图片和文本。
现在我们来分析一下上面的代码。首先是'use strict'
,这是JavaScript的严格模式,在严格模式下一些可能会带来安全隐患的JavaScript语言特性将被禁用(ES6标准Module默认采用严格模式)。然后是import React, { Component } from 'react'
这行代码的意思是将React库的Component组件导入到当前作用域中。这是采用ES6标准的写法。之后的代码就是我们创建界面的主要代码。我们可以看到render()
函数,render是渲染的意思,我们在render函数中创建我们的UI组件渲染到屏幕。
关于组件的详细信息可以在ReactNative官网找到。AppRegistry.registerComponent('ReactNativeLearn', () => ReactNativeLearn)
,表示将ReactNativeLearn组件作为整个ReactNative应用的入口。ReactNativeLearn的名称与AppDelegate中的moduleName对应:
RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation
moduleName:@"ReactNativeLearn"
initialProperties:nil
launchOptions:launchOptions];
现在我们可以通过修改组件的style属性和组件的包裹方式,实现我们想要的最终效果。代码如下:
class ReactNativeLearn extends Component {
render() {
return (
<View style={styles.container}>
<Image
style={{
width: Dimensions.get('window').width - 40,
height: 300,
marginLeft: 20,
marginRight: 20,
marginTop: 10,
marginBottom: 10,
borderColor: "rgb(0,0,0)",
borderWidth: 2,
borderRadius: 8,
justifyContent: 'flex-end',
}}
resizeMode={"stretch"}
source={{uri:'http://facebook.github.io/react/img/logo_og.png'}}
>
<View
style={{
flex: 0,
justifyContent: 'flex-start',
alignItems: 'stretch',
backgroundColor: "rgba(0,0,0,0.4)",
}}>
<Text
style={{
color: "rgb(255,255,255)",
fontSize: 20,
fontWeight: 'normal',
fontFamily: 'Helvetica Neue',
marginLeft: 10,
marginRight: 10,
marginTop: 10,
marginBottom: 10,
}}
numberOfLines={3}
>
A React component for displaying different types of images, including network images, static resources, temporary local images, and images from local disk, such as the camera roll.
</Text>
</View>
</Image>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
backgroundColor: '#F5FCFF',
},
});
最终效果:
我们不应该将全部代码都写在index.ios.js文件,而是应该分成不同的模块,方便管理。我们新建一个名为newsCell的js文件,将代码复制到该文件当中。最后将newsCell声明为一个可以被其他模块引用的组件:
export default NewsCell;
微信精选列表
完成单条内容的UI搭建,现在我们就来实现列表的界面。在React Native应用中列表通常使用ListView组件。ListView的功能类似于UITableView。先实现最基本的ListView,代码如下:
class ReactNativeLearn extends Component {
constructor(props) {
super(props);
this.state = {
dataSource: new ListView.DataSource({
rowHasChanged: (r1, r2) => r1 !== r2
})
};
this.renderRow = this.renderRow.bind(this);
}
componentDidMount() {
this.fetchNewsData();
}
// 设置数据
fetchNewsData() {
this.setState({
dataSource: this.state.dataSource.cloneWithRows([{'content':'ReactNativeLearn ListView'},
{'content':'ReactNativeLearn ListView'},
{'content':'ReactNativeLearn ListView'},
{'content':'ReactNativeLearn ListView'},
{'content':'ReactNativeLearn ListView'},
{'content':'ReactNativeLearn ListView'},
{'content':'ReactNativeLearn ListView'},
{'content':'ReactNativeLearn ListView'},
{'content':'ReactNativeLearn ListView'},
{'content':'ReactNativeLearn ListView'}])
});
}
// 渲染cell
renderRow(rowData) {
return (
<View
style={{
height: 80,
justifyContent: 'flex-start',
alignItems: 'stretch',
backgroundColor: "rgba(74,144,226,1)",
}}>
<Text
style={{
color: 'black',
fontSize: 28,
fontWeight: 'normal',
fontFamily: 'Helvetica Neue',
}}>
{rowData.content}
</Text>
</View>
);
}
// 渲染cell的分割
renderSeparator(
sectionID,
rowID
) {
return (
<View key = {'cell_'+ sectionID + '_' + rowID} style = {styles.rowSeparator}/>
);
}
render() {
return (
<View style={styles.container}>
<ListView
style = {styles.listContainer}
dataSource = {this.state.dataSource}
renderRow = {this.renderRow}
renderSeparator = {this.renderSeparator}
enableEmptySections={true}
/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
backgroundColor: '#F5FCFF',
},
listContainer: {
flex:1,
marginTop: 64,
backgroundColor: 'rgb(237, 240, 235)',
},
rowSeparator: {
backgroundColor: 'rgb(223, 218, 223)',
height: 1,
},
});
最终效果:
现在我们已经完成一个最简单的ListView。可以看到我们为ListView组件设置了三个属性:dataSource,renderRow和renderSeparator。dataSource是ListView的数据源类似于UITableView的数据源方法,这里我们手动设置了一个数组;renderRow接收一个渲染cell的函数回调,类似于UITableView的cellForRowAtIndexPath
。可以在这个函数里我们可以创建cell的UI界面,即我们之前创建的单条内容界面。还有一个renderSeparator,可以用于创建分割cell的UI。
目前为止ListView的每一个cell的数据都是我们手动设置的。实际开发中,这些数据往往来自于服务器。下面我们来实现网络请求,代码如下:
fetchNewsData() {
fetch(newsULR,{
headers: {
"apikey": "f589f2834aeab120eef2e750e4fb1dfb"
}
}).then((response) => response.json())
.catch((error) => {
console.error('error request!');
})
.then((responseData) => {
this.setState({
dataSource: this.state.dataSource.cloneWithRows(responseData.newslist)
});
})
.done();
}
React Native使用Fetch进行网络请求。Fetch是新的异步网络请求标准,相较于AJAX提供更强大高效的网络请求API。关于ReactNative的网络内容可以看这里。
我们已经实现ListView的基本功能,现在需要将之前单条新闻的界面newsCell作为组件,添加到ListView中,实现完整的列表。引入newsCell的方式与引入ReactNative原生组件的方式相同,都是通过import进行导入。
import NewsCell from './News/newsCell';
我们可以通过下面的方式向NewsCell传递数据或者回调函数:
renderRow(rowData) {
return (
<NewsCell newsData={rowData}/>
);
}
然后在NewsCell组件可以通过如下的方式获得数据:
{this.props.newsData.title}
到此我们完成完整的微信精选列表。最终列表界面:
像newsCell一样,我们把列表也封装为一个单独的组件,命名为newsMain,然后直接在index.ios.js文件导入即可。