20210122のReactに関する記事は6件です。

[React] Reactの学習をします(2-2)Node.js による API サーバー開発

Reactの学習をします(2-2)

引き続き、Reactの学習をしています。

前回の記事 : [React] Reactの学習をします(2-1)レビューサイトを作ろう

教材

Reactチュートリアル2:レビューサイトを作ろう

引き続き、likr さんが公開している「Reactチュートリアル2:レビューサイトを作ろう」という記事を教材に学習させて頂きます。

素晴らしい教材をありがとうございます。

成果物

このチュートリアル2につきましては、チュートリアルの通りに作成し既にNetlifyに公開させて頂いております。

こちらです → 日大文理ラーメンレビュー

学習日記

既に作成済ですが、少しずつチュートリアルの内容を読み返してみたいと思います。

※ 教材から箇条書き的に抜粋させて頂きます。

Node.js による API サーバー開発

・Node.js 用の人気のある Web フレームワークとして Express があります。ここでは Express を使って API サーバーを実装していきます。

・本チュートリアルでは、Node.js のバージョン 14 を前提に説明を行います。

私のローカル環境では nodist がインストールされてあり、古いバージョンのNode.jsを入れてありました。

チュートリアルと同じ環境にするため バージョン14 をさらにインスールしました。

・Express では app.get(path, handler) のように書くことで、path に対する GET リクエストを handler の関数で処理することができます。

・POST リクエストであれば、app.post 、PUT と DELETE もそれぞれ app.putとapp.delete で同じように書くことができます。

・実際にエンドポイント /restaurants に対する処理を書くと以下のようになります。

app.get("/restaurants", async (req, res) => {
  const limit = +req.query.limit || 5;
  const offset = +req.query.offset || 0;
  const restaurants = data.restaurants;
  res.json({
    rows: restaurants.slice(offset, offset + limit),
    count: data.restaurants.length,
  });
});

・handler の関数の第 1 引数にはリクエストの情報を含んだ Request オブジェクトが、第 2 引数にはレスポンスを返すための Response オブジェクトが渡されます。

・クエリ文字列に含まれるパラメータは req.query から取り出すことができます。

・レスポンスを JSON 形式で返すためには res.json にオブジェクトを渡します。

このような形で、まず /restaurants , /restaurants/:restaurantId , /restaurants/:restaurantId/reviews の3つの get について処理を書いていきます。

JavaScript リファレンス

Array.prototype.slice()

Array.prototype.find()

Array.prototype.filter()

(つづく)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JSXの中でif...else文を使って条件分岐する

Reactで開発をしていると、propsの値に応じて返すコンポーネントを変えたい場合が多々あります。
しかし、JSX内で実行できるのは式(expression)のみであり、if...else条件のような文(statement)は書くことができないため、複雑な分岐処理を書くためには工夫が必要になります。

案1. 三項演算子を組み合わせる

三項演算子を使った式A ? B : Cは、Aがfalseと評価された場合にCを返すだけなので、その後にCを起点として再び三項演算子の式を繋げることができます。
簡単な方法ですが、条件が複雑になると異様に読みづらくなります。

const Fruit = props => {
  return (
    <div>
      {props.fruit === 'apple' ? (
        <Apple />
      ) : props.fruit === 'orange' ? (
        <Orange />
      ) : (
        <Banana />
      )}
    </div>
  );
};

案2. 即時関数を使う

関数はJSX内で実行できるため、関数内でif文を使って条件分岐することができます。
ただ即時関数は読み辛いので、こちらも個人的には好きではありません。

const Fruit = props => {
  return (
    <div>
      {(() => {
        if (props.fruit === 'apple') {
          return <Apple />;
        } else if (props.fruit === 'orange') {
          return <Orange />;
        } else {
          return <Banana />;
        }
      })()}
    </div>
  );
};

案3. 条件分岐処理をJSXの外に出す

当然ですが、JSX以外の場所なら普通にJavaScriptが書けます。なので、return文の前で条件分岐の処理を書いてしまえば良いです。
コンポーネントが大きくなると、条件分岐処理とreturn文が離れてしまい読みづらくなることがありますが、多くの場合はこちらの方がシンプルに書くことができると思います。

