React Native入门指南(中)

image

前言

Learn once, write anywhere: Build mobile apps with React

FacebookReact.js Conf 2015年会议上发布了React Native(以下简称RN )。RN 是一个使用React构建Native App的框架,支持Android 4.1+iOS 8+平台。它充分利用了Facebook现有的轮子,成为前端和客户端技术的集大成者。
RN兼备H5的动态性和Native的性能,完美弥补了手机浏览器性能和Native硬编码的弊端。

RN 的前身是React,这是一个为了解决项目越来越复杂导致DOM频繁操作和界面重绘,而诞生的高性能渲染框架。
它只描述了View层,Facebook 2014年发布Flux,以及Redux等模式框架和React配套使用。

RN 社区活跃度非常高,有完善的文档和众多贡献者支持,不用担心没资料而苦恼,这是RN 能从同类框架(主要指Weex)脱颖而出的优势之一。但是也侧面反映了RN 还不稳定,至今依然没有发布1.0版本。目前RN每两周会发布一个小版本,并提供了完善的升级支持。

在正式开始阅读文章之前,希望你具备以下知识:
- JavaScript 基本知识
- 对 Android 或 iOS 平台开发有了解
- 阅读完上篇

列表

RN 提供了几种适用于展示列表数据的组件,一般我们会用FlatListSectionList
FlatList并不立即渲染所有元素,而是优先渲染屏幕上可见的元素。它是Facebook重新设计的列表组件,除了ListView支持的特性外,还支持支持下拉刷新、上拉更多。
曾经的 ListView一直被开发者诟病性能和内存问题(已废弃),开发者只能自己实现列表替换RN的ListView,不过这些问题,在FlatList都已经解决了。

不过,这里依然使用ListView作为案例,原因是不(lan)影(lan)响(lan)理(lan)解(lan)列表组件。

使用方法

constructor() {
    super();
    const dst = new ListView.DataSource({
        rowHasChanged: (prev, next) => prev !== next
    });

    this.state = {
        ListDataSource: dst,
    }
}

render() {
    return (
        <ListView
            dataSource={this.state.ListDataSource.cloneWithRows(['row 1', 'row 2'])}
            renderRow={(rowData) => <Text>{rowData}</Text>}/>
    );
}

和原生代码的思路很像,几行代码我们就可以实现一个简单的列表。
1. 创建一个数据源new ListView.DataSource();
实现rowHasChanged方法,它告诉ListView是否要重绘(数据改变)了,当state变化时,组件会调用该方法。
2. 将的数据源ListDataSourceListView绑定,<ListView dataSource=.../>;
设置dataSource属性前,必须调用cloneWithRows方法对数组进行clone。
设置renderRow属性,告诉ListView每一行渲染的组件和内容。

==通常我们把变化的属性定义在state中,如dataSource,但这并不是强制的。==

常用 Props

除了DataSourcerenderRow属性外,还有其他的属性,如:

样式
* renderSeparator : 设置每一行渲染分割线。
* renderSectionHeader : 设置列表每个小节(section)渲染的标题。类似iOS UITableView中的section
* renderFooter : 设置页脚。
* renderHeader : 设置页头。

回调
* onEndReached : 设置在到达列表尾部时的回调方法。
* onChangeVisibleRows : 设置在可见行数据发生变化时的回调方法。

优化
* pageSize : 每个事件循环渲染的行数,设置过小,上拉时卡顿并会看到新页面的加载。
* initialListSize : 设置组件刚加载的时候渲染多少行。
* scrollRenderAheadDistance: 当一个行接近屏幕可视范围多少像素内的时候,就开始渲染这一行。

案例

下面我们结合布局,完善整个列表,详细代码请参考 Demo 1

访问网络

现在我们把网络数据访问加上。多年来,在Web领域当人们谈及Ajax的时候,通常指的是基于XMLHttpRequest实现的Ajax。 它是一种能有效改进页面通信的技术。而接下来我们要说的是XMLHttpRequest的最新替代品——Fetch API 它是W3C的正式标准,编写起来更简单。

Promise对象基础

Fetch API是基于ES6Promise对象实现的。Promise是异步编程的的一种解决方案。比传统的解决方案“回调函数和事件”更合理、更强大。如果你喜欢可以使用Promise结合XMLHttpRequest实现一套自己的"Fetch API"。Promise可以理解为保存着某个未来才会结束的事件的容器。它代表一个异步操作,包含三种状态Pending(进行中)、Resolved(已完成)、Rejected(已失败)。

var promise = new Promise(function(resolve, reject) {
  // ... some code

  if (/* 异步操作成功 */){
    resolve(value);
  } else {
    reject(error);
  }
});

//使用
promise.then(function(value) {
  // success
}, function(error) {
  // failure
});

