概要
React+TypeScriptで簡単な住所検索アプリを作成する。今回はAPIの仕組み、そしてそれをreactで操作する方法を知りたいだけなので、cssは適応しない。
開発環境
Mac OS Sonoma
react-dom@18.2.0
Reactアプリの作成
アプリのおおまかなイメージ
まず以下の写真が完成例だ。
一番上がタイトルを表示するTitle、真ん中が入力欄であるForm、検索結果を表示するResultsとなっていてこれらを含む最上位のコンポーネントがAppである。このようにReactではコンポーネントという概念を元にアプリを作成する。
そしておおまかな仕組みとしては
①入力された文字列を取得
②文字列を使用してAPIへリクエストを送る
③レスポンスを検索結果欄に表示する
ここまででおおまかにアプリのイメージがつかめたかと思うので次はまず開発に向けて事前準備をする。
事前準備
以下のコードをターミナルに入力してreactアプリを作成する。これはReactアプリを作成する時のテンプレートのようなフォルダを作成してくれるコマンドである。今回はjsではなくtsで開発するのでコマンドの最後に--templete typescriptを追加した。
npx create-react-app my-app --template typescript
そして作成したフォルダをVSCodeで開き、不要なファイルを削除、必要なファイルを追加する。まず、今回の開発で不要な
App.test.tsx
index.css
logo.svg
reportWebVitals.ts
SetupTest.ts
をsrcフォルダから削除する。そしてsrcフォルダの中にcomponentsフォルダを作成し、そこへ以下のファイルを新規作成する。
Form.tsx
Results.tsx
Title.tsx
これが先ほどの述べたコンポーネントになる。さらにApp.cssは一旦書いてあるコードを消しておこう。
するとフォルダの構成が以下の写真のようになっているはずである。
そしてここからファイルの中のコードも整理していく。まずはApp.cssに書かれているコードを全て削除する。
そしてApp.tsxは以下のコードだけ残す。
import './App.css';
function App() {
return (
<div className="App">
</div>
);
}
export default App;
index.tsxは以下のコードのみ残す。
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
これで準備が完了した。
開発手順
Titleの開発
まずはtitle.tsxの開発を進めていきながらReactの仕組みについて理解していく。
以下のようなコードを打つ。
const Title = () => {
return (
<h1>郵便番号検索</h1>
);
};
export default Title;
これはReactの基本的な型にh1タグで見出しを作ったものになる。constとは一連の処理のことを指し、returnのかっこの中にはHTMLと同じようにタグをつけることができる。だが、これだけではブラウザに表示することはできない。実際にnpm startとターミナルで打っても白紙のままだ。これはなぜかというと、上位のコンポーネントにTitleコンポーネントを置くことができていないからだ。以下の写真のようにApp.tsxへとTitleコンポーネントを渡してあげることで、それがindex.tsxへ渡され、さらにindex.htmlに渡されてブラウザに表示される仕組みである。
わかりやすく図にすると以下のようになる。
index.html
↑
index.tsx
↑
app.tsx
↑
Title.tsx
Reslts.tsx
Form.tsx
APIの仕組み
Reactの仕組みが理解できたところで次はResultsとFormのコンポーネントを作成しつつ、APIについて理解を深めていく。まずはそれぞれのコードの完成形が以下の通り。
// Results.tsx
type ResultsPropsType = {
results:{
adress:string;
}
}
const Results = (props:ResultsPropsType) => {
return (
<div>
{props.results.adress && <div>{props.results.adress}</div>}
</div>
);
};
export default Results;
// Form.tsx
type FormPropsType = {
setAdress:React.Dispatch<React.SetStateAction<string>>;
getAdress:(e: any) => void;
}
const Form = (props:FormPropsType) => {
return (
<form>
<input type="number" name="adress" placeholder="郵便番号" onChange={e => props.setAdress(e.target.value)}/>
{/*onChangeでinputされた内容をsetAdressに送る。setAdress()とすることでcityに情報が書き込まれる*/}
<button type="submit" onClick={props.getAdress}>検索</button>
</form>
);
};
export default Form;
そして今回のAPIの説明ではApp.tsxのコードも必要なので以下の通り。
// App.tsx
import Title from './components/Title';
import Form from './components/Form';
import Results from './components/Results';
import { useState } from "react";
import './App.css';
type ResultsStateType = {
adress:string;
}
function App() {
const [adress,setAdress] = useState<string>("");
const [results,setResults] = useState<ResultsStateType>({
adress:"",
});
const getAdress = (e:any) => {
e.preventDefault();
fetch(`https://zipcloud.ibsnet.co.jp/api/search?zipcode=${adress}`)
.then(res=>res.json())
.then(data=> {
setResults({
adress:data.results[0].address1 + data.results[0].address2 + data.results[0].address3
})
})
}
return (
<div className="test">
<Title />
<Form setAdress={setAdress} getAdress={getAdress}/>
<Results results={results}/>
</div>
);
}
export default App;
ここで理解を深めるためにおさらいをしていく。おおまかな流れとしては以下の通り。
①入力された文字列を取得
②文字列を使用してAPIへリクエストを送る
③レスポンスを検索結果欄に表示する
①入力された文字列を取得
まずは入力された文字列を保有する場所を作ってあげる必要がある。それが
const [adress,setAdress] = useState<string>("");
である。そしてこれはForm.tsxにではなく、App.tsxに記入する必要がある。なぜならReactでは原則としてデータを保管する場所を一つにする必要があるからである。そしてこのデータはpropsを使って他のファイルへと渡してあげることが可能であある。そのデータを他ファイルで使用する際はprops.setAdressのように先頭にpropsをつけてあげればよい。
このようにして作成した、データの保管場所に入力された情報を入れるには以下のコードを打つ。
<input type="number" name="adress" placeholder="郵便番号" onChange={e => props.setAdress(e.target.value)}/>
このコードで重要なのがonChageである。e(イベントパラメータ)には入力された情報が保持されており、さらに入力された文字列はeのtargetのvalueに入っている。これをprops.setAdressに渡すことで、adressにデータが格納される。
②文字列を使用してAPIへリクエストを送る
先ほどadressに格納されたデータ(例:123-456などの郵便番号)を使用してAPIにリクエストを送る。以下のコードで作成したボタンを押すことによってapp.tsxにあるgetAdressという処理が発動し、APIへとリクエストが送られる。
<button type="submit" onClick={props.getAdress}>検索</button>
以下がgetAdressの処理で、まずはfetchを使用してAPIにadress(郵便番号)を渡す。
const getAdress = (e:any) => {
e.preventDefault();
fetch(`https://zipcloud.ibsnet.co.jp/api/search?zipcode=${adress}`)
.then(res=>res.json())
.then(data=> {
setResults({
adress:data.results[0].address1 + data.results[0].address2 + data.results[0].address3
})
})
}
then、つまり日本語で「そして」という意味を持つコードを打ち、その後の処理を決定する。まずは帰ってきた住所データが入っているresをjson形式に変換する必要がある。そして今dataにはjson形式のデータが格納されている。このdataに我々が求めている検索結果(つまり具体的な住所名)を格納したい。そのためには先ほどと同じように検索結果の格納場所を作ってあげる。
const [results,setResults] = useState<ResultsStateType>({
adress:"",
});
そしてsetResultsを使って検索結果をresultsに格納する。しかしこの時、なぜ以下のような形にしないと正しく住所が返ってこないのか。
setResults({
adress:data.results[0].address1 + data.results[0].address2 + data.results[0].address3
})
それはAPIが以下のような形式で情報が格納されており、私たちが求める「県」の情報はaddress1に、「市」の情報はaddress2に、と分かれて格納されているからである。
{
"message": null,
"results": [
{
"address1": "北海道",
"address2": "美唄市",
"address3": "上美唄町協和",
"kana1": "ホッカイドウ",
"kana2": "ビバイシ",
"kana3": "カミビバイチョウキョウワ",
"prefcode": "1",
"zipcode": "0790177"
},
{
"address1": "北海道",
"address2": "美唄市",
"address3": "上美唄町南",
"kana1": "ホッカイドウ",
"kana2": "ビバイシ",
"kana3": "カミビバイチョウミナミ",
"prefcode": "1",
"zipcode": "0790177"
}
],
"status": 200
}
これをまとめて「--県--市--町」というように表示するために+で結合してあげたのだ。
③レスポンスを検索結果欄に表示する
あとはこれをブラウザで表示できれば、完成だ。今検索結果はadressに格納されており、これをResultsコンポーネントで表示してあげたい。ではこのデータを他ファイルであるResults.tsxに渡してあげるにはどうすればいいのか。これも先ほどと同じpropsを使って渡してあげればいい。そしてResultsのreturn内に以下のコードを追加してあげればブラウザに無事表示でき、完成だ。
<div>{props.results.adress && <div>{props.results.adress}</div>}</div>
この&&はロジカルオペレーターといい、論理演算を応用している。このコードの意味は「adressがtrue、つまり存在していれば、adressを表示する」というものだ。こうすることでデータがある時の未表示ができるようになる。本当は存在しない郵便番号が入力された際の処理や、複数住所がある場合の処理などもする必要があるが、今回は主にAPIの仕組みを理解することを目的としているので、ここでは説明しない。もしご要望があれば教えていただきたいです!
まとめ
今回このアプリを作成して、記事にしてみたことで2つの学びが得られた。1つ目は闇雲に手を動かすことは悪であるということである。APIから得られた結果をadressに格納する処理に手間取り、試行錯誤していたが、落ち着いて紙に書き出して考えてみれば簡単な処理だった。理解に時間をかけて試行錯誤の時間を減らせるように意識しようと思った。2つ目は記事にすることで得られ得る効果は大きいということである。今まではアプリを作ってみたりしても、記事を作ったことはなかったので、イマイチ仕組みが理解できない部分があったがアウトプットして文章で処理を書き出していくとすごく理解が深まり、想像以上の効果だった。これからは1つのアプリにつき、1つ記事を作るぐらいの気持ちで勉強を進めていきたい。
個人用
アプリ作成にかかった時間:4時間ほど
記事作成にかかった時間:4時間ほど