React and Flux心得分享

huli@netappstore.net

What is React?

A JAVASCRIPT LIBRARY FOR BUILDING USER INTERFACES

所以,React不是

  • 一個完整的前端framework
  • 跟Angular.js在同一個層級

React

  • MVC裡面的V (View)
  • 拿來顯示UI的一個Library
  • 一種完全不同的設計模式

要了解React,先從jQuery開始

範例:通知系統

通知系統

 

  1. 有News跟Events兩類
  2. 會用websocket從後端持續接收訊息
  3. 要即時將新訊息顯示出來

jQuery

function appendNewMessage(msg){
  $target = null;
  if(msg.type=="NEWS"){
    $target = $("#news_list");
  }else{
    $target = $("#events_list");
  }
  $target.append(
    '<div class="row">'+
      '<div class="title">'+msg.title+'</div>'+
      '<div class="content">'+msg.content+'</div>'+
    '</div>'
  );
}
  1. Render現有訊息
  2. 監聽新訊息事件
  3. 收到新訊息
  4. append上去DOM物件

如果事情再變的麻煩一點...

  1. 訊息點開以後加個已讀的標籤
  2. 你可以刪除訊息
function readMessage(){
  $(this).attr('read', true);
  $(this).addClass('read');
}

function deleteMessage(msg){
  $(this).remove();
}

這些事情好像有點眼熟...

Create

Read

Update

Delete

有沒有可能,有一種更簡便的方式?

有沒有可能,我可以少寫一點code?

 

 

有沒有可能,我可以這樣子...

 

var messages = [];

function renderAll(){
  messages.forEach(function(item){
    var read = item.read?"read":"";
    render(
      <div class="row " + read>
        <div class="title">item.title</div>
        <div class="content">item.content</div>
      </div>
    );
  });
}

function addMessage(msg){
  messages.push(msg);
  renderAll();
}

function deleteMessage(msg){
  messages.find(msg.id).remove;
  renderAll();
}

function readMessage(msg){
  messages.find(msg.id).read = true;
  renderAll();
}
  1. 只要管理一份數據
  2. 操作數據而不是操作DOM
  3. 每次渲染都當做第一次

React特色#1

 always re-render

         優點

  1. 只要寫一次render的code
  2. CURD都與render無關
  3. 快速輕鬆方便

         缺點

效能不會很差嗎?

React特色#2

 virtual dom

React特色#3

Reusable Components 

React特色#4

JSX

  render: function() {
    return (
      <div>
        <h3>TODO</h3>
        <TodoList items={this.state.items} />
        <form onSubmit={this.handleSubmit}>
          <input onChange={this.onChange} value={this.state.text} />
          <button>{'Add #' + (this.state.items.length + 1)}</button>
        </form>
      </div>
    );
  }

Example

Facebook官方範例

https://facebook.github.io/react/

What is Flux?

APPLICATION ARCHITECTURE FOR BUILDING USER INTERFACES

Flux is the application architecture that Facebook uses for building client-side web applications.

It complements React's composable view components by utilizing a unidirectional data flow

  1. 使用者在網頁上按了一個按鈕(刪除),產生onClick事件
  2. 透過Action Creators,發送一個Action給Dispatcher
  3. Dispatcher接收到Action,轉發給有註冊的Store
  4. Store處理資料,刪除這筆資料
  5. Store發送一個Event給View
  6. View知道Store裡面的Data改變了,re-render

實戰

以通知系統為例

var PubcastListItem = React.createClass({
  render: function() {

    //接收上層傳來的message
    var message = this.props.message;

    return (
      <li>
        <a href={message.url} onClick={this._onClick}>
        <h1>{message.message_text}</h1>
        {getDate(message.timestamp)}
        </a>
      </li>
    );
  },
  _onClick: function(){
    PubcastActionCreators.clickMessage(this.props.message._id);
  }
});
//獲得資料
function getStateFromStores() {
  return {
    messages: MessageStore.getAll()
  };
}

//得到所有ListItem
function getMessageListItem(message) {
  return (
    <PubcastListItem
      message={message}
    />
  );
}

//從type決定資料
function isType(type){
  return function(obj){
    return obj.message_type==type;
  }
}

