React.js+redux構成でaction内でstateを参照する方法

Gakuです(*´Д`)

また、最近ちょこちょこReact.js触っているのですが、action内でstateを参照できる方法を知って、劇的に世界が広がった気がしたのでその方法を備忘録程度に掲載したいと思います。

今までの実装

今までaction内でstateにアクセスする場合、こんな感じで記述していました。

import {playMusic} from './playMusic'

//componentからstateを渡してあげる
export const playNextMusic = (state) =>{
    const searchList = state.searchList
    let nextFlg = false
    let nextVideoId = ""
    searchList.some((v,i) =>{
      if(nextFlg){
        nextVideoId = v.videoId
        return true
      }
      if(v.playing == true)
        nextFlg = true
    })
    return {
      type: 'NEXT_MUSIC',
      resutl: searchList
    }
  }
}

こんな感じですね。
componentからstateを渡してあげて、actionで参照するといった感じです。
「動く」ことはしますが、componentで無駄な「stateを渡す」といった処理を記述する必要があり、システムが大きくなるとなかなか厳しいものがありました。

劇的に世界が広がった実装

そこでいろいろ調べていると、下記のような形で実装している例を見つけました。

import {playMusic} from './playMusic'

export const playNextMusic = () =>{
  return (dispatch,getState) =>{
    //次の動画を抽出する
    const searchList = getState().searchList
    let nextFlg = false
    let nextVideoId = ""
    searchList.some((v,i) =>{
      if(nextFlg){
        nextVideoId = v.videoId
        return true
      }
      if(v.playing == true)
        nextFlg = true
    })
    dispatch(playMusic(nextVideoId))
  }
}

このようにすることで、componentから無駄なstateを渡す必要がなく、「更新に必要な情報のみを渡す」といった実装ができるので大変便利です。
また、私の中で「stateの更新内容記述場所」をreducerかactionかで非常に悩んでいたんですが、この記述方法を知ったことで、完全に「action」とすることができるようになりました。
あと、「dispatch」も使用できる点も気にいっています。

おわりに

reduxのExample眺めていれば、この記述しているのに完全に見落としていた。。。
精進します(*´Д`)

npmパッケージを「さくっ」と公開する

Gakuです。

最近、reactをちまちま触っています。
そんな中で触れることが多いnpmパッケージですが、「自分もnpmパッケージを公開したい!」と思いました。

そこで今回は簡単なreactコンポーネントをnpmパッケージとして公開する手順を備忘録として残したいと思います。

今回作ったもの

