reactの基礎知識

  • 2024年9月29日
  • 2024年10月16日
  • react
  • 18View
  • 0件

React Hooks

useEffectの使い方

import { useEffect, useState } from "react";

const Example = () => {
  const [time, setTime] = useState(0);

  useEffect(() => {
    console.log('useEffect is called');
    window.setInterval(() => {
      setTime(prev => prev + 1);
    }, 1000);
  }, [])

  // useEffectの依存配列(第二引数)のstateが更新されると、コールバック関数が実行される
  // 上記コードは依存配列を設定していないので、Exampleが実行されたときのみ実行される
  // 下記コードは1秒ごとにtimeが更新されているので、そのたびにコンソールが実行する
  useEffect(() => {
    console.log('update');
  }, [time])

  useEffect(() => {
    // ページのタイトルを変更
    document.title = 'counter:' + time;
    // ローカルストレージでtime-leyにtimeを設定する
    window.localStorage.setItem("time-ley", time);
  }, [time])

  // 依存配列をコールバックの中で書き換えることはしてはいけない。
  // 無限ループ
  // useEffect(() => {
  //   window.setInterval(() => {
  //     setTime(prev => prev + 1);
  //   }, 1000);
  // }, [time])
  
  return (
    <h3>
      <time>{time}</time>
      <span>秒経過</span>
    </h3>
  );
};

export default Example;

コンポーネントマウント時、更新時のみ呼び出したいときにuseEffectを使用する

import { useEffect, useState } from "react";

const Example = () => {
  const [time, setTime] = useState(0);

  useEffect(() => {
    console.log('useEffect is called');
    window.setInterval(() => {
      setTime(prev => prev + 1);
    }, 1000);
  }, [])
  // 上下のどちらのコードでも1秒ごとにsetTimeにプラス1を実行する
  window.setInterval(() => {
    setTime(prev => prev + 1);
  }, 1000);
  
  return (
    <h3>
      <time>{time}</time>
      <span>秒経過</span>
    </h3>
  );
};

export default Example;

useEffectを使わなくても、実装することができる

useEffectを使う理由の前に、純粋関数の説明が必要になる。

udemyの説明

純粋関数とは、画像の4つの項目を満たしている関数のことを指す

https://qiita.com/Dragon1208/items/4b4568bb61b0ca3aef3c

副作用とは、上記4つの項目が例となる

  useEffect(() => {
    // コンソールログの出力
    console.log('update');
    // DOMの操作(ページのタイトルを変更)
    document.title = 'counter:' + time;
    // サーバーとの通信(ローカルストレージでtime-leyにtimeを設定する)
    window.localStorage.setItem("time-ley", time);
  }, [time])

なので、上記の様なコードの時には、useEffectが必要になる。

useReducerの使い方

import { useState, useReducer } from "react";

const Example = () => {
  // useStateとuseReducerでカウントアップの実装
  const [state, setState] = useState(0);
  // dispatchを実行すると、useReducerの第一引数の関数が実行される
  // const [rstate, dispatch] = useReducer(prev => ++prev, 0);
  // const [rstate, dispatch] = useReducer((prev, action) => {
  //   return (action === '+') ? ++prev : --prev;
  // }, 0);
  // typeでカウントアップかカウントダウンかの確認
  // stepで増減数を設定する
  const [rstate, dispatch] = useReducer((prev, { type, step }) => (type === '+') ? prev + step : prev - step, 0);
  // useStateは使用するときにstateの実装をするが、
  // useReducerは定義するときにstateの実装をする
  const countUp = () => setState(prev => ++prev);
  const rcountUp = () => dispatch({ type: '+', step: 2 });
  const rcountDown = () => dispatch({ type: '-', step: 4 });
  return (
    <>
      <h3>{state}</h3>
      <button onClick={countUp}>+</button>
      <h3>{rstate}</h3>
      <button onClick={rcountUp}>+</button>
      <button onClick={rcountDown}>-</button>
    </>
  );
};

export default Example;

上記はuseReducerとuseStateで同じ実装を比較している

