SlideShare una empresa de Scribd logo
1 de 37
Descargar para leer sin conexión
最高のツイッター

クライアントを求めて
石井遼司
@airtoxin
2015年2月3日 ピコもん株式会社 社内勉強会
@airtoxin
• 石井遼司
•  
• 大学ではプログラムを書いて遺伝子の探索をしていた
• ボードゲームとかよくする
• 曲もほんのちょっと作ったり
最高のツイッター

クライアントを求めて
経緯
• 夜フクロウが最高だったけどもうずっとサポートされていない
• webは意外と使いやすいけど、結局アプリじゃないのが面倒
• 公式クライアントはヌルヌル動いていい感じと見せかけて、

よく引っかかるしめっちゃ落ちる
• echofon (見た目が
• mikutter (名前が
• tweetbot (値段が
じゃあ作れば良いのでは?
ツイッタークライアント
を作った
ツイッタークライアント(仮)
今できること
• アイコン、相対時間付きでTLが流れる
• ツイートの投稿
• ツイートのお気に入り登録
• エラーがあったらなんか出てくる
• マルチメディアツイートの表示
• 画像クリックでプレビュー
ツイッタークライアント(仮)
今できないこと
• TL以外のタブ機能(Reply、DM、List…)
• リプライ、リツイートボタン
• ✨👮👊
• 画像投稿
• ユーザーホームの観覧
• Lorem Ipsum
技術的なところ
アプリケーション

ビルド
nw.js
nw.js
• node.js + HTML + CSS + JSで

デスクトップアプリが作れる
• 旧 node-webkit
• node.jsのフォーク、io.jsに移行した際に名称を
変更
• Windows / Mac / Linuxいずれもサポート
nw.js
• node.jsのapiもブラウザコンテキストのライブ
ラリもどっちも使える
• 例えばnodeのfsモジュールでディレクトリ構成
を取得し、グラフィカルに表示するなども
• つい最近、ウィンドウの透過をサポート
nw.jsでビルド
• package.jsonに main としてエントリーポイン
トのHTMLパスを書く
• package.jsonがあるディレクトリを指定してビ
ルドするとアプリ化される
• 手動ならnuwk!が手軽
こんなnw.jsは嫌だ
こんなnw.jsは嫌だ
• browserコンテキストとnodeコンテキストの混在で混乱が起きる
• ショートカットにESCとかEnterが登録できない
• Atom-Shellと覇権争い
• windowオブジェクトの管理が大変
• アプリを作っているとviewのイベントに応じてトリガー引いて
処理を…という形になり、結局RESTfullなSPAっぽくなる
JSフレームワーク
(ライブラリ)
React.js
React.js
• ブラウザーサイドのjsライブラリ(フレームワーク)
• Facebook謹製
• 最近なんか流行ってるっぽい
• ViewModelなのでデータバインドが楽ちん
• 仮想DOMの差分レンダリングで超高速レンダリング
• コンポーネント指向で再利用可能なパーツ
var	
  Tweet	
  =	
  React.createClass(	
  {	
  
	
  	
  	
  	
  propTypes:	
  {	
  
	
  	
  	
  	
  	
  	
  	
  	
  tweetPayload:	
  React.PropTypes.object	
  //	
  tweet	
  object	
  https://dev.twitter.com/overview/api/tweets	
  
	
  	
  	
  	
  },	
  
	
  	
  	
  	
  getInitialState:	
  function	
  ()	
  {	
  
	
  	
  	
  	
  	
  	
  	
  	
  return	
  {	
  relativeTime:	
  ‘1s'	
  };	
  
	
  	
  	
  	
  },	
  
	
  	
  	
  	
  componentDidMount:	
  function	
  ()	
  {	
  
	
  	
  	
  	
  	
  	
  	
  	
  var	
  self	
  =	
  this;	
  
	
  	
  	
  	
  	
  	
  	
  	
  setInterval(	
  function	
  ()	
  {	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  self.setState(	
  {	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  relativeTime:	
  self.getRelativeTime(	
  self.props.tweetPayload.created_at	
  )	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  }	
  );	
  
	
  	
  	
  	
  	
  	
  	
  	
  },	
  3000	
  );	
  
	
  	
  	
  	
  },	
  
	
  	
  	
  	
  render:	
  function	
  ()	
  {	
  
	
  	
  	
  	
  	
  	
  	
  	
  var	
  payload	
  =	
  this.props.tweetPayload;	
  
	
  	
  	
  	
  	
  	
  	
  	
  var	
  medias	
  =	
  [];	
  
	
  	
  	
  	
  	
  	
  	
  	
  if	
  (	
  this.props.tweetPayload.extended_entities	
  &&	
  this.props.tweetPayload.extended_entities.media	
  &&	
  
this.props.tweetPayload.extended_entities.media.length	
  >	
  0	
  )	
  {	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  medias	
  =	
  this.props.tweetPayload.extended_entities.media.map(	
  function	
  (	
  media	
  )	
  {	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  return	
  <Media	
  mediaPayload={	
  media	
  }	
  key={	
  media.id_str	
  }	
  />	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  }	
  );	
  
	
  	
  	
  	
  	
  	
  	
  	
  }	
  
	
  	
  	
  	
  	
  	
  	
  	
  return	
  (	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  <div	
  className="tweet	
  row">	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  <div	
  className="col-­‐sm-­‐2	
  text-­‐center">	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  <img	
  className="profile-­‐icon"	
  src={	
  payload.user.profile_image_url_https.replace(	
  '_normal',	
  ''	
  )	
  }	
  />	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  </div>	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  <div	
  className="col-­‐sm-­‐9">	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  <p>	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  <span	
  className="user-­‐name">{	
  payload.user.name	
  }</span>	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  <span	
  className="user-­‐id">@{	
  payload.user.screen_name	
  }</span>	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  </p>	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  <div	
  className="text"><p>{	
  payload.text	
  }</p></div>	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  <div	
  className="row">{	
  medias	
  }</div>	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  </div>	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  <div	
  className="col-­‐sm-­‐1">	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  <div	
  className="post-­‐time	
  btn	
  disabled">{	
  this.state.relativeTime	
  }</div>	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  <div	
  className="action-­‐icons	
  btn-­‐group-­‐vertical">	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  <button	
  className="action-­‐icon	
  btn	
  btn-­‐default"	
  type="button"><i	
  className="fa	
  fa-­‐reply"></i></button>	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  <button	
  className="action-­‐icon	
  btn	
  btn-­‐default"	
  type="button"><i	
  className="fa	
  fa-­‐retweet"></i></button>	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  <Favorite	
  id={	
  payload.id_str	
  }	
  favorited={	
  payload.favorited	
  }	
  />	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  </div>	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  </div>	
  
	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  	
  </div>	
  
	
  	
  	
  	
  	
  	
  	
  	
  );	
  
	
  	
  	
  	
  },	
  
	
  	
  	
  	
  getRelativeTime:	
  function	
  (	
  targetDate	
  )	
  {	
  /*	
  do	
  something	
  */	
  }	
  
}	
  );
StateとProps
• Stateはコンポーネントの状態を表す変数

(読み書き可)

Model的
• Propsはコンポーネントの外部から受け取った変数

(読み取りのみ可)

Interface的
• stateはsetState()で書き込みを行う
• stateが変更されるとrenderが自動的に走る
よく使うメソッド等
• propTypes

コンポーネントが公開しているPropsのインターフェースを記述

開発者に向けたバリデーション
• getInitialState()

Stateの初期値を記述
• componentDidMount()

コンポーネントがDOMに追加された後に呼ばれる

DOMに関する初期化処理

ajaxでデータを取ってきてsetState()など
• render()

単一の仮想DOMを返す

{}で囲むと評価された値が挿入される

同じコンポーネントが複数ある場合はkeyを指定しなければならない
シンプルで分かりやすい
こんなReact.jsは嫌だ
こんなReact.jsは嫌だ
• jsxのトランスパイル
• jsにテンプレートが内包されている
• className ?
• CSS当てにくい
nw.js + React.js
ビルド自動化
• Grunt / gulpなどのタスクランナーで自動化
• jsxの変換や必要モジュールのロードなどを行ったものを
compileディレクトリに出力
• compileディレクトリを対象にnw.jsのアプリをbuild
!
!
var	
  gulp	
  =	
  require(	
  'gulp'	
  );	
  
var	
  NwBuilder	
  =	
  require(	
  'node-­‐webkit-­‐builder'	
  );	
  
!
gulp.task(	
  'nw',	
  function	
  ()	
  {	
  
	
  	
  	
  	
  var	
  nw	
  =	
  new	
  NwBuilder(	
  {	
  
	
  	
  	
  	
  	
  	
  	
  	
  files:	
  './compile/**/*',	
  
	
  	
  	
  	
  	
  	
  	
  	
  platforms:	
  [	
  'osx64'	
  ],	
  
	
  	
  	
  	
  	
  	
  	
  	
  version:	
  (	
  process.env.NODE_ENV	
  ===	
  'production'	
  )	
  ?	
  'latest'	
  :	
  'v0.10.5'	
  
	
  	
  	
  	
  }	
  );	
  
	
  	
  	
  	
  return	
  nw.build();	
  
}	
  );
ディレクトリ構成
browserifyとrequire
• 一連のjsコードをbrowserifyしてapp.jsとして出力
• ルートのindex.htmlでscriptタグで読み込む事で

全てnodeコンテキストで書ける様になる
• browserifyがrequireを書き換える事に注意

browserifyしても使えないfsなどのモジュールは
requireの代わりにwindow.requireを使う必要がある
アプリケーションの公開
• ビルドされたアプリはソースコードが丸見え
• oauthのコンシューマーキーなどを隠す必要がある
• nwsnapshotを使ってsnapshot.binを出力
• package.jsonで snapshot : snapshot.bin
• htmlの読み込み後にsnapshotが評価される
絶対皆で最高の

クライアントを作ろうな!
https://github.com/airtoxin/
twitter-client
最高のツイッタークライアントを求めて

Más contenido relacionado

Similar a 最高のツイッタークライアントを求めて

Jqm20120210
Jqm20120210Jqm20120210
Jqm20120210
cmtomoda
 
勉強会force#2 HTML5によるモバイルアプリ開発
勉強会force#2 HTML5によるモバイルアプリ開発勉強会force#2 HTML5によるモバイルアプリ開発
勉強会force#2 HTML5によるモバイルアプリ開発
Kazuki Nakajima
 
QML を用いた YouTube クライアントの作成 - 関東 Qt 勉強会
QML を用いた YouTube クライアントの作成 - 関東 Qt 勉強会QML を用いた YouTube クライアントの作成 - 関東 Qt 勉強会
QML を用いた YouTube クライアントの作成 - 関東 Qt 勉強会
Jumpei Ogawa
 
Twitter連携chrome extension作り方
Twitter連携chrome extension作り方Twitter連携chrome extension作り方
Twitter連携chrome extension作り方
Hiroshi Oyamada
 
Reflexの紹介
Reflexの紹介Reflexの紹介
Reflexの紹介
Rie Nakau
 

Similar a 最高のツイッタークライアントを求めて (20)

TwitterToDayOne
TwitterToDayOneTwitterToDayOne
TwitterToDayOne
 
Road to UI Library
Road to UI LibraryRoad to UI Library
Road to UI Library
 
Tizen Web UI Frameworkでデザインを組み込んでみた
Tizen Web UI Frameworkでデザインを組み込んでみたTizen Web UI Frameworkでデザインを組み込んでみた
Tizen Web UI Frameworkでデザインを組み込んでみた
 
Jqm20120210
Jqm20120210Jqm20120210
Jqm20120210
 
Djangoによるスマホアプリバックエンドの実装
Djangoによるスマホアプリバックエンドの実装Djangoによるスマホアプリバックエンドの実装
Djangoによるスマホアプリバックエンドの実装
 
jQuery勉強会#2
jQuery勉強会#2jQuery勉強会#2
jQuery勉強会#2
 
Teclab3
Teclab3Teclab3
Teclab3
 
勉強会force#2 HTML5によるモバイルアプリ開発
勉強会force#2 HTML5によるモバイルアプリ開発勉強会force#2 HTML5によるモバイルアプリ開発
勉強会force#2 HTML5によるモバイルアプリ開発
 
Monaca公式ガイドブック発売記念全国キャラバン
Monaca公式ガイドブック発売記念全国キャラバンMonaca公式ガイドブック発売記念全国キャラバン
Monaca公式ガイドブック発売記念全国キャラバン
 
Monaca公式ガイドブック発売記念全国キャラバン
Monaca公式ガイドブック発売記念全国キャラバンMonaca公式ガイドブック発売記念全国キャラバン
Monaca公式ガイドブック発売記念全国キャラバン
 
jQuery Mobileの基礎
jQuery Mobileの基礎jQuery Mobileの基礎
jQuery Mobileの基礎
 
20061125
2006112520061125
20061125
 
QML を用いた YouTube クライアントの作成 - 関東 Qt 勉強会
QML を用いた YouTube クライアントの作成 - 関東 Qt 勉強会QML を用いた YouTube クライアントの作成 - 関東 Qt 勉強会
QML を用いた YouTube クライアントの作成 - 関東 Qt 勉強会
 
Twitter連携chrome extension作り方
Twitter連携chrome extension作り方Twitter連携chrome extension作り方
Twitter連携chrome extension作り方
 
20120118 titanium
20120118 titanium20120118 titanium
20120118 titanium
 
[日本語・Japanese] Creative Technical Content for Better Developer Experience
[日本語・Japanese] Creative Technical Content for Better Developer Experience[日本語・Japanese] Creative Technical Content for Better Developer Experience
[日本語・Japanese] Creative Technical Content for Better Developer Experience
 
JPAの基礎と現場で役立つ開発Tips
JPAの基礎と現場で役立つ開発TipsJPAの基礎と現場で役立つ開発Tips
JPAの基礎と現場で役立つ開発Tips
 
Sails workshop1
Sails workshop1Sails workshop1
Sails workshop1
 
jQuery勉強会#3
jQuery勉強会#3jQuery勉強会#3
jQuery勉強会#3
 
Reflexの紹介
Reflexの紹介Reflexの紹介
Reflexの紹介
 

最高のツイッタークライアントを求めて