今回作ったものは下記になります。
「ブログ読むのめんどくさいよ~」とか「実行しつつ試したい!」という方はご参考下さい。
[blogcard url=”https://github.com/gaku3601/react-helloworld-sample”][/blogcard]
[blogcard url=”https://www.npmjs.com/package/react-helloworld-sample”][/blogcard]

npmパッケージの公開

npmデベロッパーとして登録する

何はともあれ。まず、npmへデベロッパー登録をする必要があります。
登録は下記から可能です。
[blogcard url=”https://www.npmjs.com/signup”][/blogcard]

登録が完了したなら、登録したアカウントとnpmコマンドを紐付けするために下記コマンドを実行します。

npm adduser

emailやpassword等々、先程登録したアカウントの情報を入力すればOKです。
これで公開する準備は整いました。簡単ですね。

HelloWorldコンポーネントを作成する

今回は呼び出すと「HelloWorld」と出力する簡単なreactコンポーネントを作成したいと思います。
まずはnpm initでpackage.jsonを作成しましょう。

$ npm init
name: (react-helloworld-sample)
version: (1.0.0) 0.0.1
description:
entry point: (index.js)
test command:
git repository:
keywords:
author: gaku
license: (ISC) MIT

npmパッケージとして既に公開されている名前は使用出来ませんので注意が必要です。
作成したpackage.jsonはこんな感じ

{
  "name": "react-helloworld-sample",
  "version": "0.0.1",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "gaku",
  "license": "MIT"
}

そしたらHelloWorldコンポーネントを作成します。
src/scriptsへHelloWorld.jsファイルを作成し、以下のように記述します。

import React from 'react'

const HelloWorld = () =>(
  <div>
    HelloWorld
  </div>
)

export default HelloWorld

また、エントリーポイントとなるindex.jsでHelloWorld.jsをexportします。

import HelloWorld from './src/scripts/HelloWorld.js'

export { HelloWorld }

そしてこれらのコードをbabelでコンパイルを行います。
コンパイルを行ったindex.jsのパスをpackage.jsonのmain部分に記述します。

{
  "name": "react-helloworld-sample",
  "version": "0.0.1",
  "description": "",
  "main": "./src/dist/index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "./node_modules/.bin/babel ./src/scripts --out-dir ./src/dist"
  },
  "author": "gaku",
  "license": "MIT",
}

Exampleプロジェクトの作成

公開するnpmパッケージと同階層にexampleフォルダを作成し、reactアプリケーションを動作させる環境を構築します。
react環境構築の話となってしまうため割愛しますが、詳しくはgithubのexampleフォルダをご確認下さい。
[blogcard url=”https://github.com/gaku3601/react-helloworld-sample”][/blogcard]

Exampleプロジェクトとnpmパッケージの紐付け

[npm link]コマンドを使用すればExampleプロジェクト内で作成中のnpmパッケージを使用することが可能です。
npmパッケージフォルダで[npm link]コマンドを実行し、exampleフォルダ内で[npm link パッケージ名]を実行後、package.jsonのdependenciesに[“パッケージ名”:”*”,]を記述することで紐付けを行うことが可能です。
紐付けが出来ているか確認するにはexampleフォルダ内で[npm ls パッケージ名]を実行してみて下さい。

$ npm ls react-helloworld-sample
example@1.0.0 C:\Users\proga\develop\projects\react-helloworld-sample\example
`-- react-helloworld-sample@0.0.1  -> C:\Users\proga\develop\projects\react-helloworld-sample

このように表示されれば紐付け完了です。

Exampleアプリケーションの作成

exampleアプリケーションでreact-helloworld-sampleを呼び出してみます。

import React from 'react'
import {render} from 'react-dom'

import { HelloWorld } from 'react-helloworld-sample'

render(
  <div>
    <HelloWorld />
  </div>,
  document.querySelector('#content')
)

しかし、この状態でアプリケーションを実行してもnpmパッケージ側に必要なモジュールが入っていないためエラーとなってしまいます。
適宜、必要な依存モジュールをnpmパッケージ側にインストールしてあげて下さい。
これで「HelloWorld」と出力されればアプリケーションの作成は完了です。

作成したnpmパッケージを公開する

公開手順は至極簡単です。下記コマンドをnpmパッケージのフォルダで実行してあげれば公開することが可能です。

npm publish

簡単ですね。バージョンアップを行う場合は、package.jsonのバージョンを上げてあげないとエラーとなってしまうためご注意下さい。
良きnpmパッケージライフを(´・ω・`)ノ

npm公開の注意点

npmパッケージの公開は簡単に可能です。
しかし、一旦公開してしまうと簡単には削除できない点(実際には削除は不可能)で注意が必要です。

npmのポリシー上、一旦公開したパッケージは削除することができないそうです。
(※誰かが作成したパッケージを使用している場合、そのアプリケーションを壊してしまう恐れがあるため。)

今回の記事を書くまでに2つのnpm公開勉強用パッケージを公開していたのですが、削除したい旨をnpmサポートにメールしたところ「削除はできないが開発元npmへパッケージを移譲するという形でなら対応可能」とのことでしたので、そのようにさせて頂きました。
メールも英語でかなりめんどくさかったので、「公開したものは削除できない」と覚えておけばOKかと思います。

以上となります。フロントは楽しいですが、いろいろ勉強しないとなので正直キツイっすわ(´・ω・`)

Immutable.jsでreact+redux環境が楽になりそうな話

この記事はQiitaのReact Advent Calendar 2016/14日に投稿させていただく記事です。
お粗末な文章ですが失礼致します。

さて、みなさんreact+reduxしていますか?
僕は毎日react+reduxでハッスルしています。

まだまだreact+reduxを触りだして間もないのですが、Immutable.jsなるものを導入するとreact+redux環境が劇的に楽になるとの情報をキャッチしましたので、今回はその件について書かせて頂ければと思います。

Immutable.jsとは何ぞや?

reactと同じで天下のfacebook様が開発されているjsライブラリです。
[blogcard url=”https://facebook.github.io/immutable-js/”][/blogcard]
「Immutable」と名前の通り、Immutable.jsではデータを不変なデータとして管理できるようになるそうです。

。。。なんのこっちゃ?っと思いましたので、基本的な使用方法を見ていきたいと思います。

基本的な使用方法

[blogcard url=”http://kenjimorita.jp/immutable-js/”][/blogcard]
こちらの記事が非常にわかりやすく参考になりました。

stateの更新を行う際、redux?のお作法でstate自身を更新するのではなく、新しいstateを作成しないといけません。
そのため、簡単な更新でもこのような処理になってしまします。

const todos = (state=[],action) => {
  switch (action.type) {
    case "ADD_TODO":
      return [   //この部分
        ...state,
        {text:action.text}
      ]
    default:
      return state;
  }
}
export default todos

…stateで現在のstateを展開し、その後ろに追加するtodoのtextを挿入し、新しい配列をstateとして登録するといった感じですね。
このような簡単な処理だけで見ても、直感的でないことがわかります。
stateが階層構造にでもなったならば、容易に複雑になることが想定されます。。。

つらい(´・ω・`)

そんな時にImmutable.jsです!
Immutable.jsは上にも記述したように不変なデータを扱うことが可能です。
先程のtodoReducerをImmutable.jsを使用し記述するとこのようになります。

import { List } from 'immutable'
const init = List([])
const todos = (state=init,action) => {
  switch (action.type) {
    case "ADD_TODO":
      return state.push(action.text)
    default:
      return state
  }
}
export default todo

素晴らしいですね。
state.push(action.text)で元のstateは変更せず、新しくstateを作成し返却といった処理が感覚的に記述できるようになります。

ちょっと発展した使い方

[blogcard url=”https://github.com/gaku3601/study-immutable”][/blogcard]
以下で作成するアプリケーションはこちらのリポジトリのimmtable-projectに格納されています。
記事読むの面倒くさい!って方や動きを見ながら記事読みたいって方はご活用下さい。

npm install
npm start

で実行可能です。

発展的な使用方法でRecord関数を使用することでこういった書き方もできるようになります。

import { List,Record } from 'immutable'

const ToDoRecord = Record({
  text:''
})
const init = List([])
const todos = (state=init,action) => {
  switch (action.type) {
    case "ADD_TODO":
      return state.push(new ToDoRecord({text:action.text}))
    default:
      return state
  }
}
export default todo

今まで散文していた、エンティティ定義がToDoRecord にまとめられ一目でわかるようになります。
(stateのエンティティ定義はコメントで書かなくて良くなりそうです。)

さらにさらに、recordは継承することが可能でありメソッドを記述することが可能です。
IDを振るといった処理をrecordを継承し作成したクラスに定義したものが以下になります。

import { List,Record,Seq } from 'immutable'

const ToDoRecord = Record({
  todoList:List()
})

const ToDoContentRecord = Record({
  id:null,
  text:''
})

class ToDo extends ToDoRecord{
  getNextId(){
    let newNumber = 1;
    if(this.todoList.size != 0){
      newNumber = Seq(this.todoList).map(x => x.get('id')).max() + 1;
    }
    return newNumber;
  }
}

const todo = (state=new ToDo,action) => {
  switch (action.type) {
    case "ADD_TODO":
      return state.set('todoList',state.todoList.push(new ToDoContentRecord({
        id:state.getNextId(),
        text:action.text
      })))
    default:
      return state
  }
}

export default todo

ちょっと複雑になりますが、親レコード(ToDoRecord)で子レコード(ToDoContentRecord)を管理するようにし、ToDoRecordを継承したToDoクラス内でid連番処理を実装しています。
で、reducer部分ではstate.getNextIDを呼び出し、新しく算出したIDとtextを登録する処理を実装しています。
命名とかが汚いのはご容赦下さい。(´・ω・`)

ここまで来るとさらに欲が出てくると思います。
「これ、reducer部分さらに薄くできんじゃね?」

はい、実装してみます。

import { List,Record,Seq } from 'immutable'

const ToDoRecord = Record({
  todoList:List()
})

const ToDoContentRecord = Record({
  id:null,
  text:''
})

class ToDo extends ToDoRecord{
  addToDo(text){
    return this.set('todoList',this.todoList.push(new ToDoContentRecord({
      id:this.getNextId(),
      text:text
    })))
  }

  getNextId(){
    let newNumber = 1;
    if(this.todoList.size != 0){
      newNumber = Seq(this.todoList).map(x => x.get('id')).max() + 1;
    }
    return newNumber;
  }
}
export default ToDo
import ToDo from '../models/ToDo'

const todo = (state=new ToDo,action) => {
  switch (action.type) {
    case "ADD_TODO":
      return state.addToDo(action.text)
    default:
      return state
  }
}
export default todo

新しくビジネスロジックを記述するmodelsを作成し、そちらに寄せることでreducer部分がかなり薄くなります!
今までビジネスロジックをactionに記述するかreducerに記述するか迷っていましたが、modelsにビジネスロジックを分離することが可能です。
これは嬉しいですね。スッキリです!

さらにさらにさらに、欲を出してみましょう!
immutable.jsでRecordを継承することで、メソッドを定義することができました。
そのメソッドをactionを通じてreducerへ渡して上げれば、肥大化するaction,reducerをさらに薄く出来るのではないかと。。。

実装してみます。
先程実装したmodelsはそのままで、reducer、action、container、componentを以下のように実装します。

import React from 'react'

export default class ToDoComponent extends React.Component {
  render(){
    return(
      <div>
        <input type="text" ref="inputtodo" />
        <button onClick={() => {
          this.props.addToDo(this.props.todo.addToDo(this.refs.inputtodo.value))
        }}>submit</button>
      {this.props.todo.todoList.map(todo => (
          <p >{todo.get('text')}</p>
        ))}
      </div>
    )
  }
}
import { connect } from 'react-redux'
import ToDoComponent from '../components/ToDoComponent'
import { onChange } from '../actions/todo'

const mapStateToProps = (state) =>{
  return{
    todo:state.todo
  }
}

const mapDispatchToProps = (dispatch) =>{
  return {
    addToDo: (entry) => {
      dispatch(onChange(entry))
    }
  }
}

const ToDo = connect(
  mapStateToProps,
  mapDispatchToProps
)(ToDoComponent)

export default ToDo

this.props.addToDo(this.props.todo.addToDo(this.refs.inputtodo.value))でmodel内のaddToDo()を呼び出し、

addToDo: (entry) => {
  dispatch(onChange(entry))
}

でそのaddToDoメソッドをactionに渡してあげます。
あとはいつもどおりactionとreducerを作成してあげれば完成です。

export const onChange = (entry) =>{
  return {
    type:"ON_CHANGE",
    entry:entry,
  }
}
import ToDo from '../models/ToDo'

const todo = (state=new ToDo,action) => {
  switch (action.type) {
    case "ON_CHANGE":
      return action.entry
    default:
      return state
  }
}

export default todo

何か新しくstate更新処理を実装したくなった際は、modelとcontainer,component部分を編集するだけで実装することが可能になります。

もう、素晴らしすぎますね。
これで「あれ、あのactionどこだったっけ?」とか「actionのフォルダ階層深すぎワロタ」問題を解消することが可能になります。

弊害

弊害というほどのことではありませんが、Immutable.jsを使用するとstateが今までのオブジェクト構造ではなく、Immutable.jsでラップされたオブジェクトとなってしまうため、注意が必要です。
Http request処理部分やlocalstorageへのstate保存処理などを行う際はtoJS()でオブジェクトへ変換、取得する際はfromJS()でImmutable.jsのオブジェクトでラッピングを行うという点を覚えておけば大きなハマりはないと思います。

おわりに

いかかでしたでしょうか?
react+redux環境で問題としてあげられる、「action、reducer部分の肥大化問題」、ならびに「ビジネスロジックどこに書く問題」等々がImmutable.jsで解決できたと思います。
まだまだ、勉強したてですので、他にも弊害が出てくるかもしれませんが、僕の中では一旦この構成で開発を進めて行こうと思います。

就職活動

ただいまニートのGakuはおもしろい就職先を探しております。
大小問いませんし、IT企業でなくても構いません。

少しでも興味を持っていただけたならば、TwitterのDM等で連絡いただければ即対応させて頂きます。
Twitter:https://twitter.com/gaku3601

参考文献

React使い必見! Immutable.jsでReactはもっと良くなる

material-uiでmuiThemeを入れろ!と怒られた時の対処法

Gakuです。
material-uiを入れると下記警告で怒られると思います。その際の対処法を記載します。

Warning: Failed Context Types: Required context `muiTheme` was not specified in `AppBar`.

無題

warningのくせして何も描画されません(´・ω・`)

material-uiのissueを漁っているとこんな記事がありました。
[blogcard url=https://github.com/callemall/material-ui/issues/686][/blogcard]

material-uiにはtheme機能が実装されているので、ちゃんと指定してあげないといけないみたいです。

main.jsxやindex.jsなどのmainとなるファイルを下記のように編集してあげれば良いです。

import React from 'react'
import ReactDOM from 'react-dom'
import Header from'./AppBar.jsx'
import baseTheme from 'material-ui/styles/baseThemes/lightBaseTheme';
import getMuiTheme from 'material-ui/styles/getMuiTheme';

class TestBox extends React.Component {
  constructor(props) {
    super(props);
  }

  getChildContext() {
      return { muiTheme: getMuiTheme(baseTheme) };
  }

  render() {
    return(
      <div>
        <Header />
        <p>hoge</p>
      </div>
    );
  }
}

TestBox.childContextTypes = {
    muiTheme: React.PropTypes.object.isRequired,
};

ReactDOM.render(
    <TestBox />,
    document.getElementById('content')
);

無題

きれいになりました(´・ω・`)

material-uiでprops系のエラーが出たときの対象法

Gakuです。
CSSフレームワークのmaterial-uiを使っているのですが、導入した直後、以下のような警告が出ました。

Warning: Unknown props `rounded`, `zDepth`, `circle`, `transitionEnabled` on <div> tag.
Warning: Unknown props `tooltipPosition`, `onKeyboardFocus`, `onTouchTap` on <button> tag.
Warning: Unknown prop `hoverColor` on <span> tag.

無題

ぐぬぬ、ちゃんと設定できている気がするんだけど。。。(´・ω・`)

いろいろ探していたら、material-uiのissueとして挙がっていました。

[blogcard url=https://github.com/callemall/material-ui/issues/4594][/blogcard]

なんか、reactのv15.2.0入れているとダメみたいで、次期material-uiのリリースで対応するぉって書いているみたい。
とりあえず、reactの前バージョンであるreact-v0.14.8にダウングレードしてみる。

"author": "gaku",
  "license": "ISC",
  "devDependencies": {
    "babelify": "^6.4.0",
    "browserify": "^13.0.1",
    "gulp": "^3.9.1",
    "react": "^0.14.8",     //ここと
    "react-dom": "^0.14.8", //ここ
    "vinyl-source-stream": "^1.1.0"
  },

無題

すっきりっしゅ(´・ω・`)b

reactでelectronなes6で最小構成構築

Gakuです。
electron+react+es6で最小構成な感じのものを作ったので備忘録を。

electronはインストール済みのこと。
まだの人はこの記事を参照してください。
[blogcard url=http://160.16.72.126/?p=338][/blogcard]

準備

最終的なファイル構成

.
├── app
│   ├── build
│   │   └── app.js
│   ├── client
│   │   └── main.jsx
│   ├── index.html
│   └── index.js
├── gulpfile.js
└── package.json

もろもろ導入

npm init
npm install babel
npm install babelify@6.4.0 --save-dev
npm install react --save-dev
npm install react-dom --save-dev
npm install gulp browserify vinyl-source-stream --save-dev

いろいろファイル作成

{
  "name": "electron_test4",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "gulp browserify & electron ./app"
  },
  "author": "gaku",
  "license": "ISC",
  "devDependencies": {
    "babelify": "^6.4.0",
    "browserify": "^13.0.1",
    "gulp": "^3.9.1",
    "react": "^15.1.0",
    "react-dom": "^15.1.0",
    "vinyl-source-stream": "^1.1.0"
  }
}
const electron = require('electron');
// Module to control application life.
const {app} = electron;
// Module to create native browser window.
const {BrowserWindow} = electron;

// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let win;

function createWindow() {
  // Create the browser window.
  win = new BrowserWindow({width: 800, height: 600});

  // and load the index.html of the app.
  win.loadURL(`file://${__dirname}/index.html`);

  // Open the DevTools.
  win.webContents.openDevTools();

  // Emitted when the window is closed.
  win.on('closed', () => {
    // Dereference the window object, usually you would store windows
    // in an array if your app supports multi windows, this is the time
    // when you should delete the corresponding element.
    win = null;
  });
}

// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on('ready', createWindow);

// Quit when all windows are closed.
app.on('window-all-closed', () => {
  // On OS X it is common for applications and their menu bar
  // to stay active until the user quits explicitly with Cmd + Q
  if (process.platform !== 'darwin') {
    app.quit();
  }
});

app.on('activate', () => {
  // On OS X it's common to re-create a window in the app when the
  // dock icon is clicked and there are no other windows open.
  if (win === null) {
    createWindow();
  }
});

// In this file you can include the rest of your app's specific main process
// code. You can also put them in separate files and require them here.
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Hello World!</title>
  </head>
  <body>
    <div id="content"></div>
    <script src="./build/app.js"></script>
  </body>
</html>
var babelify = require('babelify');
var gulp = require('gulp');
var browserify = require('browserify');
var source = require("vinyl-source-stream");

gulp.task('browserify', function(){
  var b = browserify({entries: ['./app/client/main.jsx']})
    .transform(babelify)
    .bundle()
    .pipe(source('app.js'))
    .pipe(gulp.dest('./app/build'));
});
import React from 'react'
import ReactDOM from 'react-dom'

class TestBox extends React.Component {
  constructor(props) {
    super(props);
  }

  render() {
    return(
      <p>hoge</p>
    );
  }
}

ReactDOM.render(
  <TestBox />,
  document.getElementById('content')
);

実行♪

npm start

おわりに

う〜ん。まだまだ友達になれない。特にbabel部分がよくわかってなくて絶賛はまった。。。
npm startも微妙だし。。。
ここからいろいろ触って勉強しようと思います。
reduxは当分触らないことに決めた(reactわかってないのにredux行っても利点がわからないため)
とりあえず勉強あるのみ( ;´Д`)

Electronアプリケーションをreactで構成してみる。

どうもGakuです。
今回はElectronアプリケーションをreactを使って構成してみたいと思います。
reactいいよreact!まだ掴みきれてないけど( ;´Д`)

Electronは導入済みの前提で記述していきます。
まだ導入していない方は下記記事を参考に構築してみてください。
[blogcard url=http://160.16.72.126/?p=338]ELECTRONを導入してみる![/blogcard]

導入

もろもろの導入

package.jsonの存在するフォルダへ遷移し、下記コマンドでbabelを導入します。

npm install babel babel-register babel-preset-react babel-preset-es2015 --save-dev

同様にreact,redux,gulp,browserify,reactify,vinyl-source-streamも導入します。

npm install react --save-dev
npm install react-dom --save-dev
npm install redux --save-dev
npm install gulp browserify reactify vinyl-source-stream --save-dev

package.jsonのscript部分を下記のように編集する。

"scripts": {
  "start": "electron ./app"
},

.babelrcを作成しreactを使用する設定を記述する。

{
  "presets": [
    "es2015",
    "stage-0",
    "react"
  ],
  "plugins": [
    "add-module-exports"
  ]
}

とりあえず動かしてみる

app/index.jsとapp/index.htmlを作成し下記のように編集します。
※下記コードはここらへんからパクってきただけなので、理解はしていない。
[blogcard url=”https://github.com/electron/electron/blob/master/docs/tutorial/quick-start.md”][/blogcard]

const electron = require('electron');
const {app} = electron;
const {BrowserWindow} = electron;
let win;

function createWindow() {
  win = new BrowserWindow({width: 800, height: 600});
  win.loadURL(`file://${__dirname}/index.html`);

  win.webContents.openDevTools();

  win.on('closed', () => {
    win = null;
  });
}

app.on('ready', createWindow);

// Quit when all windows are closed.
app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') {
    app.quit();
  }
});

app.on('activate', () => {
  if (win === null) {
    createWindow();
  }
});
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Hello World!</title>
  </head>
  <body>
    <h1>Hello World!</h1>
    We are using node <script>document.write(process.versions.node)</script>,
    Chrome <script>document.write(process.versions.chrome)</script>,
    and Electron <script>document.write(process.versions.electron)</script>.
  </body>
</html>

下記コマンドでとりあえず動かしてみる

npm start

スクリーンショット 2016-06-19 10.53.36

いい感じっすね〜*・゜゚・*:.。..。.:*・'(*゚▽゚*)’・*:.。. .。.:*・゜゚・*

reactで動かしてみる

gulpファイルを下記のように作成します。

var gulp = require('gulp');
var browserify = require('browserify');
var source = require("vinyl-source-stream");
var reactify = require('reactify');

gulp.task('browserify', function(){
  var b = browserify({
    entries: ['./app/main.jsx'],
    transform: [reactify]
  });
  return b.bundle()
    .pipe(source('app.js'))
    .pipe(gulp.dest('./app/build'));
});

index.htmlを再度下記のように編集する。

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Hello World!</title>
  </head>
  <body>
    <div id="content"></div>
    <script src="./build/app.js"></script>
  </body>
</html>

main.jsxを作成し下記のように編集する。

var React = require('react');
var ReactDOM = require('react-dom')

//コンポーネントを定義
var Index = React.createClass({
  render:function(){
    return (
        <p>hoge</p>
    );
  }
});

//id='content'の要素にコンポーネント「Index」を挿入してレンダリング
ReactDOM.render(
  <Index />,
  document.getElementById('content')
);

package.jsonのscript部分を再度下記のように編集する。

"scripts": {
  "start": "gulp browserify & electron ./app"
},

最後に実行

npm start

スクリーンショット 2016-06-19 11.40.46

うごく!うごくぞぉおお!〜*・゜゚・*:.。..。.:*・'(*゚▽゚*)’・*:.。. .。.:*・゜゚・*

最終的なファイル構成はこんな感じになるはず

.
├── app
│   ├── build
│   │   └── app.js
│   ├── index.html
│   ├── index.js
│   └── main.jsx
├── node_module
├── gulpfile.js
├── npm-debug.log
└── package.json

追伸

次回はreduxでいろいろ作ってみようかと思います。( ´ ▽ ` )ノ
いつもここまではいいんだよ。
こっからredux入ると鬼畜の極みなんだよな。。。
ただ、reactは使いこなせるようになりたい(´Д` )

以上です。

react-reduxでページ読み込み時、actionを呼び出す方法

Gakuです。
Reduxむずいっす。やばいっす。楽しいっす(´Д` )
ということで、ここ3日くらいはまってたページ読み込み時にactionを呼び出す方法がついにクリアできたので備忘録として残します。

ハマった内容

1.とりあえず、初期描画でデータを呼び出す方法が全くわからず(´Д` )になった。
2.onload使えばいいんじゃね?と閃く!
3.onloadで実装したもののなんか無限ループ起こる(今でも原因不明)
4.reduxにはそういう機能がないんじゃね?と思いreactの記事探し出す。
5.reactにあるcomponentDidMount()ってのが使えそうな雰囲気なのはわかったので実装しようと試みる。
6.ひたすら書き方がわからず2日はまる←本日21時まで。。。飯も食わず。。。

解決法

結局reduxのexample見て、実装したいことと同じようなコードを読んで解決した。
(ここに行き着いておけばもっと簡単に今までできた気がする。。。)
redux初学者は一度動かしてみて、コード読んで、いろいろやってみるとreduxで実装できるようになると思います。
https://github.com/reactjs/redux/blob/master/docs/introduction/Examples.md

実装

containerでこんな感じに記述。

import React from 'react'
import { connect } from 'react-redux'
import { fetchProject } from '../actions/operationProject'

class ProjectList extends React.Component {
  componentDidMount() {
    const { dispatch } = this.props
    dispatch(fetchProject())
  }
  render() {
    const { projects } = this.props
    return (
      <div>
      {projects.saveResult.map(project =>
        <p key={project.id}>
          {project.projectname}
        </p>
      )}
      </div>
    )
  }
}

const mapStateToProps = (state) => {
  return {
    projects: state.projects
  }
}

export default connect(mapStateToProps)(ProjectList)

結局componentDidMount()を使うところまでは合ってたみたい。
redux使うのに、reactの記述方法で書いていいのかな?という迷いが今回のハマりに繋がりました( ;´Д`)

おわりに

書方にすごくこだわりたいと思っているので(たいして綺麗じゃないけど。こだわりだけ)、そのこだわりを捨てた方が良いかと思ったハマりでした。。。
ただ、reactは一歩進むと世界が広がっていくので本当楽しいです。
今回のハマり解消した時は昇天しました。

react-reduxで「dispatch is not a function」にハマった場合の対処法

Gukuです。
React.jsのReduxフレームワークを使用していて、「dispatch is not a function」というエラーに3日ほどハマったので備忘録を記述しておきます。
(このエラーの解消法が英語文献漁りまくっても無かったので。。。)
こんなのです。
スクリーンショット 2016-02-23 5.52.17

エラー解消法

container部分でこんな感じで記述しているとこのエラーが起こります。

import React from 'react'
import { connect } from 'react-redux'
import AddProject from '../containers/AddProject'
import FetchProject from '../containers/FetchProject'
import { fetchProject } from '../actions/operationProject'

let OnLoadProcessing = ({ onLoadFetchProject }) => (
  <div>
    <AddProject />
    <FetchProject />
    <script type="text/javascript" language="JavaScript">
      window.onload = () => {
        onLoadFetchProject()
      }
    </script>
  </div>
)

const mapDispatchToProps = (dispatch,state) => {
  return {
    onLoadFetchProject: () => {
      dispatch(fetchProject()) //ここでエラーdispatchなんて関数ねぇえよ(´Д` )って怒られる。
    }
  }
}

OnLoadProcessing = connect(
  mapDispatchToProps
)(OnLoadProcessing)

export default OnLoadProcessing

解決法としてはこんな感じ。

import React from 'react'
import { connect } from 'react-redux'
import AddProject from '../containers/AddProject'
import FetchProject from '../containers/FetchProject'
import { fetchProject } from '../actions/operationProject'

let OnLoadProcessing = ({ onLoadFetchProject }) => (
  <div>
    <AddProject />
    <FetchProject />
    <script type="text/javascript" language="JavaScript">
      window.onload = () => {
        onLoadFetchProject()
      }
    </script>
  </div>
)

//空でも良いので、mapStateToPropsを記述
const mapStateToProps = (state) => {
  return {
  }
}

const mapDispatchToProps = (dispatch,state) => {
  return {
    onLoadFetchProject: () => {
      dispatch(fetchProject())
    }
  }
}

OnLoadProcessing = connect(
  mapStateToProps, //ここでconnectするのも忘れない
  mapDispatchToProps
)(OnLoadProcessing)

export default OnLoadProcessing

これでdispatch is not a functionのエラーは解消されます(=゚ω゚)ノ
mapStateToPropsで受け取っているstateの情報が無いとdispatchが動かない?ってことなんでしょうか?
なんか良くわかりませんが、解決したので良しとしましょう。
reduxでcontainerを作成する場合、mapStateToProps,mapDispatchToPropsをセットで必ず記述すれば良さそうですね。

おわりに

こんなエラーシラネ。土日と平日2日ぐらいこのエラーに悩まされた。
やはり、記述方法を自分スタイルにしだすとこういったエラーにはまるなと。
(reduxの機構とかまだよくわかってないし。。。)
ただ。。。ただ、これでサーバサイドへの書き込み、読み込みができるようになったので、これで一通りフロントAppは書き出せるかなっと。
辛い3週間が終わりそうです(=゚ω゚)ノ

Reduxでmaterial-ui使ってみる

Gakuです。
昨日から東京は暖かいですね〜。
絶好の引きこもってプログラミング日和です!
さて、今回はGoogleから出しているMaterial-uiというCSSフレームワークを使えるようになったのでその備忘録を掲載します。

構築

導入

フォルダを作成し、その中にpackage.jsonを作成。
以下のように記述します。

{
  "name": "reduxtestapp2",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "browserify": "^13.0.0",
    "express": "^4.13.4",
    "gulp": "^3.9.1",
    "react": "^0.14.7",
    "react-dom": "^0.14.7",
    "redux": "^3.3.1",
    "react-redux": "^4.4.0",
    "babelify": "^6.1.1",
    "vinyl-source-stream": "^1.1.0",
    "gulp-webserver": "^0.9.1",
    "material-ui": "^0.14.4",
    "react-tap-event-plugin": "^0.2.2"
  }
}