const Fruit = props => {
  let fruit;
  if (props.fruit === 'apple') {
    fruit = <Apple />;
  } else if (props.fruit === 'orange') {
    fruit = <Orange />;
  } else {
    fruit = <Banana />;
  }

  return <div>{fruit}</div>;
};

参考サイト

https://stackoverflow.com/questions/40477245/is-it-possible-to-use-if-else-statement-in-react-render-function:embed

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

React のはじめ方

React の環境構築の手順を紹介します。
初めてでもわかりやすいように書いていこうと思うので
なにかありましたらコメントにお願いします。

目次

  1. Node.js のインストール
  2. yarn のインストール
  3. Create React App のインストール

0. はじめに

Windows10を使用しています。

1. Node.js のインストール

https://nodejs.org/ja/
からNode.js をインストールします。

ここでは、LTS(推奨版)をインストールします。

インストール後に、コマンドプロンプトを開き「node」と入力します。

C:¥Users>node

インストールが成功していれば

C:¥Users>node
Welcome to Node.js v14.15.4.
Type ".help" for more information.

と出てきます。(バージョンはその時々です)

2. yarn のインストール

https://classic.yarnpkg.com/en/docs/getting-started
を参考に、yarn をインストールします。

yarn のインストールは、コマンドプロンプトから行います。

コマンドプロンプトに、「npm install --global yarn」を入力します。

C:¥Users>npm install --global yarn

インストールに成功していれば

C:¥Users>npm install --global yarn
> yarn@1.22.10 preinstall C:\Users\AppData\Roaming\npm\node_modules\yarn
> :; (node ./preinstall.js > /dev/null 2>&1 || true)

C:\Users\AppData\Roaming\npm\yarn -> 
C:\Users\AppData\Roaming\npm\node_modules\yarn\bin\yarn.js
C:\Users\AppData\Roaming\npm\yarnpkg -> 
C:\Users\AppData\Roaming\npm\node_modules\yarn\bin\yarn.js
+ yarn@1.22.10
added 1 package in 0.658s

と出てきます。

3. Create React App のインストール

https://ja.reactjs.org/docs/create-a-new-react-app.html
を参考にCreate React Appをインストールします。

こちらも、コマンドプロンプトからインストールを行います。

コマンドプロンプトに「npx create-react-app 〇〇」と入力します。
(〇〇はインストールされるファイルの名前になるので好きなものを入れてください。)

ここでは、React のサイトにある通りに「npx create-react-app my-app」と入力します。

C:¥Users>npx create-react-app my-app
C:¥Users>npx create-react-app my-app
yarn add v1.22.10 in 10.273s
[1/4] Resolving packages...
[2/4] Fetching packages...
info There appears to be trouble with your network connection. Retrying...
info fsevents@1.2.13: The platform "win32" is incompatible with this module.

(途中省略)

We suggest that you begin by typing:

  cd my-app
  yarn start

Happy hacking!

途中は省略してますが、Happy hacking! と出てくればインストール完了です。

次に、言われた通りに「cd my-app(自分で指定した場合はcd 〇〇)」と入れます。

C:¥Users>cd my-app

アプリが入っているディレクトリを指定できました。

C:¥Users¥my-app>

「yarn start」を入力します。

C:¥Users¥my-app>yarn start
C:¥Users¥my-app>yarn start
yarn run v1.22.10
$ react-scripts start

(途中省略)

Starting the development server...
Compiled successfully!

You can now view my-app in the browser.

と出てくるので、エクスプローラーの
C\Users\my-appの中にある src\App.js を開きます。

image.png

ここでは、Visual Studio を選択しました。

Inkedreact success_LI.jpg

この画面が出てくれば終了です。
コードをいじって反映されるものが変わることを確認してください。

参考にさせていただいた記事:https://qiita.com/rspmharada7645/items/25c496aee87973bcc7a5#comments

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Next.jsにCSS Modulesを適応させる(メタデータ設定も)

vercel社が提供するNext.js公式チュートリアル引用でございます。まるパクリです。

手順

① Next.jsのプロジェクトのルートにcomponentsフォルダを作成。

$ mkdir components

② ①で作成した/components配下にlayout.jsを作成し以下を記述。

