over 1 year ago

碰過flux的人對於這張圖應該是看到不要再看


且讓我們以這張圖為出發點,假裝我們自已是使用者,使用者看到的是網頁,所以請你由React View出發
View --> ActionCreators --> Dispatcher --> Store --> (Render View)
|                                                                                                                             |
|                                                                                                                             |
<-------------------------------------------------------------------------

實際上在redux的圖應該是這樣(參考圖)

View --> ActionCreator --> Store(middleware,reducer,state)Loop --> View(Provider,connect)
|                                                                                                                                                             |
|                                                                                                                                                             |
<------------------------------------------------------------------------------------------

為什麼呢?這就要從
createStore這個method看起了
https://github.com/rackt/redux/blob/master/src/createStore.js
input 是 reducer和 initialState 兩個參數
output 是 dispatch,subscribe,getState,replaceReducer組成的物件。

replaceReducer:做的行為就是一個一個把reducer設定為初始化狀態。
getState:取得當下的state
subscribe: 一個listener,當action被執行的時候發生,通常會有一個callback來執行getState方法。
dispatch: 提供dispatch(action)去觸發每一個動作。

於是你至少知道一件事,store本身就包含了reducer(其實是combineReducer),和initialState。
另一件事,store自已就可以控制要經過哪個reducer來得到state。

而redux middleware作用的方式其實是經過這樣的包裝(此處只留下必要的code)

import { createStore,combineReducers, applyMiddleware,compose } from 'redux';
import thunk from 'redux-thunk';
import * as reducers from '../reducers';
import promiseMiddleware from 'redux-promise-middleware';

const createStoreWithMiddleware = compose(
  applyMiddleware(thunk,promiseMiddleware())
)(createStore);

const rootReducer = combineReducers(reducers);

export default function configureStore(initialState) {
  const store = createStoreWithMiddleware(rootReducer, initialState);
  return store;
}

createStoreWithMiddleware把createStore整個包裝了起來,
於是middleware就可以在store中自由的穿插,
要寫log有redux-logger,
要寫fetch有redux-promise-middleware,
想要寫一個action來觸發其他的action就可以用redux-thunk.

這樣我們就可以依照我們的需求來控制動作(action)和狀態(state)之間的關係啦。

小結

由前面的流程做為出發點
View --> ActionCreator --> Store(middleware,reducer,state)Loop --> View(Provider,connect)
|                                                                                                                                                             |
|                                                                                                                                                             |
<------------------------------------------------------------------------------------------

store實際上有一層層的包裝
這其實是functional language的概念
給定沒有副作用的function(reducer)和一個定值(state),每次出來的結果都會相同。

然而實際redux應用在專案開發上有另外做處理來簡化你的開發流程。
除了前面講到的createStore之外

其一是combineReducer,不但合併你的每一個reducer,而且合併它產出的state.
其二是bindActionCreator,透過和store.dispatch綁定後

let bindActionCreator => (actionCreator, dispatch) {
  return (...args) => dispatch(actionCreator(...args));
}

我們就可以把actionCreator轉化成實際可以用的action了。

 
over 1 year ago

碰了一陣子React,大致整理一些如何學習和開發的思路出來,供作參考之用。

準備知識

對初期接觸的朋友來說,基本上其實知道這些東西是做什麼用,然後會用就好。
之後根據你的需求再去深入研究它的各種用法。
webpack
https://webpack.github.io/

babel
https://babeljs.io/

了解一些js es6和es7的語法 (es7可以只需知道decorators)
http://babeljs.io/blog/2015/06/07/react-on-es6-plus/

React
1.了解props和state的差別
2.了解載入元件的順序 (componentDidMount那些)
3.了解開發時你要如何思考。
英文:https://facebook.github.io/react/docs/thinking-in-react.html
中文:http://reactjs.cn/react/docs/thinking-in-react.html
重點就是

  • 拆分界面
  • 處理成靜態版
  • 了解哪些東西是state
  • 知道state應該要放在哪裡
  • 添加反向數據流(其實就是讓user的action可以導致畫面和資料的變化,這邊我們是用redux來處理)

Redux
了解action reducer store等等部分,這部分處理好之後,在開發新功能時其實就只要
新增action和reducer的相關程式就好。

如何有邏輯的分層?

我是這樣分的,供做為參考
Root:管理Provider和Router,包含Page.
Page:一個頁面,可能包含一個Container或是多個Container
Container:一個完整可運作的單位,包含多個Component,每個Component有比較緊密的資料聯結。
Component:在你拆分界面時所拆出來的組件。

透過Redux來做開發時

每個Container有屬於自已的reducer和action(儘量寫在一起)
透過bindActionCreators,生成的action從Container注入到Component

