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

5分でわかるReact Hooks

はじめに

フックって何がすごいの?クラスと何が違うの?って感じたので学習したことをここにまとめます。
Reactをこれから学ぼうと考えている方でも、

:blush: < なるほど!みんなが言っているフックについて理解できたぞ!

と思っていただけるような、わかりやすい記事を目指して作成致しました!
Reactを学習中 or React知ってるけどフックはあんまり…って方はさらに理解しやすいと思います:bow:

少しでも参考になればとても嬉しいです:relaxed:

フックとはなんぞや?

状態管理などのReactの機能を、クラスを書かずに使えるようになる機能、それがフックです:anchor:
フックとは関数で、React16.8(2019/2~)で追加された機能となります。

フックの具体的な使い方を説明する前に、フックは何ができて、どんなメリットがあるのか。なぜ注目されているのかについてお話します。

フックの登場で何が変わった?:thinking:

Reactには書き方が二種類あります。
クラスコンポーネントと関数コンポーネントです。
フック登場前のReactではクラスコンポーネントで書くのが一般的でした。

それはなぜかというと…
クラスコンポーネントには状態管理やライフサイクルの機能があるが、関数コンポーネントではそれらの機能がなかったからです。

しかし、フックがそれを変えました:sparkles:
フックは状態管理やライフサイクルを扱うことができます。
つまり、フックを使用すればクラスコンポーネントでできていたことが、関数コンポーネントでもできるようになるのです。

ただ、現時点(2019/10)ではクラスコンポーネントの持つ機能をフックは完全に再現できていないというデメリットもあります。

フック(関数コンポーネント)で書くとどんなメリットがあるの?:thinking:

まず、記述が簡潔になります。

クラス型は少々複雑で私も苦労しました…:sob:
Reactの学習コストを上げている要因の一つがクラスだと思います。
そしてフックの登場は、Reactの学習コストを下げました。

実際に見比べてみましょう。

クラス
import React from 'react'

class App 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>
      </>
    )
  }
}

export default App
フック
import React, { useState } from 'react'

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

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

export default App

上記の二つのコードは同じ動きをします。
ボタンをクリックするとcountの値が増えていくだけの簡単なコードです。

フックの方がコード量が少なく、そして読みやすいのがわかります。

その他にも、再利用性が高かったりテストコードを書きやすくなるといったメリットがあります。

フックとクラス、結局どっちで書けばいいの?:thinking:

書き方が二種類あるので当然の疑問ですね。React開発チームがこの疑問に答えています。

長期的には、フックが React のコンポーネントを書く際の第一選択となることを期待しています。
フック、クラスのいずれを使うべきですか、あるいはその両方でしょうか? [公式ドキュメント]

つまり基本的にはフックを用いた関数コンポーネントで書いていき、必要な場合のみクラスで作成する。というのが今後のスタンダードになると予想できます。

クラスコンポーネントが必要となるのは、フックで同等の物が実装されていない二つのライフサイクル(componentDidCatch getDerivedStateFromError)か、フックとの互換性のないサードパーティ製のライブラリを使いたい時ぐらいでしょうか。
一応この二つのライフサイクルも、React開発チームは近日中に実装すると言っています。

クラスにはできなくてフックならできるって機能はないの?:thinking:

あります。その名もカスタムフック。ユーザーがフックを作成することができるのです。

ユーザーがフックを作れるということは、すでに世界中のすごい人たちがフックを作って公開しています。
もしかしたらフックでできないことは既にないのかもしれません。

カスタムフックの作成 [公式ドキュメント]

とりあえずこれを覚えれば大丈夫!基本フック

ここでは、基本となるuseState, useEffect, useContextの三つを紹介します。

他にもフックはたくさんありますが、useState, useEffectの派生形のようなものが多いです。なので基本フックを理解できれば、その他のフックの習得は簡単だと思います。

注意点として、フックはクラスの内部では動作しません。まさに関数型の為の機能というわけです。

useState

最も代表的なフック。クラスコンポーネントに頼らなくてもstateを持つことができるすごいやつ:sparkles:

先ほどのコードを例に解説します。

import React, { useState } from 'react'

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

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

クラスコンポーネントでいうsetStateです。

import React, { useState } from 'react'
// ここはお約束。フックを使うならそのフックを必ずimportしよう
const [count, setCount] = useState(0)
// countならsetCount、priceならsetPriceといったように、set〇〇とキャメルケースで書くのは慣習で決まっています
// setCountは関数です
// useStateは二つの要素を持った配列を返します
// useState(0)の(0)は初期値です
<p>You clicked {count} times</p>
// {count}には、stateで保存されているcountの値が表示されます
// {count}の値が変わるたびにレンダリングされます
<button onClick={() => setCount(count + 1)}>
// このボタンがクリックされると、countの値が+1されて、{count}の表示が変わります
// setCountを使わないと、countの値を変更することはできません
// また、setCountの引数に関数を渡すこともできます

useEffect

useEffectは、レンダリングの後に処理を動作させることができます。