$ touch components/layout.js
layout.js
export default function Layout({ children }) {
  return <div>{children}</div>;
}

③ pageディレクトリの任意のjsファイル内に以下を記述。

pages配下の任意のjsファイル
〜〜〜
import Layout from "../../components/layout";
〜〜〜

export default function FirstPost() {
  return (
      <Layout>
        〜〜〜
      </Layout>
    );
  }
}

④ プロジェクトのルートにstyleフsォルダを作成し、layout.module.cssを作成。

$ touch components/layout.module.css

⑤ 適応させたいcssを記述。公式チュートリアルでのlayout.module.cssではあらかじめimgやheaderなどのよく使うスタイルを記述していました。このように機能ごとに分けてスタイルを記述しておくのは、cssmodulesではよくあることらしい・・・・。

layout.module.css
.container {
  max-width: 36rem;
  padding: 0 1rem;
  margin: 3rem auto 6rem;
}

.header {
  display: flex;
  flex-direction: column;
  align-items: center;
}

.headerImage {
  width: 6rem;
  height: 6rem;
}

.headerHomeImage {
  width: 8rem;
  height: 8rem;
}

.backToHome {
  margin: 3rem 0 0;
}

⑥ components/layout.jsに以下を記述。Headタグ内でtwitterカードなどのメタデータを設定しています。

layout.js
import Head from 'next/head'
import styles from './layout.module.css'
import utilStyles from '../styles/utils.module.css'
import Link from 'next/link'

const name = 'Your Name'
export const siteTitle = 'Next.js Sample Website'

export default function Layout({ children, home }) {
  return (
    <div className={styles.container}>
      <Head>
        <link rel="icon" href="/favicon.ico" />
        <meta
          name="description"
          content="Learn how to build a personal website using Next.js"
        />
        <meta
          property="og:image"
          content={`https://og-image.now.sh/${encodeURI(
            siteTitle
          )}.png?theme=light&md=0&fontSize=75px&images=https%3A%2F%2Fassets.vercel.com%2Fimage%2Fupload%2Ffront%2Fassets%2Fdesign%2Fnextjs-black-logo.svg`}
        />
        <meta name="og:title" content={siteTitle} />
        <meta name="twitter:card" content="summary_large_image" />
      </Head>
      <header className={styles.header}>
        {home ? (
          <>
            <img
              src="/images/profile.jpg"
              className={`${styles.headerHomeImage} ${utilStyles.borderCircle}`}
              alt={name}
            />
            <h1 className={utilStyles.heading2Xl}>{name}</h1>
          </>
        ) : (
          <>
            <Link href="/">
              <a>
                <img
                  src="/images/profile.jpg"
                  className={`${styles.headerImage} ${utilStyles.borderCircle}`}
                  alt={name}
                />
              </a>
            </Link>
            <h2 className={utilStyles.headingLg}>
              <Link href="/">
                <a className={utilStyles.colorInherit}>{name}</a>
              </Link>
            </h2>
          </>
        )}
      </header>
      <main>{children}</main>
      {!home && (
        <div className={styles.backToHome}>
          <Link href="/">
            <a> Back to home</a>
          </Link>
        </div>
      )}
    </div>
  )
}

⑦ pages/index.jsに以下を記述。Layoutコンポーネントでrender要素を全てラップしています。

pages/index.js
import Head from 'next/head'
import Layout, { siteTitle } from '../components/layout'
import utilStyles from '../styles/utils.module.css'

export default function Home() {
  return (
    <Layout home>
      <Head>
        <title>{siteTitle}</title>
      </Head>
      <section className={utilStyles.headingMd}>
        <p>[Your Self Introduction]</p>
        <p>
          (This is a sample website - youll be building a site like this on{' '}
          <a href="https://nextjs.org/learn">our Next.js tutorial</a>.)
        </p>
      </section>
    </Layout>
  )
}

これで、Layoutコンポーネントの持つheaderやmainの要素を受け継ぐことができるはず!
終わり!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

React Redux (Firebase)開発環境構築手順

npx create-react-app [フォルダ名]
cd [フォルダ名]
firebase login
firebase init

firebaseの設定が聞かれる
what do you want to use as your public directory? => build

