usomaru技術ぶろぐ

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

【React】Angularと比較しながら一覧の表示とCSS

はじめに

Angular Tour of heroesの2章(リストの表示)をReactで試してみました。

angular.jp

ヒーローのモック作成と一覧表示

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と違ってclassclassNameとかきます。
名前の指定方法は、ただ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の肝はフック

参考

ja.react.dev

lorem-co-ltd.com

ja.react.dev

chatGPTさんに頼んでもらったりもした😇