ReactApps

2019年3月23日

Hacker News APIを使ってニュースを表示する

3f_uz9dkm

はじめに

ローカル環境でこのサンプルを動かすには以下の手順に従ってください。

$ git close git@github.com:reiwa/reactapps.git
$ cd reactapps
$ yarn
$ yarn workspace hn start

※ $の部分はペーストしないでください。

内容

Hacker News APIを使ってニュースのタイトルの一覧を表示するアプリをつくります。
Hacker News APIではニュースやコメント、それからユーザなどのデータを取得できます。更新頻度も高くデータの量も十分にあります。また、データの取得にはFetch APIを使用しています。

コード

import React, { FunctionComponent, useEffect, useState } from 'react'

type Post = {
  id: number
  title: string
  url?: string
}

const App: FunctionComponent = () => {
  const [posts, setPosts] = useState<Post[]>([])

  useEffect(() => {
    getPostIds()
      .then((ids) => ids.map((id) => getPost(id)))
      .then((promises) => Promise.all(promises))
      .then((posts) => {
        setPosts(posts)
      })
  }, [])

  return (
    <ul>
      {posts.map((post) => (
        <li key={post.id}>
          <a href={post.url}>{post.title}</a>
        </li>
      ))}
    </ul>
  )
}

const getPostIds = () => {
  const input = 'https://hacker-news.firebaseio.com/v0/newstories.json'

  return fetch(input)
    .then<string[]>((res) => res.json())
    .then((ids) => ids.filter((_, i) => i < 20))
}

const getPost = async (id: string) => {
  const input = `https://hacker-news.firebaseio.com/v0/item/${id}.json`

  return fetch(input).then<Post>((res) => res.json())
}

export default App

いくつかポイント

過去500件の投稿のIdsを取得する関数を定義します。多いので `filter関数` で20件以下にしています。
この関数は投稿のIDを返すだけなのでそれぞれのIDから投稿を取得する必要があります。

const getPostIds = () => {
  const input = 'https://hacker-news.firebaseio.com/v0/newstories.json'
  return fetch(input)
    .then<string[]>(res => res.json())
    .then(ids => ids.filter((_, i) => i < 20))
}

それぞれIDから投稿を取得する関数を定義します。今回は20件のIDから20件のリクエストを投げて20件の投稿を取得します。

const getPost = async (id: string) => {
  const input = `https://hacker-news.firebaseio.com/v0/item/${id}.json`
  return fetch(input).then<Post>(res => res.json())
}

取得する投稿のTypeを定義しておきます。

type Post = {
  id: number
  title: string
  url?: string
}

`useEffect` 関数を用いて投稿を取得します。IDごとの投稿の取得は並列で処理したいので `Promise.all` 関数を用いています。あまりに多いとメモリーリークするかもしれないです。

const [posts, setPosts] = useState<Post[]>([])

useEffect(() => {
  getPostIds()
    .then(ids => ids.map(id => getPost(id)))
    .then(promises => Promise.all(promises))
    .then(posts => {
      setPosts(posts)
    })
}, [])

表示数やページを切り替える場合は `useEffect` の第二引数を用いてください。

const [page] = useState(0)

useEffect(() => {}, [page])