範例

https://github.com/motephyr/react-redux-socketio-slack-customer-service
透過各種方式(如slack)加上socketio去做你的線上客服系統,界面上其實還要再調整,不過做為範例用。
主要運作的部分是MessageBoxComponent這個組件,其他有些不相關的程式碼最近再做一次整理。

示意圖


 
almost 2 years ago

原文: Speed Up Your Rails App by 66% - The Complete Guide to Rails Caching

  1. Performance never really becomes a priority until the app is basically in flames
    效能從不被認為是重要的事,直到它爆掉為止XD(透過量測數據來告訴大家它的重要性)

  2. 1 second from the instant the user clicked or interacted with the site until that interaction is complete (the DOM finishes painting).
    In order to consistently achieve a 1 second to glass webpage, server responses should be kept below 300ms.
    1秒內完成動作是使用者可接受的(50ms的網路延遲,150ms載入你的js和css,250ms執行你的js)
    為了達成1秒完成動作,server response必須控制在300ms內

  3. ignore action caching and page caching.
    用fragment caching就可以了.

  4. rack-mini-profiler provides an excellent line-by-line breakdown of where exactly all the time goes during a particular server response.
    rack-mini-profiler提供逐行的server的回應時間

  5. Completed 200 OK in 110ms (Views: 65.6ms | ActiveRecord: 19.7ms)
    110ms是總時間
    65.6ms是 render和activeRecord lazy load的時間(ActiveRecord::Relations 會在render View時才執行)
    19.7ms只是query db的時間

  6. get a millisecond-by-millisecond breakdown of exactly where your time goes during a request, you'll need rack-mini-profiler and the flamegraph extension
    flamegraph也不錯用

  7. I suggest setting a maximum acceptable average response time, or MAART, for your site.
    設定最大可接受的回應時間目標,而且要設兩個(一個是for production的,一個是for developer的)

  8. apache bench(ab) ab -t 10 -c 10 http://localhost:3000/
    t是設時間 c是Concurrency

  9. I usually test with at least -c 2 to flush out any weird threading/concurrency errors I might have accidentally committed.
    同時發兩個resequres來看看concurrency的問題。

  10. I don't cache anything unless I'm not meeting my MAART.
    server夠快就別做cache了.

這樣就cache todo了:

<% todo = Todo.first %>
<% cache(todo) %>
  ... a whole lot of work here ...
<% end %>

可以針對目前登入的user作cache:

<% todo = Todo.first %>
<% cache([current_user, todo]) %>
  ... a whole lot of work here ...
<% end %>

Russian Doll Caching:

<% cache('todo_list') %>
  <ul>
    <% @todos.each do |todo| %>
      <% cache(todo) do %>
        <li class="todo"><%= todo.description %></li>
      <% end %>
    <% end %>
  </ul>
<% end %>

這樣做比較好(todo.updated_at有更新再更新cache)

<% cache(["todo_list", @todos.map(&:id), @todos.maximum(:updated_at)]) %>
  <ul>
    <% @todos.each do |todo| %>
      <% cache(todo) do %>
        <li class="todo"><%= todo.description %></li>
      <% end %>
    <% end %>
  </ul>
<% end %>

11.子資料更新時也更新父資料的updated_at

belongs_to :father, touch: true

if we call @brake.car.save, our two outer caches will expire (because their updated_at values changed) but the inner cache (for @brake) will be untouched and reused.

brake.car資料更新時,只有brake.car.corporation和brake.car會更新,原本brake的cache仍然在

<% cache @brake.car.corporation %>
  Corporation: <%= @brake.car.corporation.name %>
  <% cache @brake.car %>
    Car: <%= @brake.car.name %>
    <% cache @brake %>
      Brake system: <%= @brake.name %>
    <% end %>
  <% end %>
<% end %>

12.ActiveSupport::FileStore not an LRU cache
FileStore expires entries from the cache based on the time they were written to the cache, not the last time they were recently used/accessed.
FileStore根據上次資料寫入的時間排序,而不是上次的使用時間(LRU),所以會慢慢的滿出來,很吃空間

13.ActiveSupport::MemoryStore If you have one or two servers, with a few workers each, and you're storing very small amounts of cached data (<20MB)
如果你cache的資料小於20mb 它用起來還ok.再大就別用它了.

14.Memcache? If you're running more than 1-2 hosts, you should be using a distributed cache store. Cache values are limited to 1MB?
(?)它cache的value和key限制1mb和250bytes嗎?

15.Redis? If you're running more than 2 servers or processes, I recommend using Redis as your cache store. In addition to redis-store, there's a new Redis cache gem on the block: readthis. It's under active development and looks promising.
建議用readthis這個gem,不要用redis-store,它只能存string

