React備忘録(その1)

Reactは、ユーザーインターフェース構築の為のJavaScriptライブラリです
ライブラリなのでHTMLの一部に利用するなど、小さくはじめることもできますが…
*JSXを使用する場合はコンパイルが必要です参考URL
なので個人的にはCDNを使う場合、Vue.jsを選択します^^;

create-react-app
シングルページアプリケーションを作成したい場合のReactに必要な環境を構築できます

npx create-react-app プロジェクトのディレクトリ名

備考:React開発のフレームワーク: Next.js・Gatsby

目次
  1. JSX
  2. コンポーネントとprops
  3. Stateとライフサイクル
  4. StateとProps
  5. Reactでのイベント処理
  6. React Hooks
    1. useState
    2. useReducer
    3. useContext
    4. useEffect
    5. useMemo・React.memo・useCallback
  7. カスタムフックス

JSX

JSXはReact.createElementのシンタックスシュガー(構文を別の簡単な記法で書けるようにしたもの)です
HTMLのようですがJavaScript式(値を生成し、変数に代入できるもの)です
最終的にはオブジェクトReact要素)を生成します
*babelでReact.createElementに変換→関数を実行→オブジェクト

//JSX
const element = (
  <h1 className="greeting">
    Hello, world!
  </h1>
);

//React.createElement
const element = React.createElement(
  'h1',//タグ
  {className: 'greeting'},// 属性 props
  'Hello, world!'//text
);
  • 属性のclassはclassNameにします
  • 属性(イベントなど)はキャメルケースになります
  • JSXのなかでJavaScriptのコード(変数や式)を使う場合{}で囲います
    *JSX内では三項演算子で条件分岐できます
    *デフォルトで、JSX に埋め込まれた値はレンダー前にエスケープされます
    *配列は{配列の変数名}で展開されます
  • JSXには1つの親要素だけしか含められません
    *Fragmentは子要素をまとめるために、不要なdivなどを追加せずに、唯一の親要素として機能します
    また<>…</>で利用できます

Reactだけで構築されたアプリケーションでは、ReactDOMによって管理されるルートDOMノード(<div id="root"></>)を通常は1つだけ持ちます
*Reactを含める場合は、複数のルートDOMノードも可能
React要素をルートDOMノードにレンダーするには、ReactDOM.createRoot() に DOM要素を渡し
root.render() に React要素を渡します

const root = ReactDOM.createRoot(
  document.getElementById('#root')
);
root.render(<h1>Hello, world</h1>);

JSXはコンパイルが必要ですが、手軽に試してみるなら
<script>タグの中でJSX が使えるようになります
*重くなり、本番環境には向きません

<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
<script type="text/babel">

</script>

コンポーネントとprops

関数コンポーネントとクラスコンポーネント

2019年にReact16.8で「React Hooks」が導入されて以降、関数コンポーネントが主流です
「React Hooks」が使われる前は、関数コンポーネントでは状態が持てなかったので、状態を持つコンポーネントはクラスコンポーネントで実装していました

