SYM's Tech Knowledge Index & Creation Records

「INPUT:OUTPUT=1:1以上」を掲げ構築する Tech Knowledge Stack and Index. by SYM@設計者足るため孤軍奮闘する IT Engineer.

React 基本概念等

React 基本概念等

コンセプト

  • Declarative(宣言的)
    • 宣言的プログラミング:出力の性質やあるべき状態を記述してプログラムを構成する。関数型プログラミング含む。
  • Component-Based(コンポーネントベース)

  • Just The UI(UI にしか関知しない)

  • Virtual DOM(仮想DOM)

  • One-Way Dataflow(単方向データフロー)

  • Learn Once, Write Anywhere(ひとたび習得すれば、あらゆるプラットフォームで開発できる)

stateのリストアップ

子に親のstateを更新する関数を譲渡、子の変更=直接親のstate更新。(子→親の向けなので)リフトアップ

例:フォームを実装

コンポーネントとprops

props = properties。{ 属性名: 属性値 } の形式。

関数コンポーネント推奨。

コンポーネントライフサイクル

  1. Mounting フェーズ: コンポーネント初期化、仮想DOMにマウントされるまでのフェーズ。このフェーズで初めてコンポーネントレンダリングされる
  2. Updating フェーズ: 差分検出処理エンジンが変更を検知してコンポーネントが再レンダリングされるフェーズ
  3. Unmounting フェーズ: コンポーネントが仮想DOMから削除されるフェーズ
  4. Error Handling フェーズ: 子孫コンポーネントのエラーを検知、捕捉するフェーズ

コンポーネントライフサイクル図

※ 16.3以降は componentWillMount, componentWillReceiveProps, componentWillUpdate 非推奨(いずれ完全削除予定

Presentational Component / Container Component

Presentational Component はスタイルガイドと共存させやすく (スタイルガイドに登録することでデザインの運用に活用できてるため) 再利用性が高まる

公式が推奨するコンポーネントの正しい作り方

  1. デザインモックを作成、そのUIをコンポーネントの構造に分解 (Presentational)
  2. (ロジックを除外した) 静的に動作するバージョンを作成 (Presentational)
  3. UI を表現するために最低限必要な「状態」を特定 (Container)
  4. 3 の「状態」の配置場所を決定 (Container)
  5. (階層構造を逆のぼって考え) データが上階層から下階層に流れるようにする (Container)
観点 Presentational Container
関心点 どのように見えるか どのように機能するか
マークアップ 内部にDOMマークアップを多く持つ DOMマークアップを可能な限り持たない
データ/振舞い propsとして一方的に受け取る 他のコンポーネントに受け渡す
Flux依存度 (Fluxの)store等に依存しない (Fluxの)actionを実行したり、storeに依存する
状態 自身の状態を持たない(UIの状態は持つ) データの状態を持つ
データ更新 データの変更に介入しない データの変更に介入し任意の処理を行う
実装 関数コンポーネントで表現されることが多い (関数コンポーネントでも可能だが)HOC、Render Props、Hooksを使うことが多い

Hooks (関数コンポーネント合体強化パーツ)

HOC:高階関数コンポーネント (High Order Component)

type Props = { target: string };
const HelloComponent: FC<Props> = ({ target }) => <h1>Hello {target}!</h1>;
export default withTarget(HelloComponent);

Render Props:レンダリングのための関数をprops として受け取る

type Props = { target: string };
const HelloComponent: FC<Props> = ({ target }) => <h1>Hello {target}!</h1>;
<TargetProvider render={HelloComponent} />
const TargetProvider: FC<{ render: FC<Props> }> = ({ render }) => render({ target: 'Patty' });

HOC、render propsの問題

  • 共通して抱えていた問題は、ロジックの追加が著しくコンポーネントツリーを汚染してしまうこと
  • HOC もrender props も状態を持つロジックを分離できても、積極的に再利用できるほどには抽象化できなかった

Hooks

  • 状態を持ったロジックを完全に任意のコンポーネントから分離できる
  • それ単独でテストや別コンポーネントでの再利用が簡単にできる
  • コンポーネントの階層構造を変えることなく、状態を伴った再利用可能なロジックを追加できる

  • (ライフサイクルメソッドの反省のもとに)時間によって切り分けるのではなく、機能ごとに副作用を伴う処理をまとめて記述できる仕組みを提供

  • 機能ごとにまとまっているため、それをコンポーネントから切り離して別コンポーネントで再利用するのことが容易に可能

Hooks実装

  • statusを扱う : useState()
  • 副作用を扱る: useEffect()
  • メモ化: useMemo() 計算結果をコンポーネントシステムの外に保存しておくことでコンピュータリソース削減

実装(基本)

チュートリアル

サンプル

create-react-app すると以下ができるイメージ

.
|--package.json
|--package-lock.json
|--public
|  |--index.htm
|--src
|  |--App.js
|  |--index.js
|  |--styles.css
// index.js
import { StrictMode } from "react";
import ReactDOM from "react-dom";
import App from "./App";

const rootElement = document.getElementById("root");
ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  rootElement
);
// App.js
import "./styles.css";

