バイトでReactを使うことになり、hooksの利用を強行提案して書いて見たところ、楽しすぎたのでどんな感じで使っているかを紹介したい。
あくまでこんな感じで使ってみたよって話なので、良いプラクティスかどうかは自己判断でお願いします
そもそもなんでhooksを利用するのか
公式ドキュメントから引用すれば
ステートフルなロジックをコンポーネント間で再利用する
ためです。今まではHoCとかで管理してたものをhooksでやりやすくした感じですね。
例1) 初期化するときの非同期通信の共通化
まずは単純にuseEffect(あるいはcomponentDidMount)内のfetchしてjsonにしてみたいなロジックを一箇所にまとめてみる
useInitFetch.js
import { useEffect } from 'react';
export function useInitFetch(path, callback) {
useEffect(() => {
fetch(`http://path/to/api/${path}`)
.then(res => res.json())
.then(res => ({data: res}),
e => ({error: e}))
.then(res => callback(res));
}, []);
}
使い方は
MyComponent.jsx
import React, { useState } from 'react';
import { useInitFetch } from './useInitFetch'
export const MyComponent = () => {
const [value, setValue] = useState({});
useInitFetch('path/to/value', (res) => setValue(res));
return <div>{value}</div>
}
例2) socket.ioの通信をどのコンポーネントからも呼び出せるようにする
useSocketはこんなことができるようにしました
- いろんなところからsocket.emitを呼び出したいが、socket自体はコンポーネントの表現には関わらないので、バケツリレーを避けたい -> context apiを利用
- 間違ってio.connectをなんども呼び出さないようにしたい -> 同じくcontext apiで解決可能
- あるコンポーネントがマウントするまでイベントを登録したくない・アンマウント後はイベントを削除したい -> useEffectのクリーンアップを利用
useSocket.jsx
import React, { useContext, useEffect, useState, createContext } from 'react';
import io from 'socket.io-client';
export const socketContext = createContext();
export const SocketProvider = ({children}) => {
const [socket, _] = useState(() => io.connect('http://path/to/socketapi'));
useEffect(() => {
return function cleanup() {
socket.close();
}
}, []);
return (
<socketContext.Provider value={socket}>
{children}
</socketContext.Provider>
)
}
export const useSocket = (setEvent, removeEvent) => {
const socket = useContext(socketContext);
useEffect(() => {
setEvent(socket);
return () => {
removeEvent(socket);
}
}, []);
return socket;
}
使い方:
MyComponent.jsx
import React, { useState } from 'react';
import { SocketProvider, useSocket } from './useSocket';
export const MyComponent = () => {
const [value, setValue] = useState({});
const socket = useSocket((socket) => {
socket.on('myevent', (res) => {
setValue(res);
});
}, (socket) => {
socket.off('myevent');
});
return (
<>
{value}
<button onClick={() => socket.emit('myevent2', 'hoge')>Click!</button>
</>
)
export const App = () => {
return (
<SocketProvider>
<MyComponent />
</SocketProvider>
}
これに限ったことではないですが、hooks + context APIは特にグローバルに唯一存在してほしい変数がある時にとても強力であると感じました
例3) 別Windowで開く&別Windowは最大1つまで
同じくcontext api + hooksを利用できます。
useNewWindow.jsx
import { useState, createContext } from 'react';
export const windowContext = createContext();
export const WindowProvider = ({children}) => {
const [win, setWin] = useState(null);
return (
<windowContext.Provider value={[win, setWin]}>
{children}
</windowContext.Provider>
)
}
export const useNewWindow = (title, features) => {
const [win, setWin] = useContext(windowContext);
return (url) => {
if(win === null || win.isClosed) {
setWin(window.open(url, title, features));
}
}
}
使い方:
MyComponent.jsx
import React, { useState } from 'react';
import { WindowProvider, useNewWindow } from './useNewWindow';
export const MyComponent = () => {
const openWindow = useNewWindow('HogeTitle', 'resizable=yes,scrollbars=yes');
return (
<button onClick={() => openWindow('path/to/newWindow')}>Open Window</button>
)
export const App = () => {
return (
<WindowProvider>
<MyComponent />
</WindowProvider>
)
};
}
まとめ
なんかhooksがいいっていうよりcontext apiがいいみたいな記事になってしもうた
useRefとかuseMemoとかまだまだ面白そうな機能がいっぱいなので、使う場面があったら更新していきます