見出し画像

初心者にこそ勧めたいReactHooks

こんにちは。フロントエンドエンジニアの fujino です。
フルリモート生活が3ヶ月ほど続いていますが、開発メンバとのMTGや雑談のおかげで楽しくお仕事できています。
プライベートではスマブラやOCTOPATH TRAVELERを楽しんでおります✨
今週はキョダイマックスイーブイ狩りですね。ポケモン大好きです。

好きなことばかりやってる人です。よろしくお願いいたします。

最近は弊社のバックエンドエンジニアやアプリエンジニアもReactの勉強を始めたらしいので、React 16.8 で追加された新機能であるHooksの魅力よく使いそうなHookを紹介したいと思います。

よく使いそうなHook
useState
useEffect
useContext

先輩がひっそりとHooksを導入しており、勉強会まで開いてくれたので自分でも触ってみたところ、「簡単便利最高じゃん...!」となったので、本記事を読んでいただいたあとは是非とも自分の手で動かしてみてほしいです😊

本記事のターゲット
「React勉強し始めたばっかりだし新機能は難しそう」
「とりあえず今まで使われてきたやつ学んだ方がよさそう」
と思っている方々(私もそうでした)

React Hooks とは

ひとことで言うと、関数コンポーネントの利便性を高めるAPIです。
Reactには関数コンポーネントクラスコンポーネントがあり、stateによる状態管理やライフサイクルメソッド(componentDidMountなどレンダリングの前後で発生する処理を書く関数)は、クラスコンポーネント内にしか実装できませんでした。
関数コンポーネントで実装している際に、stateが必要だと気づいたらクラスコンポーネントに書き換える必要があったのです。

関数コンポーネントとクラスコンポーネント

クラスコンポーネントは状態管理ができ、関数コンポーネントはできないと既に書きました。Reactを始めた頃は以下のように考えていました。

「全部クラスコンポーネントで書けばよいのでは?」
「でもわざわざ関数コンポーネントが用意されるということはデメリットがあるのだろう」

クラスコンポーネントはstateを持つことができ、ライフサイクルメソッドによって描画制御もできるため、ひとつのクラス内に多くの処理が散らばりやすいです。その結果、クラスコンポーネントは処理や関心事がばらつきやすく、バグの元になりやすいという問題がありました。

以下はクラスコンポーネントってこんな感じという例です。

class Sample extends React.Component {
// this.stateの初期状態を設定するコンストラクタ
 constructor(props) {
   super(props);
   this.state = {isHoge: false};
 }