useState:状態の更新の仕方は利用側に託す。

useStateの引数には初期値しか渡さないため、更新方法はsetStateを呼び出す個所で設定する

useReducer:状態の更新の仕方も状態側で担当する。

useReducerの第一引数に更新方法を記述する。

チーム開発や大規模プロジェクトになると、useStateよりもuseReducerを使用するべきである。

useContextでグローバル変数を設定する

import { createContext } from "react";
import Child from "./components/Child";
export const MyContext = createContext('hello');

const Example = () => {
  return <Child />;
};

export default Example;
import { useContext } from 'react';
import { MyContext } from '../Example'

const GrandChild = () => {
  const value = useContext(MyContext);
  return (
    <div style={{ border: "1px solid black" }}>
      <h3>孫コンポーネント</h3>
      {value}
    </div>
  );
};
export default GrandChild;

propsで親から子、子から孫へ値を渡すこともできるが、毎回propsを定義するの手間である。

そんな時にuseContextだとグローバル変数の様に使用することができる

useContextでstateを渡す

import { useState, createContext } from "react";
import Child from "./components/Child";
import OtherChild from "./components/OtherChild";
export const MyContext = createContext();

const Example = () => {
  const [ value, setValue ] = useState(0);
  return (
    <MyContext.Provider value={[ value, setValue ]}>
      <Child />
      <OtherChild />
    </MyContext.Provider>
  );
};

export default Example;
import GrandChild from "./GrandChild";
import { useContext } from "react";
import { MyContext } from "../Example";
const Child = () => {
  const [value] = useContext(MyContext);
  return (
    <div style={{ border: "1px solid black", padding: 10  }}>
      <h3>子コンポーネント</h3>
      {value}
      <GrandChild />
    </div>
  );
};
export default Child;
import { useContext } from "react";
import { MyContext } from "../Example"

const OtherChild = () => {
  const [ value, setValue ] = useContext(MyContext);
  const clickHandler = (e) => {
    setValue((prev) => prev + 1);
  };

  return (
    <div>
      <h3>他の子コンポーネント</h3>
      <button onClick={clickHandler}>+</button>
    </div>
  );
};

export default OtherChild;

stateをuseContextで渡すためには、親コンポーネントでcreateContextの変数名のタグでプロバイダーを設定する

<MyContext.Provider value={[ value, setValue ]}>
    <Child />
    <OtherChild />
</MyContext.Provider>

配列でHTMLの表示

const animals = ["Dog", "Cat", "Rat"];

const Example = () => {
  // forを用いたリスト表示
  const animalList = [];
  for (const animal of animals) {
    animalList.push(<li key={animal}>{animal}</li>)
  }

  // mapを用いたリスト表示
  // const helloAnimalList = animals.map(function (animal) {
  //   return <li>hello, {animal}</li>;
  // });
  // 上記の省略
  const helloAnimalList = animals.map((animal) => <li key={animal}>hello, {animal}</li>);
  return (
    <>
      <h3>配列の操作</h3>
      <ul>
        {/* <li>{animals[0]}</li>
        <li>{animals[1]}</li>
        <li>{animals[2]}</li> */}
        {animalList}
        {helloAnimalList}
        {/* for分はJSXで使用することができないが、mapの下記省略構文だと使用することができる。
            forではなくmapを使用することが多い */}
        {animals.map((animal) => <li key={animal}>hello, {animal}</li>)}
      </ul>
    </>
  );
};

export default Example;

上記コードを参考に配列としてHTMLを表示することができる

ただし、配列で子要素を繰り返し処理する場合は、keyを設定する

理由としては、reactがkeyを確認することによって、最小限の更新で完了することができるからである。

keyを付ける際の注意点

1.キーには必ず一意の値を設定する

2.キーに設定した値を変更しない

3.インデックスを使用しない

配列の応用