デフォルトではレンダリングが行われる度に毎回動作しますが、第二引数に配列を与えることで、特定の値が変化した時のみ動作させるようにすることもできます。

従来のReactで考えると、componentDidMount componentDidUpdateと同じように使えます。
useEffectでは一括管理ができるため、コードがより綺麗になります:star:

先ほどのコードにuseEffectを追加して、動きを見ていきましょう。

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

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

  useEffect(() => {
    console.log('hello useEffect')
  })

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

export default App
import React, { useState, useEffect } from 'react'
// お約束。useEffectを使いたいので忘れずにimportしよう
useEffect(() => {
  console.log('hello useEffect')
})
// 画面がレンダリングされる度に、コンソールログに'hello useEffect'が表示されます。
// 画面がレンダリングされた後に動作するので、
const App = () => {
  const [count, setCount] = useState(0)

  useEffect(() => {
    console.log('hello useEffect')
  })

  const helloRender = () => {
    console.log('hello render')
  }

  return (
    <>
      <p>{helloRender()}</p>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </>
  )
}
// このようにすると、コンソールログに'hello render'が表示されてから、'hello useEffect'が表示されます。
useEffect(() => {
  console.log('hello useEffect')
}, [])
// このように第二引数に空配列を与えると、最初の画面レンダリング時のみ'hello useEffect'が表示されます

useEffect(() => {
  console.log('hello useEffect')
}, [count])
// このように第二引数に[count]を与えると、最初のレンダリング時と、countの値が変更された時のみレンダリング後に'hello useEffect'が表示されます

もしもuseEffectの動作タイミングが少し遅くて、視覚的なDOMの更新がユーザーに不自然に見えてしまう場合、画面がチラついてしまう場合はuseLayoutEffectについて調べてみてください。
useEffectと使い方は同じですが、動作のタイミングがuseEffectより少し早いです。

useContext

useContextの説明の前に、ある問題について知っておいてほしいです。

Reactでは、他のコンポーネントにデータを渡すための手段としてpropsがあります。
しかしこのpropsを使用すると、親コンポーネントのデータを孫コンポーネントに渡したい場合少々面倒なことをしなければなりません。

親→孫としたいだけなのに、親→子→孫とバケツリレーをしなければいけないのです:runner:
これを、prop drilling問題といいます。

prop_drilling問題
import React, { useState } from 'react'

const Mago = ({money}) => <p>{money}</p>

const Kodomo = ({money}) => <Mago money={money} />

const Oya = () => {
  const [money] = useState(10000)
  return (
    <Kodomo money={money} />
  )
}

export default Oya

上記のコードを見てみましょう。

①親コンポーネントは、money:moneybag:というpropsを子コンポーネントに渡しています
②子コンポーネントは、親から受け取ったmoney:moneybag:を孫に渡しています
③孫コンポーネントは、子から受け取ったmoney:moneybag:をpタグで表示させています。表示されるのは10000円です。

ここで見てほしいのが、②です。

子コンポーネントは親から受け取ったmoney:moneybag:を、何も手を加えることなくそのまま孫に渡しています。
であれば子コンポーネントにmoney:moneybag:を渡す必要はないですよね。

急に子コンポーネントが、

:smiling_imp: < 俺を通すなら手数料だ

と言い出して、孫に渡したいお金からマージン:money_with_wings:を抜かれても困ります…これが大規模な開発でひ孫、玄孫と出てきた時を考えると大変ですので、これはなんとかしたいです。

従来のReactで考えると、これの解決はReduxの役割の一つでした。

しかし、この為だけにReduxを導入するのは少し面倒…そこでuseContextの出番です:sparkles:
Contextのみでもprop drilling問題は解決できますが、useContextも合わせて使ったほうが簡潔でわかりやすいコードを書くことができます。

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

const Context = createContext()

const Mago = () => {
  const { money } = useContext(Context)
  return <p>{money}</p>
}

const Kodomo = () => <Mago />

const Oya = () => {
  return (
    <Context.Provider value={{ money: 10000 }}>
      <Kodomo />
    </Context.Provider>
  )
}

export default Oya

こちらの場合でも表示は同じく10000円です。無事、孫に10000円のmoney:moneybag:が届いています。
このようにすることで、親→孫へ直接データの受け渡しができました。子コンポーネントは親から何も受け取っていません。

さいごに

ここまで読んでくださってありがとうございました。

フックに対する理解が深まった!と思っていただけたらとても嬉しいです。
他にもフックはたくさんありますので、気になった方は調べてみてください!こちらでも随時追加していきます!
誤字脱字・発信した情報に間違い等ございましたら、指摘して頂けますと幸いです:bow:
また、説明にわかりづらい点がございましたら、改善致しますのでぜひコメントください:pray:

Mitsuzara
2019年5月からエンジニアとして頑張っています。前職は介護士。React, Vue.jsを勉強中でフロントエンドが好きです。自身のアウトプットや、初心者の方向けの記事を中心に発信していきます。
Why not register and get more from Qiita?
  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
No 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
ユーザーは見つかりませんでした