【React】 hook を使う

噂のhookを使ってみる。今更な気もするけど。ここを見ると何とかできそう。

準備

テンプレ作成。

~/development
❯ create-react-app try-hook

~/development 34s
❯ cd try-hook

hookを記述

App.jsにhookを書いてみる。

import React, { useState } from 'react';

function App() {

  const [count, setCount] = useState(0);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

export default App;

serve

yarnを使ってserveする。

yarn start

hook
hook

驚くほど、簡単に表示できた。

useEffect

これを使うと、click後に何かを仕込むことができると思ってた。
ボタンを押したら文字を赤くできるか試してみた。
以下の状態だと、初めから赤くなっている。なんで??

import React, { useState, useEffect } from 'react';

function App() {

  const [count, setCount] = useState(0);

  useEffect(() => {
    const elm = document.querySelector(".text");
    elm.style.color = "red";
  });

  return (
    <div>
      <p className="text">You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

export default App;

公式documentより

デフォルトでは副作用関数はレンダーが終了した後に毎回動作します

らしい。
もしかして、vueのwatchやcomputerに近い?
以下のように書いたら無限にcountが増加し始めた。

import React, { useState, useEffect } from 'react';

function App() {

  const [count, setCount] = useState(0);

  useEffect(
    () => {
      setCount(count + 1);
    }, [count]
  );

  return (
    <div>
      <p className="text">You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

export default App;

useEffectに対して、もっと調べた方がよさそう。

useContext

これ何に使うんだろう。

const value = useContext(MyContext);

サンプルで1行載ってた。
つまりどういうことなんだ?
調べてみる。

createContext()

こういうメソッドがあるらしい。useContextはこれをhookとして使えるようにしたものみたい。バケツリレーを回避するアプローチなのかな。 親側で定義したものを間を意識せず子供側で利用できるみたい。これは、好きかって親で定義してしまって、子供でも好き勝手使ってしまえるのでは? 重複して定義してしまったらどうなるんだろうか?いろいろ疑問が出る。

しかし、たまに同じデータがツリー内の異なるネスト階層にある多くのコンポーネントからアクセス可能であることが必要となります。

これを読むと、本当に限られたところでしか使うべきではないってことなのかな。よくよく読んでくと重複はできないようになっているっぽい。それに、

ツリー内の最も近い上位の一致する Provider から現在のコンテクストの値を読み取ります。

これのおかげで、好き勝手にcontextを使うみたいな感じではないかな。

実装

useContextを使ってみる。

import React, { useContext } from 'react';

const ContextA = React.createContext();
const ContextB = React.createContext();

function App() {
  return (
    <ContextA.Provider value={10}>
      <ContextB.Provider value={20}>
        <Preview />
      </ContextB.Provider>
    </ContextA.Provider>
  );
}

function Preview() {
  const valueA = useContext(ContextA);
  const valueB = useContext(ContextB);

  return <p>{valueA} + {valueB}</p>
}

export default App;

useReducer

reduxを簡単に実装できるのかな?調べながら書いてみた。

import React, { useContext, useReducer } from 'react';

const initialState = {count: 0};

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();
  }
}

const CountDispatch = React.createContext(null);

function App() {
  const [state, dispatch] = useReducer(reducer, initialState);

  return (
    <>
      Count: {state.count}
      <CountDispatch.Provider value={dispatch}>
        <IncrementButton />
        <DecrementButton />
      </CountDispatch.Provider>
    </>
  );
}

function IncrementButton() {
  const dispatch = useContext(CountDispatch);

  function handleClick() {
    dispatch({type: 'increment'});
  }

  return <button onClick={handleClick}>+ 1</button>
}

function DecrementButton() {
  const dispatch = useContext(CountDispatch);

  function handleClick() {
    dispatch({type: 'decrement'});
  }

  return <button onClick={handleClick}>- 1</button>
}

export default App;

これで無事に動いた。useContextとuseReducerを合わせながら書くといいのかもしれない。知らんけど。この辺りの知見が全く無いので今後調べてブログに書きたい。

useMemoとuseCallback

この2つは結構使われているのかな。なかなか簡単に学べるものでは無い気がする。主にはメモ化するときに使うみたい。でもメモ化ってなんだろうか?

メモ化

wikiに書いてあった。
関数のキャッシュのようなものなのかな。

useMemo

せっかくだから使ってみる。正しく使えているのかはわからないけど。

import React, { useState, useMemo } from 'react';

function App() {
  const [count, setCount] = useState(0);
  const value = useMemo(() => {
    return Math.sqrt(10000000);
  }, []);

  return (
    <div>
      <p>{ value }</p>
      <p>{ count }</p>
      <button onClick={() => { setCount(count + 1); }}>+ 1</button>
    </div>
  )
}

export default App;

こんな感じにすると、ボタンが押されてもvalueが再計算されない。つまりメモ化できてる???

useCallback

これも使ってみる。同じく正しく使えているのかはわからないけど。

import React, { useState, useCallback } from 'react';

function App() {
  const [count, setCount] = useState(0);
  const click = useCallback(() => {
    return setCount(c => c + 1)
  }, []);

  return (
    <div>
      <p>{ count }</p>
      <button onClick={click}>+ 1 memo</button>
    </div>
  )
}

export default App;

参考文献