const Profile = ({ name, age, hobbies }) => {
  return (
    <div>
      <hr />
      <div>Name: {name}</div>
      <div>Age: {age}</div>
      <div>
        <div>Hobby:</div>
        <ul>
          {hobbies.map((hobby) => (
            <li>{hobby}</li>
          ))}
        </ul>
      </div>
    </div>
  );
};

export default Profile;
import Profile from "./components/Profile";

const persons = [
  {
    name: "Geo",
    age: 18,
    hobbies: ["sports", "music"],
  },
  {
    name: "Tom",
    age: 25,
    hobbies: ["movie", "music"],
  },
  {
    name: "Lisa",
    age: 21,
    hobbies: ["sports", "travel", "game"],
  },
];

const Example = () => {
  return (
    <>
      <ul>
        {persons.map((person) => (
          <li key={person.name}>
            <Profile {...person} />
          </li>
        ))}
      </ul>
    </>
  );
};

export default Example;

filterメソッドを用いた配列の絞り込み

import { useState } from "react";

const animals = ["Dog", "Cat", "Rat"];

const Example = () => {
  const [filterVal, setFilterVal] = useState("");
  return (
    <>
      <h3>配列のフィルター</h3>
      <input type="text" value={filterVal} onChange={(e) => setFilterVal(e.target.value)} />
      <ul>
        {animals
          // filterメソッドで、入力された文字がanimals配列のそれぞれに含まれているかを確認する
          // 含まれていたらtrueを返して、含まれていなのであればfalseを返す
          // trueの要素のみをmapに渡している
          .filter(animal => animal.indexOf(filterVal) !== -1)
          .map((animal) => <li key={animal}>{animal}</li>)}
      </ul>
    </>
  );
};

export default Example;

フォーム系の制御

import { useState } from "react";

const Example = () => {
  const [val, setVal] = useState("");
  const clearVal = () => setVal("");
  
  const OPTIONS = ["Apple", "Banana", "Cherry"];
  const [selected, setSelected] = useState("Banana");

  const [fruit, setFruit] = useState("Apple");
  const onChange = (e) => setFruit(e.target.value);
  const RADIO_COLLECTION = ["Apple", "Banana", "Cherry"];

  return (
    <>
      <h3>inputとtextarea</h3>
      <div>
        {/* reactではfor属性はhtmlForで記述する */}
        <label htmlFor="123">ラベル</label>
        <input
          id="123"
          type="text"
          placeholder="input"
          value={val}
          onChange={(e) => setVal(e.target.value)}
        />
        {/* <textarea
          id="456"
          placeholder="textarea"
          value={val}
          onChange={(e) => setVal(e.target.value)}
        ></textarea> */}
        {/* 通常であれば上記のようになるが、JSXでは下記の様になる */}
        <textarea
          id="456"
          placeholder="textarea"
          value={val}
          onChange={(e) => setVal(e.target.value)}
        />
        <h3>{val}</h3>
        <button onClick={clearVal}>クリア</button>
      </div>

      <h3>select</h3>
      <div>
        <select value={selected} onChange={(e) => setSelected(e.target.value)}>
          {OPTIONS.map((item) => <option key={item} value={item}>{item}</option>)}
        </select>
        <p>選択された果物:{selected}</p>
      </div>

      <h3>radio</h3>
      <div>
        {RADIO_COLLECTION.map((item) => 
          <label>
            <input type="radio" value={item} checked={fruit === item} onChange={onChange} />
            {item}
          </label>
        )};
        
        {/* <label>
          <input type="radio" value="Apple" checked={fruit === "Apple"} onChange={onChange} />
          Apple
        </label>
        <label>
          <input type="radio" value="Banana" checked={fruit === "Banana"} onChange={onChange} />
          Banana
        </label>
        <label>
          <input type="radio" value="Cherry" checked={fruit === "Cherry"} onChange={onChange} />
          Cherry
        </label> */}
        <h3>私は{fruit}がたべたい</h3>
      </div>
    </>
  );
};

export default Example;

CSSについて(スタイリング)

・インラインでスタイリング

・cssファイルを読み込む

・CSS Modulesでスタイリング

・css in jsでスタイリング