基本的に、ユーザ定義のコンポーネントを関数で定義します(関数コンポーネント
関数コンポーネントはUI要素を返す関数です

関数コンポーネント
*関数名の先頭は大文字
props(オブジェクト)を引数に取り、JSXを返します(propsはコンポーネントに値を渡す時に使います)
*propsにはすべてのデータ型(関数など)も渡せます
例:Welcomeコンポーネントの呼び出し時に、props として {name: 'Sara'} を渡します
*コンポーネント名の先頭は大文字(DOMタグと区別するため)にします

//関数コンポーネントの定義
function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

const root = ReactDOM.createRoot(document.getElementById('#root'));
root.render( <Welcome name="Sara" />);

propsを引数に受け取り、React要素を返します

  • propsは読み取り専用です(変更してはいけません)、変更はstateで実現します
  • propsの流れは一方通行です(親コンポーネントから子コンポーネントへ)

propsの便利な渡し方(オブジェクト)

const p = {
 color : "red", 
 bg : "white"
}
<Child color={p.color} bg={p.bg} />
//これだけでOK
<Child {p} />

propsの便利な受け方(オブジェクト)

const Hello = ({color, bg})=>{
//使うときにprops.を省略できます
}

クラスコンポーネント

//クラスコンポーネントの定義
class Welcome extends React.Component {
  render() {
    return <h1>Hello, {this.props.name}</h1>;
  }
}

children(特別なprops)
コンポーネントの開始タグと終了タグではさまれた「childrenというprops」を使い、受け取った子要素を出力できます
例えば:SidebarやContainerのような「汎用的な入れ物」をあらわすコンポーネントで利用します
<Sidebar>・・・<Sidebar/>
コンポーネントをpropsで渡すこともできます

function Sidebar(props) {
  return (
    <div>
      {props.children}
    </div>
  );
}
//
function Welcome() {
  return (
    <Sidebar>
===この部分がchildren
      <h1 className="Sidebar-title">
        Welcome
      </h1>
      <p className="Sidebar-message">
        message
      </p>
===この部分がchildren
    </Sidebar>
  );
}
//コンポーネント
 <Sidebar>
   <Hoge />
 <Sidebar>

コンポーネントを条件的にレンダーする

//if 文を使用
 if (trueなら) {
    return <Hellow />;
  }

//条件部分がtrueであれば&&の後に書かれた要素を表示
{trueなら && <Hellow />}

//true?の後に書かれた要素を表示 falseなら:の後に書かれた要素を表示
{trueなら ? <Hellow /> : ''}

子コンポーネントを配列として親コンポーネントの内部でレンダリングする
map() 関数を使い、配列のそれぞれの要素にkey(更新時にkeyを利用して効率的に差分を見つけたため)を割り当ててます

function NumberList(props) {
  const numbers = props.numbers;
//returnの外
  const listItems = numbers.map((number) =>
    <ListItem key={number.toString()}
              value={number} />
  );
  return (
    <ul>
      {listItems}
    </ul>
  );
}
function NumberList(props) {
  const numbers = props.numbers;
//returnの内
  return (
    <ul>
      {numbers.map((number) =>
        <ListItem key={number.toString()}
                  value={number} />
      )}
    </ul>
  );
}

const numbers = [1, 2, 3, 4, 5];
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<NumberList numbers={numbers} />);

備考:「stateの値」はコンポーネント毎に独立して管理されています
が、同じ位置に表示された同じコンポーネントの「同じstateの値」を分けて管理する場合は「key属性(コンポーネントを一意に識別する)」を使う必要があります

Stateとライフサイクル

画面表示を変更するには
Reactに「コンポーネントの再レンダリング」をしてもらう必要があり
変更される値は「State」に保持します

ライフサイクル
コンポーネントがDOMツリーにロードされてからDOMツリーから削除される過程(コンポーネントの再レンダリング)で、大きく分けて3つのフェーズがあります
それぞれフェーズに処理を差し込むことのできるライフサイクルメソッドがあります

  • Mounting:マウント(DOMの初回ロード)された時
  • Updating:コンポーネントが更新された時
  • Unmounting : アンマウント(DOMから削除)された時

Reactでは、更新するたびにページ全体を書き換える(DOMを作り直す)のではなく、JSXにより仮想DOMを比較します
propsやstateの変更前と後を比較し、その差をチェックして、新たな仮想DOMを構築し、レンダリングの結果を画面に表示します(DOM操作を必要最小限にします)


備考:

クラスコンポーネントでのstateの更新とライフサイクルメソッド
ローカルなstateを追加する
stateの変更は、setState() を使用します
setState() を呼び出した場合、与えられたオブジェクトを現在のstateにマージします
メソッドを宣言することで、コンポーネントがマウントしたりアンマウントしたりした際にコードを実行することができます

class Clock extends React.Component {
//クラスのコンポーネントはpropsを引数として親クラスのコンストラクタを呼び出す
  constructor(props) {
    super(props);
//ローカルなstate
    this.state = {date: new Date()};
  }
//マウント時
 componentDidMount() {

  }
//アップデート時
 componentDidUpdate() {

  }
//アンマウント時
 componentWillUnmount() {
//クリーンアップ(コンポーネントが占有していたリソースを解放する)が必要な場合があります
//例えば:removeEventListenerを使ってイベント登録を解除するなど
  }

  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Clock />);

StateとProps

  • コンポーネント
    Reactのコンポーネントは再利用可能なコードの部品であり、それぞれが独自のロジックとレイアウトを持ちます。大規模なReactアプリケーションは、一般的に多数のコンポーネントから構成されます。
  • State
    Stateはコンポーネントの内部状態を表します。ユーザーの入力やAPIからのレスポンスなど、時間と共に変わり得るデータを扱います。Stateはコンポーネント内でのみ直接変更できます。
  • Props:
    親コンポーネントから子コンポーネントへデータを渡すための手段です。Propsは読み取り専用で、子コンポーネント内では変更できません。

