React Native基础教程—实现微信精选列表

从本章开始我们将一起来开发一个简单的微信精选列表应用。主要包括两个界面,主界面是微信精选列表,点击每一条信息可以进入到详细信息展示的界面。通过这个应用我们将学习如何从网络获取数据、怎样对UI组件进行布局及设置样式、以及常用UI组件的使用。

可以在React NativeReact的官方网站查看到更多ReactNative及React的相关内容。对应的中文社区:React NativeReact

项目完整源码

项目的完整源码下载地址。

注意
一、教程以开放IOS应用为基础讲解。
二、涉及到的原生代码采用Objective-C语言完成。

微信精选

单条内容

可以将微信精选列表看做一个tableview,那么每一条内容就是一个cell。我们将从搭建单条内容的界面开始讲解。先看一下最终完成的样子:

单条内容.jpeg

这个界面主要包括文本和图片,采用文本堆叠在图片之上的布局方式。

开发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,运行工程:

单条内容.jpeg

可以看到我们已经实现简单的上下布局的图片和文本。

现在我们来分析一下上面的代码。首先是'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',
  },
});

最终效果:

最终效果.jpeg

我们不应该将全部代码都写在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.jpeg

现在我们已经完成一个最简单的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}

到此我们完成完整的微信精选列表。最终列表界面:

微信精选.jpeg

像newsCell一样,我们把列表也封装为一个单独的组件,命名为newsMain,然后直接在index.ios.js文件导入即可。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,245评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,749评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,960评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,575评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,668评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,670评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,664评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,422评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,864评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,178评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,340评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,015评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,646评论 3 323
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,265评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,494评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,261评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,206评论 2 352

推荐阅读更多精彩内容