nom install ~~~~~(マテリアルUIとかReduxとか)
npm run build
firebase deploy

hosting urlが本番環境
npm startがローカル環境

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【React+TypeScript】Netflixのクローンを作るチュートリアル

React + TypeScript.png

※クローン=見た目についてです。
実際のNetflixにある機能で今回実装してしていない部分もあります。

作れるもの

React+TypeScriptを使ったNetflixの映画一覧を表示するアプリケーションです。
完成するとこんな感じになります。
Image from Gyazo

Netflixの映画一覧を取得し、カテゴリー毎に表示しています。
image.png

映画をクリックすると、youtubeのプロモーション映像が流れます。
Image from Gyazo

学べる事

  • ReactのFunctionComponent(関数コンポーネント)とTypeScriptの使用
  • React Hooksによるstate管理
  • TypeScriptで外部APIを扱い、非同期通信でデータを取得
  • movie-trailerにyoutubeを組み込む
  • Firebaseでデプロイ

こんな人に読んでほしい

React、TypeScriptで
- 入門書やチュートリアルをやった後に、実際に何か作ってみたいという人
- 普段仕事で基礎的なことはやっているけど、自分で1から何かを作ってみたい人

機能一覧

  • TMDBからNetflixの映画がを取得し表示している
  • ナビが一番上に固定してあり、スクロースにより背景色が変化する
  • バナーに映画とその詳細が表示される
  • カテゴリー毎に表示されている
  • リロード毎にバナーの映画が変わる(ランダムに取得される)

制作物のイメージが出来た所で実際に取り掛かっていきましょう。

APIKeyの取得しpostmanからAPIを送る

TMDBのAPIKEYを取得する

https://www.themoviedb.org/?language=ja

TMDBはエンタメ系の情報を多く抱えるデータベースです。
今回はここからNetflixのデータを拝借します。
まずTMDBにログインする。
スクリーンショット 2021-01-22 5.11.56.png

ログインしたら、マイページ→設定→APIと遷移してAPIキー (v3 auth)という部分の文字列をコピーしてメモを取ります。

postmanにてAPIデータを送る

postmanはローカルでのテスト用のAPIを手軽に生成できるサービスです。
https://www.postman.com/

こちらの記事に詳しい使い方などが乗っています。
API開発・テスト便利ツール Postmanの使い方メモ

GETを選択し、URLの部分に
https://api.themoviedb.org/3/movie/550?api_key=【さっきのメモとったAPIKey】を入れてSendをクリック。

これにてAPIを送信できました。

Reactの環境構築

APIを取得できたので、Reactをローカル環境にいれていきます。
npm、yarn環境がある人はcreate-react-appでReact+TypeScriptの環境を作って下さい。

足りない環境がある人はこちらの記事を参考に環境を構築しましょう。
create-react-appで React + Typescript な環境を構築する

API取得の下準備をする

さて環境ができたら実際に開発を行なっていきます!!
APIで取得したデータから映画を表示するところまでを実装しましょう。

今回必要ないファイルの削除

まずはいらないファイルを削除していきます。
具体的には
・ setupTests
・ logo.svg
・ app.test

は今回使わないので削除してOKです。

App.tsのreturn部分も、トップクラスの

タブ以外は空にしておきましょう。

 axios.jsの作成

まずはaxios。jsにてinstanceを作成します。


import axios from 'axios'

//TMDBからのbaseURLリクエストを作成
const instance = axios.create({
    baseURL:"https://api.themoviedb.org/3",
});

export default instance;

baseURLではTMDBのベースとなるURLを定義しています。

 request.jsの作成

ここでは先ほど送ったAPIKeyから取得できるデータを提示していきます。
ここで定義しているカテゴリー別の値は、先ほどのbaseURLの末尾に付けて判断させるものです。

const API_KEY = "";

export const requests ={
    feachTrending:`/trending/all/week?api_key=${API_KEY}&language=en-us`,
    feachNetflixOriginals:`/discover/tv?api_key=${API_KEY}&with_networks=213`,
    feactTopRated:`/discover/tv?api_key=${API_KEY}&languager=en-us`,
    feactActionMovies:`/discover/tv?api_key=${API_KEY}&with_genres=28`,
    feactComedyMovies:`/discover/tv?api_key=${API_KEY}&with_genres=35`,
    feactHorrorMovies:`/discover/tv?api_key=${API_KEY}&with_genres=27`,
    feactRomanceMovies:`/discover/tv?api_key=${API_KEY}&with_genres=10749`,
    feactDocumentMovies:`/discover/tv?api_key=${API_KEY}&with_genres=99`,
}