インラインでスタイリング

import { useState } from "react"

const Example = () => {
    const [isSelected, setIsSelected] = useState(false);

    const clickHandler = () => setIsSelected(prev => !prev);
    // ハイフンはキャメルケースで記載する。
    // cssのプロパティ名をそのまま使用したい場合は、ダブルクォーテーションなどで囲むこと
    const style = {
      width: 120,
      height: 60,
      display: "block",
      fontWeight: "bold",
      "border-radius": 50,
      cursor: "pointer",
      border: "none",
      margin: "auto",
      // 下記は空文字の場合は、プロパティ自体削除される
      background: isSelected ? "pink" : "",
    }

    return (
        <>
            <button onClick={clickHandler} style={style}>ボタン</button>
            <div style={{ textAlign: "center" }}>{isSelected && "クリックされました。"}</div>
        </>
    )
};

export default Example;

インラインでcssを記載する場合のデメリット

1.保守性が悪い。style属性を毎回指定する必要がある。

2.疑似要素(before、after、hover)を使用できない。

3.メディアクエリを使用することができない

4.パフォーマンスが悪い。

上記理由からあまり使用することはない

cssを読み込む

import { useState } from "react";
import SubButton from "./components/SubButton";
import "./Example.css";

const Example = () => {
  const [isSelected, setIsSelected] = useState(false);

  const clickHandler = () => setIsSelected((prev) => !prev);

  return (
    <>
      <button className={`btn ${isSelected ? "selected": ""}`} onClick={clickHandler}>
        ボタン
      </button>
    </>
  );
};

export default Example;

cssを読み込むデメリット

1.コンポーネントのcssが全て読み込まれてしまう。

例えば上記でSubButtonコンポーネントを読み込んでいる場合に、SubButton.cssでbtnクラスのcssを記載しているとする。

import "./SubButton.css";

const SubButton = () => {
    return <button className="btn">サブボタン</button>
}
export default SubButton;

親コンポーネントであるExampleでもそのcssが適用されるので、SubButtonのみにcssを適用することができない。

CSS Modulesでスタイリング

import { useState } from "react";
import SubButton from "./components/SubButton";
import styles from "./Example.module.css";

const Example = () => {
  const [isSelected, setIsSelected] = useState(false);
  const clickHandler = () => setIsSelected((prev) => !prev);

  return (
    <>
      <button className={`${styles.btn} ${isSelected ? styles.selected : ""}`} onClick={clickHandler}>
        ボタン
      </button>
      <SubButton />
    </>
  );
};

export default Example;

CSS Modulesとはクラス名を一意にしてくれる。

<button class="_btn_16hsy_1 ">ボタン</button>
<button class="_btn_4vy2m_1">サブボタン</button>

検証モードで確認すると、上記のようなクラス名が設定されている

CSS Modulesのお作法として、css名はExample.module.cssのようになる。

ただし、CSS Modulesは将来廃止される可能性があるために注意をする

css in jsでスタイリング

import { useState } from "react";
import styled from "styled-components";

const Example = () => {
  const [isSelected, setIsSelected] = useState(false);

  const StyleButton = styled.button`
    margin: auto;
    border-radius: 9999px;
    border: none;
    display: block;
    width: 120px;
    height: 60px;
    font-weight: bold;
    cursor: pointer;
    background-color: ${(props) => props.isSelected ? "pink" : ""};
    @media (max-width: 600px) {
      width: 100px;
    }
  `;

  // 背景色だけ違うボタン
  const OrangeButton = styled(StyleButton)`
    background-color: orange;
    &:hover {
      color: red;
      opacity: .7;
    }
    span {
      font-size: 1.4rem;
    }
  `;

  const clickHandler = () => setIsSelected((prev) => !prev);

  return (
    <>
      <StyleButton isSelected={isSelected} onClick={clickHandler}>ボタン</StyleButton>
      <OrangeButton isSelected={isSelected} onClick={clickHandler}><span>ボタン</span></OrangeButton>
    </>
  );
};

export default Example;

