Help us understand the problem. What is going on with this article?

reactでuseStateとuseCallbackを使う

背景

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 があります。
これらの違いに関してはこの記事がわかりやすいです。

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

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away