以下のようなエラーが出た場合
TypeError: webpack.optimize.OccurenceOrderPlugin is not a constructor
関数名が変わりました。
webpack.optimize.OccurrenceOrderPlugin
以下のようなエラーが出た場合
TypeError: webpack.optimize.OccurenceOrderPlugin is not a constructor
関数名が変わりました。
webpack.optimize.OccurrenceOrderPlugin
hachioji.pm #63 に参加してきました。#0から始まっているので64回目です。 Hachioji.pm #37 以来なのでちょうど3年ぶりらしい。そんなに時間経ってるんだ...
酔っ払いながらメモしているので情報が正確でないかもしれませんのでご了承ください。
独自のディレクトリ構成のサービスに抵抗するために開発したシェルスクリプトを紹介してくれました。
なぜか資料がマインドマップ。ピャッとしたりヒャッとしたり。
今年は Rust をやるそうです。次は Rust が来るのか!?
Live Coding で Azure Functions を使った Line Bot 実装を紹介してくれました。
メンテナンスって大事ですよね。靴磨きに例えたのは斬新。経営者の方々にはぜひ地球と同じぐらいエンジニアの命を大切にしてほしいです。
仕事内容が tcpdump な ネットワークとインフラのエンジニアさん。普段私が関わらないレイヤーの話が隣で聞けるのは貴重な体験でした。難しくて内容がよくわからない自分が残念。
実はフロントエンドエンジニアってすごくないですか?もうちょっとフォーカスしたほうがいいんじゃないでしょうか。というお話をしました。 react-storybook をもうちょっと紹介しておけばよかったかも。
https://okamuuu.github.io/talks2/storybook-static/
typora でシーケンス図とか作れて便利だったり炎上の話とか
現実世界でそうあってほしいですね。
いろいろな立場の人がいらっしゃるので日頃感じていた疑問を投げかけていました。
注: 酔っ払いが酔っ払い達と会話して酔っ払った時に記録したメモなのでところどころおかしいと感じるところがあると思いますがご了承下さい。
プログラミング初学者におすすめの言語はなんでしょうという質問をしたら色々な人からいろいろな見解が得られたので楽しかったです。「初学者には言語の選択よりもチューターの有無のほうが重要」という意見に概ね全体が同意してました。ということでチューターがいる人はその人が得意な言語でいいんだと思います。
他にも PHP は最初に composer や bundler などの使い方を覚えなくても WEB サービスを作るのに必要なものが入っているのでそういった点ではおすすめという意見をもらいました。開発環境の構築は初学者がつまずくとやる気でないから結構大事なことだと思います。
それから JavaScript だったらフロントエンドもサーバーサイドも使われるので汎用性が高いのでは?という意見もありました。 なんですが、私個人の意見だとちょっと技術の革変が急すぎるのと非同期処理が初心者に大丈夫なんだろうかという心配はあります。 非同期な処理を逐次でループするプログラムを JavaScript で書くのがちょっと面倒..
ということでチューターがいればその人のおすすめする言語がいいと思います。 チューターがいない人は PHP 案外いいのかな、と個人的には思いました。
そういえば hachiojipm の主催者はとても PHP に精通しているのでお困りの方は是非 hachiojipm にいらしてはいかがでしょうか。
注: 酔っ払いが酔っ払い達と会話して酔っ払った時に記録したメモなのでところどころおかしいと感じるところがあると思いますがご了承下さい。
「別にその言語が未経験だから採用しないという事はないと思う。縁故採用というものがあるし、経験がなくても対応できるという実力が証明できれば問題がないのでは?」という意見があり、とても腑に落ちる話でした。 そもそも経歴書だけで「経験がなくても対応できるという実力」があります、という主張が通らないほうが普通なんでしょうね。 そういえば一度 Python の仕事をした時があったのですが面談に行ったら hachiojipm で会いましたよね。みたいな事があったのであれも一種の縁故採用なのかな?
というわけでそんな縁が生まれる事もあったりするので皆さまも是非 hachiojipm にいらしてはいかがでしょうか。
職場にもエンジニアが多数いらっしゃるのですが hachiojipm にはより個性の強い人たちが集まるので色々勉強になると思います。 みなさまも是非 hachiojipm にいらしてはいかがでしょうか。
新宿から八王子までは JR よりも京王線のほうが安いです。
Datetime Picker も使用頻度が高い、というよりもほとんどマストなので記事をひとつ追加します。
Bootstrap 風のデザインの react-datetime
を最後に紹介します。
この他にも airbnb/react-dates
など沢山モジュールがありますので、各自でいろいろ試すと良いと思います。
npm install --save moment react-datetime
git diff src/components/Form.js
import React, { Component } from 'react' import { Field, reduxForm } from 'redux-form' + import Select from 'react-select' import 'react-select/dist/react-select.css' +import "react-datetime/css/react-datetime.css" +import DateTime from 'react-datetime' + export const renderField = ({ input, label, type, meta: { touched, error } }) => ( <div className="form-group"> <label htmlFor={label}>{label}</label> @@ -52,3 +56,14 @@ export const renderSelectMultiField = ({ input, label, options, meta: { touched, </div> ) } + +export const renderDatePickerField = ({ input, label, meta: { touched, error } }) => ( + <div> + <div className="form_label">{label}</div> + <div className="textarea_icon"> + <DateTime value={input.value} onChange={param => input.onChange(param)} dateFormat={"YYYY/MM/DD"} timeFormat={false} /> + </div> + <p className="text-danger">{touched && (error && <span>{error}</span>)}</p> + </div> +) +
git diff src/containers/CreatePost.js
import { connect } from 'react-redux' import { Field, reduxForm } from 'redux-form' import { createPost } from '../actions' -import { renderField, renderTextAreaField, renderSelectField, renderSelectMultiField } from '../components/Form' +import { renderField, renderTextAreaField, renderSelectField, renderSelectMultiField, renderDatePickerField } from '../components/Form'
という事で、この Tutorial を一通りやってみると、もしかすると SPA を React.js で書けるようになる気がします。なるといいですね。
前回は redux-form について触れました。 今回は react-select について触れます。
npm install --save react-select
touch src/components/Form.js
create src/components/Form.js
import React, { Component } from 'react' import { Field, reduxForm } from 'redux-form' import Select from 'react-select' import 'react-select/dist/react-select.css' export const renderField = ({ input, label, type, meta: { touched, error } }) => ( <div className="form-group"> <label htmlFor={label}>{label}</label> <input className="form-control" {...input} placeholder={label} type={type}/> <p className="text-danger">{touched && (error && <span>{error}</span>)}</p> </div> ) export const renderTextAreaField = ({ input, label, type, meta: { touched, error } }) => ( <div className="form-group"> <label htmlFor={label}>{label}</label> <textarea className="form-control" {...input} placeholder={label} type={type} rows="10"/> <p className="text-danger">{touched && (error && <span>{error}</span>)}</p> </div> ) export const renderSelectField = ({ input, label, options, meta: { touched, error } }) => { return ( <div className="form-group"> <label htmlFor={label}>{label}</label> <Select name={input.name} value={input.value} onChange={input.onChange} onBlur={() => input.onBlur(input.value)} options={options} placeholder="Select" simpleValue clearable={false} /> <p className="text-danger">{touched && (error && <span>{error}</span>)}</p> </div> ) } export const renderSelectMultiField = ({ input, label, options, meta: { touched, error } }) => { return ( <div className="form-group"> <label htmlFor={label}>{label}</label> <Select {...input} onBlur={() => input.onBlur([...input.value].map(x => (x.value)))} options={options} multi /> <p className="text-danger">{touched && (error && <span>{error}</span>)}</p> </div> ) }
edit src/containers/CreatePost.js
import React, {Component} from 'react' import { connect } from 'react-redux' import { Field, reduxForm } from 'redux-form' import { createPost } from '../actions' import { renderField, renderTextAreaField, renderSelectField, renderSelectMultiField } from '../components/Form' const userOptions = [ {value: 1, label: "userOne"}, {value: 2, label: "userTwo"}, {value: 3, label: "userThuree"}, ] const tagsOptions = [ {value: "foo", label: "foo"}, {value: "bar", label: "bar"}, {value: "hoge", label: "hoge"}, {value: "fuga", label: "fuga"}, ] let PostForm = (props) => { const { handleSubmit, pristine, submitting } = props return ( <form onSubmit={handleSubmit}> <Field name="userId" label="User" component={renderSelectField} options={userOptions} /> <Field name="title" type="text" label="Title" component={renderField} /> <Field name="tags" label="Tags" component={renderSelectMultiField} options={tagsOptions} /> <Field name="body" type="text" label="Body" component={renderTextAreaField} /> <div style={{marginTop: "30px"}}> <button className="btn btn-primary" type="submit" disabled={pristine || submitting}>Submit</button> </div> </form> ) } const validate = values => { const errors = {} if (!values.title) { errors.title = 'Required' } else if (values.title.length > 15) { errors.title = 'Must be 15 characters or less' } if (!values.body) { errors.body = 'Required' } return errors } PostForm = reduxForm({ form: "post", validate, enableReinitialize: true, })(PostForm) class CreatePost extends Component { handleSubmit() { const params = this.props.form.post.values // Note: the resource will not be really created on the server but it will be faked as if. this.props.dispatch(createPost({params})) } render() { return ( <PostForm onSubmit={this.handleSubmit.bind(this)} /> ) } } const select = state => (state) export default connect(select)(CreatePost)
以上全8回に分けて React.js で SPA の作り方を紹介しました。将来的に自分が React.js の事を忘れた時に備えた記事ですが、どこかで誰かの役に立てばうれしいです。
なのですがそういえば DatePicker の事を書くのを忘れていました。 ということであとちょっとだけ続きます。
今回は redux-form について触れます。 おそらく React, Redux で管理画面作る場合はこの module を使うことになると思います。
ちなみに react-redux-form
という似た名前のモジュールがありますが優劣に関してはここでは言及しません。
やりたいことができるならどっちを選んでも正解だと思います。
npm install --save redux-form touch src/containers/CreatePost.js
create src/containers/CreatePost.js
import React, {Component} from 'react' import { connect } from 'react-redux' import { Field, reduxForm } from 'redux-form' import { createPost } from '../actions' const renderField = ({ input, label, type, meta: { touched, error } }) => ( <div className="form-group"> <label htmlFor={label}>{label}</label> <input className="form-control" {...input} placeholder={label} type={type}/> <p className="text-danger">{touched && (error && <span>{error}</span>)}</p> </div> ) const renderTextAreaField = ({ input, label, type, meta: { touched, error } }) => ( <div className="form-group"> <label htmlFor={label}>{label}</label> <textarea className="form-control" {...input} placeholder={label} type={type} rows="10"/> <p className="text-danger">{touched && (error && <span>{error}</span>)}</p> </div> ) let PostForm = (props) => { const { handleSubmit, pristine, submitting } = props return ( <form onSubmit={handleSubmit}> <Field name="title" type="text" label="Title" component={renderField} /> <Field name="body" type="text" label="Body" component={renderTextAreaField} /> <div style={{marginTop: "30px"}}> <button className="btn btn-primary" type="submit" disabled={pristine || submitting}>Submit</button> </div> </form> ) } const validate = values => { const errors = {} if (!values.title) { errors.title = 'Required' } else if (values.title.length > 15) { errors.title = 'Must be 15 characters or less' } if (!values.body) { errors.body = 'Required' } return errors } PostForm = reduxForm({ form: "post", validate, enableReinitialize: true, })(PostForm) class CreatePost extends Component { handleSubmit() { const params = this.props.form.post.values // Note: the resource will not be really created on the server but it will be faked as if. this.props.dispatch(createPost({params})) } render() { return ( <PostForm onSubmit={this.handleSubmit.bind(this)} /> ) } } const select = state => (state) export default connect(select)(CreatePost)
edit src/Root.js
import 'babel-polyfill' import 'bootstrap' import 'bootstrap/dist/css/bootstrap.css' import React from 'react' import { Provider } from 'react-redux' import { Router, Route, IndexRoute, browserHistory } from 'react-router' import { syncHistoryWithStore } from 'react-router-redux' import configureStore from './store/configureStore' import App from './containers/App' import Home from './containers/Home' import Posts from './containers/Posts' import CreatePost from './containers/CreatePost' const store = configureStore() const history = syncHistoryWithStore(browserHistory, store) const NotFound = () => (<div><span>NOT FOUND</span></div>) export default (props) => ( <Provider store={store}> <Router history={history}> <Route path="/" component={App}> <IndexRoute component={Home} /> <Route path="posts"> <IndexRoute component={Posts} /> <Route path="create" component={CreatePost} /> </Route> </Route> <Route path="*" component={NotFound}/> </Router> </Provider> )
edit src/actions.js
import { createAction } from "redux-actions" import api from './api' export const FETCH_REQUESTED = "FETCH_REQUESTED" export const FETCH_SUCCESSED = "FETCH_SUCCESSED" export const FETCH_FAILED = "FETCH_FAILED" export const fetchRequested = createAction(FETCH_REQUESTED) export const fetchSuccessed = createAction(FETCH_SUCCESSED) export const fetchFailed = createAction(FETCH_FAILED) export function getPosts({userId}) { return fetchRequested(() => { return api.getPosts({userId}) }) } export function createPost({params}) { return fetchRequested(() => { return api.createPost({params}) }) }
edit src/api.js
import axios from 'axios' const BASE_URL = window.location.origin export function getPosts({userId}) { return axios.get(`${BASE_URL}/api/posts?userId=${userId}`).then((res) => { return { "posts": res.data } }) } export function createPost({params}) { return axios.post(`${BASE_URL}/api/posts`, params).then((res) => { return { "post": res.data } }) } export default { getPosts, createPost }
edit src/containers/App.js
import React, { Component } from 'react' import { Link } from 'react-router' import { connect } from 'react-redux' import Notifications from 'reapop' import theme from 'reapop-theme-wybo' class App extends Component { render() { return ( <div className="container"> <Notifications theme={theme}/> <h1>Single Page Application</h1> <p><Link to="/">Home</Link> | <Link to="/posts">Posts</Link> | <Link to="/posts/create">Create Post</Link></p> <div style={{ marginTop: '1.5em' }}>{this.props.children}</div> </div> ) } } export default connect(state => (state))(App)
edit src/reducers/index.js
import { combineReducers } from 'redux' import { routerReducer } from 'react-router-redux' import { reducer as formReducer } from 'redux-form' import { reducer as notificationsReducer } from 'reapop' import { FETCH_SUCCESSED } from '../actions' const fetchInitialState = { posts: [] } function fetch(state = fetchInitialState, action) { switch (action.type) { case FETCH_SUCCESSED: return Object.assign({}, state, action.payload) default: return state } } const rootReducer = combineReducers({ notifications: notificationsReducer(), fetch, routing: routerReducer, form: formReducer, }) export default rootReducer
reduxForm
関数でコンポーネントを wrap していますがこれによって this.props.form.post
にフォームの状態が保持されるようになります。
この wrap された関数内でしか Field
コンポーネントが動かないのでご注意ください。
公式サイトには example が多数掲載されているのでそちらも合わせてご確認ください。
今回は redux-form の使い方に触れました。 次回は react-select という cool なコンポーネントと redux-form を共存させる方法について紹介します。
前回は react での routing について触れました。 今回は HTTP 通信が成功した時などに表示する Notification (Flash Message) に触れます。
Notification も色々あるのですが豊富な機能を備えた reapop
を紹介します。
依存モジュールを install します。 redux-thunk
は reapop が使うので install しますが今回のチュートリアルでは自身で実装する箇所はありません。
npm install --save redux-thunk font-awesome reapop reapop-theme-wybo
edit src/store/configureStore.js
import { createStore, applyMiddleware } from 'redux' import { reducer as notificationsReducer } from 'reapop' import createSagaMiddleware from 'redux-saga' import createLogger from 'redux-logger' import thunk from 'redux-thunk' import rootReducers from '../reducers' import rootSaga from "../sagas" const sagaMiddleware = createSagaMiddleware() const logger = createLogger() const middleware = [thunk, sagaMiddleware, logger] const createStoreWithMiddleware = applyMiddleware(...middleware)(createStore) export default function configureStore(initialState) { const store = createStoreWithMiddleware(rootReducers, initialState) if (module.hot) { module.hot.accept('../reducers', () => { const nextRootReducer = require('../reducers').default store.replaceReducer(nextRootReducer) }) } sagaMiddleware.run(rootSaga) return store }
edit src/reducers/index.js
import { combineReducers } from 'redux' import { routerReducer } from 'react-router-redux' import { reducer as notificationsReducer } from 'reapop' import { FETCH_SUCCESSED } from '../actions' const fetchInitialState = { posts: [] } function fetch(state = fetchInitialState, action) { switch (action.type) { case FETCH_SUCCESSED: return Object.assign({}, state, action.payload) default: return state } } const rootReducer = combineReducers({ notifications: notificationsReducer(), fetch, routing: routerReducer, }) export default rootReducer
edit src/containers/Posts.js
import React, {Component} from 'react' import { connect } from 'react-redux' import Notifications from 'reapop' import theme from 'reapop-theme-wybo' import { fetchRequest } from '../actions' import { PostItem } from '../components/Posts' import { getPosts } from '../actions' class Posts extends Component { handleButtonClick() { this.props.dispatch(getPosts({userId: 1})) } render() { const {posts} = this.props.fetch return ( <div className="container"> <Notifications theme={theme}/> <h2>Posts</h2> {posts.map((post) => ( <PostItem key={post.id} post={post} /> ))} <button className="btn btn-default" onClick={this.handleButtonClick.bind(this)}>Push</button> </div> ) } } const select = state => (state) export default connect(select)(Posts)
edit src/sagas.js
import { takeEvery } from "redux-saga" import { put, call } from "redux-saga/effects" import { addNotification as notify } from 'reapop' import { FETCH_REQUESTED, fetchSuccessed, fetchFailed, } from "./actions" function notifySuccess(message) { return notify({ message, position: 'tc', status: 'success', dismissAfter: 3000 }) } function notifyError(message) { return notify({ message, position: 'tc', status: 'error', dismissAfter: 3000 }) } function* fetch(action) { try { const data = yield call(action.payload) yield put(fetchSuccessed(data)) yield put(notifySuccess("fetch success")) } catch (err) { yield put(fetchFailed(err)) yield put(notifyError(err.message)) } } export default function* rootSaga() { yield takeEvery(FETCH_REQUESTED, fetch) }
fetch が成功したらメッセージを表示させます。
今回の tutorial ではこのような書き方をしていますが、 redux-saga を使っているので FETCH_SUCCESSED
と FETCH_FAILED
を検知して動作させることもできます。
次回は redux-form に触れます。
前回は React で Single Page Application をするためによく使われる redux-saga
に触れました。
今回もよく使われる機能である react-router
に触れます。
react-router と一緒に react-router-redux を install します。
npm install --save react-router react-router-redux
touch src/containers/App.js src/containers/Home.js
create src/containers/App.js
import React, { Component } from 'react' import { Link } from 'react-router' import { connect } from 'react-redux' class App extends Component { render() { return ( <div className="container"> <h1>Single Page Application</h1> <p><Link to="/">Home</Link> | <Link to="/posts">Posts</Link></p> <div style={{ marginTop: '1.5em' }}>{this.props.children}</div> </div> ) } } export default connect(state => (state))(App)
create src/components/Home.js
import React, { Component } from 'react' import { connect } from 'react-redux' class Home extends Component { render() { return ( <div> <h2>Home</h2> <p>this is Home.js</p> </div> ) } } export default connect(state => (state))(Home)
edit src/Root.js
import 'babel-polyfill' import 'bootstrap' import 'bootstrap/dist/css/bootstrap.css' import React from 'react' import { Provider } from 'react-redux' import { Router, Route, IndexRoute, browserHistory } from 'react-router' import { syncHistoryWithStore } from 'react-router-redux' import configureStore from './store/configureStore' import App from './containers/App' import Home from './containers/Home' import Posts from './containers/Posts' const store = configureStore() const history = syncHistoryWithStore(browserHistory, store) const NotFound = () => (<div><span>NOT FOUND</span></div>) export default (props) => ( <Provider store={store}> <Router history={history}> <Route path="/" component={App}> <IndexRoute component={Home} /> <Route path="posts" component={Posts} /> </Route> <Route path="*" component={NotFound}/> </Router> </Provider> )
edit src/reducers/index.js
import { combineReducers } from 'redux' import { routerReducer } from 'react-router-redux' import { FETCH_SUCCESSED } from '../actions' const fetchInitialState = { posts: [] } function fetch(state = fetchInitialState, action) { switch (action.type) { case FETCH_SUCCESSED: return Object.assign({}, state, action.payload) default: return state } } const rootReducer = combineReducers({ fetch, routing: routerReducer, }) export default rootReducer
以下を実行
% node devServer.js
ここでは App.js が template の layout を担当しています。 Navigator のような共通パーツを親コンポーネントに配置して、子となるコンポーネントを URL に応じて振り分けて表示させます。 ここでは紹介しませんが親、子、孫と伝播させることも可能です。
今回は Single Page Application の Routing を紹介しました。 次回は npm で利用できる notification を紹介します。