ベストプラクティス

  • 全てのデータは、単一方向データフロー(上から下へ)
  • 上位コンポーネントでなるべく状態を持ち、末端のコンポーネントはなるべく状態をもたないようにする
  • propsやstateから派生的に作りだす事のできるデータは、stateに保持すべきではありません
  • propsとして渡せるものに制限はありません(コンポーネントも渡せます)
  • 親コンポーネントで「stateを保持」して、子コンポーネントに「props」として渡すケース(Reactの一般的なパターン)
  • Reactのコンポーネントがアンマウント(消滅)するとき、そのコンポーネントのstateも消滅します。そのため、そのstateを他のコンポーネントが必要とする場合、それを上位の親コンポーネントにリフトアップ(データ(状態)を一つの中央の場所(親コンポーネント)に移動させる)することが推奨されます
  • 特定のstateを複数の子コンポーネントで共有したいときは特定のstateが複数のコンポーネントで必要な場合、そのstateを共通の親に持つことで子コンポーネント間でstateを共有することができます
  • 親からpropsを通じて与えられたデータ、時間経過で変化しないデータ、コンポーネント内にある他のpropsやstateを使って算出可能なデータは「state」にはしません

Reactでのイベント処理

JSXのタグに「onClick」などの「Reactのイベント(SyntheticEvent)」を書き、イベントが発生したときに関数を実行するようにします

ReactのイベントはstopPropagation() preventDefault() を含む、ブラウザと同じ挙動をするようになっています

イベント名はキャメルケース(例:onSubmit)です
イベントハンドラには関数を渡します

function Form() {
  function handleSubmit(e) {
//デフォルトの動作を抑止するにはpreventDefault()
    e.preventDefault();
    console.log('You clicked submit.');
  }

  return (
    <form onSubmit={handleSubmit}>
      <button type="submit">Submit</button>
    </form>
  );
}

onClickで値を渡す場合

const handleClick = (val) => {
        console.log(val)
 }

onClick={()=>handleClick(渡す値)}

React Hooks

Hooksは関数コンポーネントのレンダリングの間に処理を差し込むための関数です
クラスコンポーネントでは使いません

Hooksのルール

  • フックを書く場所はコンポーネント内のトップレベルです(ループや条件分岐、ネストされた関数内で呼び出さない、フックの内部に入れます)
  • フックを呼び出すのはReactの関数内のみ(JavaScript関数から呼び出さない)

useState

コンポーネントを再レンダリング(コンポーネントの関数を実行)してstateの値を更新する仕組みが「useState」です
次回以降のレンダリング時には、「useState」から「stateの現在値」を受け取ります

値と値を更新するための関数を返します

useState()は配列を返すので、分割代入で利用しています

import { useState } from 'react';
//コンポーネントの内部でuseStateを使う
const [state変数, 更新するための関数] = useState(初期値);

分割代入を使うことで、state変数に異なる名前をつけることができます
複数のstate変数はそれぞれを個別に更新することができます

import React, { useState } from 'react';

function Example() {
  // 変数名・更新するための関数・初期値
  const [count, setCount] = useState(0);
  return (
    <div>
 //クリックするとcountに1をたす
      <button onClick={() => setCount(count + 1)}>
        Click
      </button>
    </div>
  );
}

注意点:再レンダリングされるまでは「stateの値」は更新されません
useStateは非同期処理されます(再レンダリング後に更新されます)
前回のstateの値を使用するケースでは
setCount(count + 1)ではなく
setCount(prevCount => prevCount + 1)と書く
*前の値が引数「prevCount」に渡る
要するに:関数を渡すとその関数内で現在のstateの値へアクセスできます

const countUp = () => {
setCount(prevCount => prevCount + 1)
}
<button onClick={countUp}>
   Click
</button>
//注意点
//この場合1回のクリックで2回更新されるわけではありません
const countUp = () => {
  setCount(count + 1)
  setCount(count + 1)
}
//前の値が引数prevCountに渡るの2回更新されます
const countUp = () => {
setCount(count + 1)
setCount(prevCount => prevCount + 1)
}

ユーザがフォームを変更するたびに、ユーザ入力を反映するようなコンポーネント

const App = () => {
  const [text, setText] = useState("");

  return (
    <div className="App">
      <input
        value={text}
        onChange={(event) => setText(event.target.value)}
      />
      <p>{text}</p>
    </div>
  );
};

「state」にプリミティブ型(オブジェクトや配列)を保持した場合
注意更新は「新しいオブジェクト」を返します
変更のないプロパティも更新する必要があります
「プロパティの数が多い場合」や「変更のないプロパティがある場合」スプレッド構文が便利です

hoge{
a:'a',
b:'b'
}
//NG
setHoge(hoge.a= 'aa')

