最近、世間を賑わせているReact Hooksですが、とりあえず細かいことは置いといて手を動かして試してみます。
環境構築
完成品はこちら。
React Hooksはまだ仕様策定中で、既存のプロジェクトに入れたくないので0から構築します。
といっても、create-react-appで。
プロジェクト名は「TrialReactHooks」とでもします。
$ mkdir TrialReactHooks
$ cd TrialReactHooks
create-react-appをインストール。$ npx create-react-app trial-react-hooks
$ cd trial-react-hooks
v16.7.0-alphaのReactを入れます。$ yarn add -D react@16.7.0-alpha.0 react-dom@16.7.0-alpha.0
linterもすでに用意されているのでこれも入れてしまいましょう。$ yarn add -D eslint-plugin-react-hooks@next
eslintrc.jsを作ります。
1 2 3 4 5 6 7 8 9 |
// .eslintrc.js 'use strict'; module.exports = { plugins: ['react', 'react-internal', 'react-hooks'], rules: { 'react-hooks/rules-of-hooks': 'error' } }; |
とりあえずはこれで環境が整いました。$ yarn start
で立ち上がります。
これからReact Hooksを触っていきます。
useStateを使う
公式: Using the State Hook React
最もシンプルなものです。
これまではstateを扱うときは、(とりあえずは)Classを使ってコンポーネントを作る必要がありましたが、hooksを使うと関数コンポーネントで以下のように簡潔に書くことができます。
以下は本家のサイトのものとほぼ同じものです。
1 2 3 4 5 6 7 8 9 10 11 12 |
// src/comopnents/TrialUseState/index.jsx import React, { useState } from 'react'; export const TrialUseState = ({ initialValue }) => { const [count, setCount] = useState(initialValue); return ( <> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}>click me</button> </> ); }; |
このコンポーネントをApp.jsxに読み込みます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
// App.jsx import * as React from 'react'; import './App.css'; import { TrialUseState } from './components/TrialUseState/index'; class App extends React.Component { render() { return ( <div className="App"> <TrialUseState initialValue={0} /> </div> ); } } export default App; |
これで完了です。
数字がインクリメントするコンポーネントが完成しました。
とりあえずブラウザで試してみると直感の通りの動きをするのがわかります。
7行目の以下の部分。
ここで、useState()関数を使用し、その2つの返り値を分割代入しています。
1 |
const [count, setCount] = useState(0); |
この関数は、現在のstateとそのstateを更新する関数を返します。
括弧の中身はそのstateの初期値になります。
例えばトグルするbooleanの例。
さっきのを書き換えてみます。
1 2 3 4 5 6 7 8 9 10 |
// src/components/TrialUseState/index.jsx export const TrialUseState = () => { const [flg, setFlg] = useState(false); return ( <> <p>flg: {flg ? 'true' : 'false'}</p> <button onClick={() => setFlg(!flg)}>click me</button> </> ); }; |
とても簡単にstateを利用することができました。
ドキュメントを読む限り、さきほどの例と同様にuseStateの2つの返り値は「hoge」と「setHoge」のように名付けるのが良さそうです。
ちなみに、最初の方の数字をインクリメントするものを従来のclassを使った書き方をすると、以下のようになります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
export class TrialUseState extends React.Component { constructor(props) { super(props); this.state = { count: 0 }; this.setCount = this.setCount.bind(this); } setCount() { this.setState({ count: this.state.count + 1 }); } render() { return ( <> <p>You clicked {this.state.count} times</p> <button onClick={this.setCount}>click me</button> </> ); } } |
だいぶ厳つさが増しますね。
Class型のコンポーネントはstateを管理したり、Reduxと接続したり、lifecycleメソッドを利用するときに必須でした。
これまでもできるだけSFCで書けるようにするために高階関数ならぬ高階コンポーネント(HoC)を使ったりして再利用性を高める方策を取っていました。
ですが、このhooksが導入されたことによりわざわざ外部モジュールに頼らずとも再利用性の高いコンポーネントを作ることができるようになりました。
HoCを実現するための便利モジュール郡の代表にrecomposeがありましたが、Hooksの発表により役目を終えたということで開発が終了されました。
【参考】acdlite/recompose: A React utility belt for function components and higher-order components.
useEffectを使う
公式: Using the Effect Hook React
次にuseEffectを使ってみます。
これはデータのfetchやDOM操作など、レンダリングの前後に行う副作用の処理を書くときに使うものです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
// src/components/TrialUseEffect/index.jsx import React, { useState, useEffect } from 'react'; export const TrialUseEffect = () => { const [flg, setFlg] = useState(false); useEffect( () => { document.title(`you checked ${flg}`); } ); return ( <> {console.log('rendering')} <p>flg: {flg ? 'true' : 'false'}</p> <button onClick={() => setFlg(!flg)}>click me</button> </> ); }; |
従来、class内で使われていたcomponentDidMount
, componentDidUpdate
, componentWillUnmount
が一つのAPIであるuseEffect
にまとめられたようです。
デフォルトではEffectはレンダリング後に実行されますが、return句を書くとunmount時に実行する関数を書けます。
以下のようにconsole.logを埋め込むことで確認できます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
const TrialUseEffect = () => { const [flg, setFlg] = useState(false); useEffect(() => { console.log('before'); return () => console.log('after'); }); return ( <> {console.log('rendering')} <p>flg: {flg ? 'true' : 'false'}</p> <button onClick={() => setFlg(!flg)}>click me</button> </> ); }; |
これの何が嬉しいのかというと、これまでcomponentDidMount
でなにかのAPIのsubscribeを開始し、componentDidUpdate
でunsubscribeするような処理に使われていました。
これらはほぼ同じような処理にもかかわらず、2つに分けて記述する必要がありました。
これをuseEffectを使うことで、密接した場所に書くことができ、可読性の高いコードを書けるようになります。
たしかに。これはclassよりも強力ですね・・。
React開発チームは完全にclassなしでも記述できるように向かっているようです。
パフォーマンスを気にするなら
参考: https://reactjs.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects
実はこのuseEffect関数は第2引数を取ることができ、ここにstateを記述すると、再レンダリング時にそのstateが更新されていれば、この関数内の処理が走るように最適化できます。
1 2 3 4 5 6 7 |
useEffect( () => { console.log('after rendering'); return () => console.log('after'); }, [flg] ); |
これは従来のshouldComponentUpdate
にも似てますね。
Custom Hooksを知る
公式: Building Your Own Hooks – React
あと1つ触ってみます。
公式のところには何かいろいろ書いてありますが、とりあえずはこれらのhooksはjsxを返すもの関数(コンポーネント)以外にも使えるということです。
以下のコードのように、返り値がJSXではなく、string型の関数を定義していますが、この中でもuseEffect
を使用することができます。
1 2 3 4 5 6 7 |
const useEffectFunctoin = num => { useEffect(() => { console.log('before'); return () => console.log('after'); }); return num >= 10 ? 'Finish' : 'Loading...'; }; |
この関数はいつもと同じように以下のように使うことができます。
1 2 3 4 5 6 7 8 9 10 11 12 |
const TrialCustomHooks = ({ initialValue }) => { const [count, setCount] = useState(initialValue); const num = useEffectFunctoin(count); return ( <> <p>You clicked {count} times</p> <p>{num}</p> <button onClick={() => setCount(count + 1)}>click me</button> </> ); }; |
こうすれば、10より小さい間は「Loading…」と出て、それより大きくなると「Finish」と表示されます。
これは一件普通の関数定義と変わらないですが、console.logを覗くと、描画の前後に出力があることがわかります。
このようにすることで、コンポーネントだけでなくステートフルな「ロジック」の部分も再利用することができるようになります。
これまでもHoCを使って「UI部分」と「ロジック部分」のコンポーネントを組み合わせるAtomicDesignなどの開発の仕方がありましたが、これまたHoCを使わずに実現できるようになりました。
このように、hooksを内部で使う関数の名前を「use」から始めて「useなんちゃら」関数と命名したもののことを「Custom Hooks」と呼びます。
この慣習に習って命名することでlinterがhooksの利用を検知し、助けを得ることができるようにみたいです。
最後に
こんな感じでとりあえずは手を動かして、大まかな挙動が確認できました。
これで、公式の「Hooks at a Glance」、「Using the State Hook」、「Using the Effect Hook」に書いてあることは一通りやりました。(少し省略していますが)
これからは、いろいろな記事や公式ページを確認して細かいことを確認していけたらなと思います。