Promise的构造函数接受一个函数作为参数,该参数分别是resolvereject函数,它们分别会在在异步操作成功或失败时调用,并将异步操作的结果,作为参数传递出去。需要注意的是这两个函数是由JavaScrpit引擎提供。
Promise实例创建好之后,可以用Promise.prototype.then()分别指定Resolved状态和Reject状态的回调函数。
通过Promise.prototype.catch().then(null, rejection)方法的别名)捕获错误。

Fetch API

有了Promise基础后,就不难理解Fetch API

function getData() {
    let url = '...'
    fetch(url).then(function(response) {
        return response.json();
    }).then(function(json) {
        ...
    });
}

上面的代码执行完getch(url)后会立刻返回一个Promise对象,通过Promise后,返回一个Response对象,通过该对象的json()方法可以将结果作为JSON对象返回。response.json()同样会返回一个Promise对象,因此在我们的例子中可以继续链接一个then()方法。

function getData() {
    let url = '...'
    var req = new Request(url, {method: 'GET', cache: 'reload'});
    fetch(url).then(function(response) {
        return response.json();
    }).then(function(json) {
        ...
    });
}

// 还可以基于req对象创建新的postReq对象
 var postReq = new Request(req, {method: 'POST'});

也可以通过Request构造函数创建一个新的请求对象,第一个参数是请求的URL,第二个参数是一个选项对象,用于配置请求,如设置请求头headers、请求方式method等。

由于Fetch API基于Promise的特点,可以发现其方法都是顺序执行,并且不能中断。

案例

现在我们将上一个章节实现的列表的数据,从本地创建改为从网络请求,案例使用了“多米音乐”搜索歌曲的API
下面给出部分核心代码,详细请参考 Demo 2

getListData() {
    const reqUrl = "http://v5.pc.duomi.com/search-ajaxsearch-searchall?kw=%E5%9B%A0%E4%B8%BA%E7%88%B1&pi=1&pz=50";
    fetch(reqUrl, {
        method: 'GET',
        headers: {
            'Accept': 'application/json',
            "Content-Type": "text/plain"
        }
    }).then((resp) => {
        if (resp.ok) {
            resp.json().then((data) => {
                this.setState({
                    dataSource: this.state.dataSource.cloneWithRows(data.tracks),
                    loaded: true        //数据加载状态
                });
            })
        } else {
            console.log("Looks like the response wasn't perfect, got status", resp.status);
        }
    }).catch((error) => {
        console.log("Fetch failed!", error);
    });
}

Navigators

Android 开发者可能对Navigators(导航)的认知比较浅,其实它就是一个容器,记录页面切换、跳转的相关信息。在本章中我们将导航中的页面称之为screen。RN 中提供了三种导航方式。
- StackNavigator : 栈导航,最常用的一个,一次展示一个screenscreen跳转时,将新screen放到栈顶。
- TabNavigator : 页签导航,显示一个标签栏,可以在标签之间切换,类似微信下面的页签栏。
- DrawerNavigator : 抽屉导航,可从屏幕左侧划入一个抽屉菜单,在Android上应用很广泛。

添加react-navigation

导航相关的组件并不在react-native库中,使用前我们需要添加react-navigation
添加方式很简单只需要在"项目根目录"下执行:

npm install --save react-navigation

等下载完成就可以在项目中使用了。

共享代码

我们想让AndroidiOS都使用demo目录下的home.js文件,作为应用的首页。 首先先删除index.ios.jsindex.android.js文件的内容,再分别添加import './demo/home'即可共享一份代码。

使用方法

1. Stack Navigator

import React from 'react';
import {
  AppRegistry,
  Text,
} from 'react-native';
import { StackNavigator } from 'react-navigation';

class HomeScreen extends React.Component {
  static navigationOptions = {
    title: 'Welcome',
  };
  render() {    
    return <Text>Hello, Navigation!</Text>;
  }
}

const SimpleApp = StackNavigator({
  Home: { screen: HomeScreen },
});

AppRegistry.registerComponent('SimpleApp', () => SimpleApp);

上面的代码创建了一个StackNavigator
通过Home: { screen: HomeScreen}HomeScreen和导航关联起来,:前面的模式名可以是任意的。
并通过static navigationOptions设置HomeScreen在父导航的标题。
最后使用registerComponent将导航注册给RN。

2. 跳转到新Screen

新定义一个ChatScreen

class ChatScreen extends React.Component {
  static navigationOptions = {
    title: 'Chat with Lucy',
  };
  render() {
    return (
      <View>
        <Text>Chat with Lucy</Text>
      </View>
    );
  }
}

HomeScreen添加一个Button, 点击跳转到ChatScreen

class HomeScreen extends React.Component {
  static navigationOptions = {
    title: 'Welcome',
  };
  render() {
    const { navigate } = this.props.navigation;
    return (
      <View>
        <Text>Hello, Chat App!</Text>
        <Button
          onPress={() => navigate('Chat')}
          title="Chat with Lucy"
        />
      </View>
    );
  }
}