setHoge({ a: 'aaa', b: 'bbb'})
setHoge({...hoge})
setHoge({...hoge, a:'aaa'})
pre=>{
 const newState = {...pre};
 // なんらかの処理
  return newState
}

子コンポーネントが親コンポーネントに何かを伝えるためには、通常は親から子へ関数をpropsとして渡すという方法を取ります。そして、その関数を子コンポーネントが呼び出すことで、親コンポーネントに情報を伝えます

子コンポーネントのイベントで、親コンポーネントの「state」を更新する
*親から子へ「props」で「useStateのstateとsetState」を渡す
備忘録:同じ関数内(例:1つのイベント)に、propsで渡ってきたsetStateとそのコンポーネントのsetStateは混在できません

//親
const App = () => {
   const [count, setCount] = useState(0);
  return (
    <Count count={count} setCount={count} />
  );
};
//子
const Count = ({ count, setCount }) => {
 return (
    <div>
      <div>{count}</div>
      <button onClick={() => setCount(count + 1)}>
        Click
      </button>
    </div>
  );
}

useReducer

useStateより複雑な状態管理を行えるフックです(状態と更新方法の両方を担当)
dispatchを下位コンポーネントに渡せます

「useState」では更新の仕方は利用側が担当しますが、「useReducer」では状態の更新の仕方も状態側で担当します
stateの更新処理をコンポーネントから切り離すことができます

const [state, dispatch] = useReducer(reducer, initialState, init);

reducerは、useStateのsetStateと同じく変更後の新しいstateを返却する関数です
useReducerは、reducerと初期値を受け取り、現在のstateをdispatchメソッドとペアにして返します
イベントリスナー(コンポーネント側)からdispatchで渡ってきたstateとactionを「reducerの引数」で受け取ります
dispatchは通常actionをオブジェクトの形で引数に取ります
action.type(変更指示書のようなもの)をチェックすることでtypeによって異なる処理ができます
action.payloadはreducerに値を渡す場合に設定します

//イベントリスナー(コンポーネント側)
dispatch({type: '', payload: ''})
const initialState = {count: 0};
//reducer関数
function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return {count: state.count + 1};
    case 'decrement':
      return {count: state.count - 1};
    default:
      throw new Error();
  }
}

function Counter() {
//useReducerにreducerと初期値を渡す
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <>
      Count: {state.count}
//dispatchで登録したactionを呼び出します
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
    </>
  );
}

初期値(initialState)作成を遅延させる場合は、init関数を第3引数に渡します

function init(initialCount) {
  return {count: initialCount};
}

function reducer(state, action) {
  switch (action.type) {
    //
    case 'reset':
      return init(action.payload);
    //
  }
}

function Counter({initialCount}) {
  const [state, dispatch] = useReducer(reducer, initialCount, init);
  return (
    <>
      Count: {state.count}
      <button
        onClick={() => dispatch({type: 'reset', payload: initialCount})}>
        Reset
      </button>
     //
    </>
  );
}

useContext

コンテクストは、React コンポーネントのツリーに対してグローバル(どのコンポーネントからでもアクセスできる)になります
props経由で値や状態を渡しません(中間のコンポーネントが必要のないpropsを受け取ると再利用が難しくなる場合があります)

  1. 上位のコンポーネントでContextを作成
  2. 受け取るコンポーネント側でuseContextを使用して受け取る

上位のコンポーネント
createContextはContext(コンテクストオブジェクト)を作成します
*引数(defaultValue)は、プロバイダを持っていない場合のみ使用される値です
デフォルト以外で設定するには、ツリーの上の階層<MyContext.Provider>が必要です

import {createContext } from 'react';

const MyContext = createContext(defaultValue);

コンテクストオブジェクトにはプロバイダ (Provider)コンポーネントが付属しています
プロバイダのvalueプロパティが変更されるたびに再レンダリングされます

<MyContext.Provider value={/* 何らかの値 */}>

受け取る側のコンポーネント
useContextは、コンテクストオブジェクト(createContext からの戻り値)を受け取り、そのコンテクストの現在値を返します
useContextの引数はコンテクストオブジェクトです
コンテクストの現在値は、上位にあるProviderからコンテクストの現在値(valueの値)を取得します

import { useContext } from "react"; 
import { MyContext } from "../Contextを作成したコンポーネント";

const value = useContext(MyContext);

ドキュメントの例