上記は代表的なcss in jsのstyled-componentsを使用している

https://styled-components.com

createPortalを使ったモーダルの作り方

import { useState } from "react";
import { createPortal } from "react-dom";
import Modal from "./components/Modal";

/* POINT createPortalの使い方
第一引数: React の子要素としてレンダー可能なもの (要素、文字列、フラグメント、コンポーネントなど)
第二引数: レンダー先のDOM要素
*/

/* POINT createPortalはどんなときに使うか?
子要素は親要素のスタイルによって表示に制限を受ける場合があります。
(overflow: hidden 、 z-index 、 width など・・・ )
それらの制限なく、子要素が親要素を「飛び出して」表示する必要があるときにcreatePortalを使うのが有効です。
モーダル、ポップアップ、トーストは使用の代表例です。
*/

const ModalPortal = ({ children }) => {
  // const target = document.querySelector("body");
  // 本来であればbodyに生成するが、今回は下記クラス直下に生成する
  const target = document.querySelector(".container.start");
  return createPortal(children, target);
}

const Example = () => {
  const [modalOpen, setModalOpen] = useState(false);
  return (
    <div>
      <div className="container start"></div>

      <button
        type="button"
        onClick={() => setModalOpen(true)}
        disabled={modalOpen}
      >
        モーダルを表示する
      </button>
      {modalOpen && (
        <ModalPortal>
          <Modal handleCloseClick={() => setModalOpen(false)} />
        </ModalPortal>
      )}
    </div>
  );
};

export default Example;
import "./Modal.css";

const Modal = ({ handleCloseClick }) => {
  return (
    <div className="modal">
      <div className="modal__content">
        <p>モーダル</p>
        <button type="button" onClick={handleCloseClick}>閉じる</button>
      </div>
    </div>
  );
};

export default Modal;

createPortalはDOMの階層に関わらず、コンポーネントをレンダリングすることができる。

モーダルの様にコンポーネントの子要素としてDOM仁レンダリングするよりも、body直下にレンダリングすることが良い場合などに、使う

useRefでDOMを操作する

import { useState, useRef } from "react";

/* POINT useRefでDOMを取得
refオブジェクトをref属性に渡すとDOMを参照することができます。
*/
const Case1 = () => {
  const [value, setValue] = useState("");
  const inputRef = useRef();
  // console.log(inputRef);

  return (
    <div>
      <h3>ユースケース1</h3>
      <input type="text" ref={inputRef} value={value} onChange={(e) => setValue(e.target.value)} />
      <button onClick={() => inputRef.current.focus()}>
        インプット要素をフォーカスする
      </button>
    </div>
  );
};

// POINT 動画の再生・停止を制御
const Case2 = () => {
  const [playing, setPlaying] = useState("");
  const videoRef = useRef();

  return (
    <div>
      <h3>ユースケース2</h3>
      <video style={{ maxWidth: "100%" }} ref={videoRef}>
        <source src="./sample.mp4"></source>
      </video>
      <button onClick={() => {
        if (playing) {
          videoRef.current.pause();
        } else {
          videoRef.current.play();
        }
        setPlaying(prev => !prev);
      }}>
        {playing ? "停止" : "再生"}
      </button>
    </div>
  );
};

/* POINT useRefは再レンダリングされません。
書き換え可能な情報としてコンポーネントに保持させておくことができます。
state は更新されるごとに再レンダーされますが、refオブジェクトの中身が変わっても再レンダーが走ることはありません。
*/
const createTimeStamp = () => new Date().getTime();
const Case3 = () => {
  const [timeStamp, setValue] = useState(createTimeStamp());
  const ref = useRef(createTimeStamp());

  const updateState = () => setValue(createTimeStamp());
  const updateRef = () => {
    /* コンソールを見るとブラウザの表示と、ref.currentの中身が異なることを確認できます */
    ref.current = createTimeStamp();
    console.log("ref.current -> ", ref.current);
  };

  return (
    <div>
      <h3>ユースケース3</h3>
      {/* useStateは再レンダリングされるため、更新される */}
      <p>
        state: {timeStamp}
        <button onClick={updateState}>更新</button>
      </p>
      {/* useRefは再レンダリングされないため、更新されない */}
      <p>
        ref: {ref.current}
        <button onClick={updateRef}>更新</button>
      </p>
    </div>
  );
};