16.LRURedux? Use LRURedux where algorithms require a performant (and large enough to the point where a Hash could grow too large) cache to function.
很快,但不支援Rails cache store.

17.Speed: LruRedux > MemoryStore > FileStore > DalliStore > RedisStore
很需要速度的用LruRedux, 小東西用MemoryStore, 作者偏好Redis(我偏好memcached).

18.When using a remote, distributed cache, figure out how long it actually takes to read from the cache.
用memcached和redis在remote環境的時間,測一下是不是真的有加比較快,網路延遲是個問題。
不加cache打的就是資料庫,所以當然很多台server就需要有cache server,但太早架是沒有意義的。

 
almost 2 years ago

原文: How Do Gems Work?

大部分的時候,RubyGem都可以直接使用。
但是Ruby有個大問題是:如果出錯的話,你很難找到為什麼出錯。

通常在使用Gem的時候你不會遇到什麼問題,如果運氣不好碰到的話,Google也沒什麼用
如果你不知道Gem到底是怎麼和程式一起運作的話,你就會花很多時間在debug上。

Gem的運作有點像在變魔術,不過花點時間研究,它其實很好懂的。

gem install做了什麼事?

一個Ruby gem只是一些打包起來的code,加上一些額外的data。
你可以用gem unpack來看裡面的code長什麼樣子:

~/Source/playground jweiss$ gem unpack resque_unit
Fetching: resque_unit-0.4.8.gem (100%)
Unpacked gem: '/Users/jweiss/Source/playground/resque_unit-0.4.8'
~/Source/playground jweiss$ cd resque_unit-0.4.8
~/Source/playground/resque_unit-0.4.8 jweiss$ find .
.
./lib
./lib/resque_unit
./lib/resque_unit/assertions.rb
./lib/resque_unit/errors.rb
./lib/resque_unit/helpers.rb
./lib/resque_unit/plugin.rb
./lib/resque_unit/resque.rb
./lib/resque_unit/scheduler.rb
./lib/resque_unit/scheduler_assertions.rb
./lib/resque_unit.rb
./lib/resque_unit_scheduler.rb
./README.md
./test
./test/resque_test.rb
./test/resque_unit_scheduler_test.rb
./test/resque_unit_test.rb
./test/sample_jobs.rb
./test/test_helper.rb
~/Source/playground/resque_unit-0.4.8 jweiss$

gem install最簡單的行為是,它抓到gem之後把這些檔案放到你的系統上一個特別的目錄下
你可以執行gem environment來看它把gem裝在哪裡:

~ jweiss$ gem environment
RubyGems Environment:
  - RUBYGEMS VERSION: 2.2.2
  - RUBY VERSION: 2.1.2 (2014-05-08 patchlevel 95) [x86_64-darwin14.0]
  - INSTALLATION DIRECTORY: /usr/local/Cellar/ruby/2.1.2/lib/ruby/gems/2.1.0
  ...
~ jweiss$ ls /usr/local/Cellar/ruby/2.1.2/lib/ruby/gems/2.1.0
bin           bundler     doc         gems
build_info    cache       extensions  specifications

所有你安裝的gem都在gems的目錄下。
這個路徑每個系統都不一樣,而它也依照你如何安裝Ruby(rvm,homebrew,rbenv)有所不同。
所以如果你想知道gems的code在哪裡,gem environment這個指令很有幫助。

gem的code是如何被載入進來的?

為了讓你使用gem裡面的code, RubyGems覆寫了Ruby的require方法. (實際的code在core_ext/kernel_require.rb)
它的注釋很清楚:

##
# When RubyGems is required, Kernel#require is replaced with our own which
# is capable of loading gems on demand.
#
# When you call <tt>require 'x'</tt>, this is what happens:
# * If the file can be loaded from the existing Ruby loadpath, it
#   is.
# * Otherwise, installed gems are searched for a file that matches.
#   If it's found in gem 'y', that gem is activated (added to the
#   loadpath).
#

例如你要載入active_support,rubyGem會試著用Ruby的require方法,然後你會得到這個error:

LoadError: cannot load such file -- active_support
  from (irb):17:in `require'
  from (irb):17
  from /usr/local/bin/irb:11:in `<main>'

RubyGems看到這個錯誤訊息,發現它需要去找某個gem的active_support.rb。
然後它去掃描所有的gem的metadata,看看哪個gem有active_support.rb

irb(main):001:0> spec = Gem::Specification.find_by_path('active_support')
=> #<Gem::Specification:0x3fe366874324 activesupport-4.2.0.beta1>

