1
2

More than 3 years have passed since last update.

【ハンズオン】ReactとNext.jsをコードを書きながらサクッと理解する!

Last updated at Posted at 2021-05-30

まえがき

以下の勉強会で使用したハンズオン資料を再編集したものです。

こちらの動画動画でも同様の内容を解説しています。(一部ソースコードに違いあり)

【ハンズオン】ReactとNext.jsをコードを書きながらサクッと理解する!

概念説明

React.jsとは

  • ユーザインターフェース構築のためのJavaScript ライブラリ
  • 宣言的なView
  • コンポーネントベース
  • 一度学習すれば、どこでも使える(サーバーサイド、React Nativeなど)

Next.js とは

  • Reactを本番環境で利用するためのフレームワーク
  • 本番環境に必要な設定が予め組み込まれている
  • Webpack、TypeScript、多言語対応、画像の最適化、サーバーサイド、静的生成、SEOなど
  • 環境構築に要する時間を短縮できる

ハンズオン

この資料のソースコードはこちらで公開しています

Next.jsアプリの環境構築

前提条件

  • Node.js 10.13 or later

CLIから環境を構築

開発ツール導入済のテンプレートから環境環境を構築

$ npx create-next-app nextjs-react-training --example "https://github.com/redimpulz/nextjs-typescript-starter"
$ cd nextjs-react-training

サーバーの起動

$ yarn dev

http://localhost:3000/

導入済の開発ツール

  • TypeScript
  • ESLint
  • Prettier
  • VScode拡張

Reactの基礎を学ぼう

  • コンポーネント
  • props
  • useState(ステートフック)
  • useEffect(副作用フック)

コンポーネント

UIを再利用可能な部品として定義する

index.tsx
import React from 'react';

// classコンポーネント
class HelloMessageClass extends React.Component {
  render() {
    return <div>Hello redimpulz</div>;
  }
}

// 関数コンポーネント
const HelloMessageFunction: React.FC = () => <div>Hello redimpulz</div>;

export default function Index() {
  return (
    <div>
      <HelloMessageClass />
      <HelloMessageFunction />
    </div>
  );
}

props

コンポーネントに動的に値を渡す

index.tsx
import React from 'react';

// propsの型定義
type Props = {
  name: string;
};

// ジェネリックスでpropsの型定義を指定
const HelloMessage: React.FC<Props> = (props) => <div>Hello {props.name}</div>;

export default function Index() {
  return (
    <div>
      {/* HTMLの属性のような形でコンポーネントに値を渡せる */}
      <HelloMessage name="redimpulz" />
    </div>
  );
}

useState(ステートフック)

コンポーネントに状態を持たせる

index.tsx
import React from 'react';

const HelloCounter: React.FC = () => {
  // コンポーネントに状態を持たせる
  const [count, setCount] = React.useState(0);

  return (
    <div>
      <div>{count}</div>
      <button onClick={() => setCount(count + 1)}>+</button>
      <button onClick={() => setCount(count - 1)}>-</button>
    </div>
  );
};

export default function Index() {
  return (
    <div>
      <HelloCounter />
    </div>
  );
}

useEffect(副作用フック)

コンポーネントで副作用を実行する

index.tsx
import React from 'react';
import Link from 'next/link';

const HelloCounter: React.FC = () => {
  const [count, setCount] = React.useState(0);

  // 副作用フック(マウント時)
  React.useEffect(() => {
    alert('mounted');
    // 副作用フック(アンマウント時)
    return () => alert('cleanup');
  }, []);

  // 副作用フック(依存の変更時)
  React.useEffect(() => {
    alert('counted');
  }, [count]);

  return (
    <div>
      <div>{count}</div>
      <button onClick={() => setCount(count + 1)}>+</button>
      <button onClick={() => setCount(count - 1)}>-</button>
      <div>
        <Link href="/">
          <a>back</a>
        </Link>
      </div>
    </div>
  );
};

export default function Index() {
  return (
    <div>
      <HelloCounter />
    </div>
  );
}

実践編:学んだことを活用してReactでアプリを実装

  • ページアクセス時にAPIからデータを取得
  • 取得結果を画面に反映する
  • ボタンをクリックして再取得
index.tsx
import React from 'react';

type Data = {
  message: string;
  status: string;
};

const HelloFetchImage: React.FC = () => {
  const [imageUrl, setImageUrl] = React.useState('');

  // データ取得とステートの保持
  const fetchData = async () => {
    try {
      const url = 'https://dog.ceo/api/breeds/image/random';
      const res = await fetch(url);
      const data: Data = await res.json();
      setImageUrl(data.message);
    } catch (error) {
      console.log(error);
    }
  };

  // 副作用フック(マウント時)
  React.useEffect(() => {
    fetchData();
  }, []);

  return (
    <div>
      {imageUrl && <img src={imageUrl} />}
      <div>
        <button onClick={fetchData}>fetch</button>
        <button onClick={() => setImageUrl('')}>clear</button>
      </div>
    </div>
  );
};

export default function Index() {
  return (
    <div>
      <HelloFetchImage />
    </div>
  );
}

もう少し改善してみる

  • 複数の画像表示に対応
  • 初期の取得画像の数をpropsで設定できるように
  • 取得する画像の数を入力で変更できるように
  • loadingの表示
index.tsx
import React from 'react';

// {
//   "message": [
//       "https://images.dog.ceo/breeds/lhasa/n02098413_16009.jpg",
//       "https://images.dog.ceo/breeds/appenzeller/n02107908_4092.jpg",
//       "https://images.dog.ceo/breeds/dalmatian/cooper2.jpg"
//   ],
//   "status": "success"
// }

type Data = {
  message: string[];
  status: string;
};

type Props = {
  defaultImageNums: number;
};

const HelloFetchImages: React.FC<Props> = ({ defaultImageNums }) => {
  const [imageNums, setImageNums] = React.useState<number | ''>(
    defaultImageNums
  );
  const [imageUrls, setImageUrls] = React.useState<string[]>([]);
  const [loading, setLoading] = React.useState(false);

  // データ取得とステートの保持
  const fetchData = async () => {
    if (!imageNums || !(imageNums >= 1 && imageNums <= 50)) {
      alert('please select 1...50 number');
      return;
    }
    setLoading(true);
    try {
      const url = `https://dog.ceo/api/breeds/image/random/${imageNums}`;
      const res = await fetch(url);
      const data: Data = await res.json();
      setImageUrls(data.message);
    } catch (error) {
      console.log(error);
    }
    setLoading(false);
  };

  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    if (e.target.value) {
      setImageNums(parseInt(e.target.value));
    } else {
      setImageNums('');
    }
  };

  // 副作用フック(マウント時)
  React.useEffect(() => {
    fetchData();
  }, []);

  return (
    <div>
      {loading
        ? 'loading....'
        : imageUrls.map((x) => (
            <img key={x} src={x} style={{ width: 200, height: 200 }} />
          ))}
      <div>
        <input
          type="number"
          value={imageNums}
          onChange={handleChange}
          max={50}
          min={1}
        />
        <button onClick={fetchData}>fetch</button>
        <button onClick={() => setImageUrls([])}>clear</button>
      </div>
    </div>
  );
};

export default function Index() {
  return (
    <div>
      <HelloFetchImages defaultImageNums={20} />
    </div>
  );
}

その他、学んでおきたいこと

  • ref、context、memo、reducer、その他独自のフック
  • ただし、使う場面はあまり多くない
1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2