この記事はNextremer Advent Calendar 2018の9日目の記事です。
弊社の対話システムであるminaraiのフロントをReactHooksを使って作ってみました。
はじめに
ReactHooksで提供されるAPIのうち
- useState
- useContext
- useMemo
の3つを使っています。
また各APIの詳細な説明は他にいくらでも良記事が転がっているのでこの記事では割愛します。
作ってみる
準備
npmモジュールのインストール
いつもの
今回は楽をしたいのでモジュールバンドラーにparcelを使ってます。parcel超便利
$ mkdir react-hooks-minarai-sample && cd $_
$ npm init -y
$ npm i -S react@next react-dom@next
$ npm i -D parcel-bundler
minaraiを動かすために必要なものをインストール
minaraiではSocket.IOを使っているので、Socket.IOクライアントとminarai用のSDKをインストール
$ npm i -S socket.io-client minarai-client-sdk-js-socket-io
あとデザイン周り
$ npm i -S styled-components @material-ui/core
minarai用のContextを作る
Socket.IOでminaraiの各種イベントを受信するためのContextを作ります。
まずはMinaraiContextを定義
const MinaraiContext = createContext()
カスタムProviderを作成。
このContextでは、 送信メッセージ、受信メッセージ、送信メソッドの3つを提供します。
const MinaraiContextProvider = ({ children }) => {
const [outgoingMessage, setOutgoingMessage] = useState('')
const [incomingMessage, setIncomingMessage] = useState('')
const send = msg => {}
return (
<MinaraiContext.Provider value={{ outgoingMessage, incomingMessage, send }}>
{children}
</MinaraiContext.Provider>
)
}
今回は単純に送信イベント、受信イベントのみをウォッチし、それぞれのメッセージ用のローカルステートを更新するようにしています。
const MinaraiContextProvider = ({ children }) => {
const [outgoingMessage, setOutgoingMessage] = useState('')
const [incomingMessage, setIncomingMessage] = useState('')
// minarai-clientの作成
const cli = new MinaraiClient({/* 略 */})
cli.on('sync', data => setOutgoingMessage(data.body.message))
cli.on('message', data => setIncomingMessage(data.body.messages[0].utterances[0].text))
cli.init()
const send = msg => cli.send(msg)
return (
<MinaraiContext.Provider value={{ outgoingMessage, incomingMessage, send }}>
{children}
</MinaraiContext.Provider>
)
}
これでコンテキストは完成。。。
と思っていたのですが、この実装だとProviderが呼び出されるたびにイベントがバインドされるので、送受信のたびにイベント発火回数が増えてしまいます。
MinaraiClientの生成、イベントバインドの処理を初回のみ行うように修正します。
ReactHooksのuseMemo
を使うと簡単に出来ますね
export const MinaraiContextProvider = ({ children }) => {
const [outgoingMessage, setOutgoingMessage] = useState('')
const [incomingMessage, setIncomingMessage] = useState('')
// minarai-clientの作成
+ const minaraiClient = useMemo(() => {
const cli = new MinaraiClient({/* 略 */})
cli.on('sync', data => setOutgoingMessage(data.body.message))
cli.on('message', data => setIncomingMessage(data.body.messages[0].utterances[0].text))
cli.init()
+ return cli
+ }, [])
- const send = message => cli.send(message)
+ const send = message => minaraiClient.send(message)
return (
<MinaraiContext.Provider value={{ outgoingMessage, incomingMessage, send }}>
{children}
</MinaraiContext.Provider>
)
}
useMemo
の第二引数に空配列を渡すことで初回のみ動作させることが出来ます。
同様の処理はuseEffect
を使うことでも可能ですが、今回はクライアントインスタンスを返却したかったのでuseMemo
を使用しています。
minaraiContextを組み込む
まずはルートコンポーネントでContextProviderでラップしてやります。
+ import { MinaraiContextProvider } from '../contexts/minarai'
return (
+ <MinaraiContextProvider>
<Main>
略
</Main>
+ </MinaraiContextProvider>
)
あとは使うコンポーネントで適宜useContext
してやればOKです。
+ import { MinaraiContext } from '../contexts/minarai'
export default () => {
const [text, setText] = useState('')
+ const { send } = useContext(MinaraiContext)
return (
<Wrapper>
<Input value={text} onChange={e => setText(e.target.value)} />
- <SendButton color="primary">
+ <SendButton color="primary" onClick={() => send(text)}>
Send
</SendButton>
</Wrapper>
)
}
+ import { MinaraiContext } from '../contexts/minarai'
export default () => {
+ const { outgoingMessage, incomingMessage } = useContext(MinaraiContext)
return (
<Wrapper>
<MessageBalloon align="left">
- <OutgoingMessage>test</OutgoingMessage>
+ <OutgoingMessage>{outgoingMessage}</OutgoingMessage>
</MessageBalloon>
<MessageBalloon align="right">
- <IncomingMessage>test</IncomingMessage>
+ <IncomingMessage>{incomingMessage}</IncomingMessage>
</MessageBalloon>
</Wrapper>
)
}
作ったものはこちらにあります。
https://github.com/k-tada/react-hooks-minarai-sample