APIからfeachTrending、feachNetflixOriginalsなどのカテゴリーの定義まで行っています。
またここではexport defaultを使わずにnamed exportを使っています。

メリットデメリットは様々ありますが、import側で命名が変わらないように僕は好んで、named exportの方を使います。
2つの違いについて詳しくはこちらの記事に書いてあります。
なぜ default export を使うべきではないのか?

これにてAPIを取得する下準備は完了です。

Rowコンポーネント

こんな感じのものを作っていきます。
_2021-01-14_0.55.38.png

必要な機能

・カテゴリー毎に映画の画像を取ってくる
・Top ratedの画像は大きめにする
・列毎に横スライドができるようにする

APIの準備ができたのでコンポーネントを作っていきます。
まずは映画のAPIが取得出来る事を確認してきます。

映画のAPIを取得できるか確認

Row.tsxを作っていきます。
propsとしてtitleとしてカテゴリー名を、fetchUrlとしてそのURLを貰って映画を1列で表示するコンポーネントです。
カテゴリーによっては画像サイズを変更sたいので、、オプショナルでisLargeRowも持っています。
まずはfetchUrlから貰ったデータで映画の情報が正しく受け取れているかを確認します。

Row.tsx

type Props = {
  title: string;
  fetchUrl: string;
  isLargeRow?: boolean;
};

type Movie = {
  id: string;
  name: string;
  title: string;
  original_name: string;
  poster_path: string;
  backdrop_path: string;
};


export const Row = ({ title, fetchUrl }: Props) => {
  const [movies, setMovies] = useState<Movie[]>([]);

  useEffect(() => {
    async function fetchData() {
      const request = await axios.get(fetchUrl);
      setMovies(request.data.results);
      return request;
    }
    fetchData();
  }, [fetchUrl]);

  console.log(movies);

  return(
      <div className="Row" /> 
  );
};

映画のデータはstateで管理します。
useStateにて[]空配列を初期値にして、データが取れ次第更新していきます。

映画のAPIデータは非同期処理にて扱っています。
普通の同期通信だと上から順に処理が終わり次第、次の処理が始まります。
しかし外部から映画の画像取得をいちいち順番にやっていたら、ページ自体の表示がとてつもなく遅くなってしまいます。

ですので、ここではページの表示をいち早く行って、非同期処理を使って後から処理が終わった順でAPIデータを入れていく形をとっています。
非同期処理についてよく分からない方は、こちらの動画が参考になります
https://youtu.be/Vhnz1V-v1cU

今回だとまず、useEffectを使って、非同期処理の発火をfetchUrlが変わる度に設定しています。
useEffectの中では取ってきたデータのstateの更新をしています。

hooksを使った外部APIの取得方法は下記に分かりやすくまとまっています。
React Hooks で外部APIの情報を表示したいときにはどうすればよいのか?

App.tsx側でRowコンポーネントを設置し、必要なtitleとfetchUrlをいれてあげましょう。

<div className="App">
    <Row
      title="NETFLIX ORIGUINALS"
      fetchUrl={requests.feachNetflixOriginals}
      isLargeRow
    />
    <Row title="Top Rated" fetchUrl={requests.feactTopRated} />
    <Row title="Action Movies" fetchUrl={requests.feactActionMovies} />
    <Row title="Comedy Movies" fetchUrl={requests.feactComedyMovies} />
    <Row title="Horror Movies" fetchUrl={requests.feactHorrorMovies} />
    <Row title="Romance Movies" fetchUrl={requests.feactRomanceMovies} />
    <Row title="DOcumentaries" fetchUrl={requests.feactDocumentMovies} />
</div>

console.log(movies);
に対して以下の画像のようなデータが取れていれば、APIは正しく取得できています。

2.png

DOM部分の表示、整形