で npm installを行いmoduleをインストールします。

ファイル構成

/
|-actions
|
|-components
|     |-App.js
|     |-LeftNav.js
|     |-MobileTearSheet.js
|     |-TopNav.js
|
|-containers
|
|-node_modules
|     |-npm installで導入された各種module(自動格納)
|
|-reducers
|     |-index.js
|
|-bundle.js
|-gulpfile.js
|-index.html
|-index.js
|-package.json

各種ファイル記述

あくまでDEMOなんで、参考にしていただけたらって感じです。
(とりあえず、NavbarとLeftMenubarを設置しただけです。)

<!--index.html-->
<html>
<head>
  <meta charset="utf-8">
  <title>DEMO</title>
</head>
<body>

  <div id="root"></div>

  <script src="bundle.js"></script>
</body>
</html>
//gulpfile.js
var gulp = require('gulp');
var browserify = require('browserify');
var babelify = require('babelify');
var source = require('vinyl-source-stream');
var webserver = require('gulp-webserver');

gulp.task('browserify', function() {
  browserify('./index.js', { debug: true })
    .transform(babelify)
    .bundle()
    .on("error", function (err) { console.log("Error : " + err.message); })
    .pipe(source('bundle.js'))
    .pipe(gulp.dest('./'))
});

gulp.task('watch', function() {
  gulp.watch('./*.jsx', ['browserify'])
});