const themes = {
  light: {
    foreground: "#000000",
    background: "#eeeeee"
  },
  dark: {
    foreground: "#ffffff",
    background: "#222222"
  }
};
// 現在のテーマ(デフォルトは "light")の為のコンテクストを作成します。
const ThemeContext = createContext(themes.light);
// このサンプルでは、"dark" を現在の値として渡しています。
function App() {
  return (
    <ThemeContext.Provider value={themes.dark}>
      <Toolbar />
    </ThemeContext.Provider>
  );
}

function Toolbar(props) {
  return (
    <div>
      <ThemedButton />
    </div>
  );
}
// この例では、現在のテーマは "dark" です。
function ThemedButton() {
  const theme = useContext(ThemeContext);
  return (
    <button style={{ background: theme.background, color: theme.foreground }}>
      I am styled by theme context!
    </button>
  );
}

useReducerと組み合わせて、グローバルにdispatchを使えるようにできます

useEffect

useEffectはライフサイクルメソッドがまとまったものです

useEffect(レンダリング後に実行される関数(コールバック), 依存変数の配列)

どんな時に必要?
コンポーネントが生成されたときに1度だけ呼びたい処理 (依存変数の配列を [])
stateの変化を監視して再レンダリングしたいとき
コンポーネントでの副作用をともなう処理(サーバーとの通信などJSXを作ることと関係ない処理)

特定の値が変わったとき
useEffect のオプションの第2引数として配列を渡します
1度だけ実行したい場合、空の配列 []を第 2 引数として渡します

useEffect(() => {
  document.title = `You clicked ${count} times`;
}, [count]); //カウントが変更された場合にのみ再実行

useEffect(() => {
  document.title = `You clicked ${count} times`;
}, []); //1度だけ実行したい

注意:useEffect内で依存変数(監視しているstate)を更新してはいけません

state変数を宣言し、副作用を使うことをReactに伝えています
これでReactがコンポーネントのレンダリング時に、副作用を呼び出しDOMが更新されます
*useEffectに渡された関数はレイアウトと描画の後で遅延されたイベントとして実行されます
useLayoutEffect は値を更新後、画面表示される前、useEffectよりも先実行されます

import { useState, useEffect } from 'react';

export const Example=()=> {
  const [count, setCount] = useState(0);

  useEffect(() => {
    document.title = `You clicked ${count} times`;
  });
}

クリーンアップ処理(ライフサイクル)
useEffectのreturn句
依存変数が空([]):コンポーネントが削除されるタイミングで呼ばれる関数を登録できます
依存変数が監視しているstate:前回の更新がされた後に呼ばれる関数を登録できます

 useEffect(() => {
    // 初回レンダリング時に実行する処理
    return () => {
      // コンポーネントが削除される時に実行する処理
    };
 },[]);

useMemo・React.memo・useCallback

レンダリングコストが高いコンポーネントなどの再レンダリングを防ぎパフォーマンスを最適化する仕組み

レンダリングのタイミングは「propsが変更されたとき」「stateが変更されたとき」です
親コンポーネントがレンダリングされると子コンポーネントもレンダリングされます

のメモ化(重い処理に利用)
useMemo
コンポーネントの再レンダリング時に値を再利用できます
依存配列の要素のいずれかが変化した場合にのみメモ化された値を再計算します

import { useMemo } from "react"

return useMemo(()=>{
//返す値がメモ化する値
},[])
useMemo(コールバック, [依存変数の配列]);

コンポーネントのメモ化
React.memo(高階コンポーネント):全体を囲みます
propsの変更のみをチェックし再レンダリングの判断をします
*countが更新されない限り、親コンポーネントのがレンダリングされても再レンダリングされません

import { memo } from "react"
export const ChildCount = memo(
  ({ count }) => {
    return (
      <div>
      <div>{count}</div>
    </div>
   );
  }
)

関数のメモ化
useCallback
propsが関数の場合、受け取った子コンポーネントはReact.memoを利用しても再レンダリングされます
useCallbackでメモ化した関数をpropsとして渡すことでレンダリングをスキップできます
useCallbackの中で使用している「state」があれば依存変数の配列に追加します

const useCallback(
 countUp = () => {
    setCount(prevCount => prevCount + 1)
  }, [])
useCallback(コールバック関数, [依存変数の配列]);

カスタムフックス

use」で始める関数名の関数を作ります(コンポーネントで使いたい変数やメソッドなどをreturnします)
カスタムフックを作成することで、コンポーネントからロジックだけ分離することができます

Hooksの関数はコンポーネント内のトップレベルでしか使用できませんが
例外的にカスタムフック内では使用できます
カスタムフックス(関数)を別ファイルに作り、利用するコンポーネントファイルにimportして使います