 render() {
   return <div />;
 }

一方で関数コンポーネントは制御されることを許さない、シンプルなただの「関数」です。クラスコンポーネントの潜在的デメリットを解消すべく、高機能な実装をより綺麗に書く関数コンポーネントのためのHooksが登場しました。

以下は関数コンポーネントってこんな感じという例です。

// 関数コンポーネントの例1
const Example = (props) => {
 // ここで Hooks が使える
 return <div />;
}

// 関数コンポーネントの例2
function Example(props) {
 // ここで Hooks が使える
 return <div />;
}

なぜHooksだと処理や関心事を整理できるのかについては、各Hookの紹介とともに説明します。

Hooksの素敵ポイント

クラスコンポーネントと共存できるため、少しずつ導入することができます。弊社サービスでも少しずつ取り入れており、ちょっとした機能追加の際はHooksで書かれていたりします。
公式でもReactからクラスコンポーネントは削除する予定はないと明言されており、新しいコードでHooksを試すことが推奨されています。

ちなみに、既存のクラスをHooksに書き換えることは(バグを直す等の理由で書き換える予定の場合を除いて)推奨されていません。

先述したとおり、クラスコンポーネントはできることが多いがゆえに無秩序に複雑化します。一方で、関数コンポーネントは状態管理や副作用といったロジックをHooksに任せられるため、記述自体がシンプルになり、書きやすくなるうえに可読性も上がります。

個人的には、componentDidMount, componentDidUpdate, componentWillMount の組み合わせを useEffect で表現できるところがわかりやすくて素敵だと思いました😊

React Hooks の紹介

前置きが大変長くなりましたが、初心者も導入しやすいHooksを紹介します!

useState

クラスコンポーネントでしかできなかったstateによる状態管理を、関数コンポーネントで実現させてくれます。

↓関数コンポーネントでuseStateを使ったカウンターの実装

import React, { useState } from 'react';

function Example() {
 // 新しいstate変数 count を宣言
 const [count, setCount] = useState(0);

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

↓クラスコンポーネントの場合

class Example extends React.Component {
 constructor(props) {
   super(props);
   this.state = {
     count: 0
   };
 }

 render() {
   return (
     <>
       <p>You clicked {this.state.count} times</p>
       <button onClick={() => this.setState({ count: this.state.count + 1 })}>
         Click me
       </button>
     </>
   );
 }
}

前者の方がすっきりしていることが伝わると思います🎉

useStateはどのようにいつ使うのか?

使い方
・useState をインポートする
・useStateに変数(例ではcount)とset関数(例ではsetCount)を宣言する
・useStateの引数に初期値を渡す
・読み出し先で変数countをそのまま使う(クラスではthis.state.countでしたね)

いつ使う?
・stateが必要になった時
(もうクラスコンポーネントに書き換える必要がない🎉)

使い方も使いどきもシンプル!
宣言方法も関数コンポーネントの方がシンプルですが、読み出し先での書き方で違いが顕著になりますね。

// Hooksを使った関数コンポーネントの場合
<button onClick={() => setCount(count + 1)}>

// クラスコンポーネントの場合
<button onClick={() => this.setState({ count: this.state.count + 1 })}>

useEffect

副作用フックと呼ばれる useEffect はその名の通り、関数コンポーネント内で副作用を実行できるようになります。クラスコンポーネントにおけるライフサイクルメソッドの役割に当たります。

ちなみに副作用という言葉、ネガティブなイメージになりがちなのでわかりにくいのですが、「関数の外に影響を与える関数」を指します。なのでuseEffectは悪い意味で副作用を発生させるのではなく、意図的に実行したい副次的な処理をしてくれるという意味になります。

Reactにおける具体的な副作用には以下のようなものがあります。

Reactにおける具体的な副作用
・DOMの変更
・APIとの通信
・変数への代入
・stateの変更
→これらは関数の外に影響を与えてしまう関数

これまでのクラスコンポーネントでライフサイクルに相当するものと書きましたが、上記の例を使うと、stateの変更(componentDidMount時にsetStateすることは多いですね)をする際に useEffect を使うことになります。

今回はマウント直後・更新直後なのかに関係なくどちらも同じ副作用を起こしたい場合を例にします。先にクラスコンポーネントの実装例を書きます。

↓クラスコンポーネントの場合

class Example extends React.Component {
 constructor(props) {
   super(props);
   this.state = {
     count: 0
   };
 }

// 同じ処理を2回書いている(マウント直後、更新直後)
 componentDidMount() {
   document.title = `You clicked ${this.state.count} times`;
 }
 componentDidUpdate() {
   document.title = `You clicked ${this.state.count} times`;
 }

 render() {
   return (
     <>
       <p>You clicked {this.state.count} times</p>
       <button onClick={() => this.setState({ count: this.state.count + 1 })}>
         Click me
       </button>
     </>
   );
 }
}

↓関数コンポーネントでuseEffectを使った場合

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

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

// マウント直後、更新直後に実行したい処理
 useEffect(() => {
   document.title = `You clicked ${count} times`;
 });

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

素晴らしくシンプルになりました🎉

しかし全ての実装でこの状態だと問題があります。
useEffect はデフォルトでは毎回レンダーおよび更新時に呼び出しており、パフォーマンスの問題を引き起こす可能性があります。これは第二引数の設定によってライフサイクルの細かな設定をすることができます。

// componentDidMountと等価
useEffect(() => {
 
}, [])

// compnoentDidUpdateと等価
// 初回と、第二引数のarrayに渡した値に変化があった時のみ副作用が実行される
useEffect(() => {
 
}, [props.hoge, state]) 

// componentWillUnmountと等価
useEffect(() => {
 return () => {
   // 副作用でreturnしたメソッドが破棄前に実行される
 }
}, [])

このようにカスタマイズして使うのがuseEffectになります。

useContext

useContext は指定したコンテキストの現在の値を取得するHookです。React.createContextで作成したコンテキストオブジェクトを useContext に渡すことでそのコンテキストの現在の値を取得します。

こちらはクラスコンポーネントと関数コンポーネントの違い云々ではなく、context API によるcontext参照をする際にHookが使えるという紹介になります。

import React, { useState, useContext, createContext } from 'react';

const SampleContext = createContext(() => {});

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

 return (
   <div>
     <span>{count}</span>
     <SampleContext.Provider value={() => setCount(count => count + 1)}>
       <IncrementButton />
     </SampleContext.Provider>
   </div>
 );
};

// ここまで共通

useContextを使わない場合

const IncrementButton = () => {
 return (
   <SampleContext.Consumer>
     {incrementHandler => (
       <>
         <button onClick={incrementHandler}>+</button>
       </>
     )}
   </SampleContext.Consumer>
 );

useContextを使う場合

const IncrementButton = () => {
 const incrementHandler = useContext(SampleContext);

 return (
   <>
     <button onClick={incrementHandler}>+</button>
   </>
 );
};

ネストがなくなって綺麗になりましたね🎉

まとめ

Hooksの登場によって、関数コンポーネントが高機能化し、コードがすっきり書けるようになりました。煩雑さが減ると書きやすい&読みやすいですね。ついでにクラスコンポーネントと関数コンポーネントの違いも(簡易ではありますが)理解していただけたと思います。

Reactは公式ドキュメントが丁寧で充実しているため、公式を読み進めていくのが一番だと思います。初心者の方は初めから読み進めると「stateとライフサイクル」の章に出会うと思うので、事前知識としてHooksを知っているとよりReactの良さを感じられるのではないかなと思います。

最後に

いつもながらの宣伝ですが、新しい技術を積極的に取り入れるのが大好きなエンジニアさんを募集しております!

一緒に楽しく開発したい方のご応募お待ちしております😊


最後までお読みいただき、ありがとうございました💕

いただいたサポートは自己研鑽用に使わせていただきますmm