映画のデータが取れていることが分かったので、DOM部分を作ってscssでスタイリングしていきます。

Rowのreturn部分


 return (
    <div className="Row">
      <h2>{title}</h2>
      <div className="Row-posters">
        {/* ポスターコンテンツ */}
        {movies.map((movie, i) => (
          <img
            key={movie.id}
            className={`Row-poster ${isLargeRow && "Row-poster-large"}`}
            src={`${base_url}${
              isLargeRow ? movie.poster_path : movie.backdrop_path
            }`}
            alt={movie.name}
          />
        ))}
      </div>
    </div>
  );


classNameにてクラス名を振りつつ、映画のポスター画像を表示しています。
mapを用いてmivies配列を1つずつ並べています。

またpropsでisLargeRowを貰った場合は、classNameとsrcが少し変更します。
ここで論理演算式や条件演算子気を使っています。

俺の論理演算子、条件演算子

Row.scss

.Row {
  margin-left: 20px;
  color: #fff;

  &-posters {
    display: flex;
    overflow-y: hidden;
    overflow-x: scroll;
    padding: 20px;

    &::-webkit-scrollbar {
      display: none;
    }
  }

  &-poster {
    object-fit: contain;
    width: 100%;
    max-height: 100px;
    margin: 10px;
    transition: transform 450ms;

    &-large {
      max-height: 250px;

      &:hover {
        transform: scale(1.09);
      }
    }

    &:hover {
      transform: scale(1.08);
    }
  }
}


bject-fit:contain;を使うと、widthかheightの片方のみにpxを入れた場合に、
アスペクト比を崩さないようにもう一方の値を設定してくれます。
画像などの拡大縮小には便利です。

overflow-y:hidden;overflow-x:scroll;はy軸なしでx軸方向にスクロールできるように。
3.png
ここまででTopRatedは大きめ、その他は普通サイズに使い分けられた映画一覧が取得されました。

Bannerコンポーネント

こんな感じのものを作っていきます。
4.png

必要な機能

・ランダムで背景のmovie画像を持ってくる
・そのmovieのタイトル持ってくる
・ボタンとdescriptionをつけてスタイリング
・descriptionを大きさに応じて切り捨てる
・Bannerの下、1/3をfadeさせる

ロジック部分

type movieProps = {
  title?: string;
  name?: string;
  orignal_name?: string;
  backdrop_path?: string;
  overview?: string;
};

export const Banner = () => {
  const [movie, setMovie] = useState<movieProps>({});
  useEffect(() => {
    async function fetchData() {
      const request = await axios.get(requests.feachNetflixOriginals);
      console.log(request.data.result);

      //apiからランダムで値を取得している
      setMovie(
        request.data.results[
          Math.floor(Math.random() * request.data.results.length - 1)
        ]
      );
      return request;
    }
    fetchData();
  }, []);
  console.log(movie);

  // descriptionの切り捨てよう関数
  function truncate(str: any, n: number) {
    // undefinedを弾く
    if (str !== undefined) {
      return str.length > n ? str?.substr(0, n - 1) + "..." : str;
    }
  }
};

映画のデータを取得するためにuseStateでmovieを扱い、useEffectでAPI取得、更新まではRowコンポーネントと同じです。
setMovieでは数あるmoviesの中からランダムに1つをmovieに格納しています。

descriptionは映画の説明なのですが、文字数が多すぎちゃう場合があるのでtruncateにて150文字ぐらいに絞っています。(150って値はDOM部分で入れます。)

DOM部分

return (
    <header
      className="Banner"
      style={{
        backgroundSize: "cover",
        backgroundImage: `url("https://image.tmdb.org/t/p/original${movie?.backdrop_path}")`,
        backgroundPosition: "center center",
      }}
    >
      <div className="Banner-contents">
        <h1 className="banner-title">
          {movie?.title || movie?.name || movie?.orignal_name}
        </h1>
        <div className="Banner-buttons">
          <button className="Banner-button">Play</button>
          <button className="Banner-button">My List</button>
        </div>

        <h1 className="Banner-description">{truncate(movie?.overview, 150)}</h1>
      </div>

      <div className="Banner-fadeBottom" />
    </header>
  );

ここはあまり解説する事ないですね。

