<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	
	xmlns:georss="http://www.georss.org/georss"
	xmlns:geo="http://www.w3.org/2003/01/geo/wgs84_pos#"
	>

<channel>
	<title>React | みんたく</title>
	<atom:link href="https://mintaku-blog.net/category/develop/react/feed/" rel="self" type="application/rss+xml" />
	<link>https://mintaku-blog.net</link>
	<description>みんたくの技術ブログ</description>
	<lastBuildDate>Sat, 29 Mar 2025 04:58:29 +0000</lastBuildDate>
	<language>ja</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.0.11</generator>

<image>
	<url>https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2018/06/cropped-ipad-820272_640.jpg?fit=32%2C32&#038;ssl=1</url>
	<title>React | みんたく</title>
	<link>https://mintaku-blog.net</link>
	<width>32</width>
	<height>32</height>
</image> 
<site xmlns="com-wordpress:feed-additions:1">144480658</site>	<item>
		<title>Refreshコンポーネントとrefreshメソッドの違いを理解する</title>
		<link>https://mintaku-blog.net/react-server-refresh/</link>
					<comments>https://mintaku-blog.net/react-server-refresh/#respond</comments>
		
		<dc:creator><![CDATA[みんたく]]></dc:creator>
		<pubDate>Tue, 04 Mar 2025 09:00:52 +0000</pubDate>
				<category><![CDATA[React]]></category>
		<guid isPermaLink="false">https://mintaku-blog.net/?p=2593</guid>

					<description><![CDATA[<p>react-serverでは、ページやコンテンツを更新するための方法が2つ提供されています。 宣言的なアプローチの&#38;lt;Refresh&#38;g …</p>
The post <a href="https://mintaku-blog.net/react-server-refresh/">Refreshコンポーネントとrefreshメソッドの違いを理解する</a> first appeared on <a href="https://mintaku-blog.net">みんたく</a>.]]></description>
										<content:encoded><![CDATA[<p><a href="https://react-server.dev/">react-server</a>では、ページやコンテンツを更新するための方法が2つ提供されています。</p>
<p>宣言的なアプローチの&lt;Refresh&gt;コンポーネントと、命令的なアプローチのrefresh()関数です。</p>
<p>一見似ているように見えるこの2つの更新方法ですが、実際どのような違いがありのかまとめてみました。</p>
<h2>2つの更新方法</h2>
<p>まず、それぞれの基本的な使い方を見てみましょう：</p><pre class="crayon-plain-tag">// 方法1: Refreshコンポーネントを使用
import { Refresh } from "@lazarv/react-server/navigation";

function RefreshButtonUsingComponent() {
  return (
    &lt;div&gt;
      &lt;Refresh&gt;更新する&lt;/Refresh&gt;
    &lt;/div&gt;
  );
}

// 方法2: refresh関数を使用
import { useClient } from "@lazarv/react-server/client";
import { startTransition } from "react";

function RefreshButtonUsingFunction() {
  const { refresh } = useClient();
  
  return (
    &lt;div&gt;
      &lt;button onClick={() =&gt; startTransition(async () =&gt; refresh())}&gt;
        更新する
      &lt;/button&gt;
    &lt;/div&gt;
  );
}</pre><p>一見すると似たような機能に見えますが、実際には重要な違いがあります。</p>
<h3>1. コンポーネント vs 関数</h3>
<p>最も明確な違いは、一方がReactコンポーネントであり、もう一方が関数であるという点です。</p>
<ul>
<li>&lt;Refresh&gt;：ユーザーインターフェースを構成するReactコンポーネント。宣言的なアプローチ</li>
<li>refresh()：プログラムから呼び出す関数。命令的なアプローチ</li>
</ul>
<h3>2. 使用方法と柔軟性</h3>
<p></p><pre class="crayon-plain-tag">// Refreshコンポーネント - シンプルに使える
&lt;Refresh&gt;クリックして更新&lt;/Refresh&gt;

// refresh関数 - より多くのコントロールが可能
const { refresh } = useClient();

// 条件付き更新
const conditionalRefresh = () =&gt; {
  if (hasChanges) {
    startTransition(async () =&gt; refresh());
  }
};

// 特定のアウトレットのみ更新
const refreshDashboard = () =&gt; {
  startTransition(async () =&gt; refresh("dashboardOutlet"));
};

// 他の処理と組み合わせる
const saveAndRefresh = async () =&gt; {
  await saveData();
  startTransition(async () =&gt; refresh());
  showNotification("保存しました");
};</pre><p>refresh()関数は柔軟性が高く、より複雑なシナリオに対応できます。</p>
<h3>3. トランジションの扱い</h3>
<p>&lt;Refresh&gt;コンポーネントは内部でReactのトランジションを自動的に処理しますが、refresh()関数を使用する場合は、通常自分でstartTransitionを使ってトランジションを開始する必要があります。</p><pre class="crayon-plain-tag">// Refreshコンポーネント - トランジションは内部で処理される
&lt;Refresh&gt;更新&lt;/Refresh&gt;

// refresh関数 - 自分でトランジションを開始する
&lt;button onClick={() =&gt; startTransition(async () =&gt; refresh())}&gt;
  更新
&lt;/button&gt;</pre><p>これにより、refresh()関数を使用する場合、トランジションのタイミングをより細かく制御できます。</p>
<h2>ユースケース：どちらを選ぶべき？</h2>
<p>それぞれの方法が適している状況を考えてみます。</p>
<h3>&lt;Refresh&gt;コンポーネントが適している場合</h3>
<ul>
<li>シンプルな「更新ボタン」が欲しい場合</li>
<li>マークアップが中心で、複雑なロジックが不要な場合</li>
<li>React Serverのデフォルトの動作で十分な場合</li>
<li>JSXの中で直感的に使いたい場合</li>
</ul>
<p></p><pre class="crayon-plain-tag">function SimplePage() {
  return (
    &lt;div className="page-container"&gt;
      &lt;header&gt;
        &lt;h1&gt;マイダッシュボード&lt;/h1&gt;
        &lt;Refresh&gt;更新&lt;/Refresh&gt;
      &lt;/header&gt;
      &lt;main&gt;
        {/* ページコンテンツ */}
      &lt;/main&gt;
    &lt;/div&gt;
  );
}</pre><p></p>
<h3>refresh()関数が適している場合</h3>
<ul>
<li>他のロジックと組み合わせる必要がある場合</li>
<li>条件付きで更新したい場合</li>
<li>特定のアウトレットだけを更新したい場合</li>
<li>カスタムUIと組み合わせたい場合</li>
<li>プログラムで更新のタイミングを制御したい場合</li>
</ul>
<p></p><pre class="crayon-plain-tag">function ComplexDashboard() {
  const { refresh } = useClient();
  const [isLoading, setIsLoading] = useState(false);
  
  const handleRefresh = async () =&gt; {
    setIsLoading(true);
    // 先にバックエンドAPIからデータを取得
    await fetchLatestData();
    // その後ページを更新
    startTransition(async () =&gt; {
      await refresh();
      setIsLoading(false);
    });
    // 分析データを送信
    trackEvent("dashboard_refreshed");
  };
  
  return (
    &lt;div className="dashboard"&gt;
      &lt;button 
        onClick={handleRefresh}
        disabled={isLoading}
        className={isLoading ? "btn-loading" : "btn-normal"}
      &gt;
        {isLoading ? "更新中..." : "最新データに更新"}
      &lt;/button&gt;
      {/* ダッシュボードコンテンツ */}
    &lt;/div&gt;
  );
}</pre><p></p>
<h2>まとめ</h2>
<p>React Serverにおける2つの更新方法は、同じ機能をベースとしながらも、異なるユースケースに合わせて最適化されています。</p>
<ul>
<li>&lt;Refresh&gt;コンポーネント：シンプルさと宣言的なアプローチを重視</li>
<li>refresh()関数：柔軟性と制御性を重視</li>
</ul>
<p>より高度なパフォーマンス最適化やユーザーエクスペリエンス向上のためには、refresh()関数とstartTransitionを組み合わせた方法が多くの場合に適していそうです。</p>
<p>しかし、シンプルなUI要素としては&lt;Refresh&gt;コンポーネントの直感的に使いやすいです。</p>The post <a href="https://mintaku-blog.net/react-server-refresh/">Refreshコンポーネントとrefreshメソッドの違いを理解する</a> first appeared on <a href="https://mintaku-blog.net">みんたく</a>.]]></content:encoded>
					
					<wfw:commentRss>https://mintaku-blog.net/react-server-refresh/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">2593</post-id>	</item>
		<item>
		<title>【ハンズオン】ReactとOpenWeatherMap APIで天気アプリを作ろう【TypeScript】</title>
		<link>https://mintaku-blog.net/react-weather-api/</link>
					<comments>https://mintaku-blog.net/react-weather-api/#respond</comments>
		
		<dc:creator><![CDATA[みんたく]]></dc:creator>
		<pubDate>Sat, 06 Jul 2024 21:38:38 +0000</pubDate>
				<category><![CDATA[React]]></category>
		<guid isPermaLink="false">https://mintaku-blog.net/?p=2505</guid>

					<description><![CDATA[<p>OpenWeatherMap APIは、世界中の天気情報を提供するオンラインサービスで、現在気象や天気予報を含む様々な気象データを無料で利用できるAPIを提 …</p>
The post <a href="https://mintaku-blog.net/react-weather-api/">【ハンズオン】ReactとOpenWeatherMap APIで天気アプリを作ろう【TypeScript】</a> first appeared on <a href="https://mintaku-blog.net">みんたく</a>.]]></description>
										<content:encoded><![CDATA[<p>OpenWeatherMap APIは、世界中の天気情報を提供するオンラインサービスで、現在気象や天気予報を含む様々な気象データを無料で利用できるAPIを提供しています。</p>
<p>Webやモバイルアプリケーションの開発者向けに、JSON、XML、HTML形式で天気情報を配信しています。</p>
<p>今回、ReactとOpenWeatherMap APIを使って、都市を入力したらその都市の天気が表示されるような簡単な天気アプリを作ってみました。</p>
<h2>1. 環境構築</h2>
<p>Node環境があることを前提にして行います。</p>
<p>templateはtypescriptを指定して、Google Cloud Vision APIをコールするためにaxiosをインストールしておきます。</p>
<p>npm startしてReactの初期画面が表示されれば成功です。</p><pre class="crayon-plain-tag">$ npx create-react-app city-weather-app --template typescript
$ cd city-weather-app
$ npm i axios
$ npm start</pre><p>&nbsp;</p>
<h2>2. OpenWeatherMap APIの設定</h2>
<p>OpenWeatherMap APIにアクセスして、ユーザー登録をします。</p>
<div class="ys-blog-card__text-link"><a href="https://openweathermap.org/" >https://openweathermap.org/</a></div>
<p><img data-attachment-id="2518" data-permalink="https://mintaku-blog.net/react-vison-api/https___qiita-image-store-s3-ap-northeast-1-amazonaws-com_0_242816_e06f98e6-ea92-1814-7f62-898e607d5933_11zon/" data-orig-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/06/https___qiita-image-store.s3.ap-northeast-1.amazonaws.com_0_242816_e06f98e6-ea92-1814-7f62-898e607d5933_11zon.jpg?fit=1400%2C744&amp;ssl=1" data-orig-size="1400,744" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="https___qiita-image-store.s3.ap-northeast-1.amazonaws.com_0_242816_e06f98e6-ea92-1814-7f62-898e607d5933_11zon" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/06/https___qiita-image-store.s3.ap-northeast-1.amazonaws.com_0_242816_e06f98e6-ea92-1814-7f62-898e607d5933_11zon.jpg?fit=300%2C159&amp;ssl=1" data-large-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/06/https___qiita-image-store.s3.ap-northeast-1.amazonaws.com_0_242816_e06f98e6-ea92-1814-7f62-898e607d5933_11zon.jpg?fit=800%2C425&amp;ssl=1" loading="lazy" src="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/06/https___qiita-image-store.s3.ap-northeast-1.amazonaws.com_0_242816_e06f98e6-ea92-1814-7f62-898e607d5933_11zon.jpg?resize=300%2C159&#038;ssl=1" alt="" width="300" height="159" class="alignnone size-medium wp-image-2518" srcset="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/06/https___qiita-image-store.s3.ap-northeast-1.amazonaws.com_0_242816_e06f98e6-ea92-1814-7f62-898e607d5933_11zon.jpg?resize=300%2C159&amp;ssl=1 300w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/06/https___qiita-image-store.s3.ap-northeast-1.amazonaws.com_0_242816_e06f98e6-ea92-1814-7f62-898e607d5933_11zon.jpg?resize=1024%2C544&amp;ssl=1 1024w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/06/https___qiita-image-store.s3.ap-northeast-1.amazonaws.com_0_242816_e06f98e6-ea92-1814-7f62-898e607d5933_11zon.jpg?resize=768%2C408&amp;ssl=1 768w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/06/https___qiita-image-store.s3.ap-northeast-1.amazonaws.com_0_242816_e06f98e6-ea92-1814-7f62-898e607d5933_11zon.jpg?w=1400&amp;ssl=1 1400w" sizes="(max-width: 300px) 100vw, 300px" data-recalc-dims="1" /></p>
<p>ユーザー名、メールアドレス、パスワードを入力して、「Create Account」を押します。</p>
<p><img data-attachment-id="2517" data-permalink="https://mintaku-blog.net/react-vison-api/https___qiita-image-store-s3-ap-northeast-1-amazonaws-com_0_242816_c468383b-2e6f-9b85-6677-acaa00713aeb_11zon/" data-orig-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/06/https___qiita-image-store.s3.ap-northeast-1.amazonaws.com_0_242816_c468383b-2e6f-9b85-6677-acaa00713aeb_11zon.jpg?fit=1400%2C976&amp;ssl=1" data-orig-size="1400,976" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="https___qiita-image-store.s3.ap-northeast-1.amazonaws.com_0_242816_c468383b-2e6f-9b85-6677-acaa00713aeb_11zon" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/06/https___qiita-image-store.s3.ap-northeast-1.amazonaws.com_0_242816_c468383b-2e6f-9b85-6677-acaa00713aeb_11zon.jpg?fit=300%2C209&amp;ssl=1" data-large-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/06/https___qiita-image-store.s3.ap-northeast-1.amazonaws.com_0_242816_c468383b-2e6f-9b85-6677-acaa00713aeb_11zon.jpg?fit=800%2C558&amp;ssl=1" loading="lazy" src="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/06/https___qiita-image-store.s3.ap-northeast-1.amazonaws.com_0_242816_c468383b-2e6f-9b85-6677-acaa00713aeb_11zon.jpg?resize=300%2C209&#038;ssl=1" alt="" width="300" height="209" class="alignnone size-medium wp-image-2517" srcset="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/06/https___qiita-image-store.s3.ap-northeast-1.amazonaws.com_0_242816_c468383b-2e6f-9b85-6677-acaa00713aeb_11zon.jpg?resize=300%2C209&amp;ssl=1 300w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/06/https___qiita-image-store.s3.ap-northeast-1.amazonaws.com_0_242816_c468383b-2e6f-9b85-6677-acaa00713aeb_11zon.jpg?resize=1024%2C714&amp;ssl=1 1024w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/06/https___qiita-image-store.s3.ap-northeast-1.amazonaws.com_0_242816_c468383b-2e6f-9b85-6677-acaa00713aeb_11zon.jpg?resize=768%2C535&amp;ssl=1 768w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/06/https___qiita-image-store.s3.ap-northeast-1.amazonaws.com_0_242816_c468383b-2e6f-9b85-6677-acaa00713aeb_11zon.jpg?w=1400&amp;ssl=1 1400w" sizes="(max-width: 300px) 100vw, 300px" data-recalc-dims="1" /></p>
<p>アカウント登録が完了したら、「My API keys」からAPI Keyをコピーしておきます。</p>
<h2>3. 環境変数の設定</h2>
<p>作成したAPIキーを.envファイルを作成し、環境変数を設定します。</p>
<p>creare-react-appで作ったプロジェクトはprocess.env.REACT_APP_から始まる変数を読み込みができるようになっています。</p>
<p>先ほどコピーしたAPI Keyを設定します。</p>
<p>.env</p><pre class="crayon-plain-tag">REACT_APP_OPENWEATHERMAP_API_KEY=xxxxxxxxxxxxx</pre><p>&nbsp;</p>
<h2>4. OpenWeatherMap APIを呼び出す</h2>
<p>API Keyを設定できたら、実際にOpenWeatherMap APIを呼び出してみます。</p>
<p>こちらの公式ドキュメントを参考にAPIの呼び出しを行います。</p>
<div class="ys-blog-card__text-link"><a href="https://openweathermap.org/current#:~:text=for%20this%20functionality.-,Built%2Din%20API%20request%20by%20city%20name,-You%20can%20call" >https://openweathermap.org/current#:~:text=for%20this%20functionality.-,Built%2Din%20API%20request%20by%20city%20name,-You%20can%20call</a></div>
<p>今回は入力した都市の天気情報を表示したいので、こちらのAPIの呼び方を参考にします。</p><pre class="crayon-plain-tag">https://api.openweathermap.org/data/2.5/weather?q={city name}&amp;appid={API key}</pre><p>では実際にAPIを呼び出してみます。まず天気アプリ画面用のコンポーネントを作成します。</p>
<p>先ほどのAPIの呼び出しを参考にコードを書いていきます。</p>
<p>・Weather.tsx</p><pre class="crayon-plain-tag">import axios from "axios";

const Weather = () =&gt; {
  const apiKey = process.env.REACT_APP_OPENWEATHERMAP_API_KEY;
  const city = 'Tokyo'

  const apiCall = async () =&gt; {
    try {
      const response = await axios.get(
        `https://api.openweathermap.org/data/2.5/weather?q=${city}&amp;appid=${apiKey}&amp;units=metric`
      );
  
      console.log('===================== response data =====================');
      console.log(response.data);
      
    } catch (error) {
      console.error('Error has occured:', error);
    }
  }

  return (
    &lt;div&gt;
      &lt;button onClick={apiCall}&gt;API Call&lt;/button&gt;
    &lt;/div&gt;
  )
}

export default Weather;</pre><p>API呼び出しの全体的なコードは上記のようになります。</p><pre class="crayon-plain-tag">const apiKey = process.env.REACT_APP_OPENWEATHERMAP_API_KEY;
const city = 'Tokyo'

...

const response = await axios.get(
  `https://api.openweathermap.org/data/2.5/weather?q=${city}&amp;appid=${apiKey}&amp;units=metric`
);</pre><p>まずAPIを呼び出しているところから見ていきます。</p>
<p>APIの呼び出しでcityとapiKeyの2つが変数になっています。そのため、apiKeyは先ほど定義した環境変数から取得し、cityは一旦固定で「Tokyo」としておきます。</p>
<p>これで、APIを呼び出すことができます。</p><pre class="crayon-plain-tag">console.log('===================== response data =====================');
console.log(response.data);</pre><p>&nbsp;</p>
<p>次に呼び出したAPIのレスポンスをコンソール上に表示させます。<br />
console.logで結果を出力することで、ブラウザのコンソール上で実際にどんなレスポンスデータが返ってきているか確認することができます。</p><pre class="crayon-plain-tag">return (
  &lt;div&gt;
    &lt;button onClick={apiCall}&gt;API Call&lt;/button&gt;
  &lt;/div&gt;
)</pre><p>トリガーはこのbuttonにしています。ボタンを押すとAPIが呼び出されるようにしています。</p>
<p>・App.tsx</p><pre class="crayon-plain-tag">import Weather from './Weather';

function App() {
  return (
    &lt;div className="App"&gt;
      &lt;Weather /&gt;
    &lt;/div&gt;
  );
};

export default App;</pre><p>最後にApp.tsxにWeatherコンポーネントを呼び出して、完了です。</p>
<p><img data-attachment-id="2516" data-permalink="https://mintaku-blog.net/react-vison-api/https___qiita-image-store-s3-ap-northeast-1-amazonaws-com_0_242816_2cbc8dd3-a23f-fd24-a6f5-d0501d0ac941_11zon/" data-orig-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/06/https___qiita-image-store.s3.ap-northeast-1.amazonaws.com_0_242816_2cbc8dd3-a23f-fd24-a6f5-d0501d0ac941_11zon.jpg?fit=1400%2C424&amp;ssl=1" data-orig-size="1400,424" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="https___qiita-image-store.s3.ap-northeast-1.amazonaws.com_0_242816_2cbc8dd3-a23f-fd24-a6f5-d0501d0ac941_11zon" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/06/https___qiita-image-store.s3.ap-northeast-1.amazonaws.com_0_242816_2cbc8dd3-a23f-fd24-a6f5-d0501d0ac941_11zon.jpg?fit=300%2C91&amp;ssl=1" data-large-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/06/https___qiita-image-store.s3.ap-northeast-1.amazonaws.com_0_242816_2cbc8dd3-a23f-fd24-a6f5-d0501d0ac941_11zon.jpg?fit=800%2C242&amp;ssl=1" loading="lazy" src="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/06/https___qiita-image-store.s3.ap-northeast-1.amazonaws.com_0_242816_2cbc8dd3-a23f-fd24-a6f5-d0501d0ac941_11zon.jpg?resize=300%2C91&#038;ssl=1" alt="" width="300" height="91" class="alignnone size-medium wp-image-2516" srcset="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/06/https___qiita-image-store.s3.ap-northeast-1.amazonaws.com_0_242816_2cbc8dd3-a23f-fd24-a6f5-d0501d0ac941_11zon.jpg?resize=300%2C91&amp;ssl=1 300w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/06/https___qiita-image-store.s3.ap-northeast-1.amazonaws.com_0_242816_2cbc8dd3-a23f-fd24-a6f5-d0501d0ac941_11zon.jpg?resize=1024%2C310&amp;ssl=1 1024w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/06/https___qiita-image-store.s3.ap-northeast-1.amazonaws.com_0_242816_2cbc8dd3-a23f-fd24-a6f5-d0501d0ac941_11zon.jpg?resize=768%2C233&amp;ssl=1 768w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/06/https___qiita-image-store.s3.ap-northeast-1.amazonaws.com_0_242816_2cbc8dd3-a23f-fd24-a6f5-d0501d0ac941_11zon.jpg?w=1400&amp;ssl=1 1400w" sizes="(max-width: 300px) 100vw, 300px" data-recalc-dims="1" /></p>
<p>npm startして実際にローカルの画面を表示すると、「API Call」のボタンが表示され、ボタンを押すと、右側のコンソールのようにAPIのレスポンスデータが表示されます。</p>
<h2>5. OpenWeatherMap APIのレスポンスデータを画面に表示する</h2>
<p>次にAPIのレスポンスデータを画面に表示させていきます。</p>
<p>・Weather.tsx</p><pre class="crayon-plain-tag">import axios from "axios";
import { useState } from "react";

const Weather = () =&gt; {
  const apiKey = process.env.REACT_APP_OPENWEATHERMAP_API_KEY;
  const city = 'Tokyo'

  const [weather, setWeather] = useState&lt;any&gt;(null);

  const apiCall = async () =&gt; {
    try {
      const response = await axios.get(
        `https://api.openweathermap.org/data/2.5/weather?q=${city}&amp;appid=${apiKey}&amp;units=metric`
      );
  
      setWeather(response.data);
    } catch (error) {
      console.error('Error has occured:', error);
    }
  }

  return (
    &lt;div&gt;
      &lt;button onClick={apiCall}&gt;API Call&lt;/button&gt;
      {weather &amp;&amp; (
        &lt;div&gt;
          &lt;h2&gt;{weather.name}&lt;/h2&gt;
          &lt;p&gt;Temperature: {weather.main.temp}°C&lt;/p&gt;
          &lt;p&gt;Weather: {weather.weather[0].description}&lt;/p&gt;
        &lt;/div&gt;
      )}
    &lt;/div&gt;
  )
}

export default Weather;</pre><p>全体像としては上記のとおりです。</p><pre class="crayon-plain-tag">const [weather, setWeather] = useState&lt;any&gt;(null);

const apiCall = async () =&gt; {
  try {
    const response = await axios.get(
      `https://api.openweathermap.org/data/2.5/weather?q=${city}&amp;appid=${apiKey}&amp;units=metric`
    );
  
    setWeather(response.data);
  } catch (error) {
    console.error('Error has occured:', error);
  }
}</pre><p>useState フックを使って、2つの要素を持つ状態変数を宣言しています。</p>
<p>weatherは天気情報を入れるための変数で、初期値はnullです。setWeatherはweatherの値を更新するための関数です。</p><pre class="crayon-plain-tag">return (
  &lt;div&gt;
    &lt;button onClick={apiCall}&gt;API Call&lt;/button&gt;
    {weather &amp;&amp; (
      &lt;div&gt;
        &lt;h2&gt;{weather.name}&lt;/h2&gt;
        &lt;p&gt;Temperature: {weather.main.temp}°C&lt;/p&gt;
        &lt;p&gt;Weather: {weather.weather[0].description}&lt;/p&gt;
      &lt;/div&gt;
    )}
  &lt;/div&gt;
)</pre><p>{weather &amp;&amp; ( &#8230; ) } 部分は、weather変数に値が存在する場合のみ、divタグ内の内容を表示します。</p>
<p>weatherがnullまたはundefinedの場合は、div タグ内の内容は表示されません。</p>
<p>weatherに値がある場合、都市名・気温・天気を表示するようにします。</p>
<p><img data-attachment-id="2515" data-permalink="https://mintaku-blog.net/react-vison-api/https___qiita-image-store-s3-ap-northeast-1-amazonaws-com_0_242816_bd9b2e51-bb9a-1e01-afd9-aac132a2618d_11zon/" data-orig-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/06/https___qiita-image-store.s3.ap-northeast-1.amazonaws.com_0_242816_bd9b2e51-bb9a-1e01-afd9-aac132a2618d_11zon.jpg?fit=780%2C414&amp;ssl=1" data-orig-size="780,414" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="https___qiita-image-store.s3.ap-northeast-1.amazonaws.com_0_242816_bd9b2e51-bb9a-1e01-afd9-aac132a2618d_11zon" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/06/https___qiita-image-store.s3.ap-northeast-1.amazonaws.com_0_242816_bd9b2e51-bb9a-1e01-afd9-aac132a2618d_11zon.jpg?fit=300%2C159&amp;ssl=1" data-large-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/06/https___qiita-image-store.s3.ap-northeast-1.amazonaws.com_0_242816_bd9b2e51-bb9a-1e01-afd9-aac132a2618d_11zon.jpg?fit=780%2C414&amp;ssl=1" loading="lazy" src="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/06/https___qiita-image-store.s3.ap-northeast-1.amazonaws.com_0_242816_bd9b2e51-bb9a-1e01-afd9-aac132a2618d_11zon.jpg?resize=300%2C159&#038;ssl=1" alt="" width="300" height="159" class="alignnone size-medium wp-image-2515" srcset="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/06/https___qiita-image-store.s3.ap-northeast-1.amazonaws.com_0_242816_bd9b2e51-bb9a-1e01-afd9-aac132a2618d_11zon.jpg?resize=300%2C159&amp;ssl=1 300w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/06/https___qiita-image-store.s3.ap-northeast-1.amazonaws.com_0_242816_bd9b2e51-bb9a-1e01-afd9-aac132a2618d_11zon.jpg?resize=768%2C408&amp;ssl=1 768w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/06/https___qiita-image-store.s3.ap-northeast-1.amazonaws.com_0_242816_bd9b2e51-bb9a-1e01-afd9-aac132a2618d_11zon.jpg?w=780&amp;ssl=1 780w" sizes="(max-width: 300px) 100vw, 300px" data-recalc-dims="1" /></p>
<p>実際に実行してみると上記のように表示されることが確認できます。</p>
<p>&nbsp;</p>
<h2>6. 都市を入力して表示できるようにする</h2>
<p>現状、東京に固定して天気情報を取得しているため、入力値に合わせて天気情報を取得するようにします。</p>
<p>・Weather.tsx</p><pre class="crayon-plain-tag">import axios from "axios";
import { useState } from "react";

const Weather = () =&gt; {
  const apiKey = process.env.REACT_APP_OPENWEATHERMAP_API_KEY;
  const [weather, setWeather] = useState&lt;any&gt;(null);
  // 追加
  const [city, setCity] = useState('');

  // 追加
  const inputCity = (event: React.ChangeEvent&lt;HTMLInputElement&gt;) =&gt; {
    setCity(event.target.value);
  };

  const apiCall = async () =&gt; {
    try {
      const response = await axios.get(
        `https://api.openweathermap.org/data/2.5/weather?q=${city}&amp;appid=${apiKey}&amp;units=metric`
      );
  
      setWeather(response.data);
      
    } catch (error) {
      console.error('Error has occured:', error);
    }
  }

  return (
    &lt;div&gt;
      // 追加
      &lt;input
        type="text"
        value={city}
        onChange={inputCity}
        placeholder="都市を入力してください"
      /&gt;
      &lt;button onClick={apiCall}&gt;API Call&lt;/button&gt;
      {weather &amp;&amp; (
        &lt;div&gt;
          &lt;h2&gt;{weather.name}&lt;/h2&gt;
          &lt;p&gt;Temperature: {weather.main.temp}°C&lt;/p&gt;
          &lt;p&gt;Weather: {weather.weather[0].description}&lt;/p&gt;
        &lt;/div&gt;
      )}
    &lt;/div&gt;
  )
}

export default Weather;</pre><p>追加したコードが入力した都市の天気情報を取得して、表示するようにして箇所です。</p><pre class="crayon-plain-tag">const [city, setCity] = useState('');

const inputCity = (event: React.ChangeEvent&lt;HTMLInputElement&gt;) =&gt; {
  setCity(event.target.value);
};

...

&lt;input
  type="text"
  value={city}
  onChange={inputCity}
  placeholder="都市を入力してください"
/&gt;</pre><p>onChangeはフォーム要素の値が変更されたときに発生するイベントハンドラで、inputタグを追加し、onChangeイベントでcity変数に入力した値を入れています。</p>
<p><img data-attachment-id="2514" data-permalink="https://mintaku-blog.net/react-vison-api/https___qiita-image-store-s3-ap-northeast-1-amazonaws-com_0_242816_110a092f-5cd2-6bef-1b21-5faabdcffa97_11zon/" data-orig-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/06/https___qiita-image-store.s3.ap-northeast-1.amazonaws.com_0_242816_110a092f-5cd2-6bef-1b21-5faabdcffa97_11zon.jpg?fit=746%2C346&amp;ssl=1" data-orig-size="746,346" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="https___qiita-image-store.s3.ap-northeast-1.amazonaws.com_0_242816_110a092f-5cd2-6bef-1b21-5faabdcffa97_11zon" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/06/https___qiita-image-store.s3.ap-northeast-1.amazonaws.com_0_242816_110a092f-5cd2-6bef-1b21-5faabdcffa97_11zon.jpg?fit=300%2C139&amp;ssl=1" data-large-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/06/https___qiita-image-store.s3.ap-northeast-1.amazonaws.com_0_242816_110a092f-5cd2-6bef-1b21-5faabdcffa97_11zon.jpg?fit=746%2C346&amp;ssl=1" loading="lazy" src="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/06/https___qiita-image-store.s3.ap-northeast-1.amazonaws.com_0_242816_110a092f-5cd2-6bef-1b21-5faabdcffa97_11zon.jpg?resize=300%2C139&#038;ssl=1" alt="" width="300" height="139" class="alignnone size-medium wp-image-2514" srcset="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/06/https___qiita-image-store.s3.ap-northeast-1.amazonaws.com_0_242816_110a092f-5cd2-6bef-1b21-5faabdcffa97_11zon.jpg?resize=300%2C139&amp;ssl=1 300w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/06/https___qiita-image-store.s3.ap-northeast-1.amazonaws.com_0_242816_110a092f-5cd2-6bef-1b21-5faabdcffa97_11zon.jpg?w=746&amp;ssl=1 746w" sizes="(max-width: 300px) 100vw, 300px" data-recalc-dims="1" /></p>
<p>実際に動かしてみると、入力した都市で天気情報を取得していることがわかります。</p>
<p>&nbsp;</p>
<h2>7. 入力した都市が存在しなかった場合のハンドリング</h2>
<p>次に、入力した都市が存在しなかった場合の対応を行います。</p>
<p><img data-attachment-id="2513" data-permalink="https://mintaku-blog.net/react-vison-api/https___qiita-image-store-s3-ap-northeast-1-amazonaws-com_0_242816_b63a1377-e696-653d-ee69-de8a6beba591_11zon/" data-orig-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/06/https___qiita-image-store.s3.ap-northeast-1.amazonaws.com_0_242816_b63a1377-e696-653d-ee69-de8a6beba591_11zon.jpg?fit=1400%2C728&amp;ssl=1" data-orig-size="1400,728" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="https___qiita-image-store.s3.ap-northeast-1.amazonaws.com_0_242816_b63a1377-e696-653d-ee69-de8a6beba591_11zon" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/06/https___qiita-image-store.s3.ap-northeast-1.amazonaws.com_0_242816_b63a1377-e696-653d-ee69-de8a6beba591_11zon.jpg?fit=300%2C156&amp;ssl=1" data-large-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/06/https___qiita-image-store.s3.ap-northeast-1.amazonaws.com_0_242816_b63a1377-e696-653d-ee69-de8a6beba591_11zon.jpg?fit=800%2C416&amp;ssl=1" loading="lazy" src="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/06/https___qiita-image-store.s3.ap-northeast-1.amazonaws.com_0_242816_b63a1377-e696-653d-ee69-de8a6beba591_11zon.jpg?resize=300%2C156&#038;ssl=1" alt="" width="300" height="156" class="alignnone size-medium wp-image-2513" srcset="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/06/https___qiita-image-store.s3.ap-northeast-1.amazonaws.com_0_242816_b63a1377-e696-653d-ee69-de8a6beba591_11zon.jpg?resize=300%2C156&amp;ssl=1 300w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/06/https___qiita-image-store.s3.ap-northeast-1.amazonaws.com_0_242816_b63a1377-e696-653d-ee69-de8a6beba591_11zon.jpg?resize=1024%2C532&amp;ssl=1 1024w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/06/https___qiita-image-store.s3.ap-northeast-1.amazonaws.com_0_242816_b63a1377-e696-653d-ee69-de8a6beba591_11zon.jpg?resize=768%2C399&amp;ssl=1 768w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/06/https___qiita-image-store.s3.ap-northeast-1.amazonaws.com_0_242816_b63a1377-e696-653d-ee69-de8a6beba591_11zon.jpg?w=1400&amp;ssl=1 1400w" sizes="(max-width: 300px) 100vw, 300px" data-recalc-dims="1" /></p>
<p>現状、入力した都市が存在していない場合はエラーが発生しますが、画面に表示されないため、ユーザーが認識できない状態となっています。</p>
<p>・Weather.tsx</p><pre class="crayon-plain-tag">import axios from "axios";
import { useState } from "react";

const Weather = () =&gt; {
  const apiKey = process.env.REACT_APP_OPENWEATHERMAP_API_KEY;
  const [weather, setWeather] = useState&lt;any&gt;(null);
  const [city, setCity] = useState('');
  // 追加
  const [error, setError] = useState&lt;string | null&gt;(null);

  const inputCity = (event: React.ChangeEvent&lt;HTMLInputElement&gt;) =&gt; {
    setCity(event.target.value);
  };


  const apiCall = async () =&gt; {
    try {
      const response = await axios.get(
        `https://api.openweathermap.org/data/2.5/weather?q=${city}&amp;appid=${apiKey}&amp;units=metric`
      );
  
      setWeather(response.data);
      // 追加
      setError(null);
    } catch (error) {
      console.error('Error has occured:', error);
      // 追加
      setWeather(null);
      if (axios.isAxiosError(error) &amp;&amp; error.response?.status === 404) {
        setError('入力した都市が見つかりませんでした。');
      } else {
        setError('予期せぬエラーが発生しました。');
      }
    }
  }

  return (
    &lt;div&gt;
      &lt;input
        type="text"
        value={city}
        onChange={inputCity}
        placeholder="都市を入力してください"
      /&gt;
      &lt;button onClick={apiCall}&gt;天気を取得&lt;/button&gt;
      // 追加
      {error &amp;&amp; &lt;p className="error"&gt;{error}&lt;/p&gt;}
      {weather &amp;&amp; !error &amp;&amp; (
        &lt;div&gt;
          &lt;h2&gt;{weather.name}&lt;/h2&gt;
          &lt;p&gt;Temperature: {weather.main.temp}°C&lt;/p&gt;
          &lt;p&gt;Weather: {weather.weather[0].description}&lt;/p&gt;
        &lt;/div&gt;
      )}
    &lt;/div&gt;
  )
}

export default Weather;</pre><p>エラー用のuseStateを用意し、エラーを表示するためのHTMLを追加しています。</p><pre class="crayon-plain-tag">if (axios.isAxiosError(error) &amp;&amp; error.response?.status === 404) {
  setError('入力した都市が見つかりませんでした。');
} else {
  setError('予期せぬエラーが発生しました。');
}</pre><p>エラーに関しては、都市が存在しない場合とそれ以外のエラーの2種類で条件分岐しています。エラー文をsetErrorで値を更新し、画面に表示します。</p>
<p><img data-attachment-id="2512" data-permalink="https://mintaku-blog.net/react-vison-api/https___qiita-image-store-s3-ap-northeast-1-amazonaws-com_0_242816_50dc7c77-971e-4fc2-af13-d73f4f51f1c6_11zon/" data-orig-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/06/https___qiita-image-store.s3.ap-northeast-1.amazonaws.com_0_242816_50dc7c77-971e-4fc2-af13-d73f4f51f1c6_11zon.jpg?fit=754%2C254&amp;ssl=1" data-orig-size="754,254" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="https___qiita-image-store.s3.ap-northeast-1.amazonaws.com_0_242816_50dc7c77-971e-4fc2-af13-d73f4f51f1c6_11zon" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/06/https___qiita-image-store.s3.ap-northeast-1.amazonaws.com_0_242816_50dc7c77-971e-4fc2-af13-d73f4f51f1c6_11zon.jpg?fit=300%2C101&amp;ssl=1" data-large-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/06/https___qiita-image-store.s3.ap-northeast-1.amazonaws.com_0_242816_50dc7c77-971e-4fc2-af13-d73f4f51f1c6_11zon.jpg?fit=754%2C254&amp;ssl=1" loading="lazy" src="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/06/https___qiita-image-store.s3.ap-northeast-1.amazonaws.com_0_242816_50dc7c77-971e-4fc2-af13-d73f4f51f1c6_11zon.jpg?resize=300%2C101&#038;ssl=1" alt="" width="300" height="101" class="alignnone size-medium wp-image-2512" srcset="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/06/https___qiita-image-store.s3.ap-northeast-1.amazonaws.com_0_242816_50dc7c77-971e-4fc2-af13-d73f4f51f1c6_11zon.jpg?resize=300%2C101&amp;ssl=1 300w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/06/https___qiita-image-store.s3.ap-northeast-1.amazonaws.com_0_242816_50dc7c77-971e-4fc2-af13-d73f4f51f1c6_11zon.jpg?w=754&amp;ssl=1 754w" sizes="(max-width: 300px) 100vw, 300px" data-recalc-dims="1" /></p>
<p>&nbsp;</p>
<h2>8. スタイルを整える</h2>
<p>最後にスタイルを整えます。特にこだわりがないようであれば、そのままコピペして使用して大丈夫です。</p>
<p>・Weather.tsx</p><pre class="crayon-plain-tag">import axios from "axios";
import { useState } from "react";

const Weather = () =&gt; {
  const apiKey = process.env.REACT_APP_OPENWEATHERMAP_API_KEY;
  const [weather, setWeather] = useState&lt;any&gt;(null);
  const [city, setCity] = useState('');
  const [error, setError] = useState&lt;string | null&gt;(null);

  const inputCity = (event: React.ChangeEvent&lt;HTMLInputElement&gt;) =&gt; {
    setCity(event.target.value);
  };


  const apiCall = async () =&gt; {
    try {
      const response = await axios.get(
        `https://api.openweathermap.org/data/2.5/weather?q=${city}&amp;appid=${apiKey}&amp;units=metric`
      );
  
      setWeather(response.data);
      setError(null);
    } catch (error) {
      console.error('Error has occured:', error);
      setWeather(null);
      if (axios.isAxiosError(error) &amp;&amp; error.response?.status === 404) {
        setError('入力した都市が見つかりませんでした。');
      } else {
        setError('予期せぬエラーが発生しました。');
      }
    }
  }

  return (
    &lt;div&gt;
      &lt;h1&gt;都市の天気検索&lt;/h1&gt;
      &lt;input
        type="text"
        value={city}
        onChange={inputCity}
        placeholder="都市を入力してください"
      /&gt;
      &lt;button onClick={apiCall}&gt;天気を取得&lt;/button&gt;
      {error &amp;&amp; &lt;p&gt;{error}&lt;/p&gt;}

      {weather &amp;&amp; !error &amp;&amp; (
        &lt;div className="weather-info"&gt;
          &lt;h2&gt;{weather.name}&lt;/h2&gt;
          &lt;p&gt;Temperature: {weather.main.temp}°C&lt;/p&gt;
          &lt;p&gt;Weather: {weather.weather[0].description}&lt;/p&gt;
        &lt;/div&gt;
      )}
    &lt;/div&gt;
  )
}

export default Weather;</pre><p>・App.css</p><pre class="crayon-plain-tag">body {
  font-family: Arial, sans-serif;
  background-color: #f4f4f9;
  color: #333;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  height: 100vh;
}

h1 {
  color: #4CAF50;
}

input[type="text"] {
  padding: 10px;
  margin: 20px 0;
  border: 1px solid #ddd;
  border-radius: 5px;
  width: 200px;
}

button {
  padding: 10px 20px;
  background-color: #4CAF50;
  color: white;
  border: none;
  border-radius: 5px;
  cursor: pointer;
}

button:hover {
  background-color: #45a049;
}

.weather-info {
  background: #e7f5e8;
  margin: 20px 0;
  padding: 20px;
  border-radius: 5px;
  width: 300px;
  text-align: center;
}

.weather-info h2 {
  margin: 0 0 10px 0;
}

.error {
  color: red;
  margin: 20px 0;
}
</pre><p><img data-attachment-id="2511" data-permalink="https://mintaku-blog.net/react-vison-api/https___qiita-image-store-s3-ap-northeast-1-amazonaws-com_0_242816_5ae4403f-6fd6-48aa-5f3a-25dade6647d9_11zon/" data-orig-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/06/https___qiita-image-store.s3.ap-northeast-1.amazonaws.com_0_242816_5ae4403f-6fd6-48aa-5f3a-25dade6647d9_11zon.jpg?fit=644%2C594&amp;ssl=1" data-orig-size="644,594" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="https___qiita-image-store.s3.ap-northeast-1.amazonaws.com_0_242816_5ae4403f-6fd6-48aa-5f3a-25dade6647d9_11zon" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/06/https___qiita-image-store.s3.ap-northeast-1.amazonaws.com_0_242816_5ae4403f-6fd6-48aa-5f3a-25dade6647d9_11zon.jpg?fit=300%2C277&amp;ssl=1" data-large-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/06/https___qiita-image-store.s3.ap-northeast-1.amazonaws.com_0_242816_5ae4403f-6fd6-48aa-5f3a-25dade6647d9_11zon.jpg?fit=644%2C594&amp;ssl=1" loading="lazy" src="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/06/https___qiita-image-store.s3.ap-northeast-1.amazonaws.com_0_242816_5ae4403f-6fd6-48aa-5f3a-25dade6647d9_11zon.jpg?resize=300%2C277&#038;ssl=1" alt="" width="300" height="277" class="alignnone size-medium wp-image-2511" srcset="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/06/https___qiita-image-store.s3.ap-northeast-1.amazonaws.com_0_242816_5ae4403f-6fd6-48aa-5f3a-25dade6647d9_11zon.jpg?resize=300%2C277&amp;ssl=1 300w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/06/https___qiita-image-store.s3.ap-northeast-1.amazonaws.com_0_242816_5ae4403f-6fd6-48aa-5f3a-25dade6647d9_11zon.jpg?w=644&amp;ssl=1 644w" sizes="(max-width: 300px) 100vw, 300px" data-recalc-dims="1" /></p>
<p>ローカルの画面を見ると、良い感じのデザインになりました。</p>
<p>&nbsp;</p>
<h2>おわりに</h2>
<p>今回はOpenWeatherMap APIを利用して簡単な天気アプリを実装しました。</p>
<p>天気アプリなどは既存のサービスで充実していますが、実際に自分で作ることでよりサービス作りの解像度を上げることができれば幸いです。</p>The post <a href="https://mintaku-blog.net/react-weather-api/">【ハンズオン】ReactとOpenWeatherMap APIで天気アプリを作ろう【TypeScript】</a> first appeared on <a href="https://mintaku-blog.net">みんたく</a>.]]></content:encoded>
					
					<wfw:commentRss>https://mintaku-blog.net/react-weather-api/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">2505</post-id>	</item>
		<item>
		<title>【ハンズオン】ReactとGoogle Cloud Vision APIで画像認識アプリを作ろう【TypeScript】</title>
		<link>https://mintaku-blog.net/react-vison-api/</link>
					<comments>https://mintaku-blog.net/react-vison-api/#respond</comments>
		
		<dc:creator><![CDATA[みんたく]]></dc:creator>
		<pubDate>Mon, 03 Jun 2024 06:34:22 +0000</pubDate>
				<category><![CDATA[React]]></category>
		<guid isPermaLink="false">https://mintaku-blog.net/?p=2491</guid>

					<description><![CDATA[<p>はじめに Google Cloud Vision APIは、Googleが提供する機械学習を活用した画像解析サービスです。 このAPIを使用することで、開発 …</p>
The post <a href="https://mintaku-blog.net/react-vison-api/">【ハンズオン】ReactとGoogle Cloud Vision APIで画像認識アプリを作ろう【TypeScript】</a> first appeared on <a href="https://mintaku-blog.net">みんたく</a>.]]></description>
										<content:encoded><![CDATA[<h2>はじめに</h2>
<p>Google Cloud Vision APIは、Googleが提供する機械学習を活用した画像解析サービスです。</p>
<p>このAPIを使用することで、開発者は画像に含まれる情報を自動的に抽出し、様々な解析を行うことができます。</p>
<p>今回Reactを使ってGoogle Cloud Vision APIを使って簡単な画像認識アプリを作ってみました。</p>
<h2>1. 環境構築</h2>
<p>Node環境があることを前提にして行います。</p>
<p>templateはtypescriptを指定して、Google Cloud Vision APIをコールするためにaxiosをインストールしておきます。</p>
<p>npm startしてReactの初期画面が表示されれば成功です。</p><pre class="crayon-plain-tag">$ npx create-react-app google-vision-app --template typescript
$ cd google-vision-app
$ npm i axios
$ npm start</pre><p>&nbsp;</p>
<h2>2. Cloud Vision APIの設定</h2>
<p>Cloud Vision APIはGCPの管理画面の検索窓から「Cloud Vision API」を実行し、有効化されていない場合は「有効化」するを押します。</p>
<p><img data-attachment-id="2493" data-permalink="https://mintaku-blog.net/react-vison-api/%e3%82%b9%e3%82%af%e3%83%aa%e3%83%bc%e3%83%b3%e3%82%b7%e3%83%a7%e3%83%83%e3%83%88-2024-05-25-17-03-45/" data-orig-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/05/c57f7b839ee6785b778b5a34211b5ca0.png?fit=1948%2C840&amp;ssl=1" data-orig-size="1948,840" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="スクリーンショット 2024-05-25 17.03.45" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/05/c57f7b839ee6785b778b5a34211b5ca0.png?fit=300%2C129&amp;ssl=1" data-large-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/05/c57f7b839ee6785b778b5a34211b5ca0.png?fit=800%2C345&amp;ssl=1" loading="lazy" src="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/05/c57f7b839ee6785b778b5a34211b5ca0.png?resize=300%2C129&#038;ssl=1" alt="" width="300" height="129" class="alignnone size-medium wp-image-2493" srcset="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/05/c57f7b839ee6785b778b5a34211b5ca0.png?resize=300%2C129&amp;ssl=1 300w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/05/c57f7b839ee6785b778b5a34211b5ca0.png?resize=1024%2C442&amp;ssl=1 1024w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/05/c57f7b839ee6785b778b5a34211b5ca0.png?resize=768%2C331&amp;ssl=1 768w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/05/c57f7b839ee6785b778b5a34211b5ca0.png?resize=1536%2C662&amp;ssl=1 1536w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/05/c57f7b839ee6785b778b5a34211b5ca0.png?w=1948&amp;ssl=1 1948w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/05/c57f7b839ee6785b778b5a34211b5ca0.png?w=1600&amp;ssl=1 1600w" sizes="(max-width: 300px) 100vw, 300px" data-recalc-dims="1" /></p>
<p>「このAPIを使用するには、認証情報が必要になる可能性があります」と表示された場合は、「認証情報」から「認証情報を作成」します。作成後、APIキーが表示されるのでコピーしておきます。</p>
<h2>3. 環境変数の設定</h2>
<p>作成したAPIキーを.envファイルを作成し、環境変数を設定します。</p>
<p>creare-react-appで作ったプロジェクトはprocess.env.REACT_APP_から始まる変数を読み込みができるようになっています。</p>
<p>・.env</p>
<div>
<div>
<pre class="crayon-plain-tag">REACT_APP_GOOGLE_CLOUD_VISION_API_KEY=xxxxxxxxxxxx
</pre>
</div>
</div>
<h2>4. Cloud Vision APIを呼び出す</h2>
<p>Cloud Vision APIのキーを設定できたら、実際にCloud Vision APIを呼び出して結果を表示するようにします。</p>
<p>流れとしては、画像をアップロードしたら、Cloud Vision APIを呼び出して、レスポンスを整形して画面に表示するといった感じです。</p>
<p>・App.tsx</p><pre class="crayon-plain-tag">import React, { useState } from 'react';
import axios from 'axios';

interface LabelAnnotation {
  description: string;
  score: number;
}

const App: React.FC = () =&gt; {
  const [selectedFile, setSelectedFile] = useState&lt;File | null&gt;(null);
  const [labels, setLabels] = useState&lt;LabelAnnotation[]&gt;([]);

  const handleFileChange = (event: React.ChangeEvent&lt;HTMLInputElement&gt;) =&gt; {
    if (event.target.files &amp;&amp; event.target.files.length &gt; 0) {
      setSelectedFile(event.target.files[0]);
    }
  };

  const handleFileUpload = async () =&gt; {
    if (!selectedFile) return;

    const reader = new FileReader();
    reader.readAsDataURL(selectedFile);
    reader.onloadend = async () =&gt; {
      const base64String = reader.result?.toString().replace(/^data:image\/[a-z]+;base64,/, "");

      try {
        const apiKey = process.env.REACT_APP_GOOGLE_CLOUD_VISION_API_KEY;
        const response = await axios.post(
          `https://vision.googleapis.com/v1/images:annotate?key=${apiKey}`,
          {
            requests: [
              {
                image: {
                  content: base64String,
                },
                features: [
                  {
                    type: 'LABEL_DETECTION',
                    maxResults: 10,
                  },
                ],
              },
            ],
          }
        );

        const labelAnnotations = response.data.responses[0].labelAnnotations;
        const labels: LabelAnnotation[] = labelAnnotations.map((annotation: {description: string, score: string}) =&gt; ({
          description: annotation.description,
          score: annotation.score,
        }));

        setLabels(labels);
      } catch (error) {
        console.error('Error uploading file:', error);
      }
    };
  };

  return (
    &lt;div className="App"&gt;
      &lt;h1&gt;Google Cloud Vision API Demo&lt;/h1&gt;
      &lt;input type="file" onChange={handleFileChange} /&gt;
      &lt;button onClick={handleFileUpload}&gt;アップロードして解析&lt;/button&gt;
      {labels.length &gt; 0 &amp;&amp; (
        &lt;div&gt;
          &lt;h2&gt;解析結果:&lt;/h2&gt;
          &lt;table&gt;
            &lt;thead&gt;
              &lt;tr&gt;
                &lt;th&gt;ラベル&lt;/th&gt;
                &lt;th&gt;スコア&lt;/th&gt;
              &lt;/tr&gt;
            &lt;/thead&gt;
            &lt;tbody&gt;
              {labels.map((label, index) =&gt; (
                &lt;tr key={index}&gt;
                  &lt;td&gt;{label.description}&lt;/td&gt;
                  &lt;td&gt;{label.score.toFixed(2)}&lt;/td&gt;
                &lt;/tr&gt;
              ))}
            &lt;/tbody&gt;
          &lt;/table&gt;
        &lt;/div&gt;
      )}
    &lt;/div&gt;
  );
};

export default App;
</pre><p>&nbsp;</p>
<h2>5. 画像認識アプリを動かす</h2>
<p><img data-attachment-id="2494" data-permalink="https://mintaku-blog.net/react-vison-api/takenoko/" data-orig-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/05/takenoko.jpg?fit=275%2C183&amp;ssl=1" data-orig-size="275,183" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="takenoko" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/05/takenoko.jpg?fit=275%2C183&amp;ssl=1" data-large-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/05/takenoko.jpg?fit=275%2C183&amp;ssl=1" loading="lazy" src="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/05/takenoko.jpg?resize=275%2C183&#038;ssl=1" alt="" width="275" height="183" class="alignnone size-full wp-image-2494" data-recalc-dims="1" /></p>
<p>では実際に動かしてみます。適当に画像を用意して、実行してみます。</p>
<p>今回はたけのこの画像を用意して、アップロードして解析してみました。結果は以下の通りです。</p>
<p><img data-attachment-id="2492" data-permalink="https://mintaku-blog.net/react-vison-api/%e3%82%b9%e3%82%af%e3%83%aa%e3%83%bc%e3%83%b3%e3%82%b7%e3%83%a7%e3%83%83%e3%83%88-2024-05-25-17-07-08/" data-orig-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/05/ae76fe338d0e8d86ef652a5bb9e46917.png?fit=916%2C832&amp;ssl=1" data-orig-size="916,832" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="スクリーンショット 2024-05-25 17.07.08" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/05/ae76fe338d0e8d86ef652a5bb9e46917.png?fit=300%2C272&amp;ssl=1" data-large-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/05/ae76fe338d0e8d86ef652a5bb9e46917.png?fit=800%2C727&amp;ssl=1" loading="lazy" src="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/05/ae76fe338d0e8d86ef652a5bb9e46917.png?resize=300%2C272&#038;ssl=1" alt="" width="300" height="272" class="alignnone size-medium wp-image-2492" srcset="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/05/ae76fe338d0e8d86ef652a5bb9e46917.png?resize=300%2C272&amp;ssl=1 300w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/05/ae76fe338d0e8d86ef652a5bb9e46917.png?resize=768%2C698&amp;ssl=1 768w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/05/ae76fe338d0e8d86ef652a5bb9e46917.png?w=916&amp;ssl=1 916w" sizes="(max-width: 300px) 100vw, 300px" data-recalc-dims="1" /></p>
<p>食べ物や植物に対して高いスコアが出ています。他も材料や自然な食べ物、主食などわりと的確にラベリングできていると思います。</p>
<p>&nbsp;</p>
<h2>6. スタイルを整える</h2>
<p>最後にスタイルを整えます。特にこだわりがないようであれば、そのままコピペして使用して大丈夫です。</p>
<div>
<div>
<pre class="crayon-plain-tag">.App {
  text-align: center;
  font-family: 'Arial', sans-serif;
  margin: 20px;
}

h1 {
  color: #333;
  font-size: 2em;
  margin-bottom: 20px;
}

input[type="file"] {
  display: block;
  margin: 20px auto;
  padding: 10px;
  font-size: 1em;
}

button {
  background-color: #4CAF50;
  color: white;
  border: none;
  padding: 10px 20px;
  font-size: 1em;
  cursor: pointer;
  border-radius: 5px;
}

button:hover {
  background-color: #45a049;
}

table {
  margin: 20px auto;
  border-collapse: collapse;
  width: 80%;
  max-width: 600px;
}

table th, table td {
  border: 1px solid #ddd;
  padding: 8px;
}

table th {
  background-color: #f2f2f2;
  color: #333;
  text-align: left;
}

table tr:nth-child(even) {
  background-color: #f9f9f9;
}

table tr:hover {
  background-color: #ddd;
}

table th, table td {
  text-align: left;
}
</pre><br />
CSSを書いたら、App.tsxに以下のimportを追加して完了です。<br />
<pre class="crayon-plain-tag">import './App.css';</pre>
</div>
</div>
<div>今度はラーメンの画像で実行してみます。</div>
<div><img data-attachment-id="2497" data-permalink="https://mintaku-blog.net/react-vison-api/ramen/" data-orig-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/05/ramen.jpg?fit=540%2C360&amp;ssl=1" data-orig-size="540,360" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="ramen" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/05/ramen.jpg?fit=300%2C200&amp;ssl=1" data-large-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/05/ramen.jpg?fit=540%2C360&amp;ssl=1" loading="lazy" src="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/05/ramen.jpg?resize=300%2C200&#038;ssl=1" alt="" width="300" height="200" class="alignnone size-medium wp-image-2497" srcset="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/05/ramen.jpg?resize=300%2C200&amp;ssl=1 300w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/05/ramen.jpg?w=540&amp;ssl=1 540w" sizes="(max-width: 300px) 100vw, 300px" data-recalc-dims="1" /></div>
<div> <img data-attachment-id="2496" data-permalink="https://mintaku-blog.net/react-vison-api/%e3%82%b9%e3%82%af%e3%83%aa%e3%83%bc%e3%83%b3%e3%82%b7%e3%83%a7%e3%83%83%e3%83%88-2024-05-25-22-06-39/" data-orig-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/05/b4e555f1787147d6dc6a484cfc22ee6d.png?fit=1548%2C1372&amp;ssl=1" data-orig-size="1548,1372" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="スクリーンショット 2024-05-25 22.06.39" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/05/b4e555f1787147d6dc6a484cfc22ee6d.png?fit=300%2C266&amp;ssl=1" data-large-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/05/b4e555f1787147d6dc6a484cfc22ee6d.png?fit=800%2C709&amp;ssl=1" loading="lazy" src="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/05/b4e555f1787147d6dc6a484cfc22ee6d.png?resize=300%2C266&#038;ssl=1" alt="" width="300" height="266" class="alignnone size-medium wp-image-2496" srcset="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/05/b4e555f1787147d6dc6a484cfc22ee6d.png?resize=300%2C266&amp;ssl=1 300w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/05/b4e555f1787147d6dc6a484cfc22ee6d.png?resize=1024%2C908&amp;ssl=1 1024w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/05/b4e555f1787147d6dc6a484cfc22ee6d.png?resize=768%2C681&amp;ssl=1 768w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/05/b4e555f1787147d6dc6a484cfc22ee6d.png?resize=1536%2C1361&amp;ssl=1 1536w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/05/b4e555f1787147d6dc6a484cfc22ee6d.png?w=1548&amp;ssl=1 1548w" sizes="(max-width: 300px) 100vw, 300px" data-recalc-dims="1" /></div>
<p>ローカルの画面を見ると、良い感じのデザインになりました。あとは画像のプレビューやラベルの日本語化などしても良いかもしれません。</p>
<h2>おわりに</h2>
<p>今回はCloud Vision APIを利用して簡単な画像認識アプリを実装しました。</p>
<p>画像認識してラベリングした結果をDBに保存するなどするとより実用的なアプリになるかと思います。</p>The post <a href="https://mintaku-blog.net/react-vison-api/">【ハンズオン】ReactとGoogle Cloud Vision APIで画像認識アプリを作ろう【TypeScript】</a> first appeared on <a href="https://mintaku-blog.net">みんたく</a>.]]></content:encoded>
					
					<wfw:commentRss>https://mintaku-blog.net/react-vison-api/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">2491</post-id>	</item>
		<item>
		<title>Gemini API × Reactでサクッと簡単なアプリを作ってみた</title>
		<link>https://mintaku-blog.net/react-gemini/</link>
					<comments>https://mintaku-blog.net/react-gemini/#respond</comments>
		
		<dc:creator><![CDATA[みんたく]]></dc:creator>
		<pubDate>Sun, 12 May 2024 12:58:40 +0000</pubDate>
				<category><![CDATA[React]]></category>
		<guid isPermaLink="false">https://mintaku-blog.net/?p=2475</guid>

					<description><![CDATA[<p>Gemini API × Reactで簡単なチャットボット的なアプリを作ってみました。 Reactアプリの環境構築をする create-react-appで …</p>
The post <a href="https://mintaku-blog.net/react-gemini/">Gemini API × Reactでサクッと簡単なアプリを作ってみた</a> first appeared on <a href="https://mintaku-blog.net">みんたく</a>.]]></description>
										<content:encoded><![CDATA[<p>Gemini API × Reactで簡単なチャットボット的なアプリを作ってみました。</p>
<h2>Reactアプリの環境構築をする</h2>
<p>create-react-appでReactのアプリの環境を構築します。typescriptを利用できるように&#8211;templateでtypescriptを指定します。</p><pre class="crayon-plain-tag">$ npx create-react-app gemini-chat-bot --template typescript</pre><p>Gemini APIを利用する際に必要なaxiosのライブラリをインストールしておきます。</p><pre class="crayon-plain-tag">$ npm install axios</pre><p>&nbsp;</p>
<h2>Gemini APIからAPI Keyを発行する</h2>
<p><img data-attachment-id="2477" data-permalink="https://mintaku-blog.net/react-gemini/%e3%82%b9%e3%82%af%e3%83%aa%e3%83%bc%e3%83%b3%e3%82%b7%e3%83%a7%e3%83%83%e3%83%88-2024-05-12-12-09-26/" data-orig-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/05/820a1879aebbd60d6cff3327a25712fb-scaled.jpg?fit=2560%2C1231&amp;ssl=1" data-orig-size="2560,1231" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="スクリーンショット 2024-05-12 12.09.26" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/05/820a1879aebbd60d6cff3327a25712fb-scaled.jpg?fit=300%2C144&amp;ssl=1" data-large-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/05/820a1879aebbd60d6cff3327a25712fb-scaled.jpg?fit=800%2C384&amp;ssl=1" loading="lazy" src="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/05/820a1879aebbd60d6cff3327a25712fb.jpg?resize=800%2C384&#038;ssl=1" alt="" width="800" height="384" class="aligncenter size-large wp-image-2477" srcset="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/05/820a1879aebbd60d6cff3327a25712fb-scaled.jpg?resize=1024%2C492&amp;ssl=1 1024w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/05/820a1879aebbd60d6cff3327a25712fb-scaled.jpg?resize=300%2C144&amp;ssl=1 300w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/05/820a1879aebbd60d6cff3327a25712fb-scaled.jpg?resize=768%2C369&amp;ssl=1 768w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/05/820a1879aebbd60d6cff3327a25712fb-scaled.jpg?resize=1536%2C739&amp;ssl=1 1536w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/05/820a1879aebbd60d6cff3327a25712fb-scaled.jpg?resize=2048%2C985&amp;ssl=1 2048w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/05/820a1879aebbd60d6cff3327a25712fb-scaled.jpg?w=1600&amp;ssl=1 1600w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/05/820a1879aebbd60d6cff3327a25712fb-scaled.jpg?w=2400&amp;ssl=1 2400w" sizes="(max-width: 800px) 100vw, 800px" data-recalc-dims="1" /></p>

<div class="ys-blog-card">
	<div class="ys-blog-card__container">
					<figure class="ys-blog-card__image">
				<img src="https://i0.wp.com/ai.google.dev/static/site-assets/images/share-ais-03.png?w=800&#038;ssl=1" alt="" data-recalc-dims="1">			</figure>
				<div class="ys-blog-card__text">
			<p class="ys-blog-card__title">
				<a class="ys-blog-card__link" href="https://aistudio.google.com/">Google AI Studio</a>
			</p>
							<div class="ys-blog-card__dscr">
					The fastest path from prompt to producti&hellip;				</div>
										<div class="ys-blog-card__domain">aistudio.google.com</div>
					</div>
	</div>
</div>

<p>Google AI Studioにログインし、「Create API Key」からAPI Keyを取得します。Keyは一度のみ表示されるので、コピーして保存しておきましょう。</p>
<p><img data-attachment-id="2481" data-permalink="https://mintaku-blog.net/react-gemini/%e3%82%b9%e3%82%af%e3%83%aa%e3%83%bc%e3%83%b3%e3%82%b7%e3%83%a7%e3%83%83%e3%83%88-2024-05-12-14-46-14/" data-orig-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/05/3a61129c91f0827d93e710da89071a4d.png?fit=1592%2C614&amp;ssl=1" data-orig-size="1592,614" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="スクリーンショット 2024-05-12 14.46.14" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/05/3a61129c91f0827d93e710da89071a4d.png?fit=300%2C116&amp;ssl=1" data-large-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/05/3a61129c91f0827d93e710da89071a4d.png?fit=800%2C309&amp;ssl=1" loading="lazy" src="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/05/3a61129c91f0827d93e710da89071a4d.png?resize=800%2C309&#038;ssl=1" alt="" width="800" height="309" class="aligncenter size-large wp-image-2481" srcset="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/05/3a61129c91f0827d93e710da89071a4d.png?resize=1024%2C395&amp;ssl=1 1024w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/05/3a61129c91f0827d93e710da89071a4d.png?resize=300%2C116&amp;ssl=1 300w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/05/3a61129c91f0827d93e710da89071a4d.png?resize=768%2C296&amp;ssl=1 768w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/05/3a61129c91f0827d93e710da89071a4d.png?resize=1536%2C592&amp;ssl=1 1536w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/05/3a61129c91f0827d93e710da89071a4d.png?w=1592&amp;ssl=1 1592w" sizes="(max-width: 800px) 100vw, 800px" data-recalc-dims="1" /></p>
<p>Keyを保存したら、.envファイルを作成し以下のように環境変数を設定します。</p>
<p>creare-react-appで作ったプロジェクトはprocess.env.REACT_APP_から始まる変数を読み込みができるようになっています。</p><pre class="crayon-plain-tag">REACT_APP_GEMINI_API_KEY=key名</pre><p>&nbsp;</p>
<h2>Gemini APIの疎通確認</h2>
<p><img data-attachment-id="2482" data-permalink="https://mintaku-blog.net/react-gemini/%e3%82%b9%e3%82%af%e3%83%aa%e3%83%bc%e3%83%b3%e3%82%b7%e3%83%a7%e3%83%83%e3%83%88-2024-05-12-14-52-17/" data-orig-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/05/ea45fd34826e106a11550f48be04fe22.png?fit=1546%2C360&amp;ssl=1" data-orig-size="1546,360" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="スクリーンショット 2024-05-12 14.52.17" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/05/ea45fd34826e106a11550f48be04fe22.png?fit=300%2C70&amp;ssl=1" data-large-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/05/ea45fd34826e106a11550f48be04fe22.png?fit=800%2C186&amp;ssl=1" loading="lazy" src="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/05/ea45fd34826e106a11550f48be04fe22.png?resize=800%2C186&#038;ssl=1" alt="" width="800" height="186" class="aligncenter size-large wp-image-2482" srcset="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/05/ea45fd34826e106a11550f48be04fe22.png?resize=1024%2C238&amp;ssl=1 1024w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/05/ea45fd34826e106a11550f48be04fe22.png?resize=300%2C70&amp;ssl=1 300w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/05/ea45fd34826e106a11550f48be04fe22.png?resize=768%2C179&amp;ssl=1 768w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/05/ea45fd34826e106a11550f48be04fe22.png?resize=1536%2C358&amp;ssl=1 1536w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/05/ea45fd34826e106a11550f48be04fe22.png?w=1546&amp;ssl=1 1546w" sizes="(max-width: 800px) 100vw, 800px" data-recalc-dims="1" /></p>
<p>実際にGemini APIとの疎通ができるか確認してみます。POSTする内容はGoogle AI StudioにあるAPI keyの下のcurlコマンドを参考します。</p>
<p>URLのkeyのところは先ほど設定した環境変数であるprocess.env.REACT_APP_GEMINI_API_KEYを指定します。まずは疎通確認のため、送信するテキストを「こんにちは」に固定します。</p><pre class="crayon-plain-tag">const response = await axios.post(
  `https://generativelanguage.googleapis.com/v1beta/models/gemini-pro:generateContent?key=${process.env.REACT_APP_GEMINI_API_KEY}`,
  {
    contents:[
      {
        parts:[
          {
            text: "こんにちは"
          }
        ]
      }
    ]
  },
  {
    headers: {
      "Content-Type": "application/json"
    }
  }
);</pre><p>&nbsp;</p>
<p>これで実行して、コンソールにレスポンスのデータを表示するようにすると以下のように「こんにちは」に対しての回答が格納されていることがわかります。</p>
<p><img data-attachment-id="2483" data-permalink="https://mintaku-blog.net/react-gemini/%e3%82%b9%e3%82%af%e3%83%aa%e3%83%bc%e3%83%b3%e3%82%b7%e3%83%a7%e3%83%83%e3%83%88-2024-05-12-14-58-07/" data-orig-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/05/04aef52d695fcdceb1fa4f288cf77ec9.png?fit=962%2C598&amp;ssl=1" data-orig-size="962,598" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="スクリーンショット 2024-05-12 14.58.07" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/05/04aef52d695fcdceb1fa4f288cf77ec9.png?fit=300%2C186&amp;ssl=1" data-large-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/05/04aef52d695fcdceb1fa4f288cf77ec9.png?fit=800%2C497&amp;ssl=1" loading="lazy" src="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/05/04aef52d695fcdceb1fa4f288cf77ec9.png?resize=800%2C497&#038;ssl=1" alt="" width="800" height="497" class="aligncenter size-full wp-image-2483" srcset="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/05/04aef52d695fcdceb1fa4f288cf77ec9.png?w=962&amp;ssl=1 962w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/05/04aef52d695fcdceb1fa4f288cf77ec9.png?resize=300%2C186&amp;ssl=1 300w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/05/04aef52d695fcdceb1fa4f288cf77ec9.png?resize=768%2C477&amp;ssl=1 768w" sizes="(max-width: 800px) 100vw, 800px" data-recalc-dims="1" /></p>
<p>&nbsp;</p>
<h2>テキストを入力したらGemini APIを通して返ってくるようにする</h2>
<p>現状だと、「こんにちは」の固定文字になっているため、ユーザーが入力したテキストを送れるようにします。</p>
<p>HTMLにinputタグを追加し、onChangeで入力したらinputTextに値を格納するようにします。その流れで処理を実行している間はisLoading変数をtrueにして「現在、問い合わせ中です&#8230;」という文言を表示するようにしました。</p>
<p>また、react-markdownライブラリをインストールして、レスポンスデータをHTMLに変換して、表示するようにしました。</p><pre class="crayon-plain-tag">import axios from "axios";
import { useState } from "react";
import ReactMarkdown from "react-markdown";
import styles from "./styles.module.css"

const Chat = () =&gt; {
  const [responseMessage, setResponseMessage] = useState&lt;string&gt;("");
  const [inputText, setInputText] = useState&lt;string&gt;("");
  const [isLoading, setIsLoading] = useState&lt;boolean&gt;(false);
  const sendMessage = async () =&gt; {
    setIsLoading(true);
    try {
      const response = await axios.post(
        `https://generativelanguage.googleapis.com/v1beta/models/gemini-pro:generateContent?key=${process.env.REACT_APP_GEMINI_API_KEY}`,
        {
          contents:[
            {
              parts:[
                {
                  text: inputText
                }
              ]
            }
          ]
        },
        {
          headers: {
            "Content-Type": "application/json"
          }
        }
      );
      
      const responseContent = response.data.candidates[0].content;
      const responseParts = responseContent.parts.map((part: { text: string }) =&gt; part.text).join("\n");
      setResponseMessage(responseParts);
      setIsLoading(false);
    } catch (error) {
      console.error(`Gemini API has error occured: ${error}`);
    }
  };

  return (
    &lt;div&gt;
      &lt;h1&gt;Gemini Chat Bot&lt;/h1&gt;
      &lt;div&gt;
        { isLoading ? &lt;p&gt;現在、問い合わせ中です...&lt;/p&gt; : null}
        &lt;ReactMarkdown&gt;{responseMessage}&lt;/ReactMarkdown&gt;
      &lt;/div&gt;
      &lt;div&gt;
        &lt;input
          type="input"
          value={inputText}
          onChange={(e) =&gt; setInputText(e.target.value)}
        /&gt;
        &lt;button onClick={sendMessage}&gt;送信&lt;/button&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  )
}

export default Chat;</pre><p>&nbsp;</p>
<p>上記を実装すると、こんな感じになりました。</p>
<p><img data-attachment-id="2484" data-permalink="https://mintaku-blog.net/react-gemini/%e3%82%b9%e3%82%af%e3%83%aa%e3%83%bc%e3%83%b3%e3%82%b7%e3%83%a7%e3%83%83%e3%83%88-2024-05-12-20-02-41/" data-orig-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/05/ba80935d7b34187bcdd330feb91e6d06.png?fit=2284%2C854&amp;ssl=1" data-orig-size="2284,854" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="スクリーンショット 2024-05-12 20.02.41" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/05/ba80935d7b34187bcdd330feb91e6d06.png?fit=300%2C112&amp;ssl=1" data-large-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/05/ba80935d7b34187bcdd330feb91e6d06.png?fit=800%2C299&amp;ssl=1" loading="lazy" src="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/05/ba80935d7b34187bcdd330feb91e6d06.png?resize=800%2C299&#038;ssl=1" alt="" width="800" height="299" class="aligncenter size-large wp-image-2484" srcset="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/05/ba80935d7b34187bcdd330feb91e6d06.png?resize=1024%2C383&amp;ssl=1 1024w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/05/ba80935d7b34187bcdd330feb91e6d06.png?resize=300%2C112&amp;ssl=1 300w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/05/ba80935d7b34187bcdd330feb91e6d06.png?resize=768%2C287&amp;ssl=1 768w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/05/ba80935d7b34187bcdd330feb91e6d06.png?resize=1536%2C574&amp;ssl=1 1536w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/05/ba80935d7b34187bcdd330feb91e6d06.png?resize=2048%2C766&amp;ssl=1 2048w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/05/ba80935d7b34187bcdd330feb91e6d06.png?w=1600&amp;ssl=1 1600w" sizes="(max-width: 800px) 100vw, 800px" data-recalc-dims="1" /></p>
<h2>デザインをあてて完成</h2>
<p>最後にデザインを当てます。せっかくなのでGeminiを使ってみました。</p>
<p>GeminiにHTMLのコードを渡して、チャット形式の良い感じのCSSを当ててと聞いて反映させると以下のようになりました。</p>
<p><img data-attachment-id="2480" data-permalink="https://mintaku-blog.net/react-gemini/%e3%82%b9%e3%82%af%e3%83%aa%e3%83%bc%e3%83%b3%e3%82%b7%e3%83%a7%e3%83%83%e3%83%88-2024-05-12-14-39-35/" data-orig-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/05/7b5a968666661dfdf153080de5173a58.png?fit=1216%2C954&amp;ssl=1" data-orig-size="1216,954" data-comments-opened="1" data-image-meta="{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}" data-image-title="スクリーンショット 2024-05-12 14.39.35" data-image-description="" data-image-caption="" data-medium-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/05/7b5a968666661dfdf153080de5173a58.png?fit=300%2C235&amp;ssl=1" data-large-file="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/05/7b5a968666661dfdf153080de5173a58.png?fit=800%2C627&amp;ssl=1" loading="lazy" src="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/05/7b5a968666661dfdf153080de5173a58.png?resize=800%2C627&#038;ssl=1" alt="" width="800" height="627" class="aligncenter size-large wp-image-2480" srcset="https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/05/7b5a968666661dfdf153080de5173a58.png?resize=1024%2C803&amp;ssl=1 1024w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/05/7b5a968666661dfdf153080de5173a58.png?resize=300%2C235&amp;ssl=1 300w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/05/7b5a968666661dfdf153080de5173a58.png?resize=768%2C603&amp;ssl=1 768w, https://i0.wp.com/mintaku-blog.net/mintaku/wp-content/uploads/2024/05/7b5a968666661dfdf153080de5173a58.png?w=1216&amp;ssl=1 1216w" sizes="(max-width: 800px) 100vw, 800px" data-recalc-dims="1" /></p>The post <a href="https://mintaku-blog.net/react-gemini/">Gemini API × Reactでサクッと簡単なアプリを作ってみた</a> first appeared on <a href="https://mintaku-blog.net">みんたく</a>.]]></content:encoded>
					
					<wfw:commentRss>https://mintaku-blog.net/react-gemini/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">2475</post-id>	</item>
		<item>
		<title>Reactに入門する</title>
		<link>https://mintaku-blog.net/react-introduction/</link>
					<comments>https://mintaku-blog.net/react-introduction/#respond</comments>
		
		<dc:creator><![CDATA[みんたく]]></dc:creator>
		<pubDate>Tue, 20 Dec 2022 05:26:37 +0000</pubDate>
				<category><![CDATA[React]]></category>
		<guid isPermaLink="false">https://mintaku-blog.net/?p=2303</guid>

					<description><![CDATA[<p>最近少しづつReactに触る機会が増えてきたので、改めてReactについて学んだ内容をまとめます。 React ReactはMeta(旧Facebook)が …</p>
The post <a href="https://mintaku-blog.net/react-introduction/">Reactに入門する</a> first appeared on <a href="https://mintaku-blog.net">みんたく</a>.]]></description>
										<content:encoded><![CDATA[<p>最近少しづつReactに触る機会が増えてきたので、改めてReactについて学んだ内容をまとめます。</p>
<h2>React</h2>
<p>ReactはMeta(旧Facebook)が作成したライブラリで、UIを作るためのコンポーネントという概念が特徴的です。</p>
<p>Reactには様々な機能が備わっていますが、そのほとんどがUI部分の構築をサポートする機能であるため、フレームワークではなくライブラリに分類されます。</p>
<p>2013年にオープンソース化された後は、Metaを中心として各種開発コミュニティによって維持管理されています。</p>
<p>&nbsp;</p>
<h2>JSX</h2>
<p>Reactでは、React要素を作成するためにReact APIのReact.createElementを利用していますが、JSXを使うとReact要素をより簡単に生成することができます。</p>
<p>JSXはJavaScriptの拡張言語で、JavaScriptとHTMLを掛け合わせて使えるようにしたものです。JSXは最終的にReact要素を生成します。</p>
<ol>
<li>JSX→React.createElementの式に変換</li>
<li>React要素を生成</li>
</ol>
<p>JSXは階層構造で、最上位コンポーネントは並列にできません。不要なdivタグを使いたくない場合などは、HTMLタグとして出力されないReact.Fragmentを使います。</p><pre class="crayon-plain-tag">&lt;React.Fragment&gt;
  &lt;div&gt;&lt;/div&gt;
  &lt;div&gt;&lt;/div&gt;
&lt;/React.Fragment&gt;

//省略した場合
&lt;&gt;
  &lt;div&gt;&lt;/div&gt;
  &lt;div&gt;&lt;/div&gt;
&lt;/&gt;</pre><p>&nbsp;</p>
<h2>コンポーネント</h2>
<p>Reactのコンポーネントは、見た目と機能を持つUI部品のようなものです。</p>
<p>コンポーネントを使うことで、コードの再利用性が高まります。また、コンポーネントごとにファイルを分けることでコードの見通しがよくなり、コードの修正に対しても変更しやすくなります。</p>
<p>Reactで扱えるコンポーネントは、クラスコンポーネントと関数コンポーネントの2種類です。</p>
<h3>クラスコンポーネント</h3>
<p>クラスコンポーネントは、React.Componentを継承したクラスによって定義されます。stateやライフサイクルなどを最初から持っている点が特徴です。</p><pre class="crayon-plain-tag">import React from 'react';

class Button extends React.Component {
  render() {
　   return (
　     &lt;button&gt;テストボタン&lt;/button&gt;
　　　  ); 　
  　　} 　
　}

export default Button;</pre><p></p>
<h3>関数コンポーネント</h3>
<p>2020年にバージョン16.7でReact Hooksが登場したことで、クラスコンポーネントと同様の機能を関数コンポーネントが使えるようになりました。クラスコンポーネントと異なり、記述量を少なく書くことができ、現在こちらが主流となっています。</p><pre class="crayon-plain-tag">function Button() {
  return (
    &lt;button&gt;テストボタン&lt;/button&gt;
  );
}

export default Button;</pre><p>&nbsp;</p>
<h3>default export</h3>
<p>推奨されるexport方法で、1ファイル1exportです。アロー関数と名前付き関数のdefault exportがあります。</p><pre class="crayon-plain-tag">// アロー関数のdefault export
const Button = (props) =&gt; {
  return &lt;button&gt;テストボタン&lt;/button&gt;
}
export default Button;</pre><p></p><pre class="crayon-plain-tag">// 名前付き関数のdefault export
export default function Button = (props) =&gt; {
  return &lt;button&gt;テストボタン&lt;/button&gt;
}</pre><p></p>
<h3>名前付きexport</h3>
<p>1ファイルから複数モジュールをexportしたいときは、Reactではエントリポイントでよく使います。</p><pre class="crayon-plain-tag">export {default as Button} from './Button'
export {default as Link} from './Link'</pre><p>基本的にReactのコンポーネントはdefault exportしており、Button.jsxだとしても中身のコンポーネントはdefault exportされているので、defaultという名前でexportされています。</p>
<p>読み込み時もdefaultという名前でインポートされ、エントリポイントは様々なdefault exportされたコンポーネントをひとまとめにしているため、同じdefaultだと被ってしまうので、asを使って別名をつける必要があります。</p>
<p>&nbsp;</p>
<h2>React Hooks</h2>
<p>クラスコンポーネントでしか使えなかった、コンポーネント内で状態管理する「state」、コンポーネントの時間の流れに基づく「ライフサイクル」がReact Hooksを使うことで関数コンポーネントでも使えるようになりました。</p>
<p>つまり、React Hooksの登場によりクラスコンポーネントでしかできないことがなくなりました。</p>
<h2>state</h2>
<p>Reactのコンポーネントは、stateというアプリケーションの状態を表すためのデータを持っています。stateによって、コンポーネントをどのようにレンダリングするかを指定することが可能です。</p>
<p>コンポーネント内の要素をDOMで直接書き換えるのは仮想DOMの良さが失われ、パフォーマンスが落ちるため、stateを使って新しい値を使って再レンダリングさせる必要があるためです。</p>
<p>コンポーネントは、stateが変更された時とpropsが変更された時に再レンダリングされます。</p>
<p>useState関数を呼び出すと、各コンポーネントのstateを作ることが可能です。コンポーネントに格納されたstateをsetState関数で変更すると、コンポーネントを自動で更新することが可能です。</p>
<p>&nbsp;</p>
<h2>ライフサイクル</h2>
<p>ライフサイクルとは、コンポーネントが生まれてから削除されるまでの時間の流れです。ライフサイクルメソッドを使うと、その時点に応じた処理を実行できます。</p>
<p>クラスコンポーネント時代ではcomponentDidMount()、componentDidUpdate()、componentWillUnmount()を使っていたが、Hooks時代ではuseEffectを使ってライフサイクルを表現しています。</p>
<p>3種類のライフサイクル</p>
<ol>
<li>Mouting コンポーネントが生成される期間</li>
<li>Updating コンポーネントが変更される期間</li>
<li>Unmounting コンポーネントが削除される期間</li>
</ol>
<p>useEffectではレンダリングによって引き起こされる処理をフックにして使います。useEffectに第二引数には配列を渡すことが可能で、配列に指定した値が変更された時に呼び出されます。</p><pre class="crayon-plain-tag">useEffect(() =&gt; {
  console.log(‘test’)
}, [trigger])</pre><p></p>
<h3>クリーンアップ</h3>
<p>クリーンアップとはイベントリスナの削除、タイマーのキャンセルなどのことです。クリーンアップ関数をreturnすると、2度目以降のレンダリング時に前回のuseEffectを消すことができます。</p>
<p>クリーンアップは、コンポーネントがUnmountされる時に実行されますが、次のrender前にも実行されます。</p>The post <a href="https://mintaku-blog.net/react-introduction/">Reactに入門する</a> first appeared on <a href="https://mintaku-blog.net">みんたく</a>.]]></content:encoded>
					
					<wfw:commentRss>https://mintaku-blog.net/react-introduction/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">2303</post-id>	</item>
	</channel>
</rss>