gulp.task('webserver', function() {
  gulp.src('./')
    .pipe(webserver({
      host: '127.0.0.1',
      livereload: true
    })
  );
});

gulp.task('default', ['browserify', 'watch', 'webserver']);
//index.js
import React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import { createStore } from 'redux'
import chatApp from './reducers'
import App from './components/App'

let store = createStore(chatApp)

render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
)
//reducers/index.js
import { combineReducers } from 'redux'

const chatApp = combineReducers({
})

export default chatApp
//components.App.js
import React from 'react'
import TopNav from './TopNav'
import LeftNav from './LeftNav'

const App = () => (
  <div>
    <div>
      <TopNav />
    </div>
    <div>
      <LeftNav />
    </div>
  </div>
)

export default App
//components/LeftNav.js
import React from 'react';
import MobileTearSheet from './MobileTearSheet';
import List from 'material-ui/lib/lists/list';
import ListItem from 'material-ui/lib/lists/list-item';
import ActionGrade from 'material-ui/lib/svg-icons/action/grade';
import ActionInfo from 'material-ui/lib/svg-icons/action/info';
import ContentInbox from 'material-ui/lib/svg-icons/content/inbox';
import ContentDrafts from 'material-ui/lib/svg-icons/content/drafts';
import ContentSend from 'material-ui/lib/svg-icons/content/send';
import Divider from 'material-ui/lib/divider';

