- 投稿日:2019-07-07T22:41:02+09:00
Reactをわかった気になる #1 Reactの特徴
本記事の目的
Reactという単語は聞いたことあるけれど概要はよく知らない人向けの記事になります(元々はバイト先のディレクターさんにReactを教えるためのスライドです)。
なのでReactの特徴やその大まかな意味などの上流の部分に触れていきます。
その中でも今回はReactの特徴について触れていこうと思います。Reactとは
Reactとはユーザインタフェースを構築するためのJSライブラリです。
よくAngularやjQueryと比較されますが、React自体はウェブフレームワークではなく「ライブラリ」ですので、実際の開発の際には他にも様々なライブラリを組み合わせて使うことが多いです。
Reactの開発はFacebookが行っていて2019年7月現在githubのスターは132,000以上もあるwebフロント界隈では主流になりつつあるライブラリです。Reactの特徴
Reactの特徴は大きく分けて3つあります。
- componentベース
- virtialDOM
- JSX
まず1つ目はcomponentベースです。
基本的にReactでは1つのwebページを複数のコンポーネントに分けて実装していきます。
コンポーネントに分けることで共通部品として使ったり、コンポーネントごとに状態を管理できるためソースコードの見通しが良くなります。
また、Atomicデザインと非常に相性がいいです。
→Reactのコンポーネントについて(後日公開予定)
例えばReactのホームページでは上記のようにコンポーネントを分けて実装されていました(便宜上、元ページの両端をカットしています)。2つ目にvirtualDOMがあげられます。
virtualDOMはJSで作られた仮想のDOMです。内容はJSONのため、ブラウザのDOMよりも軽量で扱いやすいという利点があります。
webページの1部分を動的に変化させるときにReactでは1度virtualDOMを作成し、もとのvirtualDOMと比較をして変化のある部分だけをブラウザに通知します。
そうすることでブラウザのDOMの更新による処理の負担を減らし、パフォーマンスの最適化を図ることができます。この技術によりSPA(Single Page Application)を現実的にしました。
→ReactのvirtualDOMについて(後日公開予定)
※この図はこちらの記事を参考に作成させていただきました。最後はJSXになります。
JSXはソースコードの記述方法に関する特徴になります。
JSXは簡単に言うとJSのコードの中にHTMLを書くことができる仕組みです。
ロジックとマークダウンが混在している点が初見の方にとっては違和感に感じる部分だと思います。
Reactではそもそも1つ目であげたとおり、webページをコンポーネント単位で実装していきます。
なのでJSファイルとHTMLファイルで分割して実装していくよりも、1つのコンポーネントにつき1つのJSXファイルで実装したほうが利点が大きいです。
→ReactのJSXについて(後日公開予定)helloMessage.jsxexport default class HelloMessage extends React.Component { render() { const helloStyle = { color: 'red' }; return ( <div style={helloStyle}> Hello JSX <div> ); } }上記の例だと、「Hello JSX」の文字が赤くなります。
終わりに
今回はReactの特徴について触れていきました。
次からは各特徴をもう少し深掘りして見たいと思います。
- 投稿日:2019-07-07T21:01:41+09:00
Redux Formを使ってフォーム画面を作ってみる
React+Reduxでフォーム画面を作る時に
Redux Formというライブラリが便利だと聞いたので実際に使ってみました。
redux-formRedux Formはテキストボックスなどに入力された値を即座にstoreにdispatchしてくれるライブラリのようです。
ライブラリの導入
Redux Formを使うには当然ですがreduxが必要です。
まずは必要となるライブラリをインストールしましょう$ npm install redux react-redux redux-form redux-loggerredux-loggerは別に無くても問題なく動作しますが、actionが呼び出される時にconsoleにログを表示してくれるのでデバッグに便利です。
次にindexを書き換えます。redux-loggerはミドルウェアのひとつなのでapplyMiddlewareが必要になります。index.jsimport React from 'react'; import ReactDOM from 'react-dom'; import logger from 'redux-logger'; import { createStore, applyMiddleware } from 'redux' import { Provider } from 'react-redux'; import reducer from './reducer'; import App from './App'; const store = createStore( reducer, applyMiddleware(logger) ); ReactDOM.render( <Provider store={store}> <App /> </Provider>, document.getElementById('root'));Reducerを作成
Redux Formの導入はreducer内に記載します。
Reducer.jsimport { combineReducers } from "redux"; import { reducer as formReducer } from "redux-form"; const reducer = combineReducers({ form: formReducer }); export default reducer;書くのはこれだけ。actionも書く必要ないっぽいです。便利。
コンポーネントの作成
いよいよコンポーネントを書いていきます。
App.jsimport React, { Component } from "react"; import { Field, reduxForm } from "redux-form"; import Form from "./Form"; import { connect } from "react-redux"; class App extends Component { showText() { window.alert(this.props.text.values.test); } render() { return ( <div> <Field name="test" type="text" component={Form} /> <button type="button" onClick={this.showText.bind(this)}> テキスト表示 </button> </div> ); } } const mapStateToProps = state => ({ text: state.form.text }); export default connect(mapStateToProps)( reduxForm({ form: "text", enableReinitialize: true })(App) );Form.jsimport React, { Component } from "react"; export default class Form extends Component { render() { const { input, name, type } = this.props; const disabled = false; return ( <div> <input {...input} name={name} type={type} disabled={disabled} /> </div> ); } }App.jsをRedux Formとconnectし、Redux Formを利用したテキストボックスとアラートを呼び出す用のボタンを作りました。
Form.jsにテキストボックスのコードを記載し、App.jsでそれを呼び出して使用しています。フォームの種類によって場合分けしたいときは以下のようにForm.js内で条件分岐させることも可能です。
Form.jsimport React, { Component } from "react"; export default class Form extends Component { render() { const { input, name, type } = this.props; const disabled = false; return ( <div> {type === "text" && ( <input {...input} name={name} type={type} disabled={disabled} /> )} {type === "select" && ( <select {...input} name={name} type={type}> <option value={"one"}>ひとつめ</option> <option value={"two"}>ふたつめ</option> </select> )} </div> ); } }テキストボックスに入力された値をその場ですぐ表示させるためには以下のように書く必要があるみたいです。
App.jsimport React, { Component } from "react"; import { Field, reduxForm } from "redux-form"; import Form from "./FormItem"; import { connect } from "react-redux"; class App extends Component { render() { return ( <div> <Field name="test" type="text" component={Form} /> {this.props.text && this.props.text.values && ( <p>{this.props.text.values.test}</p> )} </div> ); } } const mapStateToProps = state => ({ text: state.form.text }); export default connect(mapStateToProps)( reduxForm({ form: "text", enableReinitialize: true })(App) );&&を使って条件分岐している理由は、テキストボックスに何も入力されていない状態だとtextとvaluesがundefinedになってしまうせいでエラーになってしまうからです。
もう少し詳しくRedux Formについて調べていけばもっとスマートに書ける方法が見つかる気がする…
参考
- 投稿日:2019-07-07T19:15:49+09:00
LaravelでAPIを作成して、Reactで取得したい
開発環境
- laravel 5.8
- docker
dockerは、LaradockでLaravel+Docker環境構築(mac)の方法で環境構築しました。
概要
LaravelでReactを使用できる方法から、Apiを作成してReactのJSXで表示したい
Reactへ切り替え
laravelのあるディレクトリへ移動
$ cd projectvue.jsからreactへ切り替え
$ php artisan preset react React scaffolding installed successfully. Please run "npm install && npm run dev" to compile your fresh scaffolding.npmをインストール
$ npm installJSファイルの保存を監視とビルドのため実行(control+cで終了)
$ npm run watchreactに切り替わっているか確認
resources/js/componentsがExample.vueからExample.jsになっていれば成功Example.js編集
Example ComponentをReactに変更
Example.jsimport React, { Component } from 'react'; import ReactDOM from 'react-dom'; export default class Example extends Component { render() { return ( <div className="container"> <div className="row justify-content-center"> <div className="col-md-8"> <div className="card"> <div className="card-header">React</div> <div className="card-body">I'm an example component!</div> </div> </div> </div> </div> ); } } if (document.getElementById('example')) { ReactDOM.render(<Example />, document.getElementById('example')); }次にwelcome.blade.phpのbodyタグを下記のように修正する。
id=exampleの要素から、Exampleがクラスが呼び出される。
welcome.blade.php<body> <div id="example"></div> <script src="{{mix('js/app.js')}}" ></script> </body>localhostでアクセスすると修正した内容が反映されています。
LaravelをApiとして使う準備
次に、laravelをapi化します。今回はapp/Http/Controllers/api配下にPostControllerを作成します。
$ php artisan make:controller api/PostControllerapiのルーティングは、route/api.phpがあるので、以下のように記述します。
route/api.phpRoute::group(['middleware' => ['api']], function() { Route::resource('post' , 'api\PostController'); });modelとmigration作成
$ php artisan make:model Post -m中身はname、contentカラムを追加します
public function up() { Schema::create('posts', function (Blueprint $table) { $table->bigIncrements('id'); // name,contentカラム追加 $table->string('name'); $table->text('content'); $table->timestamps(); }); }シーダーの作成
$ php artisan make:seeder PostsTableSeederpublic function run() { \DB::table('posts')->insert([ [ 'name' => '名前1', 'content' => '内容1' ], [ 'name' => '名前2', 'content' => '内容2' ], [ 'name' => '名前3', 'content' => '内容3' ], ]); }database/seeds/DatabaseSeederにPostsTableSeederを追記して
public function run() { $this->call(PostsTableSeeder::class); }マイグレーションとシーダーを実行
$ php artisan migrate --seedPostControllerのindexで作成したPostモデルを、jsonで返す
PostController.phppublic function index() { $posts = Post::all(); return response()->json($posts, 200); }本来なら、postman等のツールで確認しますが、今回はブラウザでhttp://localhost/api/posts
を直接叩いて、jsonで帰ってきたら成功react側でapiの利用
先ほど作成したPostモデルのデータをaxiosを使ってreact側で受け取ります。
axiosは、HTTPリクエストを送信するメソッドです。
今回はGETリクエストを送信します。Example.jsを下記のように修正します。
Example.jsimport React, { Component } from 'react'; import ReactDOM from 'react-dom'; import axios from 'axios'; export default class Example extends Component { constructor() { super(); this.state = { posts: [] }; } componentDidMount() { axios .get('/api/posts') .then(response => { this.setState({posts: response.data}); }) .catch(() => { console.log('通信に失敗しました'); }); } renderPosts() { return this.state.posts.map(post => { return ( <li key={post.key}> {post.name}: {post.content} </li> ); }); } render() { return ( <div className="container"> <ul> {this.renderPosts()} </ul> </div> ); } } if (document.getElementById('example')) { ReactDOM.render(<Example />, document.getElementById('example')); }まず初めconstructorのstateに、取得するpostsを格納する為に、空配列を用意します。
constructor() { super(); this.state = { posts: [] }; }次に、コンポーネントがマウントされた直後に呼ばれるcomponentDidMountにaxiosを記述して、stateの中にapiから取得したpostsを格納します。例外処理は、コンソールでエラーを返すようにします。
componentDidMount() { axios .get('/api/posts') .then(response => { this.setState({posts: response.data}); }) .catch(() => { console.log('通信に失敗しました'); }); }最後に、renderPostsメソッドで配列をループして、JSXでこのメソッドを呼び出します。
renderPosts() { return this.state.posts.map(post => { return ( <li key={post.key}> {post.name}: {post.content} </li> ); }); } render() { return ( <div className="container"> <ul> {this.renderPosts()} </ul> </div> ); }これで、laravelでreactのセットアップから、apiからデータを取得して、jsxで表示するところまでできました。
以上です。
次回は、Reduxの導入でもしたいと思っています。
- 投稿日:2019-07-07T19:15:49+09:00
LaravelでAPIを作成して、ReactのJSXにそのデータを表示したい
開発環境
- laravel 5.8
- docker
dockerは、LaradockでLaravel+Docker環境構築(mac)の方法で環境構築しました。
概要
LaravelでReactを使用できる方法から、Apiを作成してReactのJSXで表示したい
Reactへ切り替え
laravelのあるディレクトリへ移動
$ cd projectvue.jsからreactへ切り替え
$ php artisan preset react React scaffolding installed successfully. Please run "npm install && npm run dev" to compile your fresh scaffolding.npmをインストール
$ npm installJSファイルの保存を監視とビルドのため実行(control+cで終了)
$ npm run watchreactに切り替わっているか確認
resources/js/componentsがExample.vueからExample.jsになっていれば成功Example.js編集
Example ComponentをReactに変更
Example.jsimport React, { Component } from 'react'; import ReactDOM from 'react-dom'; export default class Example extends Component { render() { return ( <div className="container"> <div className="row justify-content-center"> <div className="col-md-8"> <div className="card"> <div className="card-header">React</div> <div className="card-body">I'm an example component!</div> </div> </div> </div> </div> ); } } if (document.getElementById('example')) { ReactDOM.render(<Example />, document.getElementById('example')); }次にwelcome.blade.phpのbodyタグを下記のように修正する。
id=exampleの要素から、Exampleがクラスが呼び出される。
welcome.blade.php<body> <div id="example"></div> <script src="{{mix('js/app.js')}}" ></script> </body>localhostでアクセスすると修正した内容が反映されています。
LaravelをApiとして使う準備
非同期で処理できるようにしたので、laravelをapi化します。今回はapp/Http/Controllers/api配下にPostControllerを作成します。
$ php artisan make:controller api/PostControllerapiのルーティングは、route/api.phpがあるので、以下のように記述します。
route/api.phpRoute::group(['middleware' => ['api']], function() { Route::resource('post' , 'api\PostController'); });modelとmigration作成
$ php artisan make:model Post -m中身はcontentカラムを追加します
public function up() { Schema::create('posts', function (Blueprint $table) { $table->bigIncrements('id'); // name,contentカラム追加 $table->string('name'); $table->text('content'); $table->timestamps(); }); }シーダーの作成
$ php artisan make:seeder PostsTableSeederpublic function run() { \DB::table('posts')->insert([ [ 'name' => '名前1', 'content' => '内容1' ], [ 'name' => '名前2', 'content' => '内容2' ], [ 'name' => '名前3', 'content' => '内容3' ], ]); }database/seeds/DatabaseSeederにPostsTableSeederを追記して
public function run() { $this->call(PostsTableSeeder::class); }マイグレーションとシーダーを実行
$ php artisan migrate --seedPostControllerのindexで作成したPostモデルを、jsonで返す
PostController.phppublic function index() { $posts = Post::all(); return response()->json($posts, 200); }本来なら、postman等のツールで確認しますが、今回はブラウザでhttp://localhost/api/posts
を直接叩いて、jsonで帰ってきたら成功react側でapiの利用
先ほど作成したPostモデルのデータをaxiosを使ってreact側で受け取ります。
axiosは、HTTPリクエストを送信するメソッドです。
今回はGETリクエストを送信します。Example.jsを下記のように修正します。
Example.jsimport React, { Component } from 'react'; import ReactDOM from 'react-dom'; import axios from 'axios'; export default class Example extends Component { constructor() { super(); this.state = { posts: [] }; } componentDidMount() { axios .get('/api/posts') .then(response => { this.setState({posts: response.data}); }) .catch(() => { console.log('通信に失敗しました'); }); } renderPosts() { return this.state.posts.map(post => { return ( <li key={post.key}> {post.name}: {post.content} </li> ); }); } render() { return ( <div className="container"> <ul> {this.renderPosts()} </ul> </div> ); } } if (document.getElementById('example')) { ReactDOM.render(<Example />, document.getElementById('example')); }まず初めconstructorのstateに、取得するpostsを格納する為に、空配列を用意します。
constructor() { super(); this.state = { posts: [] }; }次に、コンポーネントがマウントされた直後に呼ばれるcomponentDidMountにaxiosを記述して、stateの中にapiから取得したpostsを格納します。例外処理は、コンソールでエラーを返すようにします。
componentDidMount() { axios .get('/api/posts') .then(response => { this.setState({posts: response.data}); }) .catch(() => { console.log('通信に失敗しました'); }); }最後に、renderPostsメソッドで配列をループして、JSXでこのメソッドを呼び出します。
renderPosts() { return this.state.posts.map(post => { return ( <li key={post.key}> {post.name}: {post.content} </li> ); }); } render() { return ( <div className="container"> <ul> {this.renderPosts()} </ul> </div> ); }これで、laravelでreactのセットアップから、apiからデータを取得して、jsxで表示するところまでできました。
以上です。
次回は、Reduxの導入でもしたいと思っています。
- 投稿日:2019-07-07T11:08:26+09:00
React + Express + Watson Assistantで自動応答ボットを作る
この記事を書いたきっかけ
今まで何となく避けて通ってきた
Reactを勉強する事になりました。
せっかくなので何か成果物を作りたいなーと思ったので、Watsonを使った自動応答ページを作ってみる事に完成品
構成
ザックリ言うと、フロントは
React、APIを呼び出すサーバにはExpressを使っています。
デモにあったの天気の取得は、裏側で
Assistant⇒IBM Cloud Functions⇒The Weather Company Dataという小技を使っています。
関連エントリ
* Watson AssistantからIBM Cloud functionsを使う時の考慮点React側の構成
package.json(一部抜粋)
"dependencies": { "bootstrap": "^4.3.1", "dotenv": "^8.0.0", "react": "^16.8.6", "react-bootstrap": "^1.0.0-beta.9", "react-dom": "^16.8.6", "react-icons": "^3.7.0", "react-loading-overlay": "^1.0.1", "react-scripts": "3.0.1", "react-spinners": "^0.5.4" },
- react-bootstrap
なれたbootstrapでフロントを開発したかったので利用react-spinnersとreact-loading-overlay
問合せ中のローディング画面用のパッケージ- dotenv
APIサーバ等をenvファイルに持たせるためのパッケージソースコード
index.html
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8" /> <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="theme-color" content="#000000" /> <link rel="manifest" href="%PUBLIC_URL%/manifest.json" /> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous" /> <title>React App</title> </head> <body> <noscript>You need to enable JavaScript to run this app.</noscript> <div id="root"></div> <div id="inputLabel"></div> </body> </html>App.js(実際の入出力フォーム)
import React from 'react'; import ReactDOM from 'react-dom'; import './App.css'; import { FaRobot } from 'react-icons/fa'; import LoadingOverlay from 'react-loading-overlay'; import ScaleLoader from 'react-spinners/ScaleLoader' function App() { /** * 入力テキストとボタンをrenderするコンポーネント */ class Input extends React.Component{ constructor(props){ super(props); this.state = { textValue : "", messages : [], session_id : "", isActive : false } this.handleClick = this.handleClick.bind(this); this.changeText = this.changeText.bind(this); this.createWatsonAssistantSession(); }; /** * Watson AssistantのセッションIDを取得して初期メッセージを表示する */ createWatsonAssistantSession(){ fetch(process.env.REACT_APP_API_SERVER + "/create-session", { mode: 'cors' }) .then((response) => { return response.json().then(res=>{ this.setState({ session_id : res.session_id }) this.fetchAssistant(); }); }) .then((error)=>{ return error; }); } fetchAssistant(){ fetch(process.env.REACT_APP_API_SERVER + "/conversation", { mode : 'cors', method: 'POST', body : JSON.stringify({ session_id : this.state.session_id, inputText : this.state.textValue }), headers : new Headers({ "Content-type" : "application/json" }) }) .then((response) => { let conversation = []; const question = {kind: "question",text: this.state.textValue}; return response.json().then(res=>{ let answer; if(res.output.generic[0]){ answer = {kind: "answer",text: res.output.generic[0].text}; }else{ answer = {kind: "answer",text: "答えがありません"}; } conversation.push(question,answer); this.setState({ messages : this.state.messages.concat(conversation) }); this.setState({ isActive:false }); }); }) .then((error)=>{ return error; }); } handleClick(){ this.setState({ isActive:true }); this.fetchAssistant(); }; changeText(e){ this.setState({ textValue: e.target.value }); }; render(){ return ( <div className="container"> <LoadingOverlay active={this.state.isActive} spinner={<ScaleLoader /> } text='Watsonに問合せ中…'> <div className="row"> <h3 className="offset-md-3 text-danger"><FaRobot /> Reactボット</h3> </div> <div className="row"> <div className="offset-md-3 col-md-6"> <input type="text" className="form-control" placeholder="入力してみてね" value={this.state.textValue} onChange={this.changeText}></input> </div> <div className="col-md-2"> <button className="btn btn-primary" onClick={this.handleClick}>Talk To Watson</button> </div> </div> </LoadingOverlay> <OutputLabel messages={this.state.messages} /> </div> ); } } /** * 会話の内容をラベルとして表示する */ class OutputLabel extends React.Component{ /** * 発信なのかbotからの応答なのかを判定してrenderする文字列を返す * @param {*} message * @param {*} index */ determineClass(message,index){ if(message.text===""){ return; } let bg = (message.kind === "question")?"offset-md-3 col-md-5 bg-warning":"offset-md-4 col-md-5 bg-success"; const actor = (message.kind === "question")?"あなた":"bot" return( <div className="row" key={index} > <div className={bg}> <label className="control-label" > {actor}:{message.text} </label> </div> </div>); }; render(){ const labels = this.props.messages.map((message, index) => { return this.determineClass(message,index); }); return( <div> {labels} </div> ); } } return ReactDOM.render(<Input />, document.getElementById('root')); } export default App;fetch部分を
axiosに変えるともう少し記述はシンプルになります。/** * Watson AssistantのセッションIDを取得して初期メッセージを表示する */ createWatsonAssistantSession(){ axios.get(process.env.REACT_APP_API_SERVER + "/create-session") .then((response) => { this.setState({ session_id : response.data.session_id }) this.fetchAssistant(); return; }) .then((error)=>{ return error; }); } fetchAssistant(){ let params = new URLSearchParams(); params.append('session_id',this.state.session_id); params.append('inputText',this.state.textValue); axios.post(process.env.REACT_APP_API_SERVER + "/conversation",params) .then((response) => { let conversation = []; const question = {kind: "question",text: this.state.textValue}; let answer; if(response.data.output.generic[0]){ answer = {kind: "answer",text: response.data.output.generic[0].text}; }else{ answer = {kind: "answer",text: "答えがありません"}; } conversation.push(question,answer); this.setState({ messages : this.state.messages.concat(conversation) }); this.setState({ isActive:false }); }) .then((error)=>{ return error; }); }環境変数(
.env).env.example
# API Server for Watson Call REACT_APP_API_SERVER=http://localhost:3010/watson/assistantExpress側
package.json(一部抜粋)
dotenvとibm-watsonを追加"dependencies": { "body-parser": "~1.18.2", "cookie-parser": "~1.4.3", "cors": "^2.8.5", "debug": "~2.6.9", "dotenv": "^8.0.0", "ejs": "~2.5.7", "express": "~4.15.5", "ibm-watson": "^4.2.1", "morgan": "~1.9.0", "serve-favicon": "~2.4.5", "watson-developer-cloud": "^4.0.1" }app.js
cors対策とwatson用のroutesを追記
var express = require('express'); var path = require('path'); var favicon = require('serve-favicon'); var logger = require('morgan'); var cookieParser = require('cookie-parser'); var bodyParser = require('body-parser'); var cors = require('cors'); var index = require('./routes/index'); var users = require('./routes/users'); var watson = require('./routes/watson'); var app = express(); // view engine setup app.set('views', path.join(__dirname, 'views')); app.set('view engine', 'ejs'); // uncomment after placing your favicon in /public //app.use(favicon(path.join(__dirname, 'public', 'favicon.ico'))); app.use(logger('dev')); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: false })); app.use(cookieParser()); app.use(express.static(path.join(__dirname, 'public'))); app.use('/', index); app.use('/users', users); //ここを追記 app.use(cors()) app.use('/watson', watson); // catch 404 and forward to error handler app.use(function(req, res, next) { var err = new Error('Not Found'); err.status = 404; next(err); }); // error handler app.use(function(err, req, res, next) { // set locals, only providing error in development res.locals.message = err.message; res.locals.error = req.app.get('env') === 'development' ? err : {}; // render the error page res.status(err.status || 500); res.render('error'); }); module.exports = app;routes
今回は手抜きで直接routeの中に処理を書きましたw
公開されているAPIリファレンスのまんま、
新規セッション作成と会話用の受け口を作成var express = require('express'); var router = express.Router(); /* GET home page. */ router.get('/assistant/create-session', function(req, res, next) { const AssistantV2 = require('ibm-watson/assistant/v2'); const service = new AssistantV2({ iam_apikey: process.env.REACT_APP_ASSISTANT_IAM_APIKEY, version: '2019-06-14', url: process.env.REACT_APP_ASSISTANT_URL }); service.createSession({ assistant_id: process.env.REACT_APP_ASSISTANT_ID }) .then(watson_response => { console.log(JSON.stringify(watson_response, null, 2)); res.send(JSON.stringify(watson_response, null, 2)); }) .catch(err => { console.log(err); }); }); router.post('/assistant/conversation', function(req, res, next) { const AssistantV2 = require('ibm-watson/assistant/v2'); const service = new AssistantV2({ iam_apikey: process.env.REACT_APP_ASSISTANT_IAM_APIKEY, version: '2019-02-28', url: process.env.REACT_APP_ASSISTANT_URL }); service.message({ assistant_id: process.env.REACT_APP_ASSISTANT_ID, session_id: req.body.session_id, input: { 'message_type': 'text', 'text': req.body.inputText } }) .then(watson_response => { res.send(JSON.stringify(watson_response, null, 2)); }) .catch(err => { console.log(err); }); }); module.exports = router;環境変数(.env)
.env.example
# Environment variables PORT=3010 REACT_APP_ASSISTANT_ID="XXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX" REACT_APP_ASSISTANT_IAM_APIKEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx REACT_APP_ASSISTANT_URL=https://gateway.watsonplatform.net/assistant/apiハマったポイント
- CORS対策
React側から通信をすると、CORSでエラーとなってしまいました。対策としてExpress側のサーバでCORS対策として、corsをインストール- React側で環境変数が読めない! よくあるケースの様で、環境変数の先頭に
REACT_APP_をつける必要があるようです。Express側では不要ですが、今回は平仄を合わせる意味でこちらの環境変数名にもREACT_APP_をつけています。Reactを触った感想
今回はチュートリアルをザっと眺めてからトライしてみましたが、思ったよりも簡単に実装をする事が出来ました。
特に各コンポーネントを部品として扱うので、再利用性や他のReactのパッケージ組み込みが簡単で、今までよりもリッチな画面を作りやすいかな…という感じです。(この機能の実装にかかったのは4h程度)
一方で設計をしっかりしないと、いつどこでstateに値が設定されているか分かり辛くなったり、コンポーネントを重複して開発しそうだなぁ。。。