export default function App() {
  return (
    <div className="App">
      <h1>Hello CodeSandbox</h1>
      <h2>Start editing to see some magic happen!</h2>
    </div>
  );
}

JSX

HTML記述

返却するHTML要素は、1タグで囲われていなければならない

  • 単一
import React from 'react';
import ReactDom from "react-dom";

const App = () => {
  return <h1>Hello</h1>;
}

ReactDom.render(<App />, document.getElementById("root"));
  • 複数

<div>で囲うと余計な要素がレンダリングされる。その時は<React.Fragment>で囲う。

<React.Fragment><>~</>で省略可

import React from 'react';
import ReactDom from "react-dom";

const App = () => {
  return (
    <>
      <h1>Hello</h1>
      <p>topic</p>
    </>
  );
}

ReactDom.render(<App />, document.getElementById("root"));

コンポーネント

コンポーネント名は必ず先頭を大文字。推奨:パスカルケース

// App.jsx
import React from 'react';

const App = () => {
  return (
    <>
      <h1>Hello</h1>
      <p>topic</p>
    </>
  );
}

export App;
// index.js
import React from 'react';
import ReactDom from "react-dom";
import App from "./App";

ReactDom.render(<App />, document.getElementById("root"));

イベント実装

import React from 'react';

const App = () => {
  const onClickHandler = () => alert();
  return (
    <>
      <h1>Hello</h1>
      <button onClick={onClickHandler}></button>
    </>
  );
}

export App;

スタイル実装

CSSのプロパティ名もキャメルケースで書く

// App.jsx
import React from 'react';

const App = () => {
  const textStyle = {
    color: 'blue',
    fontSize: '16px'
  };
  return (
    <>
      <h1 style={{ color: 'red' }}>Hello</h1>
      <p style={textStyle}>topic</p>
    </>
  );
}

export App;

props

  • コンポーネントに渡す引数のようなもの。
  • props で条件などを渡して表示を切り替える。
// components/Message.jsx  受け取る側
import React from 'react';

const Message = (props) => {
  const textStyle = {
    color: props.color,
    fontSize: '16px'
  };
  return (
    <p style={textStyle}>{props.message}</p>
  );
}

export Message;
// App.jsx  渡す側
import React from 'react';
import Message from './components/Message';

const App = () => {
  const onClickHandler = () => alert();
  return (
    <>
      <h1 style={{ color: 'red' }}>Hello</h1>
      <Message color="blue" message="topic" />
      <button onClick={onClickHandler}></button>
    </>
  );
}

export default App;
  • コンポーネントタグで囲った値は props.children で渡る
  • propsには分割代入を使うべし
// components/MessageTwo.jsx  受け取る側
import React from 'react';

const MessageTwo = (props) => {
  const { color, children } = props;
  const textStyle = {
    color,  // プロパティ名と変数名同じなら省略可
    fontSize: '16px'
  };
  return (
    <p style={textStyle}>{children}</p>
  );
}

export default MessageTwo;
// App.jsx  渡す側
import React from 'react';
import MessageTwo from './components/MessageTwo';

const App = () => {
  const onClickHandler = () => alert();
  return (
    <>
      <h1 style={{ color: 'red' }}>Hello</h1>
      <MessageTwo color="green">topic2</MessageTwo>
      <button onClick={onClickHandler}></button>
    </>
  );
}

export App;

State (useState)

コンポーネントの状態。状態が変わると再レンダリングされる。

  • useState(): State(値)とセッターを取得する

例:カウントアップ

// App.jsx  渡す側
import React, { useState } from 'react';

const App = () => {
  const [num, setNum] = useState(0);
  const onClickCountUp = () => {
    setNum(num + 1);
  };
  return (
    <>
      <h1 style={{ color: 'red' }}>Hello</h1>
      <button onClick={onClickCountUp}>count up</button>
    </>
  );
}

export App;

レンダリングと抑止 (useEffect)

コンポーネントが再レンダリングされる条件

バグる例

以下をベースに

