37
最高のツイッター クライアントを求めて 石井遼司 @airtoxin 2015年2月3日 ピコもん株式会社 社内勉強会

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

Embed Size (px)

Citation preview

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

石井遼司 @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