Blogブログ

SEO

システム

javascript

next.js

2022.03.18

react/Next.jsでAPIから値を取得して表示する使い方

こんにちは。月岡です。
今日はNext.jsでAPIから値をとる使い方を書こうかな思います。

まず何よりも先にnodenvの設定をします。

あるフォルダに入った時に自動で必要なnode.jsのバージョンに切り替えてくれるバージョン管理ツールです。


git clone https://github.com/nodenv/nodenv.git ~/.nodenv
cd ~/.nodenv && src/configure && make -C src
echo 'export PATH="$HOME/.nodenv/bin:$PATH"' >> ~/.zshrc
echo 'eval "$(nodenv init -)"' >> ~/.zshrc
exec $SHELL -l
nodenv --version
mkdir -p "$(nodenv root)"/plugins
git clone https://github.com/nodenv/node-build.git "$(nodenv root)"/plugins/node-build
git clone https://github.com/nodenv/nodenv-update.git "$(nodenv root)"/plugins/nodenv-update
nodenv update
nodenv install --list
nodenv --version
nodenv install 12.16.3 #自分のPCにバージョンを入れる
nodenv global 12.16.3 #PC全体のバージョンの指定
cd /XXXXX   #特定のフォルダに移動
nodenv local 12.16.3  #特定のフォルダに入ったときだけこのバージョンに自動で変更
cat .node-version #このフォルダに入ったときだけ実行されるバージョンの確認

これで、プロジェクトフォルダのnodeのバージョンを決めたとして、本題にいってみます。

Next.jsの環境構築をやってみます。

nextjsはSSRとかもやってくれて便利です^^b

では早速、(nodeとかは入ってる前提で。)

npm init -y
npm install --save react react-dom next
mkdir pages
vim package.json

で、これに差し替えてください。

{
  "name": "self_project",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "dev": "next",
    "build": "next build",
    "start": "next start"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "next": "^9.4.4",
    "react": "^16.13.1",
    "react-dom": "^16.13.1"
  } 
} 

で、保存をします。

そして、

npm run dev

これで http://localhost:3000 にアクセスすると見事に404のページがw

どうやら成功したようだな…

では、pagesのフォルダに入りまして、
そこでindex.jsを作ってみましょう。
それで404はなくなります。

デフォルトでそこを見るようになってるからです。

ちなみに、

export default () => (
  <>
    <h1>トップなりよ。</h1>
    本文
  </>
);

これでも動くけど、
だいたいは、

const Index = () => (
  <>
    <h1>トップなりよ。</h1>
    本文
  </>
);
 
export default Index;

こうしますよ。

ちなみに、頭に、
import Link from ‘next/Link’;

を書くと、リンクのための記述を使えるようになりますよ。

例えば、index.jsと同じpagesの階層に、blog.jsとかを作って、

import Link from 'next/Link';


const Blog = () => (
  <>
    <h1>ブログなりよ。</h1>
    本文
    <Link href="index.js"><a>TOPへリンク</a></Link>
  </>
);

export default Blog;

で、index.jsは

import Link from 'next/Link';


const Index = () => (
  <>
    <h1>TOPなりよ。</h1>
    本文
    <Link href="blog.js"><a>ブログへリンク</a></Link>
  </>
);

export default Index;

とかにすると、リンクがつながるのがわかると思います。
使い方わかりやすいですね。

Layout.jsでレイアウトを組んでみるか…。

次に、pagesフォルダと同階層にcomponentsと言うフォルダを作り
その中に、Layout.jsを作ってみましょう。

const Layout = (props) => (
    <>
      {props.children}
    </>
);

export default Layout;

これだけだと価値がわからないので、
メニューを追加しようと思います。

このファイルはいったん保存してください。

そして同階層(つまりcomponentsフォルダの中)にNavi.jsを作りましょう。

そして、そのNavi.jsをこのようにします。

import Link from 'next/link';

const Navi = () => (
   <div>
     <ul>
       <li><Link href="/"><a>トップ</a></Link></li>
       <li><Link href="/blog"><a>ブログ</a></Link></li>
     </ul>
    </div>

);

export default Navi;

これを先ほどのLayout.jsから呼び出すんですが、
ちょっとcssもこのメニューに付け足したいのでcssを書きます。
cssをそのまま書くときは<style jsx>{`XXXXXX`}</style>この中に書くので、

import Link from 'next/link';

const Navi = () => (
   <div>
     <ul>
       <li><Link href="/"><a>トップ</a></Link></li>
       <li><Link href="/blog"><a>ブログ</a></Link></li>
     </ul>
   

    <style jsx>{`
       ul {
          background: #333;
          color: #fff;
          list-style:none;
          display:flex;
       }
      ul li {
          font-size:18px;
          margin-right:20px;
       }

      ul li a {
       color: #FFF;
       text-decoration:none;

      }
    `}</style>

    </div>

);

export default Navi;

こんな感じで書くことができます。
では、このNavi.jsをLayout.jsで呼び出してみます。

import Navi from './Navi';

const Layout = (props) => (
    <>
      <Navi/>
      {props.children}
       
    </>   
);        
          
