usomaru技術ぶろぐ

学んだことをちょこちょこメモ( ..)φ

【React】Angularと比較しながら親子コンポーネント間のデータ受け渡し

はじめに

Angular Tour of heroesの3章(フィーチャーコンポーネントの作成)をReactで試してみました。

angular.jp

久しぶりすぎていっぱい忘れてました😇

HeroDetailComponentを作成する

Heroの詳細が表示されるコンポーネントを新たに作成します。
今回は普通にファイルを追加して作成しました。

ファイル名ですが、Angularでは基本ケバブケース(ハイフンのやつ)でつけてました。
が、Reactではどうなのでしょうか。
とりあえずこちらのサイトにのっとり、Angularと同様ケバブケースでつくりました。

www.uxpin.com

詳細の内容を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というものを使うみたいです。

ja.react.dev

親:heroes.tsx

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>
        </>
      )}
    </>
  );
}

完成品は前回と変わらないのですが、リファクタリングができました。