@mizumotokのブログ

テクノロジー、投資、書評、映画、筋トレなどについて

React NativeでRSSリーダーをつくろう

Swift版に続き、React Native版のRSSリーダーを作ってみましょう。React Native版ならjavascriptで実装でき、iOSでもAndroidでも動きます。

今回はシンプルにYahoo!トピックスの見出しのみを表示して、内容はSafariに飛ばして表示するようにしています。

f:id:mizumotok:20180412193421j:plain

プロジェクト作成

React Nativeのプロジェクトはコマンドで作成します。react-nativeコマンドのインストール等事前準備はこちらを参考にしてください。

$ react-native init ReactNativeRssReader
$ cd ReactNativeRssReader
$ react-native run-ios

シミュレータがさくっと起動して、テンプレートのアプリが立ち上がります。

App.jsを改修していきましょう。

RSSのパース

RSSのURLを指定します。今回はソースに直書きです。

const FEED_URL = 'https://news.yahoo.co.jp/pickup/rss.xml';

Javascriptには標準のXMLパーサーがありません。node-xml2jsが使いやすいのですが、これをReact Nativeで使えるようにしたreact-native-xml2jsを使ってみます。

$ yarn add react-native-xml2js

componentDidMountでRSSを取得して、stateに保持しておきます。

type Props = {};

type State = {
  feedItems: Array,
};

export default class App extends Component<Props, State> {
  state = {
    feedItems: [],
  }

  componentDidMount() {
    fetch(FEED_URL)
      .then(res => res.text())
      .then((xml) => {
        const parser = xml2js.Parser();
        parser.parseString(xml, (err, result) => {
          this.setState({ feedItems: result.rss.channel[0].item });
        });
      });
  }

これでstateのfeedItemsにニュース一覧が入ります。
flowで型を指定しておくとよいです。

type FeedItem = {
  link: Array<string>,
  title: Array<string>,
};

type State = {
  feedItems: Array<FeedItem>,
};

描画処理

stateに格納されたデータを使用して表示部分を作成します。データの取得と表示を切り離して考えられるのはReact Nativeの本当にいいところですね。Swift版だとデータを取得した後tableViewをreloadDataする必要がありました。
React Nativeは描画だけでUIに関してはほとんど何もないので、StyleSheetを使って自分で整えていかないといけません。NativeBaseのようなUIライブラリを使ってもいいですが。

それほどデータ量は多くないですが、任意のデータ量を扱うのでFlatListを使っておきましょう。任意のデータのリストの描画にはScrollView内にViewを並べると極端にパフォーマンスが落ちることがあります。特にAndroidの場合はクラッシュしやすいです。
こういうケースではFlatListやSectionListを使いましょう。

  render() {
    return (
      <View style={styles.container}>
        <FlatList
          ItemSeparatorComponent={() => <View style={styles.separator} />}
          data={this.state.feedItems}
          renderItem={renderItem}
          keyExtractor={item => item.link[0]}
        />
      </View>
    );
  }

あとは実際に描画するrenderItemを実装するだけです。

const renderItem = (params: { item: FeedItem }) => (
  <TouchableOpacity onPress={() => Linking.openURL(params.item.link[0])}>
    <View style={styles.row}>
      <Text>{params.item.title[0]}</Text>
    </View>
  </TouchableOpacity>
);

stylesを適切に記述すれば完成です。

Learning React Native: Building Native Mobile Apps with JavaScript

Learning React Native: Building Native Mobile Apps with JavaScript


ソースコード

App.js

// @flow

import React, { Component } from 'react';
import {
  FlatList,
  Linking,
  StyleSheet,
  Text,
  TouchableOpacity,
  View,
} from 'react-native';
import xml2js from 'react-native-xml2js';

const FEED_URL = 'https://news.yahoo.co.jp/pickup/rss.xml';

const styles = StyleSheet.create({
  container: {
    flex: 1,
    flexDirection: 'row',
    backgroundColor: '#FFF',
    paddingTop: 20,
  },
  row: {
    flex: 1,
    height: 50,
    padding: 10,
  },
  separator: {
    flex: 1,
    height: 1,
    backgroundColor: '#ddd',
    marginLeft: 10,
  },
});

type Props = {};

type FeedItem = {
  link: Array<string>,
  title: Array<string>,
};

type State = {
  feedItems: Array<FeedItem>,
};

const renderItem = (params: { item: FeedItem }) => (
  <TouchableOpacity onPress={() => Linking.openURL(params.item.link[0])}>
    <View style={styles.row}>
      <Text>{params.item.title[0]}</Text>
    </View>
  </TouchableOpacity>
);

export default class App extends Component<Props, State> {
  state = {
    feedItems: [],
  }

  componentDidMount() {
    fetch(FEED_URL)
      .then(res => res.text())
      .then((xml) => {
        const parser = xml2js.Parser();
        parser.parseString(xml, (err, result) => {
          this.setState({ feedItems: result.rss.channel[0].item });
        });
      });
  }

  render() {
    return (
      <View style={styles.container}>
        <FlatList
          ItemSeparatorComponent={() => <View style={styles.separator} />}
          data={this.state.feedItems}
          renderItem={renderItem}
          keyExtractor={item => item.link[0]}
        />
      </View>
    );
  }
}