はじめに
フックって何がすごいの?クラスと何が違うの?って感じたので学習したことをここにまとめます。
Reactをこれから学ぼうと考えている方でも、
< なるほど!みんなが言っているフックについて理解できたぞ!
と思って頂けるような、わかりやすい記事を目指しました!
少しでも参考になればとても嬉しいです
フックとはなんぞや?
状態管理などのReactの機能を、クラスを書かずに使えるようになる機能、それがフックです
フックとは関数で、React16.8(2019/2~)で追加された機能となります。
フックの具体的な使い方を説明する前に、フックは何ができて、どんなメリットがあるのか。なぜ注目されているのかについてお話します。
フックの登場で何が変わった?
Reactには書き方が二種類あります。
クラスコンポーネントと関数コンポーネントです。
フック登場前のReactではクラスコンポーネントで書くのが一般的でした。
それはなぜかというと…
クラスコンポーネントには状態管理やライフサイクルの機能があるが、関数コンポーネントではそれらの機能がなかったからです。
しかし、フックがそれを変えました
フックは状態管理やライフサイクルを扱うことができます。
つまり、フックを使用すればクラスコンポーネントでできていたことが、関数コンポーネントでもできるようになるのです。
ただ、現時点(2019/10)ではクラスコンポーネントの持つ機能をフックは完全に再現できていないというデメリットもあります。
フック(関数コンポーネント)で書くとどんなメリットがあるの?
まず、記述が簡潔になります。
クラス型は記述が少々複雑です。私も苦労しました…
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の値が増えていくだけの簡単なコードです。
フックの方がコード量が少なく、そして読みやすいのがわかります。
その他にも、再利用性が高かったりテストコードを書きやすくなるといったメリットがあります。
フックとクラス、結局どっちで書けばいいの?
書き方が二種類あるので当然の疑問ですね。React開発チームがこの疑問に答えています。
長期的には、フックが React のコンポーネントを書く際の第一選択となることを期待しています。
フック、クラスのいずれを使うべきですか、あるいはその両方でしょうか? [公式ドキュメント]
つまり基本的にはフックを用いた関数コンポーネントで書いていき、必要な場合のみクラスで作成する。というのが今後のスタンダードになると予想できます。
クラスコンポーネントが必要となるのは、フックで同等の物が実装されていない3つのライフサイクル(getSnapshotBeforeUpdate
, getDerivedStateFromError
, componentDidCatch
)か、フックとの互換性のないサードパーティ製のライブラリを使いたい時ぐらいでしょうか。
一応この3つのライフサイクルもReact開発チームは近いうちに実装すると話していて、将来的にフックでできないことはなくなるようです。
我々の目標はできるだけ早急にフックがすべてのクラスのユースケースをカバーできるようにすることです。
まだ使用頻度の低い getSnapshotBeforeUpdate、getDerivedStateFromError および componentDidCatch についてはフックでの同等物が存在していませんが、すぐに追加する予定です。
フックはクラスのユースケースのすべてをカバーしていますか? [公式ドキュメント]
クラスにはできなくてフックならできるって機能はないの?
あります。その名もカスタムフック。ユーザーがフックを作成することができるのです。
ユーザーがフックを作れるということは、すでに世界中のすごい人たちがフックを作って公開しています。
もしかしたらフックでできないことは既にないのかもしれません。
カスタムフックの作成 [公式ドキュメント]
後述するuseEffectですが、こちらも厳密に言うと全く同じ動作をする機能はクラスにはないようです。
コメントを頂きました。ありがとうございます!
フックでしかできないこととして、useEffect (passive effect)があります。「動作のタイミングが遅い」とありますように、そのタイミングでの動作がポイントで、その仕組みはクラスコンポーネントにはありません。
とりあえずこれを覚えれば大丈夫!基本フック
ここでは、基本となるuseState
, useEffect
, useContext
の三つを紹介します。
他にもフックはたくさんありますが、useState
, useEffect
の派生形のようなものが多いです。なので基本フックを理解できれば、その他のフックの習得は簡単だと思います。
注意点として、フックはクラスの内部では動作しません。
まさに関数型の為の機能というわけです。
useState
最も代表的なフック。クラスコンポーネントに頼らなくてもstateを持つことができるすごいやつ
先ほどのコードを例に解説します。
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)}>
Click me
</button>
// このボタンがクリックされると、countの値が+1されて、{count}の表示が変わります
// setCountを使わないと、countの値を変更することはできません
// また、setCountの引数に関数を渡すこともできます
useEffect
useEffect
は、レンダリングの後に処理を動作させることができます。
デフォルトではレンダリングが行われる度に毎回動作しますが、第二引数に配列を与えることで、特定の値が変化した時のみ動作させるようにすることもできます。
従来のReactで考えると、componentDidMount
componentDidUpdate
と同じように使えます。
useEffectでは一括管理ができるため、コードがより綺麗になります
先ほどのコードにuseEffectを追加して、動きを見ていきましょう。
- import React, { useState } from 'react'
+ 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'が表示されます。
// 画面がレンダリングされた後に動作するので、
// 下記のようにすると、コンソールログに'hello render'が表示されてから、'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>
</>
)
}
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を使用すると、親コンポーネントのデータを孫コンポーネントに渡したい場合少々面倒なことをしなければなりません。
親→孫としたいだけなのに、親→子→孫とバケツリレーをしなければいけないのです
これを、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というpropsを子コンポーネントに渡しています
②子コンポーネントは、親から受け取ったmoneyを孫に渡しています
③孫コンポーネントは、子から受け取ったmoneyをpタグで表示させています。表示されるのは10000円です。
ここで見てほしいのが、②です。
子コンポーネントは親から受け取ったmoneyを、何も手を加えることなくそのまま孫に渡しています。
であれば子コンポーネントにmoneyを渡す必要はないですよね。
急に子コンポーネントが、
< 俺を通すなら手数料だ
とか言い出して、孫に渡したいお金から手数料を抜かれても困ります。
<俺は何も悪いことはしないぞ
と言われても、我々エンジニアはpropsが渡されている以上、本当に何もしていないのか自分の目で見て確かめないと夜も眠れないでしょう。
何も手を加えていないことを証明するためには一度も持たせないのが望ましく、これが大規模な開発でひ孫、玄孫と出てきた時を考えると大変です。なんとかしたいですね。
従来のReactで考えると、これの解決はReduxの役割の一つでした。
しかし、この為だけにReduxを導入するのは少し面倒…そこでuseContextの出番です
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が届いています。
このようにすることで、親→孫へ直接データの受け渡しができました。
子コンポーネントは親から何も受け取っていません。
さいごに
ここまで読んでくださってありがとうございました。
フックの役割や魅力は伝わりましたか?
他にもフックはたくさんありますので、気になった方はぜひ調べてみてください!
フック API リファレンス – React