はじめに
Angular Tour of heroesの3章(フィーチャーコンポーネントの作成)をReactで試してみました。
久しぶりすぎていっぱい忘れてました😇
HeroDetailComponentを作成する
Heroの詳細が表示されるコンポーネントを新たに作成します。
今回は普通にファイルを追加して作成しました。
ファイル名ですが、Angularでは基本ケバブケース(ハイフンのやつ)でつけてました。
が、Reactではどうなのでしょうか。
とりあえずこちらのサイトにのっとり、Angularと同様ケバブケースでつくりました。
詳細の内容をHeroDetailComponentに移動
作成したHeroDetailComponentに詳細表示の内容を一旦移動します。
hero-detail.tsx
import { useState } from "react"; export default function HeroDetail() { const [selectedHero, setSelectedHero] = useState<Hero | null>(null); const onChangeName = (e: React.ChangeEvent<HTMLInputElement>) => { if (selectedHero) { setSelectedHero({ ...selectedHero, name: e.target.value }); } } return ( <> {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> </> )} </> ); }
このままでは表示する値がないため、heroes.tsxから値をもらいます。
親コンポーネントから子コンポーネントに値を渡す
ここでは親コンポーネント=heroes.tsx、子コンポーネント=hero-detail.tsxという関係になります。
親から子へのデータの値渡しは、Angularではinputを使って渡していました。
Reactではpropsというものを使うみたいです。
import { useState } from "react"; import styles from "./heroes.module.css"; import HeroDetail from "../hero-detail/hero-detail"; export 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 [data, 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> <HeroDetail selectedHero={data}/> </> ); }
Heroモデルは別ファイルに移動しときたいところ
子:hero-detail.tsx
import { useState } from "react"; import { Hero } from "../heroes/heroes"; export default function HeroDetail({ selectedHero }) { // useStateを使って親コンポーネントからもらった値を別の変数として管理 const [hero, setSelectedHero] = useState<Hero | null>(selectedHero); const onChangeName = (e: React.ChangeEvent<HTMLInputElement>) => { if (hero) { setSelectedHero({ ...hero, name: e.target.value }); } } return ( <> {hero && ( <> <h2>{hero.name} Details</h2> <div><span>id: </span>{hero.id}</div> <div> <label>Hero name: </label> <input type="text" value={hero.name} onChange={onChangeName} /> </div> </> )} </> ); }
useStateの初期値に親コンポーネントからもらった値をいれます。
でもまだ表示されません。
useEffectを使う
発火のタイミングを決めることができます。
タイミングはuseEffectの第二引数で指定します。
つまり、以下のコードの場合は、親コンポーネントから値を受け取った時に発生します。
ちなみに、第二引数を空配列にすると初回レンダリング後に発火させることができるので、
AngularでいうngOnInitはuseEffectを使うことで同様のようなことができるのかなと思います。
import { useEffect, useState } from "react"; import { Hero } from "../heroes/heroes"; export default function HeroDetail({ selectedHero }) { // useStateを使って親コンポーネントからもらった値を別の変数として管理 const [hero, setSelectedHero] = useState<Hero | null>(selectedHero); // selectedHeroが変更されたときに値も更新したい場合 useEffect(() => { setSelectedHero(selectedHero); }, [selectedHero]); const onChangeName = (e: React.ChangeEvent<HTMLInputElement>) => { if (hero) { setSelectedHero({ ...hero, name: e.target.value }); } } return ( <> {hero && ( <> <h2>{hero.name} Details</h2> <div><span>id: </span>{hero.id}</div> <div> <label>Hero name: </label> <input type="text" value={hero.name} onChange={onChangeName} /> </div> </> )} </> ); }
完成品は前回と変わらないのですが、リファクタリングができました。