LoginSignup
40

More than 3 years have passed since last update.

reactでuseStateとuseCallbackを使う

Last updated at Posted at 2019-07-25

背景

reactでこんなことを実現したいとします。

b93ad4f3459da18cfff396179fbbeb83.gif

まあよくある、ボタンを押すと数字がインクリメントされるやつです。
日本ではvueが盛り上がっていますが、reactも盛り上がって欲しいところ...!
あまり日本語の文献がないので書いていきます。

react16.8より追加されたHookを使って作っていきましょう。
typescriptで書いていきます。

だめな例1

// Sample.tsx
import React, { useState } from "react";

const Sample = () => {
  const [count, setCount] = useState(0);
  return (
    <div>
      <button
        onClick={() => {
          setCount(count + 1);
        }}
      >
        click here!
      </button>
      <p>count: {count}</p>
    </div>
  );
};
export default Sample;

これをすると、tslintから以下のような警告が出てしまいます。
スクリーンショット 2019-07-26 0.35.49.png

Lambdas are forbidden in JSX attributes due to their rendering performance impact (jsx-no-lambda)
レンダリングのときのパフォーマンスがよろしくないから、jsx(拡張子は.tsxですが)のなかでlambdaを書かないでね、と言っています。
このように書くと、レンダリングのたびにこの関数が新しく宣言されてしまうので、無駄が発生してしまいます。

だめな例2

すこし書き換えてみましょう。

import React, { useState } from "react";

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

  const handleClick = () => {
    setCount(count + 1);
  };

  return (
    <div>
      <button onClick={handleClick}>click here!</button>
      <p>count: {count}</p>
    </div>
  );
};
export default Sample;

onClickの処理の部分を、handleClickとして切り出してみました。
これでtslintからは怒られなくなります。
ですが、これでもやはりhandleClickがレンダリングのたびに宣言されてしまいます。
この記事や、このstackoverflowの回答に書いてあります。

いいけど面倒な例→だめでした

なるほど、では更に書き換えてみましょう。

import React, { useState } from "react";

const handleClick = (
  count: number,
  setCount: React.Dispatch<React.SetStateAction<number>>
) => () => {
  setCount(count + 1);
};

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

  return (
    <div>
      <button onClick={handleClick(count, setCount)}>click here!</button>
      <p>count: {count}</p>
    </div>
  );
};
export default Sample;

こんどはhandleClickの関数宣言を、Sampleの外に出してみました。
これでレンダリングのたびに宣言される問題は解決します。 →解決しないらしいです...

ですがなんでしょうか、このoverkill感。
typescriptというのも相まって、単にインクリメントさせたいだけなのに記述量が半端ない...泣

ちなみに筆者はずっとこれで書いていました、上記でも紹介したこの記事に出会うまでは。

useCallbackを一緒に使う→まだだめ

外に出すと宣言が面倒です、できれば関数の中に書いてスコープの恩恵を受けつつ、レンダリング時に再定義しないようにしたい。。。
そんな事できないと思ってました。
全然できました。
useCallbackというhookを使いましょう。

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

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

  const handleClick = useCallback(() => {
    setCount(count + 1);
  }, [count]);

  return (
    <div>
      <button onClick={handleClick}>click here!</button>
      <p>count: {count}</p>
    </div>
  );
};
export default Sample;

ただ、このままだとcountが更新されるたびに定義されてしまいます

useCallbackを一緒に使う→OKバージョン

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

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

  const handleClick = useCallback(() => {
    setCount(prev => prev + 1);
  }, []);

  return (
    <div>
      <button onClick={handleClick}>click here!</button>
      <p>count: {count}</p>
    </div>
  );
};
export default Sample;

最高ですね、めちゃくちゃスッキリ。
もっと早く知っていたかった...独学のつらみです。

ちなみに、 useCallback と似たようなものに useMemo があります。
これらの違いに関してはこの記事がわかりやすいです。

だれかの参考になれば幸いです。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
40