scssによるスタイリングです。

.Banner {
  color: #fff;
  object-fit: contain;
  height: 448px;

  &-contents {
    margin-left: 30px;
    padding-top: 140px;
    height: 190px;
  }

  &-title {
    font-size: 3rem;
    font-weight: 800;
    padding-bottom: 0.3rem;
  }

  &-description {
    width: 45rem;
    line-height: 1.3;
    padding-top: 1rem;
    font-size: 0.8rem;
    max-width: 360px;
    height: 80px;
  }

  &-button {
    cursor: pointer;
    color: #fff;
    outline: none;
    border: none;
    font-weight: 700;
    border-radius: 0.2vw;
    padding-left: 2rem;
    padding-right: 2rem;
    margin-right: 1rem;
    padding-top: 0.5rem;
    background-color: rgba(51, 51, 51, 0.5);
    padding-bottom: 0.5rem;

    &:hover {
      color: #000;
      background-color: #e6e6e6;
      transition: all 0.2s;
    }
  }

  &-fadeBottom {
    height: 7.4rem;
    background-image: linear-gradient(
      180deg,
      transparent,
      rgba(37, 37, 37, 0.61),
      #111
    );
  }
}


値を細かく指定していますが、好き好きでOKです。
BAnnerコンポーネントの下をフェードさせたかったので、linear-gradientを使いました。
https://developer.mozilla.org/ja/docs/Web/CSS/linear-gradient()

このような形になればOKです。
4.png

Navコンポーネント

こんな形のものを作っていきます
Image from Gyazo

必要な機能

  • Netflixロゴ
  • ユーザーアバター画像
  • スクロールすると背景が黒くなる
  • 背景の黒が現れた時のフェード

ロジック、DOM部分

type Props = {
  className?: string;
};

export const Nav = (props: Props) => {
  const [show, setShow] = useState(false);
  useEffect(() => {
    const handleShow = () => {
      if (window.scrollY > 100) {
        setShow(true);
      } else {
        setShow(false);
      }
    };

    window.addEventListener("scroll", handleShow);
    return () => {
      window.removeEventListener("scroll", handleShow);
    };
  }, []);
  return (
    <div className={`Nav ${show && "Nav-black"}`}>
      <img
        className="Nav-logo"
        src="https://upload.wikimedia.org/wikipedia/commons/thumb/0/08/Netflix_2015_logo.svg/1920px-Netflix_2015_logo.svg.png"
        alt="Netflix Logo"
      />
      <img
        className="Nav-avater"
        src="https://i.pinimg.com/originals/0d/dc/ca/0ddccae723d85a703b798a5e682c23c1.png"
        alt="Avatar"
      />
    </div>
  );
};

ロジック部分とDOM部分で気になるとポイントは1つだけで、スクロール時の対応です。
useEffectでスクロールが一定値に達したら、stateを更新するようにしています。
stateに応じて、DOM部分のNav-blackが付け外しされています。

scssによるスタイリング

.Nav {
  position: fixed;
  top: 0;
  width: 100%;
  height: 30px;
  padding: 20px;
  z-index: 1;
  display: flex;
  justify-content: space-between;

  /* Animations */
  transition-timing-function: ease-in;
  transition: all 0.5s;

  &-black {
    background-color: #111;
  }

  &-logo {
    position: fixed;
    left: 20px;
    width: 80px;
    object-fit: contain;
  }

  &-avater {
    position: fixed;
    right: 20px;
    width: 30px;
    object-fit: contain;
  }
}

position:fixにてNavコンポーネントの位置を固定しています。
display:flex にてロゴを横並びにして、
justify-content:spacebetween にて右端と左端に設置しています。

映画の画像クリックでトレイラー表示

このように映画の画像をクリックした時にYoutubeのトレイラー映像があればiFrameにて表示します。
Image from Gyazo

必要な機能

  • プロモーション映像が流れる →YoutubeをAPIから読み込んでifarmeで表示
  • もう一度クリックすると消える

モジュールのimport

これら2つのモジュールを使うのでセットアップする。
https://www.npmjs.com/package/movie-trailer
https://www.npmjs.com/package/react-youtube

処理を加える

Rowコンポーネントに処理を加える。