const Example = () => {
  return (
    <>
      <Case1 />
      <Case2 />
      <Case3 />
    </>
  );
};

export default Example;

添付の画像の様な画面となる

useRefとuseStateの違い

useRefは再レンダリングが発生しないため、DOMの値が更新されない

ReactでJSON serverとaxiosを使用して、RestAPIのサーバー通信をする

{
  "todo": [
    {
      "id": "c5868bfe-fa1d-4891-acd3-bc43959a9bb7",
      "content": "洗濯",
      "editing": false
    },
    {
      "id": "5d87d115-7ebb-4d17-adce-4ffe4b39f8c5",
      "content": "掃除",
      "editing": false
    },
    {
      "id": "f2c38014-e2df-40ae-ac93-36303b8771ce",
      "content": "買い物",
      "editing": false,
      "completed": false
    }
  ],
  "user": [
    {
      "id": 1,
      "username": "hoge太郎",
      "age": 20,
      "hobbies": ["サッカー", "野球"],
      "premiumAccount": true
    },
    {
      "id": 2,
      "username": "fuga太郎",
      "age": 17,
      "hobbies": ["カメラ"],
      "premiumAccount": false
    },
    {
      "id": 3,
      "username": "piyo三郎",
      "age": 50,
      "hobbies": ["筋トレ", "水泳"],
      "premiumAccount": true
    }
  ]
}

JSON serverに上記ファイルのデータを渡すことで、モックを作成することができる。

を立ち上げると下記のURL(ドメインとポートは環境次第)が生成される。

http://127.0.0.1:3003/todo
http://127.0.0.1:3003/user

[
  {
    "id": "1",
    "username": "hoge太郎",
    "age": 20,
    "hobbies": [
      "サッカー",
      "野球"
    ],
    "premiumAccount": true
  },
  {
    "id": "2",
    "username": "fuga太郎",
    "age": 17,
    "hobbies": [
      "カメラ"
    ],
    "premiumAccount": false
  },
  {
    "id": "3",
    "username": "piyo三郎",
    "age": 50,
    "hobbies": [
      "筋トレ",
      "水泳"
    ],
    "premiumAccount": true
  }
]

例えば、「http://127.0.0.1:3003/user」にアクセスすると、上記のデータを確認することができる。

import { useEffect, useState } from "react";
import axios from "axios";

// 前提条件
// AP のモックを手軽に作成できるnode.jsのライブラリであるJSON serverをインストールする
// 16_rest_api\db\db.json
// JSON serverに上記ファイルのデータを渡す
// 
// http://127.0.0.1:3003/todo
// http://127.0.0.1:3003/user
// npm startを実行することで上記のURLが起動する

// axiosライブラリでHTTPリクエストを使ってサーバからデータを取得する

const Example = () => {
  const  [users, setUsers ] = useState([]);
  // useEffect(() => {
  //   axios.get("http://127.0.0.1:3003/todo").then((res) => {
  //     console.log(res.data);
  //   });
  // })
  // Exampleコンポーネントの初回レンダリングのみ実行するように、依存配列に空の配列を渡す
  useEffect(() => {
    // awaitを使用する場合は、関数の前にasyncを記述する
    const getUer = async () => {
      const res = await axios.get("http://127.0.0.1:3003/user");
      // console.log(res.data);
      setUsers(res.data);
    }
    getUer();
  }, [])
  return (
    <div>
      {users.map(user => {
        return (
          <div key={user.id}>
            <h3>{user.username}</h3>
            <p>age:{user.age}</p>
            <p>hobbies:{user.hobbies.join(',')}</p>
          </div>
        )
      })}
    </div>
  );
};

export default Example;

上記のコードで画面上に出力される。