React16.8 がリリースされたらしい。巷で噂になっている。
なんでも Hooks という新しい機能が入ってそれが便利なんだとか。
ということで、もう一度環境構築から初めて Hooks がどういうものなのか
使ってみようとおもう。
ベースは ここ をベースにした。ただ、react
、react-dom
はバージョンを指定してインストールし、src/index.js
も修正する。
パッケージの再インストール
$ npm i --save react@16.8 react-dom@16.8
src/index.js の作成
import React from 'react' import ReactDOM from 'react-dom' import App from './components/App' ReactDOM.render( <App />, document.getElementById("root") )
参考サイト
🎉React 16.8: 正式版となったReact Hooksを今さら総ざらいする - Qiita
useState
現在のstateとsetState関数を返す関数らしい。
src/components/App.js の作成
import React, { useState } from 'react'; export default () => { const [a, b] = useState(0); return ( <div> <button onClick={() => { b(a + 1); }}> \ovo/ {"<"}{a} times!! </button> </div> ) };
実行
npx webpack-dev-server --open --mode production
所感
普通なら function で state は管理できないけど、useSate() を使うことで管理できるようになって便利ってことなんだと思う。 たしかに class を書くよりも記述量が減ってると思うし、ちょっとしたコンポーネントで且つ state が必要そうな場合に重宝するのかもしれない。
後、初期値として渡している引数は初回呼び出しの一回だけしか有効ではないらしい。実際にクリックしても値がカウントするということはそういうことなんだろう。なぜそうなるのかは全く分からないけど。。。
useState(Functional updates)
setState
の引数に関数を渡すと、前回値を受けて現在値を作成することが可能らしい。
src/components/App.js の修正
import React, { useState } from 'react'; function Counter({initialCount}) { const [count, setCount] = useState(initialCount); return ( <> <h1>Count: {count}</h1> <button onClick={() => setCount(initialCount)}>Reset</button> <button onClick={() => setCount(prevCount => prevCount + 1)}>+</button> <button onClick={() => setCount(prevCount => prevCount - 1)}>-</button> </> ); } export default () => { const [a, b] = useState(0); return ( <Counter initialCount={10} /> ) };
実行
npx webpack-dev-server --open --mode production
所感
前回値を保持していなくても関数で引き回せるっていうのがメリットなんだろうか?でも setCount(count + 1)
とかでも同じ動作するし、使いどころがいまいちわからない。
あと、ここ の注意書きに、useState はオブジェクトのマージを自動的には行ってくれないから自前でやってね。的なことが書いてる。setCount(prev => { return { ...prev, ...updated }; });
みたいにするか、useReducer
を使えばいいみたい。
useState(Lazy initial state)
initialState
には関数も指定できるようだ。しかもその関数は初回のみ実行される。
src/components/App.js の修正
今回はページを更新する度に値がランダムに変化するようにしてみた。
import React, { useState } from 'react'; function getRandomInit(max) { return Math.floor(Math.random() * Math.floor(max)); } function Counter({initialCount}) { const [count, setCount] = useState(initialCount); return ( <> <h1>Count: {count}</h1> <button onClick={() => setCount(initialCount)}>Reset</button> <button onClick={() => setCount(count + 1)}>+</button> <button onClick={() => setCount(count - 1)}>-</button> </> ); } export default () => { const [a, b] = useState(0); return ( <Counter initialCount={() => { return getRandomInit(100) }} /> ) };
実行
所感
複雑な初期値を要求されるケースには重宝するんだろうなー。という感じだけど、リアルユースケースとしてはパッと思いつかないので、忘れそう。あと、Resetは普通に動作するので、初回実行時の値を保持しておかないと、リセット的な動作は実現できなそうだった。
useState(Bailing out of a state update)
同じ値を現在値として使って state を更新しようとしても、state は更新されないしレンダリングもしませんよ。的なことが書いてあるみたい。
useEffect
レンダリングが完了した後に呼び出される関数らしい。再描画時も完了すると呼び出される。
useEffect(Cleaning up an effect)
return で返す関数によってクリーンナップができるとのこと。go言語言うところのdefer みたいな使い方かな?
import * as React from 'react'; const { useState, useEffect } = React; export default () => { const [a, b] = useState(0); useEffect(() => { const timerid = setTimeout(() => { console.log("triggered!!"); // レンダリングが完了してから1秒後に表示される }, 1000); console.log("timer"); // レンダリングが完了した時点で表示される。 return () => { clearTimeout(timerid); console.log("clean up useEffect") // ボタンをクリックして state が更新されることによって描画がリセットされるので、そのタイミングで表示される(?) } }, [a]); return ( <> time: <b>{a}</b> <button onClick={() => b(a+1)}>+</button> </> ) };
所感
上の例ではあまりメリットがないような感じだけど、再描画に伴って何か処理を行うときに前回のオブジェクトを破棄しないといけない場合に重宝しそう
useEffect(Timing of effects)
特記することはなさそうだったのでスキップします。
useEffect(Conditionally firing an effect)
useEffectの第二引数に配列を渡すとその中のオブジェクトに変化があったときのみuseEffect()関数が呼び出されるようになるらしい。
useContext
React.createContext() で作ったコンテキストを取得できるようになるらしい。コンポーネント間の値のやり取りに使うと便利で、コンポーネント間の距離が遠いほどメリットを享受できるとかなんとか。
詳しい説明は こちら が詳しい。
useReducer
Reduxみたいなことができるらしい。
import * as React from 'react'; const { useReducer } = React; const initialState = {count: 0}; function reducer(state, action) { switch(action.type) { case 'increment': return {count: state.count + 1}; case 'decrement': return {count: state.count - 1}; default: throw new Error(); } } function Counter({initialCount}) { const [state, dispatch] = useReducer(reducer, initialState); return ( <> Count: {state.count} <button onClick={() => dispatch({type: 'increment'})}>+</button> <button onClick={() => dispatch({type: 'decrement'})}>-</button> </> ); } export default () => { return ( <Counter initialCount={10} /> ); };
実行
所感
今作ってる Todo アプリくらいであれば置き換えられそう。Redux 使わずに React だけで完結できるから覚えることが減ってよさそうではある。
useMemo
値がメモ化できるらしい。複雑な計算を必要とする関数があり、でも前回値と同じ場合に計算をスキップして結果を再利用することができるとのこと。
import * as React from 'react'; const { useState, useMemo } = React; function Counter({a, b}) { const [cv, setState] = useState(0); const add = useMemo(() => { console.log("test") return a + b; }, [a,b]); const add2 = () => { console.log("test2") return a + b; } return ( <> <p>Count1: {add}</p> <p>Count2: {add2()}</p> <p>Count3: {cv}</p> <button onClick={() => setState(prev => prev + 1)}>+</button> </> ); } export default () => { const [a, setA] = useState(1); const [b, setB] = useState(2); return ( <> <Counter a={a} b={b}/> <button onClick={() => setA(prev => prev + 1)}>a++</button> <button onClick={() => setB(prev => prev + 1)}>b++</button> </> ); };
所感
上記の例では、+ボタンを押すたびに、"test2"がコンソール上に表示されるが、メモ化された関数は1回しか呼ばれない。でも、a++やb++ボタンを押すと"test"も表示され、値も更新されるので値が更新される場合に限り再メモ化がなされて便利。という感じらしい。確かにパフォーマンスを気にしないといけないような処理の場合にはメモ化は便利かもしれない。
useCallback
useMemo(() => fn, inputs) と同等の処理を別名にして、すこし書きやすくしている感じらしい。
詳しくは こちら
useRef
React.createRef のオブジェクトを返すものらしい。
import * as React from 'react'; const { useRef } = React; function TextInputWithFocusButton() { const inputEl = useRef(null); const onButtonClick = () => { inputEl.current.focus(); }; return ( <> <input ref={inputEl} type="text" /> <button onClick={onButtonClick}>Focus the input</button> </> ); } export default () => { return ( <> <TextInputWithFocusButton /> </> ); };
所感
React.createRef と何が違うのだろう? という疑問もありつつ、こちら によると、レンダリング間の変数として使用することで今までにないrefの使い方ができるらしい。
useLayoutEffect
useEffect のようなフックで、レンダリングが完了して描画をする前に呼び出される関数。useEffect では、useEffectフック内で描画に関係するような処理を行っていた場合はuseEffectより先に描画がなされるので一瞬ちらついてしまうが、useLayoutEffectだと描画する前に実行されるので、そのようなことにはならない。ただ、useLayoutEffectは描画をブロックするので描画が遅れてしまう。不必要に使うべきではないフックらしい。
以下に こちら から引用したコードを載せておく。useLayoutEffect の部分を useEffect にすると確かに一瞬ちらついて見える。
import * as React from 'react'; const { useRef, useEffect, useLayoutEffect } = React; const UseLayoutEffectSample = () => { const displayAreaRef = useRef(); const renderCountRef = useRef(0); useLayoutEffect(() => { renderCountRef.current++; displayAreaRef.current.textContent = String(renderCountRef.current); }); return ( <p> このコンポーネントは <b ref={displayAreaRef} /> 回描画されました。 </p> ); }; export default () => { return ( <> <UseLayoutEffectSample /> </> ); };
実行
useDebugValue
React DevTools 内にカスタムフックに関するラベル付きデバッグメッセージを表示できるフックらしい。カスタムフックがよくわからないけど、まぁ、デバッグメッセージがだせるのかな。程度に思っておく。
useImperativeHandle
親から渡ってきたref に対してメソッドをはやすことができるようになる?
import * as React from 'react'; const { useRef, forwardRef, useImperativeHandle } = React; function FancyInput(props, ref) { const inputRef = useRef(); useImperativeHandle(ref, () => ({ focus: () => { inputRef.current.focus(); } })); return <input ref={inputRef} />; } FancyInput = forwardRef(FancyInput); export default () => { const inputRef = useRef(); const focus = () => { inputRef.current.focus(); } return ( <> <FancyInput ref={inputRef} /> <button onClick={focus} >Focus</button> </> ); };
所感
親コンポーネントが簡素化されて、子コンポーネントに責務を移譲させることができる。っていうのがメリットなのかなぁ??
まとめ
useReducer は使いどころがあるかなぁという感じで、それ以外はまだReactに慣れてからもう一度再勉強かなという感じ。
いつものようにここに載せたコードは Github にもアップしている。
GitHub - bamchoh/react16_8_study: My study repo for react 16.8