// App.jsx  渡す側
import React, { useState } from 'react';

const App = () => {
  const [isShow, setShowFlag] = useState(true);
  const [num, setNum] = useState(0);
  const onClickCountUp = () => {
    setNum(num + 1);
  };
  const onClickChange = () => {
    setShowFlag(!isShow);
  };
  return (
    <>
      <h1 style={{ color: 'red' }}>Hello</h1>
      <button onClick={onClickChange}>on/off</button>
      {isShow && <p>★★★</p>}
    </>
  );
}

export App;

機能追加する:3の倍数の時に表示(Too many re-renders.)

// App.jsx  渡す側
import React, { useState } from 'react';

const App = () => {
  const [isShow, setShowFlag] = useState(true);
  const [num, setNum] = useState(0);
  const onClickCountUp = () => {
    setNum(num + 1);
  };
  const onClickChange = () => {
    setShowFlag(!isShow);
  };

  if (num % 3 === 0) { // stateの値が変わるので無限再レンダリング
    setShowFlag(true);
  } else {
    setShowFlag(false);
  }

  return (
    <>
      <h1 style={{ color: 'red' }}>Hello</h1>
      <button onClick={onClickCountUp}>count up</button>
      <br />
      <button onClick={onClickChange}>on/off</button>
      {isShow && <p>★★★</p>}
    </>
  );
}

export App;
  • 対策後 (true -> true, false -> false の無駄な上書きしないようにする)
    • だが、on/off が利かなくなる
// App.jsx  渡す側
import React, { useState } from 'react';

const App = () => {
  const [isShow, setShowFlag] = useState(true);
  const [num, setNum] = useState(0);
  const onClickCountUp = () => {
    setNum(num + 1);
  };
  const onClickChange = () => {
    setShowFlag(!isShow);
  };

  if (num > 0) {  // 対策後
    if (num % 3 === 0) {
      isShow || setShowFlag(true);
    } else {
      isShow && setShowFlag(false);
    }
  }

  return (
    <>
      <h1 style={{ color: 'red' }}>Hello</h1>
      <button onClick={onClickCountUp}>count up</button>
      <br>
      <button onClick={onClickChange}>on/off</button>
      {isShow && <p>★★★</p>}  // ボタン押下 → 再レンダリング. isShowはnumに応じて決まるため機能しない
    </>
  );
}

export App;

別の機能が影響してバグる(num表示とon/off機能)。その場合はuseEffectを使って関心を分離する必要がある。

useEffect で関心を分離

  • 最初の一回のみ実行(第二引数を空配列にする)
useEffect(() => {
  // ここに書いた処理は最初の一回しか実行されない
}, []);
  • stateの値が変わった時のみ実行
const [num, setNum] = useState(0);

useEffect(() => {
  // num が変わった時だけ処理実行
}, [num]);
// App.jsx  渡す側
import React, { useState } from 'react';

const App = () => {
  const [isShow, setShowFlag] = useState(true);
  const onClickChange = () => {
    setShowFlag(!isShow);
  };

  useEffect(() => {
    if (num > 0) {
      if (num % 3 === 0) {
        isShow || setShowFlag(true);
      } else {
        isShow && setShowFlag(false);
      }
    }
  }, [num]);

  return (
    <>
      <h1 style={{ color: 'red' }}>Hello</h1>
      <button onClick={onClickCountUp}>count up</button>
      <br>
      <button onClick={onClickChange}>on/off</button>
      {isShow && <p>★★★</p>}
    </>
  );
}

export App;

※上記だと、Eslint で以下エラーになる /* eslint react-hooks/exhaustive-deps */

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

実践メモ

input の入力値をstateに即時反映

const App = () => {
  const [inputTodoText, setInputTodoText] = useState("");
  
  const onChangeTodoText = (event) => setInputTodoText(event.target.value);

  return (
    <input id="js-add-text" value={inputTodoText} onChange={onChangeTodoText} className="ul-brd ul-pad-6-16" type="text" placeholder="Todo入力" />
  );
}

イベントハンドラに渡すのは関数

onClick={onClickDelete(index)} としてしまうと即時実行される

<ul>
  {incompleteTodos.map((todo, index) => {
    <button className="ul-brd" onClick={() => onClickDelete(index)}>削除</button>
  })}
</ul>

default export と export

export推奨

  • default export

使う側で名前付けするので、タイポに気付きにくい

import Sample from './components/SampleComp';
  • export

使用時は分割代入必須かつ定義された名前以外はエラーになる=タイポ防止

import { SampleComp } from './components/SampleComp';