5
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

NextremerAdvent Calendar 2018

Day 9

ReactHooksを使ってみる

Last updated at Posted at 2018-12-27

この記事はNextremer Advent Calendar 2018の9日目の記事です。

弊社の対話システムであるminaraiのフロントをReactHooksを使って作ってみました。

はじめに

ReactHooksで提供されるAPIのうち

  • useState
  • useContext
  • useMemo
    の3つを使っています。
    また各APIの詳細な説明は他にいくらでも良記事が転がっているのでこの記事では割愛します。

作ってみる

準備

npmモジュールのインストール

いつもの
今回は楽をしたいのでモジュールバンドラーにparcelを使ってます。parcel超便利 :sparkles:

$ 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を定義

src/js/contexts/minarai.js
const MinaraiContext = createContext()

カスタムProviderを作成。
このContextでは、 送信メッセージ、受信メッセージ、送信メソッドの3つを提供します。

src/js/contexts/minarai.js
const MinaraiContextProvider = ({ children }) => {
  const [outgoingMessage, setOutgoingMessage] = useState('')
  const [incomingMessage, setIncomingMessage] = useState('')
  const send = msg => {}
  return (
    <MinaraiContext.Provider value={{ outgoingMessage, incomingMessage, send }}>
      {children}
    </MinaraiContext.Provider>
  )
}

今回は単純に送信イベント、受信イベントのみをウォッチし、それぞれのメッセージ用のローカルステートを更新するようにしています。

src/js/contexts/minarai.js
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を使うと簡単に出来ますね :sparkles:

src/js/contexts/minarai.js
  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でラップしてやります。

src/js/components/App.js
+ import { MinaraiContextProvider } from '../contexts/minarai'

  return (
+   <MinaraiContextProvider>
      <Main>
        略
      </Main>
+   </MinaraiContextProvider>
  )

あとは使うコンポーネントで適宜useContextしてやればOKです。

src/js/components/Form.js
+ 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>
    )
  }
src/js/components/Messages.js
+ 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

5
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?