はじめに
Angular Tour of heroesの2章(リストの表示)をReactで試してみました。
ヒーローのモック作成と一覧表示
Angularで一覧の表示をするとき、HTML側で*ngFor
をすることで表示できます。
配列のデータをテーブルで表示したいときとかによく使いました。
Reactではmap
を使います。
interface Hero { id: number; name: string; } const HEROES: Hero[] = [ { id: 12, name: 'Dr. Nice' }, { id: 13, name: 'Bombasto' }, { id: 14, name: 'Celeritas' }, { id: 15, name: 'Magneta' }, { id: 16, name: 'RubberMan' }, { id: 17, name: 'Dynama' }, { id: 18, name: 'Dr. IQ' }, { id: 19, name: 'Magma' }, { id: 20, name: 'Tornado' } ]; export default function Heroes() { const heroes = HEROES; return ( <> <h2>My Heroes</h2> <ul> {heroes.map(item => <li key={item.id}> <button type="button"> <span>{item.id}</span> <span>{item.name}</span> </button> </li> )} </ul> </> ); }
一覧が表示できました。
CSSの適用
調べると様々な方法があったのですが、今回はCSSモジュールという方法で試しました。
(何が正解なのかわからず、CSSのファイルをtsxファイルごとに分けてるところがAngularと似たものを感じたためこちらにしました)
CSSファイルをimportしてあげることで使えるようになります。
.heroes { margin: 0 0 2em 0; list-style-type: none; padding: 0; width: 15em; } .heroes li { display: flex; } .heroes button { flex: 1; cursor: pointer; position: relative; left: 0; background-color: #EEE; margin: .5em; padding: 0; border-radius: 4px; display: flex; align-items: stretch; height: 1.8em; } .heroes .badge { display: inline-block; font-size: small; color: white; padding: 0.8em 0.7em 0 0.7em; background-color: #405061; line-height: 1em; margin-right: .8em; border-radius: 4px 0 0 4px; } .heroes .name { align-self: center; }
一般的なHTMLと違ってclass
はclassName
とかきます。
名前の指定方法は、ただCSSファイルでかいたクラス名をかくのではなく、
importのときにつけた「モジュール名(今回はstyles) + CSSファイルでかいたクラス名」となります。
// CSSファイルをimport import styles from "./heroes.module.css"; interface Hero { id: number; name: string; } const HEROES: Hero[] = [ { id: 12, name: 'Dr. Nice' }, { id: 13, name: 'Bombasto' }, { id: 14, name: 'Celeritas' }, { id: 15, name: 'Magneta' }, { id: 16, name: 'RubberMan' }, { id: 17, name: 'Dynama' }, { id: 18, name: 'Dr. IQ' }, { id: 19, name: 'Magma' }, { id: 20, name: 'Tornado' } ]; export default function Heroes() { const heroes = HEROES; return ( <> <h2>My Heroes</h2> <ul className={styles.heroes}> {heroes.map(item => <li key={item.id}> <button type="button"> <span className={styles.badge}>{item.id}</span> <span className={styles.name}>{item.name}</span> </button> </li> )} </ul> </> ); }
レイアウトがよくなりました。
一覧から詳細に飛ぶ
一覧から選択したヒーローを同じ画面下に表示されるようにします。
ここでselectedHero
が存在した時だけ表示したかったので、
Angularでいう*ngIf
をReactでは{}内で&&
や三項演算子を使うことで実装しています。
はじめ、状態管理をせずに、宣言したヒーローを表示しようとしました。
let selectedHero: Hero | null = null; const onSelect = (hero: Hero) => { selectedHero = hero; } return ( <> {selectedHero && ( <> <h2>{selectedHero.name} Details</h2> </> )} </> );
何も表示されませんでした🥹
Angularでは何も考えずに{{}}でくくればバインドしてくれたけど、Reactでは状態管理をしないと値が描画されないという バインディングの違いが慣れないですね。
(初めてAngularの良さに気づく)
最終的コード
import { useState } from "react"; import styles from "./heroes.module.css"; interface Hero { id: number; name: string; } const HEROES: Hero[] = [ { id: 12, name: 'Dr. Nice' }, { id: 13, name: 'Bombasto' }, { id: 14, name: 'Celeritas' }, { id: 15, name: 'Magneta' }, { id: 16, name: 'RubberMan' }, { id: 17, name: 'Dynama' }, { id: 18, name: 'Dr. IQ' }, { id: 19, name: 'Magma' }, { id: 20, name: 'Tornado' } ]; export default function Heroes() { // const initHero: Hero = { // id: 1, // name: 'Windstorm' // }; // const [hero, setHero] = useState(initHero); const onChangeName = (e: React.ChangeEvent<HTMLInputElement>) => { if (selectedHero) { setSelectedHero({ ...selectedHero, name: e.target.value }); } } const [selectedHero, setSelectedHero] = useState<Hero | null>(null); const onSelect = (hero: Hero) => { setSelectedHero(hero); } const heroes = HEROES; return ( <> <h2>My Heroes</h2> <ul className={styles.heroes}> {heroes.map(item => <li key={item.id}> <button type="button" onClick={() => onSelect(item)}> <span className={styles.badge}>{item.id}</span> <span className={styles.name}>{item.name}</span> </button> </li> )} </ul> {selectedHero && ( <> <h2>{selectedHero.name} Details</h2> <div><span>id: </span>{selectedHero.id}</div> <div> <label>Hero name: </label> <input type="text" value={selectedHero.name} onChange={onChangeName} /> </div> </> )} </> ); }
一覧からヒーローを選択すると下に詳細が表示されるように!
学び
Reactの肝はフック
参考
chatGPTさんに頼んでもらったりもした😇