var Pubcastpanel = React.createClass({

  getInitialState: function() {
    return {};
  },

  componentDidMount: function() {
    MessageStore.addChangeListener(this._onChange);
  },

  componentWillUnmount: function() {
    MessageStore.removeChangeListener(this._onChange);
  },

  //把message分類別 render
  render: function() {
    var newMessageListItems = [];
    var eventMessageListItems = [];
    if(this.state.messages){
      newMessageListItems = this.state.messages.filter(isType(4)).map(getMessageListItem);
      eventMessageListItems = this.state.messages.filter(isType(1)).map(getMessageListItem);
    }

    return (
      <div>
      <div className="back">Back</div>
      <ul>
          <li>
            <a className="gift">Pubgame Gift</a>
            <div className="newnumber">
            </div>
          </li>

          <li className="news">NEWS</li>
          <ul className="list">
              {newMessageListItems}
          </ul>
          <li className="event">EVENTS</li>
          <ul className="list ev">
              {eventMessageListItems}
          </ul>
      </ul>
      </div>
    );
  },
  _onChange: function() {
    this.setState(getStateFromStores());
  }
});

module.exports = Pubcastpanel;

Action Creators

var ActionTypes = PubcastConstants.ActionTypes;

module.exports = {
  //跟dispatcher說:我收到資料囉
  receiveAll: function(rawMessages) {
    PubcastAppDispatcher.dispatch({
      type: ActionTypes.RECEIVE_RAW_MESSAGES,
      rawMessages: rawMessages
    });
  },

  //新訊息
  receiveNewMessage: function(newMessage) {
    PubcastAppDispatcher.dispatch({
      type: ActionTypes.RECEIVE_NEW_MESSAGE,
      rawMessages: newMessage
    });
  },

  //點擊某訊息
  clickMessage: function(_id){
    PubcastAppDispatcher.dispatch({
      type: ActionTypes.CLICK_MESSAGE,
      _id: _id
    });
  }
};

Message Store

var ActionTypes = PubcastConstants.ActionTypes;
var CHANGE_EVENT = 'change';

var _messages = {};
var _unreadCount = 0;

//獲取全部訊息
function _addMessages(rawMessages) {
  _messages = rawMessages;

  //計算未讀數量
  rawMessages.forEach(function(msg){
    if(msg.unread===true){
      _unreadCount++;
    }
  });
}

//新增新訊息
function _newMessages(rawMessages){

  rawMessages = [rawMessages];
  rawMessages.forEach(function(msg){
    msg.unread=true;
    msg._id = msg.message_id;
    msg.timestamp = msg.message_time;
    _messages.unshift(msg);
    _unreadCount++;
  });;
}

var MessageStore = assign({}, EventEmitter.prototype, {

  emitChange: function() {
    this.emit(CHANGE_EVENT);
  },

  addChangeListener: function(callback) {
    this.on(CHANGE_EVENT, callback);
  },

  removeChangeListener: function(callback) {
    this.removeListener(CHANGE_EVENT, callback);
  },

  get: function(id) {
    return _messages[id];
  },

  getAll: function() {
    return _messages;
  },

  getUnreadCount: function(){
    return _unreadCount;
  }
});

//跟dispacher註冊事件
//當有人觸發「ActionTypes.RECEIVE_RAW_MESSAGES」時就知道有新訊息
MessageStore.dispatchToken = PubcastAppDispatcher.register(function(action) {

  switch(action.type) {

    //獲取全部訊息
    case ActionTypes.RECEIVE_RAW_MESSAGES:
      _addMessages(action.rawMessages);
      MessageStore.emitChange();
      break;

    //收到新訊息  
    case ActionTypes.RECEIVE_NEW_MESSAGE:
      _newMessages(action.rawMessages);
      MessageStore.emitChange();
      break;

    //有人點擊訊息
    case ActionTypes.CLICK_MESSAGE:

      //標記為已讀
      _unreadCount--;
      _messages.forEach(function(item){
        if(item._id===action._id){
          item.unread = false;
          return;
        }
      });

      //跟server講
      PubcastAPIUtils.clickMessage(action._id);
      MessageStore.emitChange();

    default:
      // do nothing
  }

});

module.exports = MessageStore;

PubcastAPIUtils

module.exports = {

  init: function(){

    //初始化pubcast,與server連線
    Pubcast.init(..., {

      //獲取全部訊息
      getAllActiveMessage: function(data){
        PubcastActionCreators.receiveAll(data.activeMessages);
      },

      //新訊息
      notifyNewMessage: function(data){
        PubcastActionCreators.receiveNewMessage(data.newMessage);
      }
    });
  },

  clickMessage: function(id){
    Pubcast.updateClickEvent(id);
  },

  getAllMessages: function() {

    //利用action creator 送給dispatcher event
    PubcastActionCreators.receiveAll(rawMessages);
  }

};

結論

  • 不同於MVC的另一種模式
  • 可以只用React,或是React+Flux都用
  • 單向資料流讓debug變簡單
  • 固定開發流程
  • 號稱很好上手,學習曲線低

More

React and Flux 心得分享

By huli

React and Flux 心得分享

簡單分享一下自己初學React跟Flux的心得

  • 2,196