LoginSignup
4
5

More than 1 year has passed since last update.

React hooksによる関数コンポーネントの作成

Last updated at Posted at 2020-11-11

クラスコンポーネントが書ける人向けの関数コンポーネントの書き方まとめです。

基本

JSXを戻り値として返す関数を作成する。その関数がコンポーネントになる。

const SampleComponent = () => {
  return (<div>Hello!!</div>);
}

export default SampleComponent;

プロパティ

プロパティは、引数からオブジェクトとして渡ってくる。

const SampleComponent = ({name}) => {
  return (<div>Hello!! {name}!!</div>);
}

export default SampleComponent;

State

StateuseStateで利用できる。引数は初期値。

戻り値として配列が返る。配列の一つ目にStateの値が、二つ目にStateを設定するための関数が入っている。

const SampleComponent = () => {
  const [name, setName] = useState('hoge');

  setName('sfjwr');

  return (<div>Hello!! {name}!!</div>);
}

export default SampleComponent;

関数(コンポーネント)が呼ばれるタイミング

DOMの描画が必要なタイミングにて呼び出される。

呼ばれるタイミングの具体例

  • 初期描画時
  • 親から渡されているプロパティが更新された時
  • 自身で持っているStateが更新された時
  • 参照しているReduxの値が更新された時

など。

呼び出しの依存関係

コンポーネントに階層があるため、以下のような呼び出しが発生する。

  • 親コンポーネント(自身を埋め込んでいるコンポーネント)の呼び出し時、自分も呼びだされる
  • 自コンポーネントの関数が呼ばれた時、自分の子コンポーネントの関数も呼びだされる

コンポーネント階層の部分的な更新

とあるコンポーネントが自身のStateを変更した時など、当然自分自身の関数は呼びだされるが、親の関数は呼びだされない。変更のあったコンポーネントより下の階層の関数は呼び出される。

親が呼ばれた時、自分を呼んで欲しくない

プロパティなど、何も状態が変わっていないなら、パフォーマンスの都合で呼んで欲しくない。そのような場合はReact.memoを利用する。

const SampleComponent = React.memo(({message}) => {
  return (<div>Hello!! {message}</div>);
});

export default SampleComponent;

React.memoで関数をラップすることで、プロパティの変化を監視し、プロパティに変更がなければ関数の呼び出しが省略される。

上記例ではプロパティmessageが変更されない限り、関数は呼び出されない。

React.memoしたが、プロパティにコールバック関数を受け取っている時

コールバック関数をプロパティに受け取るコンポーネントに対して、ローカル関数をそのまま渡していると、そのプロパティは毎回変化することになってしまい、React.memoした意味がなくなる。

const App = () => {
  const click = () => {
    console.log('Hello');
  }

  return (<SampleComponent onClick={click} />);
}

export default SampleComponent;

上記例では、コンポーネントAppからSampleComponentを利用しており、onClickにローカルの関数を渡している。

しかし、渡している関数clickAppの呼び出しの度に生成されているため、onClickには毎回異なるclickが渡されている。そのため、Appの呼び出しの都度SampleComponentも呼び出されてしまう。

これを回避するためには、関数clickuseCallbackでラップする

const App = () => {
  const click = useCallback(() => {
    console.log('Hello');
  });

  return (<SampleComponent onClick={click} />);
}

export default SampleComponent;

useCallbackでラップすることによって、初回呼び出し時に作成された関数が保存され、以降はそれがuseCallbackから戻されるようになる。

2回目以降も関数は作成されているが、useCallbackが無視して捨ててくれる。

ただし、もしそのコールバック関数からStateなどを参照している場合は注意が必要である。

なぜなら、

const App = () => {
  const [message, setMessage] = useState('Hello');

  const click = useCallback(() => {
    console.log(message);
  });

  return (<SampleComponent onClick={click} />);
}

export default SampleComponent;

このような例の場合、useCallback初回呼び出し時にはmessageには'Hello'が入っている。

しかし以降のタイミングでsetMessageされると、messageの内容は変化することになる。

が、useCallbackは初回の関数を保持しており、初回の関数は初回のmessageを参照しているため、console.logされる内容が変化しなくなってしまう。

そのため、messageが変化した時にはclick関数を作り直す必要がある。

これはuseCallbackの第二引数で可能であり、

const App = () => {
  const [message, setMessage] = useState('Hello');

  const click = useCallback(() => {
    console.log(message);
  }, [message]);

  return (<SampleComponent onClick={click} />);
}

export default SampleComponent;

としておけばuseCallbackmessageの変更を監視し、変更があった時には保持している古い関数を破棄し、引数から渡ってきている新しい関数で置き換えてくれる。

こうすることでmessageの変化時のみclick関数が入れ替えられるようになり、またSampleComponentonClickプロパティもその時のみ変化するようになり、うまく動作する。

useEffectについて

ネットワークからのデータ取得など、タイミングをずらして処理しなくてはならないものを記述する。

useCallbackと同様に、変数の変化をトリガーに実行できる。

const SampleComponent = ({userId}) => {
  const [showName, setShowName] = useState('');

  useEffect(() => {
    const load = async () => {
      const name = await getNameFromUserId(userId);
      setShowName(name);
    }

    load();
  }, [userId]);

  return (<div>Hello!! {showName}</div>);
}

export default SampleComponent;

このようにすることで、プロパティから渡ってくるuserIdに応じてネットワークからユーザ名を取得して画面に反映させる、というようなことができる。

その他

  • コンポーネントが再描画されても更新されるのは仮想DOMなので、本当に再描画されるわけではない(はず)
    • 変化のあったところのみ本当に描画される
  • パフォーマンスに問題がなければ、無理にReact.memoしなくていいかも
4
5
0

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
4
5