React

Hello World

index.html

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <title>Document</title>
  </head>
  <body>
    <div id="app"></div>
  </body>
  <script src="./index.tsx"></script>
</html>

index.tsx

import * as React from "react";
import * as ReactDOM from "react-dom";

ReactDOM.render(<p>Hello World!!!</p>, document.getElementById("app"));

便利なもの

clsx

https://www.npmjs.com/package/clsx

yarn add clsx

使用例。 visible=trueの場合はクラスにvisibleが追加される。

import clsx from "clsx";

interface Props {
  visible: boolean;
}

const AppMenu: React.FunctionComponent<Props> = props => {
  const { visible } = props;
  return <div className={clsx("app-menu", { visible })}>{props.children}</div>;
};

classnames

boolean型のtrue, falseによってスタイルを設定するときに簡単に書けるようになる。

yarn add classnames
const classNames = require("classnames");

React Router

https://reach.tech/router

React Hooks

useEffect で非同期処理(async/await)を実行する

useEffectをラップしたカスタムフックを作ればいい。

import { useEffect, DependencyList } from "react";

export default function useEffectAsync(
  effect: () => any,
  deps?: DependencyList
) {
  useEffect(() => {
    effect();
  }, deps);
}
useEffectAsync(async () => {
  // 初期マウント時に一度だけデータを非同期で取得する
  const items = await fetchSomeItems();
  console.log(items);
}, []);

Ref: https://stackoverflow.com/a/54637708

最初のマウント時のみ処理する

useEffectの第二引数に[]を指定することで再描画時の処理はせず、最初の描画時のみ処理ができる。 イベントリスナの登録などに便利。

useEffect(() => console.log("initial mounted."), []);

現在の state を非同期処理の中でアクセスする

useEffectでコンポーネントマウント時にaddEventListenerし、その処理の中で state にアクセスしたい場合は、useRefを使う。

以下のようにカスタムコンポーネント化すると便利。

const useRefState = <T>(
  initialValue: T
): [T, React.MutableRefObject<T>, React.Dispatch<React.SetStateAction<T>>] => {
  const [state, setState] = useState<T>(initialValue);
  const stateRef = useRef(state);
  useEffect(() => {
    stateRef.current = state;
  }, [state]);
  return [state, stateRef, setState];
};

5 秒間カウントアップしながらアラートを表示する場合の例。

const component = () => {
  const [count, countRef, setCount] = useRefState(0);
  useEffect(() => {
    setTimeout(() => {
      alert("Value: " + counterRef.current);
    }, 5000);
  }, []);
  return <div>{count}</div>;
};

Ref: https://blog.castiel.me/posts/2019-02-19-react-hooks-get-current-state-back-to-the-future/

描画後に DOM に対して処理

普通にuseRefを使う例。

const component = () => {
  const ref = useRef();
  const handleClick = () => console.log(ref.current);
  return (
    <p ref={ref} onClick={handleClick}>
      hoge
    </p>
  );
};

しかし、useRefuseEffectを組み合わせて、**初期描画時に 1 度だけ DOM に対して処理(css クラスの追加など)**をしたい場合はこの方法が使えない。 理由はuseEffectのタイミングではrefが null になる場合があるため。 そういう場合はuseCallbackを使う。

const component = () => {
  const applyClass = useCallback(node => {
    if (node != null) {
      const el = node as any;
      el.classList.add("new-class");
    }
  });

  return <p ref={applyClass}>hoge</p>;
};

Ref: https://github.com/facebook/react/issues/14387#issuecomment-493677168

インストール

yarn add next react react-dom

# TypeSciriptを使う場合は追加で入れる
yarn add -D @types/next

ルール

pagesディレクトリの中にあるコンポーネントは自動で URL と紐づけられる

pages
  - Index.jsx
  - About.jsx
  - contents
    - ContentA.jsx

この場合、以下のページが存在することになる。

  • localhost:3000/index
  • localhost:3000/about
  • localhost:3000/contents/contentA

共通コンポーネントや util 的なコンポーネントはpages以外に置く。

スニペット

input[type="text"]の値を state に保持する

export default () => {
  const [state, setState] = useState("");
  return <input value={state} onChange={e => setState(e.target.value)} />;
};

DOMのカスタム属性(data-***)を利用する

const Component = () => {
  const [hoge, setHoge] = useState('aaa')

  return <div className='my-component' data-hoge={hoge}></div>
}
.my-component {
  &[data-hoge='aaa']{
    background: red;
  }
}

useStateで関数を管理する

関数をそのままuseStateの引数に渡すとFunctionalUpdate扱いされてしまうのでオブジェクトとして渡す。

const [func setFunc] = useState({fn: () => 'hoge'})

// 使い方
func.fn()
setFunc({fn: () => 'fuga'})

Ref: https://qiita.com/terrierscript/items/6a8acbc7d1ce6521f879

カスタムフックにすると便利。

const useStateF = <T extends (a: any) => any>(initialState: T): [T, (fn: T) => void] => {
  const [f, setF] = useState<{ fn: T }>({ fn: initialState })
  return [f.fn, fn => setF({ fn })]
}

// 使い方
const [myFunc, setMyFunc] = useStateF<() => void>(() => console.log('hello'))
myFunc()
setMyFunc(() => console.log('world))

props.childrenについて

汎用的なコンテナコンポーネントを作る場合にchildrenを利用できる。

Ref: コンポジション vs 継承 – React

React の最上位 API – React

関数としてchildrenを渡す

Ref: A deep dive into children in React - Max Stoibers Blog

コンポーネント作成

ひな形

import * as React from "react";

interface Props {
  label: string;
  onClick?: () => void;
}

const Button: React.FunctionComponent<Props> = props => {
  return (
    <div className="button" onClick={props.onClick}>
      {props.label}
    </div>
  );
};

export default Button;