type Props = {
  title: string;
  fetchUrl: string;
  isLargeRow?: boolean;
};

type Movie = {
  id: string;
  name: string;
  title: string;
  original_name: string;
  poster_path: string;
  backdrop_path: string;
};

//trailerのoption
type Options = {
  height: string;
  width: string;
  playerVars: {
    autoplay: 0 | 1 | undefined;
  };
};

export const Row = ({ title, fetchUrl, isLargeRow }: Props) => {
  const [movies, setMovies] = useState<Movie[]>([]);
  const [trailerUrl, setTrailerUrl] = useState<string | null>("");

  //urlが更新される度に
  useEffect(() => {
    async function fetchData() {
      const request = await axios.get(fetchUrl);
      setMovies(request.data.results);
      return request;
    }
    fetchData();
  }, [fetchUrl]);

  const opts: Options = {
    height: "390",
    width: "640",
    playerVars: {
      // https://developers.google.com/youtube/player_parameters
      autoplay: 1,
    },
  };

  const handleClick = async (movie: Movie) => {
    if (trailerUrl) {
      setTrailerUrl("");
    } else {
      let trailerurl = await axios.get(
        `/movie/${movie.id}/videos?api_key=fb34530271b349314af0de263d16ab5a`
      );
      setTrailerUrl(trailerurl.data.results[0]?.key);
    }
  };

  return (
    <div className="Row">
      <h2>{title}</h2>
      <div className="Row-posters">
        {/* ポスターコンテンツ */}
        {movies.map((movie, i) => (
          <img
            key={movie.id}
            className={`Row-poster ${isLargeRow && "Row-poster-large"}`}
            src={`${base_url}${
              isLargeRow ? movie.poster_path : movie.backdrop_path
            }`}
            alt={movie.name}
            onClick={() => handleClick(movie)}
          />
        ))}
      </div>
      {trailerUrl && <YouTube videoId={trailerUrl} opts={opts} />}
    </div>
  );
};

handleClickにてトレイラーにyoutubeの映像を呼び出す処理をしている。
クリックが偶数で閉じるようにif文でtrailerUrlによって処理を分けている。

DOM部分では{trailerUrl && <YouTube videoId={trailerUrl} opts={opts} />}によってtrailerUrl=trueの時はYoutubeのiFrameでの動画が流れるようにしている。
ここまででNetflixクローンは完成です!!!

ローカルでは問題なく動くはずです。

firebaseでデプロイ

最後に公の場からアクセスできるようにfirebaseにて公開します。
まずはfirebaseに登録して、プロジェクトを追加します。
https://firebase.google.com/?gclid=CjwKCAiA6aSABhApEiwA6Cbm_5aqkU8r61ihrYp3xuHCUo-4zJLBqGTGyUrb1mHxV0Vd-vPwJle6ERoC5Q4QAvD_BwE

デプロイ作業はコマンドラインから行います。

npm install -g firebase-toolsにてfirebase-toolsをインストールしてください。
``firebase loginにてログインします。

次にfirebase initにてデプロイするプロジェクトの子毎回設定を行なっていきます。

npm run buildを行います。
すると下記のようにbuildフォルダの中身が生成されます。
スクリーンショット 2021-01-22 5.35.48.png

firebase deployにてデプロイされます!!!!

ここまでがちゃんとできていれば、正常にURLが表示されます。

まとめ

さて、Netflixのクローンアプリの作り方について解説していきましたが、いかがだったでしょうか??
外部APIを通じたReactアプリケーションの作成は、初級者から中級者を目指す人にとって、ちょうど良い難易度だったと思います。

もし難易度が高すぎると感じた方には、初級者向けの下記の課題もあるので、そちらから挑戦してみてください。
【 React Hooks入門】useStateを使って買い物リストを作るチュートリアル
React + TypeScriptでコネクト4を作った報告(クラスコンポーネント)
ReactでのCOVID-19トラッカー作成で学べる3つの事

参考

https://www.youtube.com/watch?v=XtMThy8QKqU&t=1047s&ab_channel=CleverProgrammer


フリーランスでフロントエンドエンジニアをしています。
お仕事のご相談こちらまで
gunners6518@gmail.com

技術ブログ
Twitter

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む