2019/02/06にReact16.8がリリースされ、16.7のalphaから入っていたReact Hooksが安定版にやってきました。
そこで今回このブログで使われているReactを16.8に上げて、ステートフルなコンポーネントをReact Hooksを使いFunction Compoenentに書き換えてみました。
React Hooksの解説をした記事はたくさんあると思うので、今回は書き換えてどう変わったかを見ていきましょう。
該当する部分はHeaderの検索フォームです。
プルリクエストだけ見たいかたはこちら。
https://github.com/mottox2/website/pull/39
これはいろんな記事で言及されているuseStateを用いて書き換えます。
useStateに初期値を引数として与えると、値と更新用の関数が返ってきます。
const NewComponent = props => {
const [query, updateQuery] = useState('')
const handleInput = (e) => {
updateQuery(e.target.value)
}
return <input onChange={handleInput}/>
}
書き換え前のClassComponentです。主にデータの取得とキーボードイベントのつけ外しです。
コンポーネントの各所に処理が散らばってて可読性が低い状態です。
class OldComponent from Component {
async componentDidMount() {
const res = await axios.get('/search.json')
this.setState({ data: res.data })
this.focusShortcutHandler = this.focusShortcut.bind(this);
window.addEventListener('keydown', focusShortcutHandler)
}
componentWillUnmount() {
window.removeEventListener('keydown', focusShortcutHandler)
}
focusShortcut(e) {
if (e.keyCode === keyCodes.SLASH && !this.state.isActive) {
this.focusInput()
e.preventDefault()
}
}
}
useEffectで書き換えると以下のようになります。
useEffectはcomponentDidMount, componentDidUpdateのタイミングで呼ばれて、returnで返した関数がcomponentWillUnmountのタイミングで呼ばれるHookと考えてください。
つまりイベントの付け外しが同じHooksの中で操作出来るので、以前は散らばっていた同じような処理がまとめられ読みやすくなっていると思います。
また、axiosでデータ取得を行っている部分はコンポーネントがマウントされたタイミングでのみ実行したいので第2引数に[]
を渡しています。(第2引数は監視したい値を配列で渡す。この場合変更を監視しないという意味でから配列を渡している)
useEffectに関してはReactの公式ドキュメントに詳しく書いてあるので読むといいでしょう。
https://reactjs.org/docs/hooks-effect.html
const NewComponent = (props) => {
useEffect(() => {
axios.get('/search.json').then(res => {
updatePosts(res.data)
})
return undefined
}, [])
useEffect(() => {
const focusShortcut = (e: KeyboardEvent) => {
if (e.keyCode === keyCodes.SLASH && !isActive) {
inputEl.current.focus()
e.preventDefault()
}
}
window.addEventListener('keydown', focusShortcut)
return () => {
window.removeEventListener('keydown', focusShortcut)
}
})
return <div/>
}
Hooksの公式Q&Aにもある内容ですが、prevStateとstateを比較して処理を行う部分があってその書き換えにusePreviousというCustom Hooksを利用しました。
もともとのコードがあまり綺麗でないのはご愛嬌…
const usePrevious = (value) => {
const ref = useRef(null)
useEffect(() => {
ref.current = value
})
return ref.current
}
const NewComponent = () => {
const inputEl = useRef(null)
const prevMobileShow = usePrevious(props.isMobileShow)
if (prevMobileShow !== props.isMobileShow) {
window.setTimeout(() => {
inputEl.current.focus()
}, 10)
}
return <input ref={inputEl} />
}
今回、Hooksに書き換えたコンポーネントは検索フォームです。言ってしまえば、入力されたクエリに応じて表示する記事をフィルタリングするためのコンポーネントです。
こういったコンポーネントで更新するたびにフィルリングの処理を実行すれば動作は重くなっていきます。
そういったときに利用したいのがuseMemoというフックです。useMemoはいわゆるメモ化を行ってくれるフックで関数の実行結果を保持してくれます。
この場合、posts(記事の配列)とquery(入力されたクエリ)に変化がなければ再計算は必要ないはずなので次のような実装になりました。
const filterPosts = (posts, query) => {
return posts.filter(item => {
const itemString = `${item.title} ${item.tags.join('')}`.toLowerCase()
if (!(itemString.indexOf(query) > -1)) {
return false
}
return true
})
}
const NewComponent = (props) => {
const [query, updateQuery] = useState('')
const [posts, updatePosts] = useState([])
const filteredPosts = useMemo(() => filterPosts(posts, query), [posts, query])
return <div />
}
パラダイムシフトを感じた。Viewのすべてが関数で表現出来る世界が近づきつつあるのかもしれない。
これは自分がjQuery文化からReact文化に入った4年前ぐらいの時と同じような衝撃を受けた。
今までVueとReactは大きく変わらないという印象を持っていたが、今回のアップデートで別物という印象に変わった。
React公式のスタンスとしては、React Hooksへの利用を強制するようなものではない…1としているので今まで通りの書き方を続けてもいいが、ぜひReactユーザーにはHooksを触って欲しいと思いました。
You don’t have to learn Hooks right now. Hooks have no breaking changes, and we have no plans to remove classes from React. https://reactjs.org/blog/2019/02/06/react-v16.8.0.html ↩
ここ最近、Web技術を利用した画像生成に興味があります。本記事では、日本語における表現の一種である縦書きに焦点を当て、Web技術を使った縦書きを含む画像生成方法についての調査をまとめました。 > 現
追記(2022/12/29): 問い合わせに対応する窓口をTwitterに統一したいので、フォームページは削除しました。 当ブログは静的サイトホスティングサービスのNetlifyでホスティングされ
毎年10月に開催されるHacktoberfestに参加しました。このイベントはOSSへの貢献を行い、期間中に規定数(4つ)の貢献を行った人に特典がプレゼントされるものになっています。 自分はドキュメ