package.jsonから開始するreactチュートリアル。Dog APIで犬画像ギャラリー
参考URL
https://zenn.dev/likr/articles/6be53ca64f29aa035f07
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
# 適当な名前のフォルダを作る。これがreactプロジェクト名になる。 mkdir react_tutorial cd react_tutorial # npmでパッケージ管理するよ宣言のpackage.json生成 npm init -y { "name": "react-tutorial", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC" } # reactのパッケージをインストール npm install react react-dom # vite(開発ツール) npm install --save-dev vite @vitejs/plugin-react |
node_modulesフォルダ(パッケージ置き場)と、package-lock.json(実際にインストールされたパッケージのバージョンが記載)が生成される。
package.jsonは、このパッケージのこのバージョン以上(以下)という条件的な指定で記載されている。
package.jsonを編集して、コマンド追加
1 2 3 4 5 6 7 8 9 10 |
{ "name": "react-tutorial", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "dev": "vite", "build": "vite build", "preview": "vite preview" }, |
hello,worldと表示だけなのに、4つもファイルを生成する!!
index.html -> src/main.jsx -> App.jsx(ここでhello,worldしている)
vite.config.jsは開発ローカルサーバ用
vite.config.js
1 2 3 4 5 6 |
import { defineConfig } from "vite"; import react from "@vitejs/plugin-react"; export default defineConfig({ plugins: [react()], }); |
index.html
1 2 3 4 5 6 7 8 9 10 11 |
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>React Tutorial</title> </head> <body> <div id="content"></div> <script type="module" src="/src/main.jsx"></script> </body> </html> |
src/App.jsx
1 2 3 4 5 6 7 8 |
function App() { return ( <div> <h1>Hello, World!</h1> </div> ); } export default App; |
src/main.jsx
1 2 3 4 |
import { createRoot } from "react-dom/client"; import App from "./App"; createRoot(document.querySelector("#content")).render(<App />); |
viteを使って、ローカルサーバで実行。
ブラウザから、http://localhost:5173/ でhello,worldが表示される
1 2 3 4 5 6 7 8 9 10 11 12 |
npm run dev > react-tutorial@1.0.0 dev > vite The CJS build of Vite's Node API is deprecated. See https://vitejs.dev/guide/troubleshooting.html#vite-cjs-node-api-deprecated for more details. VITE v5.2.10 ready in 241 ms ➜ Local: http://localhost:5173/ ➜ Network: use --host to expose ➜ press h + enter to show help |
Dog APIを使った犬画像ギャラリーを作る
CSSフレームワークのbulmaを使う
1 |
npm install bulma |
src/main.jsxに先頭に、追加
1 |
import "bulma/css/bulma.css"; |
src/App.jsを修正。jsxではclassじゃなくてclassNameでクラス指定する言語仕様
さらにコンポーネントの分割!
各htmlパーツを関数化する事により、複雑な構造のHTMLも、一覧性が良くなる。
Header()
Main() -> Gallery() -> Image()
Footer()
関数化して引数を渡せるので、複数URLからループ文で写真表示などもできる
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 |
function Header() { return ( <header className="hero is-dark is-bold"> <div className="hero-body"> <div className="container"> <h1 className="title">Cute Dog Images</h1> </div> </div> </header> ); } function Image(props) { return ( <div className="card"> <div className="card-image"> <figure className="image"> <img src={props.src} alt="cute dog!" /> </figure> </div> </div> ); } function Gallery(props) { const {urls} = props; return ( <div className="columns is-vcentered is-multiline"> {urls.map((url) => { return ( <div key={url} className="column is-3"> <Image src={url} /> </div> ); })} </div> ); } function Main() { const urls = [ "https://images.dog.ceo/breeds/shiba/shiba-11.jpg", "https://images.dog.ceo/breeds/shiba/shiba-12.jpg", "https://images.dog.ceo/breeds/shiba/shiba-14.jpg", "https://images.dog.ceo/breeds/shiba/shiba-17.jpg", "https://images.dog.ceo/breeds/shiba/shiba-2.jpg", "https://images.dog.ceo/breeds/shiba/shiba-3i.jpg", "https://images.dog.ceo/breeds/shiba/shiba-4.jpg", "https://images.dog.ceo/breeds/shiba/shiba-5.jpg", "https://images.dog.ceo/breeds/shiba/shiba-6.jpg", "https://images.dog.ceo/breeds/shiba/shiba-7.jpg", "https://images.dog.ceo/breeds/shiba/shiba-8.jpg", "https://images.dog.ceo/breeds/shiba/shiba-9.jpg", ]; return ( <main> <section className="section"> <div className="container"> <Gallery urls={urls}/> </div> </section> </main> ); } function Footer() { return ( <footer className="footer"> <div className="content has-text-centered"> <p>Dog images are retrieved from Dog API</p> <p> <a href="https://dog.ceo/dog-api/about">Donate to Dog API</a> </p> </div> </footer> ); } function App() { return ( <div> <Header /> <Main /> <Footer /> </div> ); } export default App; |
Dog APIからランダム画像を取得して、表示する。
さらに犬種を指定できるようにする。
Formコンポーネントをみると、
HTML部分とjs関数の部分が分離されつつも、
同じFormコンポーネント関数内にあるのが、すごいプログラマ的!
これぞフロントエンドエンジニア技術!?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 |
import { useEffect, useState } from "react"; // ReactのフックからuseEffectとuseStateをインポート import { fetchImages } from "./api"; // "./api"からfetchImages関数をインポート // ヘッダーコンポーネント function Header() { return ( <header className="hero is-dark is-bold"> {/* ヘッダーエレメントを表示 */} <div className="hero-body"> <div className="container"> <h1 className="title">Cute Dog Images</h1> {/* タイトルとして「Cute Dog Images」を表示 */} </div> </div> </header> ); } // 画像を表示するコンポーネント function Image(props) { return ( <div className="card"> {/* カード形式で画像を表示 */} <div className="card-image"> <figure className="image"> {/* 受け取った画像URLをsrc属性に設定 */} <img src={props.src} alt="cute dog!" /> </figure> </div> </div> ); } // 画像ローディング中の表示コンポーネント function Loading() { return <p>Loading...</p>; {/* 「Loading...」と表示 */} } // 画像ギャラリーを表示するコンポーネント function Gallery(props) { const {urls} = props; {/* urlsがnullの場合、Loadingコンポーネントを表示 */} if (urls == null) { return <Loading />; } return ( <div className="columns is-vcentered is-multiline"> {/* 複数のカラムで画像を表示 */} {urls.map((url) => ( <div key={url} className="column is-3"> <Image src={url} /> {/* 各URLに対してImageコンポーネントを使用して表示 */} </div> ))} </div> ); } // 画像取得用のフォームコンポーネント function Form(props) { function handleSubmit(event) { event.preventDefault(); {/* フォーム送信時のデフォルト動作を防止 */} const { breed } = event.target.elements; {/* フォーム内のbreed要素を取得 */} props.onFormSubmit(breed.value); {/* フォームの値を親コンポーネントへ送信 */} } return ( <div> <form onSubmit={handleSubmit}> {/* フォーム送信イベントのハンドラとしてhandleSubmitを設定 */} <div className="field has-addons"> <div className="control is-expanded"> <div className="select is-fullwidth"> {/* 犬種選択のセレクトボックス */} <select name="breed" defaultValue="shiba"> <option value="shiba">Shiba</option> <option value="akita">Akita</option> </select> </div> </div> <div className="control"> <button type="submit" className="button is-dark"> Reload {/* リロードボタン */} </button> </div> </div> </form> </div> ); } // メインコンポーネント function Main() { {/* urlsが変数、setUrlsが更新する関数(画面も自動的に反映される)。nullで初期化 */} const [urls, setUrls] = useState(null); {/* nullで初期化 */} {/* 初期画像として「shiba」の画像を取得し、ステートに設定 */} useEffect(() => { fetchImages("shiba").then((urls) => { setUrls(urls); }); }, []); {/* 新たに選択された犬種の画像を取得し、ステートを更新 */} {/* function in functionってOKなん? */} function reloadImages(breed) { fetchImages(breed).then((urls) => { setUrls(urls); }); } return ( <main> <section className="section"> <div className="container"> <Form onFormSubmit={reloadImages} /> {/* フォームコンポーネントを使用し、画像再取得をハンドリング */} </div> </section> <section className="section"> <div className="container"> <Gallery urls={urls}/> {/* ギャラリーコンポーネントを表示 */} </div> </section> </main> ); } // フッターコンポーネント function Footer() { return ( <footer className="footer"> <div className="content has-text-centered"> <p>Dog images are retrieved from Dog API</p> {/* APIのクレジットを表示 */} <p> <a href="https://dog.ceo/dog-api/about">Donate to Dog API</a> {/* APIへの寄付リンク */} </p> </div> </footer> ); } // アプリケーションのルートコンポーネント function App() { return ( <div> <Header /> {/* ヘッダーコンポーネントを表示 */} <Main /> {/* メインコンポーネントを表示 */} <Footer /> {/* フッターコンポーネントを表示 */} </div> ); } export default App; // Appコンポーネントをエクスポート |