然後它啟動這個gem,把這個gem的code加到了ruby的load path裡
(就是你可以載入程式的列表裡)

irb(main):002:0> $LOAD_PATH
=> ["/usr/local/Cellar/ruby/2.1.2/lib/ruby/site_ruby/2.1.0", "/usr/local/Cellar/ruby/2.1.2/lib/ruby/site_ruby/2.1.0/x86_64-darwin14.0", "/usr/local/Cellar/ruby/2.1.2/lib/ruby/site_ruby", "/usr/local/Cellar/ruby/2.1.2/lib/ruby/vendor_ruby/2.1.0", "/usr/local/Cellar/ruby/2.1.2/lib/ruby/vendor_ruby/2.1.0/x86_64-darwin14.0", "/usr/local/Cellar/ruby/2.1.2/lib/ruby/vendor_ruby", "/usr/local/Cellar/ruby/2.1.2/lib/ruby/2.1.0", "/usr/local/Cellar/ruby/2.1.2/lib/ruby/2.1.0/x86_64-darwin14.0"]
irb(main):003:0> spec.activate
=> true
irb(main):004:0> $LOAD_PATH
=> ["/usr/local/Cellar/ruby/2.1.2/lib/ruby/gems/2.1.0/gems/i18n-0.7.0.beta1/lib", "/usr/local/Cellar/ruby/2.1.2/lib/ruby/gems/2.1.0/gems/thread_safe-0.3.4/lib", "/usr/local/Cellar/ruby/2.1.2/lib/ruby/gems/2.1.0/gems/activesupport-4.2.0.beta1/lib", "/usr/local/Cellar/ruby/2.1.2/lib/ruby/site_ruby/2.1.0", "/usr/local/Cellar/ruby/2.1.2/lib/ruby/site_ruby/2.1.0/x86_64-darwin14.0", "/usr/local/Cellar/ruby/2.1.2/lib/ruby/site_ruby", "/usr/local/Cellar/ruby/2.1.2/lib/ruby/vendor_ruby/2.1.0", "/usr/local/Cellar/ruby/2.1.2/lib/ruby/vendor_ruby/2.1.0/x86_64-darwin14.0", "/usr/local/Cellar/ruby/2.1.2/lib/ruby/vendor_ruby", "/usr/local/Cellar/ruby/2.1.2/lib/ruby/2.1.0", "/usr/local/Cellar/ruby/2.1.2/lib/ruby/2.1.0/x86_64-darwin14.0"]

既然 active_support 已经被加到了load path中,你就可以像往常一样 require gem 中的檔案了。你甚至可以使用原始的 require 方法,也就是被 RubyGems 所覆寫的那個方法:

irb(main):005:0> gem_original_require 'active_support'
=> true

小知識,但它很有用。

RubyGems看起來有點複雜,但它最基本的情況只是幫你管理Ruby的load path。
這不是指它很簡單,我沒有講到RubyGems如何管理Gems之間的版本衝突,
gem的binary(像rails和rake),C的extension等其他內容

但即使是了解RubyGems的基礎也很有幫助,透過這些code和在irb上的測試,
你就知道怎樣去讀 gems裡面的原始碼。
你知道了你的 gems 在哪裡,所以能確定 RubyGems 也能找到它們。
你明白了 gems 是如何被載入進來的,你就可以去找出一些看來很難的載入程式時發生的問題了。

 
almost 2 years ago

前陣子寫了個失物招領的服務(其實還沒寫完,只抓到六月底的資料)
暫時放在heroku上
http://findlost.herokuapp.com/

想解決的問題是,在同一個地方就可以找到所有的遺失物資料
覺得每個單位都做了一個頁面在做同樣的事情,這不是沒有效率嗎?

而且內政部警政署這個超級麻煩的 http://eli.npa.gov.tw/NPA97-217Client/


連我爬資料都爬了半天。

目前抓的資訊來源是"內政部警政署","台鐵","警廣" 這三個單位已經是相對完整了。
如果東西是被ubike或是捷運站撿到,那真的是說明的不清不楚。


只知道掉了個"什麼",除此之外就都沒有資訊。

做到一個程度,遇到的二個問題是:
1.搜集的資料其實不太齊全,大家要因此找到自已的失物有點困難。
2.要找到失物,統一要有的資訊是
物品的照片,描述,遺失地點,遺失時間(當然其實撿到的人也不會馬上就送到警察局,需要往前推一段)

這部分來說,照片只有警廣有,但警廣不會告訴你東西是在哪裡掉的。(我想通常是在計程車上)

有機會的話希望可以把分散在各處的失物招領頁面和資料集結起來。
讓key資料的人也方便,找東西的人也方便。