export default Layout; 

このように、初めにNaviを読み込んで、
Naviを表示したいところに<Navi/>を書くだけで終わりです。

使い方の例としては、このような感じで、たくさんコンポートネントを作って埋め込むことができます。
Layout.jsは
<header/>
<content/>
<sidebar/>
<footer/>
とかになっていくイメージですね。

<head>を書いて、cssを読み込んだりする場合やtitleタグを書く場合はどうするのか

それは、next/headを読み込みます。
layout.jsで読み込んでみましょう。

import Head from 'next/head';
import Navi from './Navi';

const Layout = (props) => (
    <>
      <Head>
         <title>タイトルくるんごね</title>
         <link rel="stylesheet" href="https://bootswatch.com/4/cerulean/bootstrap.min.css" />
      </Head>
      <Navi/>
      {props.children}
       
    </>   
);        
          
export default Layout; 

こんな感じになります。

では、index.jsやblog.jsにこのレイアウトを適応させていきます。

まずindex.js

import Link from 'next/Link';
import Layout from '../components/Layout';

const Index = () => (
  <Layout>
  <>
    <h1>トップなりよ。</h1>
    本文
  </>
  </Layout>
);
 
export default Index;

このように、Layout.jsを読み込んで、<Layout>タグで囲ってあげると
その中身が、Layout.jsの{props}に入ると言う考え方です。

blog.jsも同じように

import Link from 'next/Link';
import Layout from '../components/Layout';

const Blog = () => (
  <Layout>
  <>
    <h1>ブログなりよ。</h1>
    本文
  </>
  </Layout>
);
 
export default Blog;

と書きます。

これで、メニューができて、ブログと行き来するようなページができました。

次にapi通信してみます。

わりと参考ページがレイアウトまでのところが多くて、
どうやってapi通信して表示するのかまで書いていなかったりするので、
今回はapiを通信をして値を取得し、表示すると言うところまでやってみます。

まず、ターミナルで、

npm install isomorphic-unfetch

を実行しましょう。

その後にpackage.jsonをみると…

{
  "name": "self_project",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "dev": "next",
    "build": "next build",
    "start": "next start"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "@zeit/next-css": "^0.1.5",
    "isomorphic-unfetch": "^3.0.0",
    "next": "^9.4.4",
    "react": "^16.13.1",
    "react-dom": "^16.13.1",
    "react-redux": "^5.0.7",
    "react-scripts": "1.1.4",
    "redux": "^4.0.0",
    "redux-devtools": "^3.4.1"
  }
}

“isomorphic-unfetch”: “^3.0.0”,が追加されてますね。

そしたらpagesフォルダのindex.jsを編集しましょう。

import Fetch from 'isomorphic-unfetch';
import Layout from '../components/Layout';

const Index = () => (
  <Layout>
  <>   
    <h1>トップなりよ。</h1>
    本文
  </>
  </Layout>
);  
       
Index.getInitialProps = async function(){
  const res = await fetch('https://api.coindesk.com/v1/bpi/currentprice.json');

  const data = await res.json();
  
  return {
    bpi: data
  }

}


export default Index; 

上の方で、非同期通信するためにisomorphic-unfetchをインポートをしています。
asyncやawaitは非同期するときに昔から出てくるjavascript表現なので直感的にもわかりやすいです。

apiをコールしてresに入れて、json型にしたものをdataに入れると


{“time”:{“updated”:”Sep 18, 2013 17:27:00 UTC,”updatedISO”:”2013-09-18T17:27:00+00:00″},”disclaimer”:”This data was produced from the CoinDesk Bitcoin Price Index. Non-USD currency data converted using hourly conversion rate from openexchangerates.org”,”bpi”:{“USD”:{“code”:”USD”,”symbol”:”$”,”rate”:”126.5235″,”description”:”United States Dollar”,”rate_float”:126.5235},”GBP”:{“code”:”GBP”,”symbol”:”£”,”rate”:”79.2495″,”description”:”British Pound Sterling”,”rate_float”:79.2495},”EUR”:{“code”:”EUR”,”symbol”:”€”,”rate”:”94.7398″,”description”:”Euro”,”rate_float”:94.7398}}}
こんな感じで入ってるので、
bpi: dataとすることでdataのなかのbpiオブジェクトを
bpiに入れています。

非同期で取得したオブジェクトを表示する

index.jsの引数にpropsを入れます。
非同期でreturnされたオブジェクトはpropsに入ってくるので、
上のオブジェクトの太くなってる部分を表示してみましょう。

bpiのなかのtimeのなかのupdatedを指定すれば、Sep 18, 2013 17:27:00 UTCと表示されるはずです。

import Fetch from 'isomorphic-unfetch';
import Layout from '../components/Layout';

const Index = (props) => (
  <Layout>
  <>   
    <h1>トップなりよ。</h1>
    {props.bpi.time.updated}
  </>
  </Layout>
);  
       
Index.getInitialProps = async function(){
  const res = await fetch
  ('https://api.coindesk.com/v1/bpi/currentprice.json');

  const data = await res.json();
  
  return {
    bpi: data
  }

}


