【React】新機能hooks
【React】新機能hooks:
会社で色々な事件に巻き込まれしまってなかなか書けなかった不幸な日々でした。
ちょっと乗り遅れた感あるんですが、自分の勉強と理解がてら新しい機能であるhooksを書いていきます。
hooksはclassを書くことなくstateとか、色々な機能を使うことができる新しい機能です!React v16.7.0-alphaから使えます。
そして、かつてはSFCと呼ばれる関数定義のコンポーネントたちも、ここではSFCではなくFCになります。
なにせstatelessではないからね・・・!!。
この機能を使うと、functional componentがstateを扱えるようになります。ちゃんとre-renderされようとも値を保持してくれるみたいです。
useStateの書式はこうなります。
こんな感じでuseStateは現在値と、setStateに似た関数を返します。
ざっとこんな感じになります。
ということでやってみましょう。
見事に消滅しましたね。
結果として、第一引数と第一引数を変更する値を保持ている関数と言った感じですね。
array destructingを使うことによっていろんな名前を持たせることもできます。
なので今までは、
としていたのをこうしようぜといったところでしょうか?。
データ取得やDOM変更といったside effectsを扱うためにはこのhookを使います。
effect hookは以前からある
こうしてみるとわかるんですが、まずcomponentDidmountと同じようなタイミングでuseEffectにある関数が発火しています。
なので、
初回に
次に、ボタンを押すと、これまたalertが再び発火するといったことができます。
デフォルトだと、renderのたびにeffectが呼ばれることになります。
そして、すでにこのサンプルでお気づきかもしれませんが、現状のままだと、helloの方を押してもuseEffectが発火します・・・。
そこで、第二引数の登場です。
第二引数に、アレイの形で比較対象を渡すことで、判定をおこないます。
つまり、こうするんです。
すると、countが変化したかを判定してくれて、
もう少し深掘りしていくと、
こちらのケースでは3回に一回に発火します。
小ネタですが、一度だけ動かしたいのであれば
componentDidMountとかはこんな感じですね。
複数の値の検知も行ってくれるので
こうすると、countかhelloのどちらかが変更されれば発火するというギミックを組めます。
関数を返すことでクリーンアップの処理を挟むことができます。
例えばこちらの例。でいくと、ログはこうなります。
最後に関数を返すと次回、effectが処理を行うときに前もってcleanupを実行してくれるようになるという挙動です。
一応紹介だけしておくと、他のとこでも呼べるよと言う話。
eslint-plugin-react-hooksこれ使おうなって話なだけです。
ここら辺の話は、Rules of Hooksで書かれてます。
contextを使うことができるやつです。
contextに思い入れないため、感想薄いです。
これが僕がとても楽しみにしていた機能群!
これがとても楽しみでたまらなかった人たくさんいるのでは・・・????ってぐらい、楽しみだった機能。
仕事忙しくてさわれなくて悲しかった。もっと自由に時間割ける環境にいこうかなぁ・・・・・。
めっちゃ公式通りで恐縮なんですが、
こんな感じです。
なので当然こんなこともできちゃいます。
これやる意味があるのかと言われると複雑ですが・・・・。
引数は3つとれて、返り値は2つです。
initialActionに書いたものは、初回render後に発火する手筈みたいです。
なので、初期化するときに任意の値を入れ込むと言ったことがここでできますね。
ちょっとreduxと違うところとしては結局useStateに近い概念なところですね。なので、connectみたいな感じでどっからでもstore取ってこれるかというとそうでも無いところがちょっと気になりますね。
これででかい奴を作ったことがないので、ちょっとこれから検討になりますが、ベストプラクティスはなんだろうかと思います。はしご渡しだけは絶対にないとは思ってます。
コールバックをしてくれます。
なんか、こころやさしいおばはんみたいな機能ですかね。
値が変更されるたびに内部の関数が実行されます。
なので、ボタンを押すとこんな感じで値が出力されます。
インターフェースは、
となっています。
これの面白いところとしては値が保持されることです。
watchValuesに何も与えないと、functionが実行されないので試してみます。
となります。
初回は実行されて、以後実行されなくなるので、直前に保持された
となります。
useMemoと基本的には一緒です。
ただし一つだけ違う点があります。
返り値が関数になっている点です。
なので
こうしてます。
useRefを使うことでref objectが返ります。それを
refに対して処理を紐づけることができます。
このコードでは、inputElにfocusが割り当てられていて、います。
ボタンを押すことによって、focusが実行されるので、useImperativeMethodsのfocusも実行されるという手筈になっています。
いつものように第三引数にwatchValuesを入れることができます。
こうすると、countが 0 ~ 2の間は、focusボタンを押したときに
と出力されますが、countが3のときは
が出力されます。
forwardRefがどーたらとドキュメントに書かれたのでforwardRefを使ったサンプルを書きました。
ちょっとややこしいサンプルで申し訳ないです・・・。
こうすると、testボタンを押すと、hiボタンを押したことになります。
と言うのも、
testボタンは押すと
次に、
これによって、newButtonElement=CustomButtonとなります。
refがわたされたcreateButtonは、渡されたrefをuseImperativeMethosに渡しているため、このような挙動になります。
以上です。
ちょっと感動したものは厚めに、そうで無いものは薄めにといった紹介になってしまいすみません。
このサンプル書くまでは、
反面
とりわけrecomposeとかもあまり好きではなかったのでこんな簡単に色々とできるようになってさぞかしSFC、いやFCも幸せだろうと思います!僕もFC多用していきたいと思いますー!
はじめに
会社で色々な事件に巻き込まれしまってなかなか書けなかった不幸な日々でした。ちょっと乗り遅れた感あるんですが、自分の勉強と理解がてら新しい機能であるhooksを書いていきます。
hooks
hooksはclassを書くことなくstateとか、色々な機能を使うことができる新しい機能です!React v16.7.0-alphaから使えます。create-react-app
でつくると、僕がやってみたときは16.6だったので、ちょっとアップデートしないとできないので注意です。そして、かつてはSFCと呼ばれる関数定義のコンポーネントたちも、ここではSFCではなくFCになります。
なにせstatelessではないからね・・・!!。
state
この機能を使うと、functional componentがstateを扱えるようになります。ちゃんとre-renderされようとも値を保持してくれるみたいです。useStateの書式はこうなります。
const a = useState(b); a: [currentState, dispatchAction] b: initialState
import React, { useState } from 'react'; export default () => { const [a, b] = useState(0); return ( <div> <button onClick={() => { b(a + 1); }}> \ovo/ {"<"}{a} times!! </button> </div> ) };
this.stateと異なり、objectを使うべきではない。・・・・しかし、もし望むならそれも可能だ。initialStateは初回だけ使われるであろう・・・・。的なことが書いてあります。
ということでやってみましょう。
import React, { useState } from 'react'; export default () => { const [a, b] = useState({ count: 0, str: 'hello', }); const { count, str, } = a; console.log(a); return ( <div> <button onClick={() => {b({ count: count + 1 })}}> \ovo/ {"<"} {count}times!! {str}!! </button> </div> ) };
見事に消滅しましたね。
結果として、第一引数と第一引数を変更する値を保持ている関数と言った感じですね。
Declaring multiple state variables
array destructingを使うことによっていろんな名前を持たせることもできます。import React, { useState } from 'react'; export default () => { const [a, b] = useState(0); const [_a, _b] = useState('!'); return ( <div> <button onClick={() => { b(a + 1); }}> \ovo/ {"<"} {a}times!! </button> <button onClick={() => { _b(`${_a}!`)}}> \omo/ {"<"} hello{_a} </button> </div> ) };
state = { a: 0, _a: '!', };
effect
データ取得やDOM変更といったside effectsを扱うためにはこのhookを使います。effect hookは以前からある
componentDidMount
やcomponentDidUpdate
、componentWillUnmount
と同じ目的で使うことができるみたいです。import React, { useState, useEffect } from 'react'; export default () => { const [a, b] = useState(0); const [_a, _b] = useState('!'); useEffect(() => { alert(`updated! ${a}times!!`); }); return ( <div> <button onClick={() => { b(a + 1); }}> \ovo/ {"<"} {a}times!! </button> <button onClick={() => { _b(`${_a}!`)}}> \omo/ {"<"} hello{_a} </button> </div> ) };
なので、
初回に
updated! 0times!
とアラートが表示されます。次に、ボタンを押すと、これまたalertが再び発火するといったことができます。
デフォルトだと、renderのたびにeffectが呼ばれることになります。
そして、すでにこのサンプルでお気づきかもしれませんが、現状のままだと、helloの方を押してもuseEffectが発火します・・・。
そこで、第二引数の登場です。
skipping effect
第二引数に、アレイの形で比較対象を渡すことで、判定をおこないます。useEffect(() => { alert(`updated! ${count}times!!`); }, [count]);
すると、countが変化したかを判定してくれて、
prev === current
であればeffectをスキップするということが可能になっています。もう少し深掘りしていくと、
useEffect(() => { alert(`updated! ${count}times!!`); }, [Math.floor(count/3)]);
count: 1 -> 0 count: 2 -> 0 count: 3 -> 1 (0 -> 1になったので発火) count: 4 -> 1 count: 5 -> 1 count: 6 -> 2 (1 -> 2になったので発火)
[]
を使うことで一度だけ発火します。useEffect(() => { console.log('------', count); }, []);
複数の値の検知も行ってくれるので
import React, { useState, useEffect } from 'react'; const Sub = (props) => { const { count, hello, } = props; useEffect(() => { console.log('------', count); }, [hello, count]); return ( <div> {count} </div> ) }; export default () => { const [a, b] = useState(0); const [_a, _b] = useState('!'); return ( <div> <Sub count={a} hello={_a} /> <button onClick={() => { b(a + 1); }}> \ovo/ {"<"} {a}times!! </button> <button onClick={() => { _b(`${_a}!`)}}> \omo/ {"<"} hello{_a} </button> </div> ) };
cleanup
関数を返すことでクリーンアップの処理を挟むことができます。useEffect(() => { console.log('subscribe', count); console.log('---------------'); return () => { console.log('unsubscribe', count) }; });
subscribe 0 --------------- ボタン押した。 unsubscribe 0 subscribe 1 --------------- ボタン押した。 unsubscribe 1 subscribe 2 --------------- ボタン押した。 unsubscribe 2 subscribe 3
Building Your Own Hooks
一応紹介だけしておくと、他のとこでも呼べるよと言う話。import React, { useState, useEffect } from 'react'; const useStatus = () => { const [id, setId] = useState(0); const newId = id + 1; return { newId, dispatch: setId, }; }; export default () => { const { newId, dispatch, } = useStatus(); return ( <div> <button onClick={() => { dispatch(newId)}}> \owo/ {"<"} status{newId} </button> </div> ) };
use
という プレフィックスをつけるとlinterがどうのこうのと書いてありますが基本それだけで動作上はuseとつけてuseStatus
にする必要はないです。eslint-plugin-react-hooksこれ使おうなって話なだけです。
ここら辺の話は、Rules of Hooksで書かれてます。
useContext
contextを使うことができるやつです。contextに思い入れないため、感想薄いです。
import React, { useContext, createContext, useState } from 'react'; const context = createContext(); const { Provider, Consumer, } = context; const Child = () => { return ( <Consumer> {({ count, increment, decrement }) => ( <div> <span>{count}</span> <button onClick={increment}> + </button> <button onClick={decrement}> - </button> </div> )} </Consumer> ); } export default () => { useContext(context); const [count, setCount] = useState(0); return ( <Provider value={{ count, increment: () => { setCount(count + 1); }, decrement: () => { setCount(count - 1); }, }} > <Child/> </Provider> ); };
Hooks APIs
これが僕がとても楽しみにしていた機能群!
useReducer
これがとても楽しみでたまらなかった人たくさんいるのでは・・・????ってぐらい、楽しみだった機能。仕事忙しくてさわれなくて悲しかった。もっと自由に時間割ける環境にいこうかなぁ・・・・・。
めっちゃ公式通りで恐縮なんですが、
こんな感じです。
import React, { useState, useReducer } from 'react'; const initialState = { count: 0, }; const INCREMENT = 'INCREMENT'; const DECREMENT = 'DECREMENT'; const RESET = 'RESET'; const reducer = (state, action) => { const { type, } = action; const { count, } = state; switch (type) { case INCREMENT: return { ...state, count: count + 1, }; case DECREMENT: return { ...state, count: count - 1, }; case RESET: return initialState; default: return state; } }; export default () => { const [ state, dispatch, ] = useReducer(reducer, initialState); const { count, } = state; return ( <div> <button onClick={() => { dispatch({ type: INCREMENT }); }}> add </button> <button onClick={() => { dispatch({ type: DECREMENT }); }}> del </button> <button onClick={() => { dispatch({ type: RESET }); }}> reset </button> \owo/ {"<"} {count} happy!! </div> ) };
useState
にreducerを突っ込んでいる形になりますね。なので当然こんなこともできちゃいます。
const [ state, dispatch, ] = useReducer(reducer, initialState); const [ state2, dispatch2, ] = useReducer(reducer, initialState);
引数は3つとれて、返り値は2つです。
[state, dispatch] = useReducer(reducer, initialState, initialAction);
なので、初期化するときに任意の値を入れ込むと言ったことがここでできますね。
ちょっとreduxと違うところとしては結局useStateに近い概念なところですね。なので、connectみたいな感じでどっからでもstore取ってこれるかというとそうでも無いところがちょっと気になりますね。
これででかい奴を作ったことがないので、ちょっとこれから検討になりますが、ベストプラクティスはなんだろうかと思います。はしご渡しだけは絶対にないとは思ってます。
useMemo
コールバックをしてくれます。なんか、こころやさしいおばはんみたいな機能ですかね。
import React, { useState, useMemo } from 'react'; export default () => { const [count, setCount] = useState(0); const newValue = useMemo(() => { console.log('Don\'t you forget?', count); return `${count} push`; }, [count]); console.log(newValue); return ( <div> <button onClick={() => { setCount(count + 1); }}> \owo/ {"<"} {count} times! </button> </div> ) };
なので、ボタンを押すとこんな感じで値が出力されます。
Don't you forget? 0 0 push Don't you forget? 1 1 push Don't you forget? 2 2 push
returnValue = useMemo(function, watchValues);
これの面白いところとしては値が保持されることです。
watchValuesに何も与えないと、functionが実行されないので試してみます。
Don't you forget? 0 0 push 0 push 0 push
初回は実行されて、以後実行されなくなるので、直前に保持された
0 push
がuseMemoの返り値となります。Don't you forget? 0 0 push 0 push -> このときcount:1 0 push -> count:2 Don't you forget? 3 -> count:3 でwatchValuesが更新されるので関数が発火。 3push -> count: 3を認識して返り値変更
useCallback
useMemoと基本的には一緒です。ただし一つだけ違う点があります。
import React, { useState, useCallback } from 'react'; export default () => { const [count, setCount] = useState(0); const newValue = useCallback(() => { console.log('Don\'t you forget?', count); return `${count} push`; }, [Math.floor(count/3)]); console.log(newValue()); return ( <div> <button onClick={() => { setCount(count + 1); }}> \owo/ {"<"} {count} times! </button> </div> ) };
なので
console.log(newValue());
useRef
useRefを使うことでref objectが返ります。それをref
属性として渡すことによって、refを扱うことができます。import React, { useState, useRef } from 'react'; export default () => { const [count, setCount] = useState(0); const buttonElement = useRef(null); console.log(buttonElement); return ( <div> <button ref={buttonElement} onClick={() => { setCount(count + 1); }}> \owo/ {"<"} {count} times! </button> </div> ) };
useImperativeMethods
refに対して処理を紐づけることができます。import React, { useRef, useImperativeMethods } from 'react'; export default () => { const inputEl = useRef(null); const onButtonClick = () => { inputEl.current.focus(); }; useImperativeMethods(inputEl, () => ({ focus: () => { console.log('focus'); }, })); return ( <> <input ref={inputEl} type="text" /> <button onClick={onButtonClick}>Focus the input</button> </> ); };
ボタンを押すことによって、focusが実行されるので、useImperativeMethodsのfocusも実行されるという手筈になっています。
いつものように第三引数にwatchValuesを入れることができます。
import React, { useRef, useImperativeMethods, useState } from 'react'; export default () => { const [count, setCount] = useState(0); const inputEl = useRef(null); const onButtonClick = () => { inputEl.current.focus(); }; useImperativeMethods(inputEl, () => ({ focus: () => { console.log('focus', count); }, }), [Math.floor(count/3)]); return ( <> <input ref={inputEl} type="text" /> <button onClick={onButtonClick}>Focus the input</button> <button onClick={() => { setCount(count + 1); }}> {count} </button> </> ); };
focus 0
と出力されますが、countが3のときは
focus 3
が出力されます。
おまけ
forwardRefがどーたらとドキュメントに書かれたのでforwardRefを使ったサンプルを書きました。ちょっとややこしいサンプルで申し訳ないです・・・。
こうすると、testボタンを押すと、hiボタンを押したことになります。
import React, { useRef, useImperativeMethods, forwardRef } from 'react'; const createButton = (props, ref) => { const buttonElement = useRef(null); useImperativeMethods(ref, () => ({ click: () => { buttonElement.current.click(); }, })); return <button ref={buttonElement} onClick={() => { console.log('click child');}}> hi </button>; }; const CustomButton = forwardRef(createButton); export default () => { const newButtonElement = useRef(null); const parentButtonClick = () => { newButtonElement.current.click(); }; return ( <div> <CustomButton ref={newButtonElement} /> <button onClick={parentButtonClick}> test </button> </div> ) };
export default () => { const newButtonElement = useRef(null); const parentButtonClick = () => { newButtonElement.current.click(); }; return ( <div> <CustomButton ref={newButtonElement} /> <button onClick={parentButtonClick}> test </button> </div> ) };
newButtonElement.current.click()
を実行するように書かれています。次に、
<CustomButton ref={newButtonElement} />
const createButton = (props, ref) => { const buttonElement = useRef(null); useImperativeMethods(ref, () => ({ click: () => { buttonElement.current.click(); }, })); return <button ref={buttonElement} onClick={() => { console.log('click child');}}> hi </button>; }; c
おわりに
以上です。ちょっと感動したものは厚めに、そうで無いものは薄めにといった紹介になってしまいすみません。
このサンプル書くまでは、
useState
の何が嬉しいのかと思ってたんですが、これ書きながら何回もuseState
って書いてたら、こっちの方が楽なきがしてきました。useState
, useEffect
, useMemo
は使い所がたくさんあると思うのでぜひこれから使っていきたいと思います。反面
useReducer
すごいけど、これどうやってつかおう・・・。って感じが結構あります。useContext
と組み合わせて使うのが正かなぁという気がしないでも無いですが自信はないですね・・・。とりわけrecomposeとかもあまり好きではなかったのでこんな簡単に色々とできるようになってさぞかしSFC、いやFCも幸せだろうと思います!僕もFC多用していきたいと思いますー!
コメント
コメントを投稿