Open60

はじめてつくる React アプリ

hagiwarahagiwara

State でデータを一時保管する

// useState をインポートする
import { useState } from 'react'

cost Func = () => {
  // hoge が state
  // setHoge が state を操作する仕組み(慣例的に set + State名 とされる)
  // useState() の引数が初期データ
  const [hoge, setHoge] = useState('')

  return {
    ...
    // setHoge() で state に値を書き込む(この場合、onChange 時に input の値が hoge に入る)
    <input type="text" name="hoge" onChange={e => setHoge(e.target.value)} />

    // { hoge } で state の値を読み込む(この場合、input に入力した値が表示される)
    { hoge }
    ...
  )
}

export default Func
hagiwarahagiwara

axios を使って API にアクセスする

// axios をインポート
import axios from 'axios'

const Func = () => {
  const getWeather = (e) => {
    // データを get する
    axios.get('https://api.weatherapi.com/v1/current.json?key=***')
      // get したデータを hoge() に渡す
      .then(res => hoge(res))
  }

  return (
    ...
    // onClick 時に getWeather を実行する
    <button onClick={ getWeather }>Get Weather</button>
    ...
}

export default Func
hagiwarahagiwara

props を使ってコンポーネント間でデータを受け渡す

// 親

...
  const [hoge, setHoge] = useSate('')
  const getWeather = (e) => {
    axios.get('https://api.weatherapi.com/***')
      .then(res => fuga())
  ...

  return (
    ...
    // コンポーネントの属性?に渡すデータを追加する
    <Form setHoge={setHoge} getWeather={getWeather} />
    ...
  )
...
// 子

// 引数に props を追加
const Func = (props) => {
  return (
    ...
      // props.hoge で渡された props にアクセスする
      <input type="text" name="hoge" onChange={e => props.setHoge(e.target.value)} />
      <button onClick={ props.getWeather }>Get Weather</button>
    ...
  )
}
hagiwarahagiwara

input に入力した(state に保存された)値を使用して API から取得する

...
  // テンプレートリテラルを使用する
  //  - 引用符ではなくバッククオートで囲む ``
  //  - ${state} で state の値を呼び出す
  axios.get(`https://api.weatherapi.com/v1/current.json?key=***&q=${city}&aqi=no`)
...
hagiwarahagiwara

ChatGPT に聞いてみた

React の hooks とはなんですか?

ReactのHooksは、関数コンポーネントで状態管理やライフサイクルイベントなどの機能を提供するための機能です。Hooksを使用すると、Classコンポーネントを使用せずに状態を管理することができます。

Hooksにはいくつか種類がありますが、最も一般的なものはuseState、useEffect、useContextです。

useStateは、関数コンポーネントで状態を管理するための機能であり、状態を定義して更新するための関数を提供します。

useEffectは、関数コンポーネントで副作用を実行するための機能であり、コンポーネントのレンダリング後に実行されます。例えば、APIからデータを取得するなどの副作用があります。

useContextは、関数コンポーネントでグローバルな値を共有するための機能であり、コンポーネントツリーをまたがってデータを渡すことができます。

Hooksは、Reactの関数コンポーネントによる開発をより簡単に、効率的に、そして可読性の高いものにすることができます。

hagiwarahagiwara

上記の副作用とはなんですか

プログラミングにおける副作用 (side effect) とは、関数の外部の状態や環境に影響を与えることを指します。副作用は、関数の実行によって変更される状態、外部APIへのリクエスト、ファイルへの書き込み、コンソールログの出力など、さまざまな形を取ることができます。

ReactのuseEffect Hookでは、コンポーネント内で副作用を扱うことができます。例えば、useEffect内でAPIからデータを取得し、それをコンポーネントの状態として設定することができます。この場合、APIからのデータ取得が副作用であり、その結果がコンポーネントの状態に影響を与えています。

また、副作用はプログラムの可読性や保守性を損なう可能性があるため、副作用を制御することが重要です。ReactのHooksを使用することで、副作用を実行するタイミングや範囲を明確に定義し、コンポーネントの挙動をより安全かつ予測可能にすることができます。

hagiwarahagiwara

なぜ hooks という名称なのでしょうか

ReactのHooksという用語は、プログラムの実行中に「フック」する(hook into)点を提供するために使用されています。これは、関数コンポーネントのライフサイクルイベントや状態の変更などの様々なポイントで、関数を呼び出すことで処理を挿入することができるという意味です。

「Hooks」という用語はReactに特有の用語であり、一般的に使われるようなものではありません。また、Hooksという名前の関数はReactに限らず、様々なプログラミング言語やフレームワークで使用されることがあります。

ReactのHooksには、useState、useEffect、useContextなどがありますが、これらはReactに限ったものではなく、Hooksという概念を持つ多くのフレームワークやライブラリで類似した機能が提供されています。

hagiwarahagiwara

イベントドリブン的なもの?

ReactのHooksは、イベントドリブン型プログラミングのアプローチを取っています。つまり、コンポーネント内で発生するイベントに応じて、適切な処理を実行することで、UIの動的な振る舞いを実現しています。

useState Hookは、コンポーネントの内部状態が変化したときに再レンダリングをトリガーし、useEffect Hookは、コンポーネントのマウント、アンマウント、および再レンダリングのイベントに応じて副作用を発生させます。また、useCallback HookやuseMemo Hookなど、他のHooksもイベントドリブン型プログラミングに基づいています。

ReactのHooksは、コンポーネントが自己完結的で、コンポーネント間の通信が簡単に行えるようにすることができるため、イベントドリブン型アプリケーションの構築に適しています。

hagiwarahagiwara

useContext はどんなもの?

useContextは、ReactのHooksの1つで、Reactのコンテキスト(context)を扱うためのものです。コンテキストは、コンポーネント間でデータを共有するための仕組みであり、プロパティを一つ一つ渡していく必要がなく、コンポーネントの階層構造を越えてデータを共有できます。

useContext Hookは、コンテキストを使用するコンポーネント内で、コンテキストの値にアクセスするために使用されます。useContext Hookを使用するには、まずReact.createContext()メソッドを使用してコンテキストを作成し、コンテキストのProviderコンポーネントで値を設定します。そして、useContext Hookを使用して、コンテキストの値を取得することができます。

例えば、以下のようなコードで、カウンターの値をコンテキストで管理し、異なる階層のコンポーネントで値を共有することができます。

import React, { createContext, useContext, useState } from 'react';

const CounterContext = createContext();

function App() {
  const [count, setCount] = useState(0);

  return (
    <CounterContext.Provider value={{ count, setCount }}>
      <div>
        <Counter />
      </div>
    </CounterContext.Provider>
  );
}

function Counter() {
  const { count, setCount } = useContext(CounterContext);

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

上記の例では、CounterコンポーネントでuseContext Hookを使用して、CounterContextの値を取得しています。これにより、カウンターの値をコンポーネントの階層構造を越えて共有し、カウントアップのボタンをクリックしたときに、カウントが増加することができます。

hagiwarahagiwara

グローバル変数みたいなもの?

useContextはグローバル変数に似たものとも言えますが、いくつかの点で異なります。

グローバル変数は、コードのあらゆる場所から直接アクセスでき、変更が簡単にできるため、予期せぬ副作用を引き起こす可能性があります。一方、useContextで定義されたコンテキストは、Reactコンポーネント内でのみ使用され、必要に応じて明示的にインポートして使用するため、より安全な使い方ができます。

また、グローバル変数と異なり、useContextで定義されたコンテキストは、Reactコンポーネントの階層構造を通じて、一部のコンポーネントでのみ利用されることができます。これにより、特定のコンポーネント内でのみ必要な値をコンテキストに格納し、コンポーネント間で疎結合を維持することができます。

hagiwarahagiwara

フックとは、関数コンポーネントに state やライフサイクルといった React の機能を “接続する (hook into)” ための関数です。フックは React をクラスなしに使うための機能ですので、クラス内では機能しません。今すぐに既存のコンポーネントを書き換えることはお勧めしませんが、新しく書くコンポーネントで使いたければフックを利用し始めることができます。

React は useState のような幾つかのビルトインのフックを提供します。異なるコンポーネント間でステートフルな振る舞いを共有するために自分自身のフックを作成することもできます。

https://ja.reactjs.org/docs/hooks-overview.html

hagiwarahagiwara

ChatGPT に聞いてみた

axios の用途、利点について教えてください
競合があれば併せてお願いします

axios は、JavaScript のクライアントサイドで使用される HTTP リクエストライブラリです。以下は、axios の用途と利点のいくつかです。

用途:

  • サーバーからデータを取得する際に使用することができます。API からデータを取得する際に特に便利です。
  • データを送信するためにも使用できます。フォームなどからサーバーにデータを送信することができます。
  • Promise を返すので、非同期処理に使用することができます。

利点:

  • Promise を使用するため、非同期処理を簡単に記述できます。
  • ブラウザと Node.js で使用できるため、クライアントサイドとサーバーサイドの双方で使用することができます。
  • シンプルで使いやすい API を提供しており、初心者でも扱いやすいです。
  • HTTP リクエストのキャンセルや、リクエストのトランスフォーム、エラーハンドリングなどの機能が提供されています。

競合するライブラリとしては、Fetch API や jQuery.ajax があります。Fetch API は標準のブラウザ API であり、axios と同様に Promise を返しますが、リクエストのキャンセル機能やトランスフォーム機能が提供されていないため、axios の方が柔軟な使用ができます。jQuery.ajax は、jQuery ライブラリの一部であり、axios よりも機能が豊富ですが、ライブラリのサイズが大きくなります。また、jQuery に依存するため、プロジェクトに jQuery を導入する必要があります。

hagiwarahagiwara

ChatGPT に聞いてみた

slug とはなんですか?

"Slug" にはいくつかの意味がありますが、最も一般的な意味は、以下のようなものです。

  1. ナメクジ:地面にいる粘液を分泌するゼリー状の生き物です。
  2. ウェブサイトのURLに含まれる一部のテキスト:ウェブサイトのURLには、一般的にページのタイトルや内容を示す短い説明が含まれています。これらの説明のことをスラッグと呼びます。スラッグは、SEO(検索エンジン最適化)の観点から重要な役割を果たしています。
  3. 重い一撃:スポーツなどで、強い一撃を意味する場合があります。
  4. 慢性的な仕事量:仕事量が非常に多く、重労働であることを示す場合があります。
hagiwarahagiwara
  1. が slug と言われる理由、語源を教えてください

「Slug」がウェブサイトのURLに含まれる説明のことを指すようになった理由は、正確にはわかっていませんが、いくつかの説があります。

一つの説は、ウェブサイトのURLに含まれる説明のような短いテキストが、ナメクジの痕跡のように残ることからきているというものです。すなわち、ナメクジが体液で残した軌跡のように、URLに含まれるスラッグもまた、ウェブページの内容を表す痕跡として残るという意味合いが含まれています。

また、もう一つの説は、新聞業界での「スラッグライン(slug line)」という用語に由来するというものです。スラッグラインとは、記事のタイトルの前に付けられる短い説明文で、新聞社内での記事の管理や編集作業に使用されます。このように、スラッグという用語は、ウェブサイトのURLに含まれる説明文を、記事の管理や編集に使用されるスラッグラインになぞらえて使用されるようになったという説もあります。

どちらにしても、スラッグという用語がウェブサイトのURLに含まれる説明文を指すようになった理由についてははっきりしていません。ただし、この用語が初めて使用されたのは、1990年代初頭のウェブの初期段階であったとされています。

hagiwarahagiwara

ChatGPT にバグを見つけてもらった

このコードの問題は、map のコールバック関数がJSXを返さないことです。map を使用する場合、JSXを返す必要があります。

修正されたコードは以下のようになります。

import countriesJson from '../countries.json'

const Selector = () => {
  return (
    <div>
      <select>
        {
          countriesJson.map((country) => {
            console.log(country.Country)
            // ↓ return が無かった
            return <option>{country.Country}</option>
          })
        }
      </select>
    </div>
  )
}

export default Selector

return 文でJSX要素を返すように修正することで、オプションが生成されます。

hagiwarahagiwara

ChatGPT に聞いてみた

以下のコードを効率化して

  const getCountryData = () => {
    fetch(`https://example.com/${country}`)
      .then(res => res.json())
      .then(data => setCountryData({
        date: data[data.length - 1].Date,
        newConfirmed: data[data.length - 1].Confirmed - data[data.length - 2].Confirmed,
        totalConfirmed: data[data.length - 1].Confirmed,
        newRecovered: data[data.length - 1].Recovered - data[data.length - 2].Recovered,
        totalRecovered: data[data.length - 1].Recovered
      }))
  }

回答) ↓

const getCountryData = async () => {
  const response = await fetch(`https://example.com/${country}`);
  const data = await response.json();
  const lastData = data[data.length - 1];
  const secondLastData = data[data.length - 2];
  setCountryData({
    date: lastData.Date,
    newConfirmed: lastData.Confirmed - secondLastData.Confirmed,
    totalConfirmed: lastData.Confirmed,
    newRecovered: lastData.Recovered - secondLastData.Recovered,
    totalRecovered: lastData.Recovered
  });
}

このように修正することで、コードが簡潔になり、可読性が向上します。また、async/await を使うことで、非同期処理の扱いがより簡単になります。

hagiwarahagiwara

react-router-dom を使って URL 毎に異なるページを表示する

import { Route, Routes, BrowserRouter } from 'react-router-dom'
...

export default function App() {
  ...  

  return (
    <BrowserRouter>
      <Routes>
        // path: パス
        // element: 表示するコンポーネント
        <Route path="/" element={<TopPage countriesJson={countriesJson} setCountry={setCountry} getCountryData={getCountryData} countryData={countryData} />} />
        <Route path="/world" element={<WorldPage allCountriesData={allCountriesData} getAllCountriesData={getAllCountriesData} />} />
      </Routes>
    </BrowserRouter>
  )
}
hagiwarahagiwara

useEffect を使って、ページアクセス時にデータを読み込み表示するようにする

修正前

// App.js
import { useState } from 'react'
...

const getAllCountiresData = () => {
  fetch('https://example.com/api/corona-tracker/summary')
    .then(res => res.json())
    .then(data => setAllCountriesData(data.Countries))
}
// Card.js
...

// button をクリックした際にデータを取得して表示
<button onClick={props.getAllCountriesData>Get All Data</button>

↓↓↓

修正後

// App.js
import { useState, useEffect } from 'react'
...

useEffect(() => {
  fetch('https://example.com/api/corona-tracker/summary')
    .then(res => res.json())
    .then(data => setAllCountriesData(data.Countries))
}, [])
// Card.js
...

// button を削除
// <button onClick={props.getAllCountriesData>Get All Data</button>
hagiwarahagiwara

ChatGPT に聞いてみた

useEffect の最後の [] の意味を教えて

[] は、Reactの useEffect フックに渡される2番目の引数で、この引数が渡された場合、useEffect はコンポーネントがマウントされたときにだけ一度だけ実行されることを示します。これは、useEffect が、第2引数に渡された値に変更があった場合にのみ再度実行されるように指定することができるためです。

したがって、引数に空の配列 [] を渡すことで、useEffect の初回レンダー時に1回だけ実行され、以降の再レンダリングでは実行されないことを意味します。これは、この特定の useEffect 内で依存関係がない場合に一般的に使用されます。

hagiwarahagiwara

副作用フックの利用法 – React
https://ja.reactjs.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects

ヒント:副作用のスキップによるパフォーマンス改善

いくつかの場合では、副作用のクリーンアップと適用とをレンダーごとに毎回行うことはパフォーマンスの問題を引き起こす可能性があります。〜

これはよくある要求なので、useEffect フックの API にはこの動作が組み込まれています。再レンダー間で特定の値が変わっていない場合には副作用の適用をスキップするよう、React に伝えることができるのです。そのためには、useEffect のオプションの第 2 引数として配列を渡してください。

補足

もしも副作用とそのクリーンアップを 1 度だけ(マウント時とアンマウント時にのみ)実行したいという場合、空の配列 ([]) を第 2 引数として渡すことができます。こうすることで、あなたの副作用は props や state の値のいずれにも依存していないため再実行する必要が一切ない、ということを React に伝えることができます。これは特別なケースとして処理されているわけではなく、依存配列を普通に処理すればそうなるというだけの話です。

hagiwarahagiwara

リンクを追加する
react-router-dom を使用している場合、<a href=""> ではなく <Link to=""> を使用する

<Link to="/">Linkt to Home</Link>
hagiwarahagiwara

分割代入を使用してコードを簡略化する

修正前

const Results = (props) => {
  return (
    <div className="results-container">
      <div>
        <p>日付: <span>{props.countryData.date.slice(0, 10)}</span></p>
        <p>新規感染者: <span>{props.countryData.newConfirmed.toLocaleString()}</span></p>
        <p>感染者総数: <span>{props.countryData.totalConfirmed.toLocaleString()}</span></p>
        <p>新規回復者: <span>{props.countryData.newRecovered.toLocaleString()}</span></p>
        <p>回復者総数: <span>{props.countryData.totalRecovered.toLocaleString()}</span></p>
      </div>
    </div>
  )
}

...

↓↓↓

修正後

const Results = (props) => {
  const { date, newConfirmed, totalConfirmed, newRecovered, totalRecovered } = props.countryData
  return (
    <div className="results-container">
      <div>
        <p>日付: <span>{date.slice(0, 10)}</span></p>
        <p>新規感染者: <span>{newConfirmed.toLocaleString()}</span></p>
        <p>感染者総数: <span>{totalConfirmed.toLocaleString()}</span></p>
        <p>新規回復者: <span>{newRecovered.toLocaleString()}</span></p>
        <p>回復者総数: <span>{totalRecovered.toLocaleString()}</span></p>
      </div>
    </div>
  )
}

...

or

const Results = ({countryData}) => {
  const { date, newConfirmed, totalConfirmed, newRecovered, totalRecovered } = countryData
  return (
    <div className="results-container">
      <div>
        <p>日付: <span>{date.slice(0, 10)}</span></p>
        <p>新規感染者: <span>{newConfirmed.toLocaleString()}</span></p>
        <p>感染者総数: <span>{totalConfirmed.toLocaleString()}</span></p>
        <p>新規回復者: <span>{newRecovered.toLocaleString()}</span></p>
        <p>回復者総数: <span>{totalRecovered.toLocaleString()}</span></p>
      </div>
    </div>
  )
}

...
hagiwarahagiwara

fetch() にエラーハンドリングを追加する

...

const getCountryData = () => {
  fetch(`https://example.com/api/corona-tracker/country/${country}`)
    .then(res => res.json())
    .then(data => setCountryData({
      date: data[data.length - 1].Date,
      ...
    }))
    // 追加)
    // fetch が失敗した場合、アラートを表示する
    .catch(err => alert('エラーが発生しました。ページをリロードして、もう一度トライしてください。'))
}

...
hagiwarahagiwara

fetch 時に読み込み中の表示 Loading を追加する

// App.jsx
...

export default function App() {
  // loading 中を判定するための state を追加
  const [loading, setLoading] = useState(false)
  ...

  const getCountryData = () => {
    // 読み込みを開始する際に true にする
    setLoading(true)
    fetch(`https://example.com/${country}`)
      .then(res => res.json())
      .then(data => {
        setCountryData({
          ...
        })
        // 読み込みが成功したら false にする
        setLoading(false)
      })
  }

  ...

  return (
    <BrowserRouter>
      <Routes>
        // 表示するコンポーネントに state を追加
        <Route path="/" element={<TopPage countriesJson={countriesJson} setCountry={setCountry} getCountryData={getCountryData} countryData={countryData} loading={loading} />} />
      </Routes>
    </BrowserRouter>
  )
}
// TopPage.jsx
...
    // 表示するコンポーネントに state を追加
    <Results countryData={countryData} loading={loading} />
...
// Results.jsx
...

const Results = ({ countryData, loading }) => {
    return (
      {loading ?
        // 読み込み中に表示する
        <Loading />
      :
        // 読み込み前後に表示する
        <div>
          ...
        </div>
      }
    </div>
  )
}

...
hagiwarahagiwara

useEffect を使って、select を変更したらデータを読み込み・更新するようにする

export default function App() {
  // 初期値 japan を設定する
  const [country, setCountry] = useState('japan')
  ...

  useEffect(() => {
    // データを取得する関数を useEffect に移動する
    const getCountryData = () => {
      setLoading(true)
      fetch(`https://example.com/api/corona-tracker/country/${country}`)
      ...
    }
    // 関数を実行する
    getCountryData()
  }, [country])  // country state が更新されたら実行するように設定する
hagiwarahagiwara

nodemon
ファイルの変更を保存したらサーバを自動で再起動してくれる

Nodemon is a utility depended on about 3 million projects, that will monitor for any changes in your source and automatically restart your server. Perfect for development.

https://nodemon.io/
https://www.npmjs.com/package/nodemon

hagiwarahagiwara
  1. Install nodemon.
  2. In "package.json" under "scripts" enter the following (for index.js entry pt):
    "start": "nodemon index.js"
    p.s. don't forget the comma if any other scripts come afterwards.
  3. Don't use the Run button To run the code open the shell and enter "npm start". Your app should now run the same as if you were developing locally. Press ctrl + c to stop the server.

How to use Nodemon w/ Repl.it - Replit
https://replit.com/talk/ask/How-to-use-Nodemon-with-Replit/81525

hagiwarahagiwara

proxy 用のミドルウェア

chimurai/http-proxy-middleware: The one-liner node.js http-proxy middleware for connect, express, next.js and more
https://github.com/chimurai/http-proxy-middleware

hagiwarahagiwara

/corona-tracker-world-data にアクセスすると、target の URL にアクセスしてデータを取得する

app.use('/corona-tracker-world-data', (req, res, next) => {
  createProxyMiddleware({
    // target ↓↓↓
    target: 'https://example.com/api/corona-tracker/summary',
    changeOrigin: true,
    pathRewrite: {
      [`^/corona-tracker-world-data`]: '',
    },
  })(req, res, next)
})
hagiwarahagiwara

アクセス数の制限ができるミドルウェア

express-rate-limit/express-rate-limit: Basic rate-limiting middleware for express
https://github.com/express-rate-limit/express-rate-limit

hagiwarahagiwara

アクセス数制限の設定

const limiter = rateLimit({
  // アクセス数の制限を設ける時間幅(15分)
  windowMs: 15 * 60 * 1000,
  // 最大アクセス数(15分間に5回まで)
  max: 5,
})

app.use(limiter)
hagiwarahagiwara

Headers に追加されてる

x-ratelimit-limit : 上限回数
x-ratelimit-remaining : 残り回数
x-ratelimit-reset : 回数がリセットされる時刻(UNIX 時間)
hagiwarahagiwara
// サーバ全体に適用する場合
app.use(limiter)

app.use('/corona-tracker-world-data', (req, res, next) => {
  createProxyMiddleware({
  ...
// 特定の URL だけに適用する場合
app.use('/corona-tracker-world-data', limiter, (req, res, next) => {
  createProxyMiddleware({
  ...
hagiwarahagiwara

Secrets and Environment Variables | Replit Docs
https://docs.replit.com/programming-ide/workspace-features/storing-sensitive-information-environment-variables

認証情報やAPIキーなど、機密性の高い情報は安全に保管する必要があります。これらの情報をコードから分離しておくことで、コードを他人と共有する際に不正なアクセスから保護することができます。

アプリケーションに機密情報へのアクセスを与えるための一般的なソリューションは、環境変数の使用です。環境変数にキーと値のペアとして情報を格納することで、バックエンドのコードでその情報にアクセスすることができます。Replitでは、環境変数を追加し、シークレットを使用してアクセスすることができます。

あなたのReplをクローンしたユーザーは、コードにアクセスすることができますが、環境変数に独自の値を設定しなければならないことに注意することが重要です。これは、機密情報のセキュリティを維持するのに役立ちます。

hagiwarahagiwara

環境変数を使う場合

// 修正前
app.use('/hoge', limiter, (req, res, next) => {
  createProxyMiddleware({
    target: 'https://example.com/api/hoge,
  ...
// 修正後
app.use('/hoge', limiter, (req, res, next) => {
  createProxyMiddleware({
    target: process.env.BASE_API_URL_HOGE,
  ...
// 修正後(URL にクエリがある場合)
app.use('/hoge', limiter, (req, res, next) => {
  createProxyMiddleware({
    target: `${process.env.BASE_API_URL_HOGE}&q=${foo}`,
  ...