export default Index; 

Pricesクラスから現在のUSDの値段を表示

次に、componentsフォルダの中にPrices.jsを作り、
その中に、
このコードを書いてください。

class Prices extends React.Component {
  state = {
    currency: 'USD'
  }
  
  render(){
    return(
    <div> 
     <ul className="list-group">
        <li className="list-group-item">
          bitcoin rate for {this.props.okane.USD.description}
          :<span className='badge badge-primary'>{this.props.okane.USD.code}</span>
          <strong>{this.props.okane.USD.rate}</strong>
          
       </li>
    </ul>
    </div>
    );
  
  }       
  
}   
export default Prices;

クラスの書き方と、renderするとコンポーネントとしてindex.jsに<Prices />で表示できるようにする文法がわかればいいかなと。

気になるのが、
this.props.okane ですよね。

これはindex.jsにこのように書くことで、
使えるようになります。

import Fetch from 'isomorphic-unfetch';
import Layout from '../components/Layout';
import Prices from '../components/Prices';
   
const Index = (props) => (
  <Layout>
  <>   
    <h1>トップなりよ。</h1>
    {props.bpi.time.updated}
    <Prices okane={props.bpi.bpi}/>
  </>
  </Layout>
);        
          
Index.getInitialProps = async function(){
  const res = await fetch
  ('https://api.coindesk.com/v1/bpi/currentprice.json');
      
  const data = await res.json();
          
  return {
    bpi: data
  };  
       
}      
export default Index;

つまり、apiのデータをbpiというオブジェクトに入れましたが、
その中にさらにbpiという同名のオブジェクトがたまたまあり、その中にUSDとかのパラメータがあり、そこに現在のUSDの金額とかがあったので、
bpi.bpiのオブジェクトをokaneという名前で、Priceクラスに引き渡してると考えてください。

ちなみに、今、画面ではこんな風に見えています。

選択したらリアルタイムに金額が出るように変更…するか..。

Price.jsをこのように変えてみてください。

class Prices extends React.Component {
  state = {
    currency: 'USD'
  }
  
  render(){
    let list = '';
    
    if (this.state.currency === 'USD') {
       
      list = <li className="list-group-item">
             bitcoin rate for {this.props.okane.USD.description}
             :<span className='badge badge-primary'>{this.props.okane.USD.code}</span>
              <strong>{this.props.okane.USD.rate}</strong>
              </li>
    } else if (this.state.currency === 'GBP') {
      list =  <li className="list-group-item">
              bitcoin rate for {this.props.okane.GBP.description}
              :<span className='badge badge-primary'>{this.props.okane.GBP.code}</span>
              <strong>{this.props.okane.GBP.rate}</strong>
              </li>

    } else if (this.state.currency === 'EUR') {
      list =  <li className="list-group-item">
               bitcoin rate for {this.props.okane.EUR.description}
               :<span className='badge badge-primary'>{this.props.okane.EUR.code}</span>
               <strong>{this.props.okane.EUR.rate}</strong>
              </li>

    }

    
    return(
    <div>
     <ul className="list-group">
      {list}
    </ul><br/>

    <select onChange={e => this.setState({currency: e.target.value})} className="form-control">
       <option value="USD">USD</option>
       <option value="GBP">GBP</option>
       <option value="EUR">EUR</option>
    </select>
    </div>
    );

  }       
  
}   
export default Prices;

さっきのliをlistという変数に入れてます。
そして「stateのcurrencyがUSDだったらこのli出してね」というif文にしています。


下の方にselect文を書いています。
onChange={e=>this.setState({currency: e.target.value})}で、
このselectのoptionが選択されるたびにそのvalueがe.target.valueで取れるので、
それをthis.setState({XXX:YYY})で上に書いたstateの値を入れ替えるという機構を用いて、動的にstateの値を変えました。

その結果、

成果物としてはこのようになりました。

それっぽいのができました。

本当にSSRされてるのかいな..

されてますね。

ちなみにSPAでも最近はgoogleに読まれるからSEO的に問題ないという話

それはほぼ間違っていると言っていいのでご注意を。

実際に、難易度が同じくらいの、
サイトキーワードを変えたサイトを5個ずつ作りましょう。

5つはSPAで、5つはSSRで作りましょう。

はい、SSRなしのSPAはgoogleに認識はされますが上位には来ません。

■コンソールで見ると画面が表示されてるから問題ない

■googleクローラーも賢くなってきてSPAでも読んでくれるようになってる

確かにそうですが、
SEOは強くなりませんのでご注意を。

まとめ

  1. まずはpackage.jsonで使うモジュールを管理できるようにする
  2. ページ間の移動をLinkを使ってできるようにする
  3. cssを直接書くのは<style jsx>{` `}</style>ですよ。
  4. headを読み込めばいつもの<head>がかけるよ。
  5. classも変数もコンポーネントとして呼び出せるよ
  6. layout.jsの使い方覚えてね。
  7. 値の受け渡しはpropsの引数に入ってくるよ
  8. apiをコールしてjsonにしてpropsに渡せば安心だよ
  9. stateを利用して動的に変更できるよ。

ですね。