const ListExampleSimple = () => (
  <MobileTearSheet>
    <List>
      <ListItem primaryText="Inbox" leftIcon={<ContentInbox />} />
      <ListItem primaryText="Starred" leftIcon={<ActionGrade />} />
      <ListItem primaryText="Sent mail" leftIcon={<ContentSend />} />
      <ListItem primaryText="Drafts" leftIcon={<ContentDrafts />} />
      <ListItem primaryText="Inbox" leftIcon={<ContentInbox />} />
    </List>
    <Divider />
    <List>
      <ListItem primaryText="All mail" rightIcon={<ActionInfo />} />
      <ListItem primaryText="Trash" rightIcon={<ActionInfo />} />
      <ListItem primaryText="Spam" rightIcon={<ActionInfo />} />
      <ListItem primaryText="Follow up" rightIcon={<ActionInfo />} />
    </List>
  </MobileTearSheet>
);

export default ListExampleSimple;
//components/TopNav.js
import React from 'react';
import AppBar from 'material-ui/lib/app-bar';

const AppBarExampleIcon = () => (
  <AppBar
    title="Title"
    iconClassNameRight="muidocs-icon-navigation-expand-more"
  />
);

export default AppBarExampleIcon;
//components/MobileTearSheet.js
import React from 'react';