const SimpleApp = StackNavigator({
  Home: { screen: HomeScreen },
  Chat: { screen: ChatScreen },
});

这里使用到了Screen的一个属性navigation,通过const { navigate }解构解析出子属性navigate
navigate接受一个叫routeName的参数Chat,这是跳转页面的标识,ChatStackNavigator定义。

3.传参

接下来我们让HomeScreen传递一个字符串给ChatScreen,动态配置标题。

class HomeScreen extends React.Component {
  static navigationOptions = {
    title: 'Welcome',
  };
  render() {
    const { navigate } = this.props.navigation;
    return (
      <View>
        <Text>Hello, Chat App!</Text>
        <Button
          onPress={() => navigate('Chat', { user: 'Lucy' })}
          title="Chat with Lucy"
        />
      </View>
    );
  }
}

navigate除了routeName参数外,又指定了一个新参数{user: 'Lucy'}user即为传递参数的属性名。

class ChatScreen extends React.Component {
  // Nav options can be defined as a function of the screen's props:
  static navigationOptions = ({ navigation }) => ({
    title: `Chat with ${navigation.state.params.user}`,
  });
  render() {
    // The screen's current route is passed in to `props.navigation.state`:
    const { params } = this.props.navigation.state;
    return (
      <View>
        <Text>Chat with {params.user}</Text>
      </View>
    );
  }
}

新页面通过navigation.state.params.user即可取到传递的参数。navigationOptions接受navigationscreenProps参数的函数,这个参数是可选的。
这两个属性的相关参数是在不同状态下进行设置的,下面会详细的说明。

自定义导航栏

//页面跳转
this.props.navigation.navigate('Chat', { user:  'Lucy' });

class ChatScreen extends React.Component {
  static navigationOptions = ({ navigation }) => ({
    //动态设置标题
    title: `Chat with ${navigation.state.params.user}`,
    headerRight: <Button title="Info" />
  });
  ...
}

上面的代码通过navigationOptions静态对象动态设置标题,添加导航栏右侧按钮。
不仅如此它还有其他headerTintColor:Header颜色,headerStyleHeader样式等。如果使用了StackNavigator还可以配置mode:设置转场动画风格等等。

Screen导航的属性

App中每个Screen还可以通过this.props.navigation获取下列属性进行动态配置:
- navigate : 链接(跳转)到其他Screen
- state : Screen当前的state/routes
- setParams : 设置更改route的参数。
- goBack : 关闭一个活动的screen
- dispatch : 给router送一个事件。

navigate上面我们介绍过,下面我们介绍下其他几个属性的使用。

1. setParams

class ProfileScreen extends React.Component {
  render() {
    const {setParams} = this.props.navigation;
    return (
      <Button
        onPress={() => setParams({name: 'Lucy'})}
        title="Set title name to 'Lucy'"
      />
     )
   }
}

改变当前Screen的标题。

2. goBack

navigation.goBack();        //从当前`Scrren`返回 
navigation.goBack(null);    //效果同上
navigation.goBack('Home');  //指定从哪个`Screen`返回

goBack('Home')看起来参数是routeName,实际上是自动生成的随机key
this.props.navigation.state.key可以拿到该值。

3. dispatch

使用dispatch可以给router发送任意的导航动作。下面跳转到ProfileScreen

import { NavigationActions } from 'react-navigation'

const navigateAction = NavigationActions.navigate({
  routeName: 'Profile',
  params: {},

  // navigate can have a nested navigate action that will be run inside the child router
  action: NavigationActions.navigate({ routeName: 'SubProfileRoute'})
})
this.props.navigation.dispatch(navigateAction)

上面代码创建了一个navigateAction,指定了routeName,最终使用dispatchaction发送给router
NavigationActions还提供了几个静态方法来快速创建Action,如Back,Reset,NavigateSetParams等。

const action = NavigationActions.reset({
    index: 0,
    actions: [
        NavigationActions.navigate({ routeName: 'Home'})
    ]
})
this.props.navigation.dispatch(action);

上面代码使用一个新的actions数组重新设置了当前导航的路由状态。

Screen另一个属性

除了navigationprops还有另外一个this.props.screenProps属性。screenProps在配置导航路径时使用,在渲染时相关设置参数才会被传入。

const SimpleApp = StackNavigator({
    // config
});
<SimpleApp
  screenProps={{tintColor: 'blue'}}
  // navigation={{state, dispatch}} // optionally control the app
/>

案例

现在继续接着上之前的代码,继续完善Demo。当用户点击列表的时候,跳转到歌曲的详情页,并将歌曲的信息传递到详情页进行展示。详情参考 Demo 3

发表评论

电子邮件地址不会被公开。 必填项已用*标注

返回主页看更多
狠狠的抽打博主 支付宝 扫一扫