const MobileTearSheet = React.createClass({

  propTypes: {
    children: React.PropTypes.node,
    height: React.PropTypes.number,
  },

  contextTypes: {
    muiTheme: React.PropTypes.object,
  },

  render() {

    const styles = {
      root: {
        float: 'left',
        marginBottom: 24,
        marginRight: 24,
        width: 260,
      },
      container: {
        border: 'solid 1px #d9d9d9',
        overflow: 'hidden',
      },
    };

    return (
      <div style={styles.root}>
        <div style={styles.container}>
          {this.props.children}
        </div>
      </div>
    );
  },

});

export default MobileTearSheet;

実行

準備ができたら以下のコマンドで実行しhttp://127.0.0.1:8000で確認します。

gulp

こんな感じで出力されれば成功
スクリーンショット 2016-02-14 10.32.56
(※エラーが出てるのはreducerを一個も記述していないせい。1個もreducerを書かないSPAなんて無い!ということなのだろうか。。。)

おわりに

念願のmaterial-ui導入することができました。
正直これを入れたいがためにreactを勉強し出したと言っても過言ではない。
グラフィカルなUIは痺れます。

Reduxがまだまだ理解できていないので、これからは何か作りつつ勉強していきます(=゚ω゚)ノ

参考文献

Material-UI